gfx_draping 0.3.0

Drape polygons over terrain with gfx
Documentation
use gfx;
use gfx::traits::FactoryExt;

use polygon::*;
use vertex::Vertex;

gfx_pipeline!(z_fail_polyhedron_pipeline {
    mvp: gfx::Global<[[f32; 4]; 4]> = "u_mvp",
    vertex_buffer: gfx::VertexBuffer<Vertex> = (),
    out_color: gfx::BlendTarget<gfx::format::Srgba8> = (
        "o_color",
        gfx::state::ColorMask::all(),
        gfx::preset::blend::ALPHA,
    ),
    out_depth_stencil: gfx::DepthStencilTarget<gfx::format::DepthStencil> = (
        gfx::preset::depth::LESS_EQUAL_TEST,
        gfx::state::Stencil {
            front: gfx::state::StencilSide {
                fun: gfx::state::Comparison::Always,
                mask_read: 255,
                mask_write: 255,
                op_fail: gfx::state::StencilOp::Keep,
                op_depth_fail: gfx::state::StencilOp::DecrementWrap,
                op_pass: gfx::state::StencilOp::Keep,
            },
            back: gfx::state::StencilSide {
                fun: gfx::state::Comparison::Always,
                mask_read: 255,
                mask_write: 255,
                op_fail: gfx::state::StencilOp::Keep,
                op_depth_fail: gfx::state::StencilOp::IncrementWrap,
                op_pass: gfx::state::StencilOp::Keep,
            },
        },
    ),
});

gfx_pipeline!(z_fail_bounding_box_pipeline {
    out_color: gfx::BlendTarget<gfx::format::Srgba8> = (
        "o_color",
        gfx::state::ColorMask::all(),
        gfx::preset::blend::ALPHA,
    ),
    mvp: gfx::Global<[[f32; 4]; 4]> = "u_mvp",
    color: gfx::Global<[f32; 4]> = "u_color",
    vertex_buffer: gfx::VertexBuffer<Vertex> = (),
    out_depth_stencil: gfx::DepthStencilTarget<gfx::format::DepthStencil> = (
        gfx::preset::depth::PASS_TEST,
        gfx::state::Stencil::new(
            // A fragment is only "inside" the polyhedron, and thus supposed to be drawn, if the
            // stencil buffer is nonzero at that point.
            gfx::state::Comparison::NotEqual,
            255,
            (
                // An important property of the stencil is that it is all zeroes after this
                // pipeline runs, so that the next draw doesn't need to clear the stencil first.
                //
                // If the stencil test fails, the value is zero and thus should be kept.
                gfx::state::StencilOp::Keep,
                // This never happens, because the depth test always passes.
                gfx::state::StencilOp::Keep,
                // The stencil test passed, so the value should be reset to zero.
                gfx::state::StencilOp::Replace,
            ),
        ),
    ),
});

/// Drives graphics operations.
///
/// This struct contains the shaders and stencil operations necessary to render draped polygons
/// onto a terrain.
#[derive(Clone, Debug)]
pub struct DrapingRenderer<R: gfx::Resources> {
    polyhedron_pso: gfx::pso::PipelineState<R, z_fail_polyhedron_pipeline::Meta>,
    bounding_box_pso: gfx::pso::PipelineState<R, z_fail_bounding_box_pipeline::Meta>,
}

impl<R: gfx::Resources> DrapingRenderer<R> {
    /// Set up the pipeline state objects needed for rendering draped polygons.
    pub fn new<F: gfx::Factory<R>>(factory: &mut F) -> DrapingRenderer<R> {
        DrapingRenderer {
            polyhedron_pso: Self::polyhedron_pso(factory),
            bounding_box_pso: Self::bounding_box_pso(factory),
        }
    }

    /// Render polygons in `buffer` using `indices` to choose the polygons.
    ///
    /// The depth buffer in `depth_stencil_target` should contain the depth values of your terrain
    /// -- in other words, draw your terrain just before you call this function, and make sure you
    /// don't clear the buffer until after rendering all the polygons you wish to draw.
    ///
    /// In addition, the stencil buffer should be cleared to zero before calling this function. The
    /// stencil buffer is guaranteed to remain zero after each call, so there is no need to clear
    /// the stencil buffer between calls to this function.
    ///
    /// `mvp` should be a model-view-projection matrix. *You probably want to use a different `mvp`
    /// than what you're normally using.* The polygons will only render onto terrain with z-values
    /// between 0 and 1; you should apply a transformation to alter these z-bounds. For example, if
    /// your terrain is bounded in height between `z_min` and `z_max`, your mvp use you from this
    /// library might be constructed as:
    ///
    /// ```rust,compile_fail
    /// // With the relevant imports, this is working code when using the `cgmath` crate.
    ///
    /// let translate_z = Matrix4::from_translation([0.0, 0.0, min_z].into());
    /// let stretch_z = Matrix4::from_nonuniform_scale(1.0, 1.0, max_z - min_z);
    /// let draping_mvp = usual_mvp * translate_z * stretch_z;
    /// ```
    pub fn render<C: gfx::CommandBuffer<R>>(
        &self,
        encoder: &mut gfx::Encoder<R, C>,
        render_target: gfx::handle::RenderTargetView<R, gfx::format::Srgba8>,
        depth_stencil_target: gfx::handle::DepthStencilView<R, gfx::format::DepthStencil>,
        mvp: [[f32; 4]; 4],
        color: [f32; 4],
        buffer: &RenderablePolygonBuffer<R>,
        indices: &RenderablePolygonIndices<R>,
    ) {
        let polyhedron_bundle = gfx::Bundle {
            pso: self.polyhedron_pso.clone(),
            slice: indices.polyhedron_slice.clone(),
            data: z_fail_polyhedron_pipeline::Data {
                mvp: mvp,
                out_color: render_target.clone(),
                out_depth_stencil: (depth_stencil_target.clone(), (0, 0)),
                vertex_buffer: buffer.polyhedron_vertex_buffer.clone(),
            },
        };

        let bounding_box_bundle = gfx::Bundle {
            pso: self.bounding_box_pso.clone(),
            slice: indices.bounding_box_slice.clone(),
            data: z_fail_bounding_box_pipeline::Data {
                color: color,
                mvp: mvp,
                out_color: render_target.clone(),
                out_depth_stencil: (depth_stencil_target.clone(), (0, 0)),
                vertex_buffer: buffer.bounding_box_vertex_buffer.clone(),
            },
        };

        polyhedron_bundle.encode(encoder);
        bounding_box_bundle.encode(encoder);
    }

    fn polyhedron_pso<F: gfx::Factory<R>>(
        factory: &mut F,
    ) -> gfx::pso::PipelineState<R, z_fail_polyhedron_pipeline::Meta> {
        let shaders = factory
            .create_shader_set(
                include_bytes!("shaders/polyhedron.vert"),
                include_bytes!("shaders/polyhedron.frag"),
            )
            .unwrap();

        let rasterizer = gfx::state::Rasterizer::new_fill();

        factory
            .create_pipeline_state(
                &shaders,
                gfx::Primitive::TriangleList,
                rasterizer,
                z_fail_polyhedron_pipeline::new(),
            )
            .unwrap()
    }

    fn bounding_box_pso<F: gfx::Factory<R>>(
        factory: &mut F,
    ) -> gfx::pso::PipelineState<R, z_fail_bounding_box_pipeline::Meta> {
        let shaders = factory
            .create_shader_set(
                include_bytes!("shaders/bounding_box.vert"),
                include_bytes!("shaders/bounding_box.frag"),
            )
            .unwrap();

        let rasterizer = gfx::state::Rasterizer {
            cull_face: gfx::state::CullFace::Front,
            ..gfx::state::Rasterizer::new_fill()
        };

        factory
            .create_pipeline_state(
                &shaders,
                gfx::Primitive::TriangleList,
                rasterizer,
                z_fail_bounding_box_pipeline::new(),
            )
            .unwrap()
    }
}

/// A set of vertex buffer handles ready for rendering.
#[derive(Clone, Debug)]
pub struct RenderablePolygonBuffer<R: gfx::Resources> {
    polyhedron_vertex_buffer: gfx::handle::Buffer<R, Vertex>,
    bounding_box_vertex_buffer: gfx::handle::Buffer<R, Vertex>,
}

impl<R: gfx::Resources> RenderablePolygonBuffer<R> {
    /// Prepare a `PolygonBuffer` for rendering.
    pub fn new<F: gfx::Factory<R>>(
        factory: &mut F,
        buffer: &PolygonBuffer,
    ) -> RenderablePolygonBuffer<R> {
        RenderablePolygonBuffer {
            polyhedron_vertex_buffer: factory.create_vertex_buffer(&buffer.polyhedron_vertices),
            bounding_box_vertex_buffer: factory.create_vertex_buffer(&buffer.bounding_box_vertices),
        }
    }
}

/// A set of index buffer handles ready for rendering.
#[derive(Clone, Debug)]
pub struct RenderablePolygonIndices<R: gfx::Resources> {
    polyhedron_slice: gfx::Slice<R>,
    bounding_box_slice: gfx::Slice<R>,
}

impl<R: gfx::Resources> RenderablePolygonIndices<R> {
    /// Prepare a `PolygonBufferIndices` for rendering.
    pub fn new<F: gfx::Factory<R>>(
        factory: &mut F,
        indices: &PolygonBufferIndices,
    ) -> RenderablePolygonIndices<R> {
        RenderablePolygonIndices {
            polyhedron_slice: Self::create_slice(factory, &indices.polyhedron_indices),
            bounding_box_slice: Self::create_slice(factory, &indices.bounding_box_indices),
        }
    }

    fn create_slice<F: gfx::Factory<R>>(factory: &mut F, indices: &[u32]) -> gfx::Slice<R> {
        gfx::Slice {
            start: 0,
            end: indices.len() as u32,
            base_vertex: 0,
            instances: None,
            buffer: factory.create_index_buffer(indices),
        }
    }
}