From 8da46b48acd12988db2e1d2aeab1416b41584ff8 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 6 Jun 2019 10:04:37 +0100 Subject: [PATCH] Added frustum culling for chunks --- Cargo.lock | 7 +++ voxygen/Cargo.toml | 1 + voxygen/src/scene/mod.rs | 16 ++++--- voxygen/src/scene/terrain.rs | 86 +++++++++++++++++++++++++++--------- 4 files changed, 81 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c16661e04..5b4d241c35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -782,6 +782,11 @@ name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "frustum_query" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -2635,6 +2640,7 @@ dependencies = [ "euc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "frustum_query 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "gfx 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_device_gl 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", "gfx_window_glutin 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2952,6 +2958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum frustum_query 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e1771c26abed26b2527d888742fffd27dab86d205bf4846748abf29c06ef5a05" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 56738e2c2b..d98b06017e 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -55,3 +55,4 @@ portpicker = "0.1" num = "0.2" backtrace = "0.3" rand = "0.5" +frustum_query = "0.1.2" diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 4f74fae068..64e37f4947 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -151,7 +151,14 @@ impl Scene { .expect("Failed to update global constants"); // Maintain the terrain. - self.terrain.maintain(renderer, client); + self.terrain.maintain( + renderer, + client, + self.camera.get_focus_pos(), + self.loaded_distance, + view_mat, + proj_mat, + ); // Maintain the figures. self.figure_mgr.maintain(renderer, client); @@ -166,12 +173,7 @@ impl Scene { renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); // Render terrain and figures. - self.terrain.render( - renderer, - &self.globals, - self.camera.get_focus_pos(), - self.loaded_distance, - ); + self.terrain.render(renderer, &self.globals); self.figure_mgr.render(renderer, client, &self.globals); renderer.render_post_process( diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index dfa13703a5..19d4251a2d 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -8,13 +8,16 @@ use common::{ vol::{SampleVol, VolSize}, volumes::vol_map_2d::VolMap2dErr, }; -use std::{collections::HashMap, i32, sync::mpsc, time::Duration}; +use frustum_query::frustum::Frustum; +use std::{collections::HashMap, i32, ops::Mul, sync::mpsc, time::Duration}; use vek::*; struct TerrainChunk { // GPU data model: Model, locals: Consts, + visible: bool, + z_bounds: (f32, f32), } struct ChunkMeshState { @@ -26,6 +29,7 @@ struct ChunkMeshState { /// A type produced by mesh worker threads corresponding to the position and mesh of a chunk. struct MeshWorkerResponse { pos: Vec2, + z_bounds: (f32, f32), mesh: Mesh, started_tick: u64, } @@ -33,12 +37,14 @@ struct MeshWorkerResponse { /// Function executed by worker threads dedicated to chunk meshing. fn mesh_worker( pos: Vec2, + z_bounds: (f32, f32), started_tick: u64, volume: >>::Sample, range: Aabb, ) -> MeshWorkerResponse { MeshWorkerResponse { pos, + z_bounds, mesh: volume.generate_mesh(range), started_tick, } @@ -70,7 +76,15 @@ impl Terrain { } /// Maintain terrain data. To be called once per tick. - pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { + pub fn maintain( + &mut self, + renderer: &mut Renderer, + client: &Client, + focus_pos: Vec3, + loaded_distance: f32, + view_mat: Mat4, + proj_mat: Mat4, + ) { let current_tick = client.get_tick(); // Add any recently created or changed chunks to the list of chunks to be meshed. @@ -165,7 +179,13 @@ impl Terrain { // Queue the worker thread. client.thread_pool().execute(move || { - let _ = send.send(mesh_worker(pos, current_tick, volume, aabb)); + let _ = send.send(mesh_worker( + pos, + (min_z as f32, max_z as f32), + current_tick, + volume, + aabb, + )); }); todo.active_worker = true; } @@ -194,6 +214,8 @@ impl Terrain { .into_array(), }]) .expect("Failed to upload chunk locals to the GPU!"), + visible: false, + z_bounds: response.z_bounds, }, ); } @@ -202,28 +224,48 @@ impl Terrain { _ => {} } } - } - pub fn render( - &self, - renderer: &mut Renderer, - globals: &Consts, - focus_pos: Vec3, - loaded_distance: f32, - ) { - for (pos, chunk) in &self.chunks { - // Limit focus_pos to chunk bounds - let chunk_pos = pos.map2(TerrainChunkSize::SIZE.into(), |e, sz: u32| { - e as f32 * sz as f32 - }); - let nearest_in_chunk = Vec2::from(focus_pos).clamped( - chunk_pos, - chunk_pos + Vec2::from(TerrainChunkSize::SIZE).map(|e: u32| e as f32), + // Construct view frustum + let frustum = Frustum::from_modelview_and_projection( + &view_mat.into_col_array(), + &proj_mat.into_col_array(), + ); + + // Update chunk visibility + let chunk_sz = TerrainChunkSize::SIZE.x as f32; + for (pos, chunk) in &mut self.chunks { + let chunk_pos = pos.map(|e| e as f32 * chunk_sz); + + // Limit focus_pos to chunk bounds and ensure the chunk is within the fog boundary + let nearest_in_chunk = Vec2::from(focus_pos).clamped(chunk_pos, chunk_pos + chunk_sz); + let in_range = Vec2::::from(focus_pos).distance_squared(nearest_in_chunk) + < loaded_distance.powf(2.0); + + // Ensure the chunk is within the view frustrum + let chunk_mid = Vec3::new( + chunk_pos.x + chunk_sz / 2.0, + chunk_pos.y + chunk_sz / 2.0, + (chunk.z_bounds.0 + chunk.z_bounds.1) * 0.5, + ); + let chunk_radius = (chunk.z_bounds.1 - chunk.z_bounds.0) + .max(chunk_sz / 2.0) + .powf(2.0) + .mul(2.0) + .sqrt(); + let in_frustum = frustum.sphere_intersecting( + &chunk_mid.x, + &chunk_mid.y, + &chunk_mid.z, + &chunk_radius, ); - if Vec2::::from(focus_pos).distance_squared(nearest_in_chunk) - < loaded_distance.powf(2.0) - { + chunk.visible = in_range && in_frustum; + } + } + + pub fn render(&self, renderer: &mut Renderer, globals: &Consts) { + for (pos, chunk) in &self.chunks { + if chunk.visible { renderer.render_terrain_chunk(&chunk.model, globals, &chunk.locals); } }