├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── examples ├── demo.rs ├── south_africa.geojson ├── south_africa.rs ├── terrain.frag └── terrain.vert ├── screencaptures ├── demo.gif └── south_africa.jpg └── src ├── lib.rs ├── polygon.rs ├── render.rs ├── shaders ├── bounding_box.frag ├── bounding_box.vert ├── polyhedron.frag └── polyhedron.vert └── vertex.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfx_draping" 3 | description = "Drape polygons over terrain with gfx" 4 | version = "0.3.0" 5 | authors = ["Ulysse Carion "] 6 | homepage = "https://github.com/ucarion/gfx-draping" 7 | repository = "https://github.com/ucarion/gfx-draping" 8 | documentation = "https://docs.rs/gfx_draping" 9 | license = "MIT" 10 | 11 | [dependencies] 12 | gfx = "0.17" 13 | geo = "^0.6" 14 | 15 | [dev-dependencies] 16 | camera_controllers = "0.22" 17 | cgmath = "0.15" 18 | fps_counter = "0.2" 19 | geo = "0.6" 20 | geojson = "0.9" 21 | gfx_text = "0.18" 22 | piston_window = "0.70" 23 | vecmath = "0.3" 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Ulysse Carion 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gfx\_draping 2 | 3 | ![A screenshot of this library in action](screencaptures/demo.gif) 4 | 5 | > An example of this crate in action. An array of polygons (*not a texture!*) 6 | > are rendered on a sinusoidal terrain. 7 | > 8 | > Note how the polygons rise and fall with the terrain, and can be partially 9 | > occluded by a bump/mountain in the way. 10 | 11 | ![Another screenshot](screencaptures/south_africa.jpg) 12 | 13 | > Another example, showing the Republic of South Africa. Note the hole in the 14 | > mainland part of the country (which is where South Africa borders the Kingdom 15 | > of Lesotho, a separate country), and the distant Marion Islands at the bottom 16 | > right. 17 | > 18 | > This library is designed to be easy to use with GIS data, but you can easily 19 | > construct polygons manually too. 20 | 21 | This is a library for helping you draw polygons on a terrain. It uses a 22 | screen-space algorithm based on the depth and stencil buffers. Performance is 23 | affected only by the complexity of the polygons you're drawing, not the terrain 24 | it's drawn on. 25 | 26 | This library is a Rust implementation the algorithm described by [Schneider & 27 | Klein (2007)][sch2007], titled "Efficient and Accurate Rendering of Vector Data 28 | on Virtual Landscapes". 29 | 30 | The first screenshot was taken from this minimalist demo: 31 | 32 | ```bash 33 | # You'll want to zoom out to see anything. Do that by holding down CTRL and 34 | # dragging down with your mouse. Then pan around with shift+drag. 35 | cargo run --example demo 36 | ``` 37 | 38 | The second screenshot was taken from the `south_africa` demo, which demonstrates 39 | how to combine this library with GIS data: 40 | 41 | ```bash 42 | # See instructions above on how to use the camera. Be sure to use --release, 43 | # otherwise you won't get a smooth, 60 fps experience. 44 | cargo run --release --example south_africa 45 | ``` 46 | 47 | ## Can I use this library? 48 | 49 | You can, as long the following things are true: 50 | 51 | * You are using `gfx` to do your graphics. 52 | * For now, only OpenGL is supported. But this is not a for any deep reason; 53 | it's just that the relevent shaders haven't been written. If you would 54 | like to use another backend, PRs will kindly be accepted. :smile: 55 | * You use the Z-Axis for your "up" in world-space -- that is, mountains go in 56 | the positive Z direction, and valleys go in the negative Z direction. 57 | 58 | ## How do I use this library? 59 | 60 | See [the top-level documentation](https://docs.rs/gfx_draping/) for high-level 61 | guidance on how you'd use this crate. 62 | 63 | ## TODO: 64 | 65 | If any of these problems interest you, open an issue or contact me, and I can 66 | give you a hand. Or just open a PR if you're bold! :smile: 67 | 68 | * Add support for non-OpenGL backends. 69 | * Implement the z-pass algorithm described in Schneider (2007). This algorithm 70 | can be slightly faster, but can only be used when the bounding box of the 71 | polygon doesn't intersect with the near plane of the view frustum. Currently, 72 | only the z-fail method, which works in all cases, is currently implemented. 73 | 74 | [sch2007]: http://cg.cs.uni-bonn.de/en/publications/paper-details/schneider-2007-efficient/ 75 | -------------------------------------------------------------------------------- /examples/demo.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate gfx; 3 | 4 | extern crate camera_controllers; 5 | extern crate cgmath; 6 | extern crate gfx_draping; 7 | extern crate piston_window; 8 | extern crate vecmath; 9 | 10 | use camera_controllers::{CameraPerspective, OrbitZoomCamera, OrbitZoomCameraSettings}; 11 | use cgmath::Matrix4; 12 | use gfx::Factory; 13 | use gfx::traits::FactoryExt; 14 | use gfx_draping::{DrapingRenderer, Polygon, PolygonBuffer, PolygonBufferIndices}; 15 | use piston_window::{OpenGL, PistonWindow, RenderEvent, ResizeEvent, Window, WindowSettings}; 16 | 17 | gfx_vertex_struct!(Vertex { 18 | position: [f32; 2] = "a_position", 19 | tex_coords: [f32; 2] = "a_tex_coords", 20 | }); 21 | 22 | gfx_pipeline!(terrain_pipeline { 23 | color_texture: gfx::TextureSampler<[f32; 4]> = "t_color", 24 | mvp: gfx::Global<[[f32; 4]; 4]> = "u_mvp", 25 | time: gfx::Global = "u_time", 26 | out_color: gfx::RenderTarget = "o_color", 27 | out_depth: gfx::DepthTarget<::gfx::format::DepthStencil> = gfx::preset::depth::LESS_EQUAL_WRITE, 28 | vertex_buffer: gfx::VertexBuffer = (), 29 | }); 30 | 31 | fn get_projection(window: &PistonWindow) -> [[f32; 4]; 4] { 32 | let draw_size = window.window.draw_size(); 33 | 34 | CameraPerspective { 35 | fov: 45.0, 36 | near_clip: 10.0, 37 | far_clip: 1000.0, 38 | aspect_ratio: (draw_size.width as f32) / (draw_size.height as f32), 39 | }.projection() 40 | } 41 | 42 | const TERRAIN_SIDE_LENGTH: u16 = 100; 43 | 44 | fn main() { 45 | let mut window: PistonWindow = WindowSettings::new("Shadow Volume Draping Demo", [800, 600]) 46 | .exit_on_esc(true) 47 | .opengl(OpenGL::V3_2) 48 | .build() 49 | .unwrap(); 50 | 51 | let mut factory = window.factory.clone(); 52 | 53 | // First, set up the terrain ... 54 | let mut terrain_vertices = Vec::new(); 55 | let mut terrain_indices = Vec::new(); 56 | let mut terrain_texture_data = Vec::new(); 57 | 58 | for y in 0..TERRAIN_SIDE_LENGTH { 59 | for x in 0..TERRAIN_SIDE_LENGTH { 60 | let max_value = TERRAIN_SIDE_LENGTH - 1; 61 | if y != max_value && x != max_value { 62 | let a = (x + 0) + (y + 0) * TERRAIN_SIDE_LENGTH; 63 | let b = (x + 1) + (y + 0) * TERRAIN_SIDE_LENGTH; 64 | let c = (x + 0) + (y + 1) * TERRAIN_SIDE_LENGTH; 65 | let d = (x + 1) + (y + 1) * TERRAIN_SIDE_LENGTH; 66 | 67 | terrain_indices.extend_from_slice(&[a, b, c, b, d, c]); 68 | } 69 | 70 | let (x, y) = (x as f32, y as f32); 71 | let (u, v) = (x / max_value as f32, y / max_value as f32); 72 | terrain_vertices.push(Vertex { 73 | position: [x, y], 74 | tex_coords: [u, v], 75 | }); 76 | 77 | terrain_texture_data.push([(255.0 * u) as u8, (255.0 * v) as u8, 0, 255]); 78 | } 79 | } 80 | 81 | let (terrain_vertex_buffer, terrain_slice) = 82 | factory.create_vertex_buffer_with_slice(&terrain_vertices, terrain_indices.as_slice()); 83 | let (_, terrain_texture_view) = factory 84 | .create_texture_immutable::( 85 | gfx::texture::Kind::D2( 86 | TERRAIN_SIDE_LENGTH, 87 | TERRAIN_SIDE_LENGTH, 88 | gfx::texture::AaMode::Single, 89 | ), 90 | &[terrain_texture_data.as_slice()], 91 | ) 92 | .unwrap(); 93 | 94 | let terrain_sampler = factory.create_sampler(gfx::texture::SamplerInfo::new( 95 | gfx::texture::FilterMethod::Bilinear, 96 | gfx::texture::WrapMode::Clamp, 97 | )); 98 | 99 | let terrain_shader_set = factory 100 | .create_shader_set( 101 | include_bytes!("terrain.vert"), 102 | include_bytes!("terrain.frag"), 103 | ) 104 | .unwrap(); 105 | 106 | let terrain_pso = factory 107 | .create_pipeline_state( 108 | &terrain_shader_set, 109 | gfx::Primitive::TriangleList, 110 | gfx::state::Rasterizer::new_fill().with_cull_back(), 111 | terrain_pipeline::new(), 112 | ) 113 | .unwrap(); 114 | 115 | let terrain_data = terrain_pipeline::Data { 116 | color_texture: (terrain_texture_view.clone(), terrain_sampler.clone()), 117 | mvp: [[0.0; 4]; 4], 118 | time: 0.0, 119 | out_color: window.output_color.clone(), 120 | out_depth: window.output_stencil.clone(), 121 | vertex_buffer: terrain_vertex_buffer, 122 | }; 123 | 124 | let mut terrain_bundle = gfx::Bundle { 125 | slice: terrain_slice, 126 | pso: terrain_pso, 127 | data: terrain_data, 128 | }; 129 | 130 | // Next, step up the polygons ... 131 | let mut buffer = PolygonBuffer::new(); 132 | let mut indices1 = PolygonBufferIndices::new(); 133 | let mut indices2 = PolygonBufferIndices::new(); 134 | 135 | for x in 0..TERRAIN_SIDE_LENGTH / 4 { 136 | for y in 0..TERRAIN_SIDE_LENGTH / 4 { 137 | let bounds = [ 138 | (x as f32 * 4.0, x as f32 * 4.0 + 4.0), 139 | (y as f32 * 4.0, y as f32 * 4.0 + 4.0), 140 | ]; 141 | 142 | let points = vec![ 143 | (x as f32 * 4.0 + 0.5, y as f32 * 4.0 + 0.5), 144 | (x as f32 * 4.0 + 3.5, y as f32 * 4.0 + 0.5), 145 | (x as f32 * 4.0 + 3.5, y as f32 * 4.0 + 3.5), 146 | (x as f32 * 4.0 + 0.5, y as f32 * 4.0 + 3.5), 147 | (x as f32 * 4.0 + 0.5, y as f32 * 4.0 + 0.5), 148 | ]; 149 | 150 | let polygon = Polygon::new(bounds, points); 151 | let indices = buffer.add(&polygon); 152 | 153 | if x % 2 == y % 2 { 154 | indices1.extend(&indices); 155 | } else { 156 | indices2.extend(&indices); 157 | } 158 | } 159 | } 160 | 161 | // Finally, prepare the polygons for rendering. 162 | let renderer = DrapingRenderer::new(&mut factory); 163 | let renderable_buffer = buffer.as_renderable(&mut factory); 164 | let renderable_indices1 = indices1.as_renderable(&mut factory); 165 | let renderable_indices2 = indices2.as_renderable(&mut factory); 166 | 167 | let mut camera_controller = 168 | OrbitZoomCamera::new([50.0, 50.0, 0.0], OrbitZoomCameraSettings::default()); 169 | camera_controller.distance = 50.0; 170 | 171 | let max_z = 20.0; 172 | let min_z = -20.0; 173 | let polygon_model = Matrix4::from_translation([0.0, 0.0, min_z].into()) * 174 | Matrix4::from_nonuniform_scale(1.0, 1.0, max_z - min_z); 175 | 176 | while let Some(event) = window.next() { 177 | camera_controller.event(&event); 178 | 179 | window.draw_3d(&event, |window| { 180 | let render_args = event.render_args().unwrap(); 181 | 182 | window.encoder.clear( 183 | &window.output_color, 184 | [0.3, 0.3, 0.3, 1.0], 185 | ); 186 | window.encoder.clear_depth(&window.output_stencil, 1.0); 187 | window.encoder.clear_stencil(&window.output_stencil, 0); 188 | let mvp = camera_controllers::model_view_projection( 189 | vecmath::mat4_id(), 190 | camera_controller.camera(render_args.ext_dt).orthogonal(), 191 | get_projection(window), 192 | ); 193 | 194 | terrain_bundle.data.time += 0.01; 195 | terrain_bundle.data.mvp = mvp; 196 | terrain_bundle.encode(&mut window.encoder); 197 | 198 | let cgmath_mvp: Matrix4 = mvp.into(); 199 | 200 | renderer.render( 201 | &mut window.encoder, 202 | window.output_color.clone(), 203 | window.output_stencil.clone(), 204 | (cgmath_mvp * polygon_model).into(), 205 | [0.0, 0.0, 1.0, 0.5], 206 | &renderable_buffer, 207 | &renderable_indices1, 208 | ); 209 | 210 | renderer.render( 211 | &mut window.encoder, 212 | window.output_color.clone(), 213 | window.output_stencil.clone(), 214 | (cgmath_mvp * polygon_model).into(), 215 | [0.0, 1.0, 1.0, 0.5], 216 | &renderable_buffer, 217 | &renderable_indices2, 218 | ); 219 | }); 220 | 221 | event.resize(|_, _| { 222 | terrain_bundle.data.out_color = window.output_color.clone(); 223 | terrain_bundle.data.out_depth = window.output_stencil.clone(); 224 | }); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /examples/south_africa.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate gfx; 3 | 4 | extern crate camera_controllers; 5 | extern crate cgmath; 6 | extern crate fps_counter; 7 | extern crate geo; 8 | extern crate geojson; 9 | extern crate gfx_draping; 10 | extern crate gfx_text; 11 | extern crate piston_window; 12 | extern crate vecmath; 13 | 14 | use camera_controllers::{CameraPerspective, OrbitZoomCamera, OrbitZoomCameraSettings}; 15 | use cgmath::Matrix4; 16 | use fps_counter::FPSCounter; 17 | use geo::boundingbox::BoundingBox; 18 | use geo::map_coords::MapCoords; 19 | use geo::simplify::Simplify; 20 | use geo::MultiPolygon; 21 | use geojson::GeoJson; 22 | use geojson::conversion::TryInto; 23 | use gfx::Factory; 24 | use gfx::traits::FactoryExt; 25 | use gfx_draping::{DrapingRenderer, PolygonBuffer, PolygonBufferIndices}; 26 | use piston_window::{OpenGL, PistonWindow, RenderEvent, ResizeEvent, Window, WindowSettings}; 27 | 28 | gfx_vertex_struct!(Vertex { 29 | position: [f32; 3] = "a_position", 30 | tex_coords: [f32; 2] = "a_tex_coords", 31 | }); 32 | 33 | gfx_pipeline!(terrain_pipeline { 34 | out_color: gfx::RenderTarget = "o_color", 35 | color_texture: gfx::TextureSampler<[f32; 4]> = "t_color", 36 | time: gfx::Global = "u_time", 37 | mvp: gfx::Global<[[f32; 4]; 4]> = "u_mvp", 38 | vertex_buffer: gfx::VertexBuffer = (), 39 | out_depth_stencil: gfx::DepthStencilTarget = ( 40 | gfx::preset::depth::LESS_EQUAL_WRITE, 41 | gfx::state::Stencil::new( 42 | gfx::state::Comparison::Always, 43 | 255, 44 | ( 45 | gfx::state::StencilOp::Keep, // never happens if Comparison::Always 46 | gfx::state::StencilOp::Keep, // when depth test fails 47 | gfx::state::StencilOp::Keep, // when depth test passes 48 | ), 49 | ), 50 | ), 51 | }); 52 | 53 | fn get_projection(window: &PistonWindow) -> [[f32; 4]; 4] { 54 | let draw_size = window.window.draw_size(); 55 | 56 | CameraPerspective { 57 | fov: 45.0, 58 | near_clip: 0.1, 59 | far_clip: 1000.0, 60 | aspect_ratio: (draw_size.width as f32) / (draw_size.height as f32), 61 | }.projection() 62 | } 63 | 64 | fn get_elevation(x: f32, y: f32) -> f32 { 65 | ((x / 3.0).sin() + (y / 2.0).sin()) * 5.0 66 | } 67 | 68 | fn main() { 69 | let mut window: PistonWindow = WindowSettings::new("South Africa Draping Demo", [800, 600]) 70 | .exit_on_esc(true) 71 | .opengl(OpenGL::V3_2) 72 | .build() 73 | .unwrap(); 74 | 75 | let mut factory = window.factory.clone(); 76 | 77 | let geojson: GeoJson = include_str!("south_africa.geojson").parse().unwrap(); 78 | let mut feature_collection = match geojson { 79 | GeoJson::FeatureCollection(fc) => fc, 80 | _ => panic!("Unexpected geojson object type!"), 81 | }; 82 | 83 | let feature = feature_collection.features.remove(0); 84 | let geometry = feature.geometry.unwrap(); 85 | 86 | let multi_polygon: MultiPolygon = geometry.value.try_into().unwrap(); 87 | let multi_polygon = multi_polygon.simplify(&0.01); 88 | let bbox = multi_polygon.bbox().unwrap(); 89 | 90 | let multi_polygon = multi_polygon.map_coords(&|point| { 91 | let x = point.0 - bbox.xmin; 92 | let y = point.1 - bbox.ymax; 93 | 94 | (x, y) 95 | }); 96 | 97 | let max_x_value = (bbox.xmax - bbox.xmin).ceil() as u16; 98 | let max_y_value = (bbox.ymax - bbox.ymin).ceil() as u16; 99 | 100 | let mut terrain_vertices = Vec::new(); 101 | let mut terrain_indices = Vec::new(); 102 | let mut terrain_texture_data = Vec::new(); 103 | for y in 0..max_y_value + 1 { 104 | for x in 0..max_x_value + 1 { 105 | if y != max_y_value && x != max_x_value { 106 | let a = (x + 0) + (y + 0) * (max_x_value + 1); 107 | let b = (x + 1) + (y + 0) * (max_x_value + 1); 108 | let c = (x + 0) + (y + 1) * (max_x_value + 1); 109 | let d = (x + 1) + (y + 1) * (max_x_value + 1); 110 | 111 | terrain_indices.extend_from_slice(&[a, c, b, b, c, d]); 112 | } 113 | 114 | let (x, y) = (x as f32, y as f32); 115 | let (u, v) = (x / max_x_value as f32, y / max_y_value as f32); 116 | terrain_vertices.push(Vertex { 117 | position: [x, -y, get_elevation(x, y)], 118 | tex_coords: [u, v], 119 | }); 120 | 121 | terrain_texture_data.push([(255.0 * u) as u8, (255.0 * v) as u8, 0, 255]); 122 | } 123 | } 124 | 125 | let (terrain_vertex_buffer, terrain_slice) = 126 | factory.create_vertex_buffer_with_slice(&terrain_vertices, terrain_indices.as_slice()); 127 | let (_, terrain_texture_view) = factory 128 | .create_texture_immutable::( 129 | gfx::texture::Kind::D2( 130 | max_x_value + 1, 131 | max_y_value + 1, 132 | gfx::texture::AaMode::Single, 133 | ), 134 | &[terrain_texture_data.as_slice()], 135 | ) 136 | .unwrap(); 137 | 138 | let terrain_sampler = factory.create_sampler(gfx::texture::SamplerInfo::new( 139 | gfx::texture::FilterMethod::Bilinear, 140 | gfx::texture::WrapMode::Clamp, 141 | )); 142 | 143 | let terrain_shader_set = factory 144 | .create_shader_set( 145 | include_bytes!("terrain.vert"), 146 | include_bytes!("terrain.frag"), 147 | ) 148 | .unwrap(); 149 | 150 | let terrain_pso = factory 151 | .create_pipeline_state( 152 | &terrain_shader_set, 153 | gfx::Primitive::TriangleList, 154 | gfx::state::Rasterizer::new_fill().with_cull_back(), 155 | terrain_pipeline::new(), 156 | ) 157 | .unwrap(); 158 | 159 | let terrain_data = terrain_pipeline::Data { 160 | color_texture: (terrain_texture_view.clone(), terrain_sampler.clone()), 161 | time: 0.0, 162 | mvp: [[0.0; 4]; 4], 163 | out_color: window.output_color.clone(), 164 | out_depth_stencil: (window.output_stencil.clone(), (255, 255)), 165 | vertex_buffer: terrain_vertex_buffer, 166 | }; 167 | 168 | let mut terrain_bundle = gfx::Bundle { 169 | slice: terrain_slice, 170 | pso: terrain_pso, 171 | data: terrain_data, 172 | }; 173 | 174 | let renderer = DrapingRenderer::new(&mut factory); 175 | let mut buffer = PolygonBuffer::new(); 176 | let mut indices = PolygonBufferIndices::new(); 177 | for polygon in multi_polygon { 178 | indices.extend(&buffer.add(&polygon.into())); 179 | } 180 | 181 | let renderable_buffer = buffer.as_renderable(&mut factory); 182 | let renderable_indices = indices.as_renderable(&mut factory); 183 | 184 | let mut camera_controller = 185 | OrbitZoomCamera::new([0.0, 0.0, 0.0], OrbitZoomCameraSettings::default()); 186 | 187 | let mut fps_counter = FPSCounter::new(); 188 | let mut text_renderer = gfx_text::new(factory).build().unwrap(); 189 | 190 | let max_z = 10.0; 191 | let min_z = -10.0; 192 | let polygon_model = Matrix4::from_translation([0.0, 0.0, min_z].into()) * 193 | Matrix4::from_nonuniform_scale(1.0, 1.0, max_z - min_z); 194 | 195 | while let Some(event) = window.next() { 196 | camera_controller.event(&event); 197 | 198 | window.draw_3d(&event, |window| { 199 | let render_args = event.render_args().unwrap(); 200 | 201 | window.encoder.clear( 202 | &window.output_color, 203 | [0.3, 0.3, 0.3, 1.0], 204 | ); 205 | window.encoder.clear_depth(&window.output_stencil, 1.0); 206 | window.encoder.clear_stencil(&window.output_stencil, 0); 207 | let mvp = camera_controllers::model_view_projection( 208 | vecmath::mat4_id(), 209 | camera_controller.camera(render_args.ext_dt).orthogonal(), 210 | get_projection(window), 211 | ); 212 | 213 | terrain_bundle.data.time += 0.01; 214 | terrain_bundle.data.mvp = mvp; 215 | terrain_bundle.encode(&mut window.encoder); 216 | 217 | let cgmath_mvp: Matrix4 = mvp.into(); 218 | 219 | renderer.render( 220 | &mut window.encoder, 221 | window.output_color.clone(), 222 | window.output_stencil.clone(), 223 | (cgmath_mvp * polygon_model).into(), 224 | [0.0, 0.0, 1.0, 0.5], 225 | &renderable_buffer, 226 | &renderable_indices, 227 | ); 228 | 229 | let fps_message = format!("Frames per second: {}", fps_counter.tick()); 230 | text_renderer.add(&fps_message, [10, 10], [0.0, 0.0, 0.0, 1.0]); 231 | text_renderer 232 | .draw(&mut window.encoder, &window.output_color) 233 | .unwrap(); 234 | }); 235 | 236 | event.resize(|_, _| { 237 | terrain_bundle.data.out_color = window.output_color.clone(); 238 | terrain_bundle.data.out_depth_stencil.0 = window.output_stencil.clone(); 239 | }); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /examples/terrain.frag: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec4 v_color; 4 | 5 | out vec4 o_color; 6 | 7 | void main() { 8 | o_color = v_color; 9 | } 10 | -------------------------------------------------------------------------------- /examples/terrain.vert: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | uniform mat4 u_mvp; 4 | uniform float u_time; 5 | uniform sampler2D t_color; 6 | 7 | in vec2 a_position; 8 | in vec2 a_tex_coords; 9 | 10 | out vec4 v_color; 11 | 12 | float z() { 13 | float x = a_position.x; 14 | float y = a_position.y; 15 | 16 | return 10.0 * sin(u_time + x / 10.0 + y / 20.0); 17 | } 18 | 19 | void main() { 20 | v_color = texture(t_color, a_tex_coords); 21 | gl_Position = u_mvp * vec4(a_position, z(), 1.0); 22 | } 23 | -------------------------------------------------------------------------------- /screencaptures/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ucarion/gfx-draping/fcc7f575cca62937c542b8753eb53c1451b980a6/screencaptures/demo.gif -------------------------------------------------------------------------------- /screencaptures/south_africa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ucarion/gfx-draping/fcc7f575cca62937c542b8753eb53c1451b980a6/screencaptures/south_africa.jpg -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library for rendering polygons onto 3d terrain. 2 | //! 3 | //! This crate uses a screen-space algorithm; it uses your GPU's stencil buffer to detect pixels 4 | //! where the polygon lies on terrain unoccluded. In addition, this crate is meant for drawing 5 | //! multiple polygons at once. If you have a collection of polygons which may need to be drawn 6 | //! simultaneously, put them all in a `PolygonBuffer`. You can choose which of those polygons to 7 | //! render by combining their respective `PolygonBufferIndices`. 8 | //! 9 | //! Typically, usage of this crate will look something like: 10 | //! 11 | //! ```rust,compile_fail 12 | //! extern crate geo; 13 | //! extern crate gfx_draping; 14 | //! 15 | //! use gfx_draping::{DrapingRenderer, PolygonBuffer, PolygonBufferIndices}; 16 | //! 17 | //! // Let's say you're using `geo` (a Rust GIS crate) to construct polygons. 18 | //! let polygons: Vec = a_vec_of_polygons(); 19 | //! 20 | //! // Prepare assets for rendering. 21 | //! let mut buffer = PolygonBuffer::new(); 22 | //! let mut indices = PolygonBufferIndices::new(); 23 | //! for polygon in polygons { 24 | //! indices.extend(buffer.add(polygon.into())); 25 | //! } 26 | //! 27 | //! let renderer = DrapingRenderer::new(); 28 | //! let renderable_buffer = buffer.as_renderable(&mut window.factory); 29 | //! let renderable_indices = indices.as_renderable(&mut window.factory); 30 | //! 31 | //! while your_event_loop() { 32 | //! // Render your 3d terrain 33 | //! render_terrain(); 34 | //! 35 | //! // At this point, your depth stencil should be the result of drawing the terrain. Your 36 | //! // stencil buffer should be all zeroes. 37 | //! renderer.render( 38 | //! window.encoder, 39 | //! window.output_color, 40 | //! window.output_stencil, 41 | //! // See docs for `DrapingRenderer::render` for a caveat about what `mvp` should be. 42 | //! your_scaled_mvp(), 43 | //! // R - G - B - A 44 | //! [1.0, 0.0, 1.0, 0.5], 45 | //! &renderable_buffer, 46 | //! &renderable_indices, 47 | //! ); 48 | //! 49 | //! // Now you can clear / clean-up as you do usually. 50 | //! } 51 | //! ``` 52 | 53 | extern crate geo; 54 | #[macro_use] 55 | extern crate gfx; 56 | 57 | mod polygon; 58 | mod render; 59 | mod vertex; 60 | 61 | pub use polygon::{Polygon, PolygonBuffer, PolygonBufferIndices}; 62 | pub use render::{DrapingRenderer, RenderablePolygonBuffer, RenderablePolygonIndices}; 63 | -------------------------------------------------------------------------------- /src/polygon.rs: -------------------------------------------------------------------------------- 1 | use geo; 2 | use geo::algorithm::boundingbox::BoundingBox; 3 | use gfx; 4 | 5 | use render::*; 6 | use vertex::Vertex; 7 | 8 | /// A collection of polygons that could all be rendered in a single draw call. 9 | #[derive(Clone, Debug)] 10 | pub struct PolygonBuffer { 11 | pub(crate) polyhedron_vertices: Vec, 12 | pub(crate) bounding_box_vertices: Vec, 13 | } 14 | 15 | impl PolygonBuffer { 16 | /// Create a new, empty buffer. 17 | pub fn new() -> PolygonBuffer { 18 | PolygonBuffer { 19 | polyhedron_vertices: Vec::new(), 20 | bounding_box_vertices: Vec::new(), 21 | } 22 | } 23 | 24 | /// Add a polygon to this buffer. 25 | /// 26 | /// The `PolygonBufferIndices` returned can be used to render the passed polygon in a future 27 | /// call to `DrapingRenderer::render` using this buffer. 28 | pub fn add(&mut self, polygon: &Polygon) -> PolygonBufferIndices { 29 | let polyhedron_offset = self.polyhedron_vertices.len() as u32; 30 | let bounding_box_offset = self.bounding_box_vertices.len() as u32; 31 | 32 | self.polyhedron_vertices.extend( 33 | polygon.polyhedron_vertices(), 34 | ); 35 | self.bounding_box_vertices.extend( 36 | polygon.bounding_box_vertices(), 37 | ); 38 | 39 | PolygonBufferIndices { 40 | polyhedron_indices: polygon 41 | .polyhedron_indices() 42 | .map(|i| i + polyhedron_offset) 43 | .collect(), 44 | bounding_box_indices: polygon 45 | .bounding_box_indices() 46 | .map(|i| i + bounding_box_offset) 47 | .collect(), 48 | } 49 | } 50 | 51 | /// Prepare this buffer for rendering. 52 | pub fn as_renderable, R: gfx::Resources>( 53 | &self, 54 | factory: &mut F, 55 | ) -> RenderablePolygonBuffer { 56 | RenderablePolygonBuffer::new(factory, &self) 57 | } 58 | } 59 | 60 | /// A set of indices into a `PolygonBuffer`. 61 | /// 62 | /// You can combine these indices using `extend` to render multiple polygons at once. 63 | #[derive(Clone, Debug)] 64 | pub struct PolygonBufferIndices { 65 | pub(crate) polyhedron_indices: Vec, 66 | pub(crate) bounding_box_indices: Vec, 67 | } 68 | 69 | impl PolygonBufferIndices { 70 | /// Create an empty set of indices. 71 | /// 72 | /// Rendering the returned indices would be a no-op unless you call `extend` on it. This is a 73 | /// convenience method that you can use as the "zero" value to a `reduce`-like operation. 74 | pub fn new() -> PolygonBufferIndices { 75 | PolygonBufferIndices { 76 | polyhedron_indices: Vec::new(), 77 | bounding_box_indices: Vec::new(), 78 | } 79 | } 80 | 81 | /// Add all the polygons in `other` into this set of indices. 82 | /// 83 | /// After calling `extend`, rendering `this` will draw all the polygons previously in `this` as 84 | /// well as all the polygons in `other`. In other words, you can think of this as a 85 | /// "union"/"add all" operation. 86 | pub fn extend(&mut self, other: &PolygonBufferIndices) { 87 | self.polyhedron_indices.extend_from_slice( 88 | &other.polyhedron_indices, 89 | ); 90 | self.bounding_box_indices.extend_from_slice( 91 | &other.bounding_box_indices, 92 | ); 93 | } 94 | 95 | /// Prepare these indices for rendering. 96 | pub fn as_renderable, R: gfx::Resources>( 97 | &self, 98 | factory: &mut F, 99 | ) -> RenderablePolygonIndices { 100 | RenderablePolygonIndices::new(factory, &self) 101 | } 102 | } 103 | 104 | /// A polygon with a bounding box. 105 | /// 106 | /// This struct implements `From`, so for GIS applications you can instantiate this 107 | /// from any `geo::Polygon`. 108 | #[derive(Clone, Debug)] 109 | pub struct Polygon { 110 | bounding_ring: [(f32, f32); 5], 111 | points: Vec<(f32, f32)>, 112 | } 113 | 114 | impl Polygon { 115 | /// Construct a Polygon from a bounding-box and set of points. 116 | /// 117 | /// `bounds` should be `[(min_x, max_x), (min_y, max_y)]`. 118 | /// 119 | /// `points` should be an exterior ring concatenated with a (possibly empty) set of interior 120 | /// rings, where a "ring" is a list of points where the first and last point are equal. 121 | /// 122 | /// The exterior ring of `points` should be *positively oriented*, i.e. it should go in 123 | /// counter-clockwise order. The interior rings should be *negatively oriented*. 124 | pub fn new(bounds: [(f32, f32); 2], points: Vec<(f32, f32)>) -> Polygon { 125 | let bounding_ring = [ 126 | (bounds[0].0, bounds[1].0), 127 | (bounds[0].1, bounds[1].0), 128 | (bounds[0].1, bounds[1].1), 129 | (bounds[0].0, bounds[1].1), 130 | (bounds[0].0, bounds[1].0), 131 | ]; 132 | 133 | Polygon { 134 | bounding_ring: bounding_ring, 135 | points: points, 136 | } 137 | } 138 | 139 | fn bounding_box_vertices<'a>(&'a self) -> Box<'a + Iterator> { 140 | Box::new(Self::prism_vertices(&self.bounding_ring)) 141 | } 142 | 143 | fn bounding_box_indices(&self) -> Box> { 144 | Self::prism_indices(5) 145 | } 146 | 147 | fn polyhedron_vertices<'a>(&'a self) -> Box<'a + Iterator> { 148 | Self::prism_vertices(&self.points) 149 | } 150 | 151 | fn polyhedron_indices(&self) -> Box> { 152 | Self::prism_indices(self.points.len() as u32) 153 | } 154 | 155 | fn prism_vertices<'a>(points: &'a [(f32, f32)]) -> Box<'a + Iterator> { 156 | Box::new(points.iter().flat_map(move |&(x, y)| { 157 | let below = Vertex { position: [x, y, 0.0] }; 158 | let above = Vertex { position: [x, y, 1.0] }; 159 | vec![below, above] 160 | })) 161 | } 162 | 163 | fn prism_indices(num_points: u32) -> Box> { 164 | Box::new((0..num_points).flat_map(move |index| { 165 | let below_index = 2 * index; 166 | let above_index = below_index + 1; 167 | let after_below_index = 2 * ((1 + index) % num_points); 168 | let after_above_index = after_below_index + 1; 169 | 170 | // When on an exterior ring, whose points are in counter-clockwise orientation, 171 | // this face should face outward. 172 | // 173 | // For interior rings, with clockwise orientation, this face should face inward. 174 | let mut indices = vec![ 175 | below_index, 176 | after_below_index, 177 | above_index, 178 | after_below_index, 179 | after_above_index, 180 | above_index, 181 | ]; 182 | 183 | if index != 0 && index != num_points - 1 { 184 | // The top faces should face upward; the bottom faces, downward. 185 | let cap_triangles = vec![ 186 | 0, 187 | after_below_index, 188 | below_index, 189 | 1, 190 | above_index, 191 | after_above_index, 192 | ]; 193 | 194 | indices.extend(cap_triangles); 195 | } 196 | 197 | indices 198 | })) 199 | } 200 | } 201 | 202 | impl From> for Polygon { 203 | fn from(polygon: geo::Polygon) -> Polygon { 204 | let bounding_box = polygon.bbox().unwrap(); 205 | let bounds = [ 206 | (bounding_box.xmin, bounding_box.xmax), 207 | (bounding_box.ymin, bounding_box.ymax), 208 | ]; 209 | 210 | let mut points = Vec::new(); 211 | points.extend(polygon.exterior.into_iter().map(|point| (point.x(), point.y()))); 212 | 213 | for interior in polygon.interiors { 214 | points.extend(interior.into_iter().map(|point| (point.x(), point.y()))); 215 | } 216 | 217 | Polygon::new(bounds, points) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | use gfx; 2 | use gfx::traits::FactoryExt; 3 | 4 | use polygon::*; 5 | use vertex::Vertex; 6 | 7 | gfx_pipeline!(z_fail_polyhedron_pipeline { 8 | mvp: gfx::Global<[[f32; 4]; 4]> = "u_mvp", 9 | vertex_buffer: gfx::VertexBuffer = (), 10 | out_color: gfx::BlendTarget = ( 11 | "o_color", 12 | gfx::state::ColorMask::all(), 13 | gfx::preset::blend::ALPHA, 14 | ), 15 | out_depth_stencil: gfx::DepthStencilTarget = ( 16 | gfx::preset::depth::LESS_EQUAL_TEST, 17 | gfx::state::Stencil { 18 | front: gfx::state::StencilSide { 19 | fun: gfx::state::Comparison::Always, 20 | mask_read: 255, 21 | mask_write: 255, 22 | op_fail: gfx::state::StencilOp::Keep, 23 | op_depth_fail: gfx::state::StencilOp::DecrementWrap, 24 | op_pass: gfx::state::StencilOp::Keep, 25 | }, 26 | back: gfx::state::StencilSide { 27 | fun: gfx::state::Comparison::Always, 28 | mask_read: 255, 29 | mask_write: 255, 30 | op_fail: gfx::state::StencilOp::Keep, 31 | op_depth_fail: gfx::state::StencilOp::IncrementWrap, 32 | op_pass: gfx::state::StencilOp::Keep, 33 | }, 34 | }, 35 | ), 36 | }); 37 | 38 | gfx_pipeline!(z_fail_bounding_box_pipeline { 39 | out_color: gfx::BlendTarget = ( 40 | "o_color", 41 | gfx::state::ColorMask::all(), 42 | gfx::preset::blend::ALPHA, 43 | ), 44 | mvp: gfx::Global<[[f32; 4]; 4]> = "u_mvp", 45 | color: gfx::Global<[f32; 4]> = "u_color", 46 | vertex_buffer: gfx::VertexBuffer = (), 47 | out_depth_stencil: gfx::DepthStencilTarget = ( 48 | gfx::preset::depth::PASS_TEST, 49 | gfx::state::Stencil::new( 50 | // A fragment is only "inside" the polyhedron, and thus supposed to be drawn, if the 51 | // stencil buffer is nonzero at that point. 52 | gfx::state::Comparison::NotEqual, 53 | 255, 54 | ( 55 | // An important property of the stencil is that it is all zeroes after this 56 | // pipeline runs, so that the next draw doesn't need to clear the stencil first. 57 | // 58 | // If the stencil test fails, the value is zero and thus should be kept. 59 | gfx::state::StencilOp::Keep, 60 | // This never happens, because the depth test always passes. 61 | gfx::state::StencilOp::Keep, 62 | // The stencil test passed, so the value should be reset to zero. 63 | gfx::state::StencilOp::Replace, 64 | ), 65 | ), 66 | ), 67 | }); 68 | 69 | /// Drives graphics operations. 70 | /// 71 | /// This struct contains the shaders and stencil operations necessary to render draped polygons 72 | /// onto a terrain. 73 | #[derive(Clone, Debug)] 74 | pub struct DrapingRenderer { 75 | polyhedron_pso: gfx::pso::PipelineState, 76 | bounding_box_pso: gfx::pso::PipelineState, 77 | } 78 | 79 | impl DrapingRenderer { 80 | /// Set up the pipeline state objects needed for rendering draped polygons. 81 | pub fn new>(factory: &mut F) -> DrapingRenderer { 82 | DrapingRenderer { 83 | polyhedron_pso: Self::polyhedron_pso(factory), 84 | bounding_box_pso: Self::bounding_box_pso(factory), 85 | } 86 | } 87 | 88 | /// Render polygons in `buffer` using `indices` to choose the polygons. 89 | /// 90 | /// The depth buffer in `depth_stencil_target` should contain the depth values of your terrain 91 | /// -- in other words, draw your terrain just before you call this function, and make sure you 92 | /// don't clear the buffer until after rendering all the polygons you wish to draw. 93 | /// 94 | /// In addition, the stencil buffer should be cleared to zero before calling this function. The 95 | /// stencil buffer is guaranteed to remain zero after each call, so there is no need to clear 96 | /// the stencil buffer between calls to this function. 97 | /// 98 | /// `mvp` should be a model-view-projection matrix. *You probably want to use a different `mvp` 99 | /// than what you're normally using.* The polygons will only render onto terrain with z-values 100 | /// between 0 and 1; you should apply a transformation to alter these z-bounds. For example, if 101 | /// your terrain is bounded in height between `z_min` and `z_max`, your mvp use you from this 102 | /// library might be constructed as: 103 | /// 104 | /// ```rust,compile_fail 105 | /// // With the relevant imports, this is working code when using the `cgmath` crate. 106 | /// 107 | /// let translate_z = Matrix4::from_translation([0.0, 0.0, min_z].into()); 108 | /// let stretch_z = Matrix4::from_nonuniform_scale(1.0, 1.0, max_z - min_z); 109 | /// let draping_mvp = usual_mvp * translate_z * stretch_z; 110 | /// ``` 111 | pub fn render>( 112 | &self, 113 | encoder: &mut gfx::Encoder, 114 | render_target: gfx::handle::RenderTargetView, 115 | depth_stencil_target: gfx::handle::DepthStencilView, 116 | mvp: [[f32; 4]; 4], 117 | color: [f32; 4], 118 | buffer: &RenderablePolygonBuffer, 119 | indices: &RenderablePolygonIndices, 120 | ) { 121 | let polyhedron_bundle = gfx::Bundle { 122 | pso: self.polyhedron_pso.clone(), 123 | slice: indices.polyhedron_slice.clone(), 124 | data: z_fail_polyhedron_pipeline::Data { 125 | mvp: mvp, 126 | out_color: render_target.clone(), 127 | out_depth_stencil: (depth_stencil_target.clone(), (0, 0)), 128 | vertex_buffer: buffer.polyhedron_vertex_buffer.clone(), 129 | }, 130 | }; 131 | 132 | let bounding_box_bundle = gfx::Bundle { 133 | pso: self.bounding_box_pso.clone(), 134 | slice: indices.bounding_box_slice.clone(), 135 | data: z_fail_bounding_box_pipeline::Data { 136 | color: color, 137 | mvp: mvp, 138 | out_color: render_target.clone(), 139 | out_depth_stencil: (depth_stencil_target.clone(), (0, 0)), 140 | vertex_buffer: buffer.bounding_box_vertex_buffer.clone(), 141 | }, 142 | }; 143 | 144 | polyhedron_bundle.encode(encoder); 145 | bounding_box_bundle.encode(encoder); 146 | } 147 | 148 | fn polyhedron_pso>( 149 | factory: &mut F, 150 | ) -> gfx::pso::PipelineState { 151 | let shaders = factory 152 | .create_shader_set( 153 | include_bytes!("shaders/polyhedron.vert"), 154 | include_bytes!("shaders/polyhedron.frag"), 155 | ) 156 | .unwrap(); 157 | 158 | let rasterizer = gfx::state::Rasterizer::new_fill(); 159 | 160 | factory 161 | .create_pipeline_state( 162 | &shaders, 163 | gfx::Primitive::TriangleList, 164 | rasterizer, 165 | z_fail_polyhedron_pipeline::new(), 166 | ) 167 | .unwrap() 168 | } 169 | 170 | fn bounding_box_pso>( 171 | factory: &mut F, 172 | ) -> gfx::pso::PipelineState { 173 | let shaders = factory 174 | .create_shader_set( 175 | include_bytes!("shaders/bounding_box.vert"), 176 | include_bytes!("shaders/bounding_box.frag"), 177 | ) 178 | .unwrap(); 179 | 180 | let rasterizer = gfx::state::Rasterizer { 181 | cull_face: gfx::state::CullFace::Front, 182 | ..gfx::state::Rasterizer::new_fill() 183 | }; 184 | 185 | factory 186 | .create_pipeline_state( 187 | &shaders, 188 | gfx::Primitive::TriangleList, 189 | rasterizer, 190 | z_fail_bounding_box_pipeline::new(), 191 | ) 192 | .unwrap() 193 | } 194 | } 195 | 196 | /// A set of vertex buffer handles ready for rendering. 197 | #[derive(Clone, Debug)] 198 | pub struct RenderablePolygonBuffer { 199 | polyhedron_vertex_buffer: gfx::handle::Buffer, 200 | bounding_box_vertex_buffer: gfx::handle::Buffer, 201 | } 202 | 203 | impl RenderablePolygonBuffer { 204 | /// Prepare a `PolygonBuffer` for rendering. 205 | pub fn new>( 206 | factory: &mut F, 207 | buffer: &PolygonBuffer, 208 | ) -> RenderablePolygonBuffer { 209 | RenderablePolygonBuffer { 210 | polyhedron_vertex_buffer: factory.create_vertex_buffer(&buffer.polyhedron_vertices), 211 | bounding_box_vertex_buffer: factory.create_vertex_buffer(&buffer.bounding_box_vertices), 212 | } 213 | } 214 | } 215 | 216 | /// A set of index buffer handles ready for rendering. 217 | #[derive(Clone, Debug)] 218 | pub struct RenderablePolygonIndices { 219 | polyhedron_slice: gfx::Slice, 220 | bounding_box_slice: gfx::Slice, 221 | } 222 | 223 | impl RenderablePolygonIndices { 224 | /// Prepare a `PolygonBufferIndices` for rendering. 225 | pub fn new>( 226 | factory: &mut F, 227 | indices: &PolygonBufferIndices, 228 | ) -> RenderablePolygonIndices { 229 | RenderablePolygonIndices { 230 | polyhedron_slice: Self::create_slice(factory, &indices.polyhedron_indices), 231 | bounding_box_slice: Self::create_slice(factory, &indices.bounding_box_indices), 232 | } 233 | } 234 | 235 | fn create_slice>(factory: &mut F, indices: &[u32]) -> gfx::Slice { 236 | gfx::Slice { 237 | start: 0, 238 | end: indices.len() as u32, 239 | base_vertex: 0, 240 | instances: None, 241 | buffer: factory.create_index_buffer(indices), 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/shaders/bounding_box.frag: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | uniform vec4 u_color; 4 | 5 | out vec4 o_color; 6 | 7 | void main() { 8 | o_color = u_color; 9 | } 10 | -------------------------------------------------------------------------------- /src/shaders/bounding_box.vert: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | uniform mat4 u_mvp; 4 | 5 | in vec3 a_position; 6 | 7 | void main() { 8 | gl_Position = u_mvp * vec4(a_position, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /src/shaders/polyhedron.frag: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | out vec4 o_color; 4 | 5 | void main() { 6 | o_color = vec4(0.0, 0.0, 0.0, 0.0); 7 | } 8 | -------------------------------------------------------------------------------- /src/shaders/polyhedron.vert: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | uniform mat4 u_mvp; 4 | 5 | in vec3 a_position; 6 | 7 | void main() { 8 | gl_Position = u_mvp * vec4(a_position, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /src/vertex.rs: -------------------------------------------------------------------------------- 1 | #[cfg_attr(rustfmt, rustfmt_skip)] 2 | gfx_vertex_struct!(Vertex { 3 | position: [f32; 3] = "a_position", 4 | }); 5 | 6 | --------------------------------------------------------------------------------