Cleaned up implementation, addressed review comments

This commit is contained in:
Joshua Barretto 2023-03-01 12:54:43 +00:00
parent 81ec1f726c
commit 175ae0da7b
8 changed files with 119 additions and 77 deletions

View File

@ -463,8 +463,8 @@ pub fn generate_mesh<'a>(
create_opaque(atlas_pos, pos, norm, meta)
},
);
let max_alt = mesh_delta.z + max_z.unwrap_or(0.0);
let min_alt = mesh_delta.z + min_z.unwrap_or(0.0);
let max_alt = mesh_delta.z + max_z.expect("quad had no vertices?");
let min_alt = mesh_delta.z + min_z.expect("quad had no vertices?");
if max_alt < deep_alt {
opaque_deep.push(quad);

View File

@ -18,7 +18,6 @@ impl<'a, T: Copy + Pod> SubInstances<'a, T> {
self.buf.slice(start..end)
}
#[allow(clippy::len_without_is_empty)]
pub fn count(&self) -> u32 { self.inst_range.end - self.inst_range.start }
}

View File

@ -54,7 +54,7 @@ pub use self::{
TerrainDrawer, TerrainShadowDrawer, ThirdPassDrawer, TrailDrawer,
TransparentPassDrawer, UiDrawer, VolumetricPassDrawer,
},
AltIndices, ColLightInfo, Renderer,
AltIndices, ColLightInfo, CullingMode, Renderer,
},
texture::Texture,
};

View File

@ -1555,9 +1555,29 @@ fn create_quad_index_buffer_u32(device: &wgpu::Device, vert_length: usize) -> Bu
/// Terrain-related buffers segment themselves by depth to allow us to do
/// primitive occlusion culling based on whether the camera is underground or
/// not. This struct specifies the buffer offsets at whcih various layers start
/// not. This struct specifies the buffer offsets at which various layers start
/// and end.
///
/// 'Deep' structures appear within the range `0..deep_end`.
///
/// 'Shallow' structures appear within the range `deep_end..underground_end`.
///
/// 'Surface' structures appear within the range `underground_end..`.
pub struct AltIndices {
pub deep_end: usize,
pub underground_end: usize,
}
/// The mode with which culling based on the camera position relative to the
/// terrain is performed.
#[derive(Copy, Clone)]
pub enum CullingMode {
/// We need to render all elements of the given structure
None,
/// We only need to render surface and shallow (i.e: in the overlapping
/// region) elements of the structure
Surface,
/// We only need to render shallow (i.e: in the overlapping region) and deep
/// elements of the structure
Underground,
}

View File

@ -7,7 +7,7 @@ use super::{
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, shadow,
skybox, sprite, terrain, trail, ui, ColLights, GlobalsBindGroup,
},
AltIndices,
AltIndices, CullingMode,
},
rain_occlusion_map::{RainOcclusionMap, RainOcclusionMapRenderer},
Renderer, ShadowMap, ShadowMapRenderer,
@ -796,20 +796,22 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainShadowDrawer<'pass_ref, 'pass> {
model: &'data Model<terrain::Vertex>,
locals: &'data terrain::BoundLocals,
alt_indices: &'data AltIndices,
is_underground: Option<bool>,
culling_mode: CullingMode,
) {
let index_range = match culling_mode {
// Don't bother rendering shadows when underground
// TODO: Does this break point shadows in certain cases?
CullingMode::Underground => return, //0..alt_indices.underground_end as u32,
CullingMode::Surface => alt_indices.deep_end as u32..model.len() as u32,
CullingMode::None => 0..model.len() as u32,
};
// Don't render anything if there's nothing to render!
if is_underground == Some(true)
|| (alt_indices.deep_end == model.len() && is_underground == Some(false))
{
if index_range.is_empty() {
return;
}
let submodel = match is_underground {
Some(true) => model.submodel(0..alt_indices.underground_end as u32),
Some(false) => model.submodel(alt_indices.deep_end as u32..model.len() as u32),
None => model.submodel(0..model.len() as u32),
};
let submodel = model.submodel(index_range);
self.render_pass.set_bind_group(1, &locals.bind_group, &[]);
self.render_pass.set_vertex_buffer(0, submodel.buf());
@ -992,15 +994,21 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
col_lights: &'data Arc<ColLights<terrain::Locals>>,
locals: &'data terrain::BoundLocals,
alt_indices: &'data AltIndices,
is_underground: Option<bool>,
culling_mode: CullingMode,
) {
let index_range = match culling_mode {
CullingMode::Underground => 0..alt_indices.underground_end as u32,
CullingMode::Surface => alt_indices.deep_end as u32..model.len() as u32,
CullingMode::None => 0..model.len() as u32,
};
// Don't render anything if there's nothing to render!
if (alt_indices.underground_end == 0 && is_underground == Some(true))
|| (alt_indices.deep_end == model.len() && is_underground == Some(false))
{
if index_range.is_empty() {
return;
}
let submodel = model.submodel(index_range);
if self.col_lights
// Check if we are still using the same atlas texture as the previous drawn
// chunk
@ -1014,12 +1022,6 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
self.render_pass.set_bind_group(3, &locals.bind_group, &[]);
let submodel = match is_underground {
Some(true) => model.submodel(0..alt_indices.underground_end as u32),
Some(false) => model.submodel(alt_indices.deep_end as u32..model.len() as u32),
None => model.submodel(0..model.len() as u32),
};
self.render_pass.set_vertex_buffer(0, submodel.buf());
self.render_pass
.draw_indexed(0..submodel.len() / 4 * 6, 0, 0..1);
@ -1060,25 +1062,23 @@ impl<'pass_ref, 'pass: 'pass_ref> SpriteDrawer<'pass_ref, 'pass> {
terrain_locals: &'data terrain::BoundLocals,
instances: &'data Instances<sprite::Instance>,
alt_indices: &'data AltIndices,
is_underground: Option<bool>,
culling_mode: CullingMode,
) {
let instance_range = match culling_mode {
CullingMode::Underground => 0..alt_indices.underground_end as u32,
CullingMode::Surface => alt_indices.deep_end as u32..instances.count() as u32,
CullingMode::None => 0..instances.count() as u32,
};
// Don't render anything if there's nothing to render!
if (alt_indices.underground_end == 0 && is_underground == Some(true))
|| (alt_indices.deep_end == instances.count() && is_underground == Some(false))
{
if instance_range.is_empty() {
return;
}
self.render_pass
.set_bind_group(3, &terrain_locals.bind_group, &[]);
let subinstances = match is_underground {
Some(true) => instances.subinstances(0..alt_indices.underground_end as u32),
Some(false) => {
instances.subinstances(alt_indices.deep_end as u32..instances.count() as u32)
},
None => instances.subinstances(0..instances.count() as u32),
};
let subinstances = instances.subinstances(instance_range);
self.render_pass.set_vertex_buffer(0, subinstances.buf());
self.render_pass.draw_indexed(

View File

@ -1,8 +1,8 @@
use crate::{
render::{
pipelines::lod_terrain::{LodData, Vertex},
FirstPassDrawer, Instances, LodObjectInstance, LodObjectVertex, LodTerrainVertex, Mesh,
Model, Quad, Renderer, Tri,
CullingMode, FirstPassDrawer, Instances, LodObjectInstance, LodObjectVertex,
LodTerrainVertex, Mesh, Model, Quad, Renderer, Tri,
},
scene::{camera, Camera},
settings::Settings,
@ -200,17 +200,19 @@ impl Lod {
);
}
pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) {
pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>, culling_mode: CullingMode) {
if let Some((_, model)) = self.model.as_ref() {
drawer.draw_lod_terrain(model);
}
// Draw LoD objects
let mut drawer = drawer.draw_lod_objects();
for groups in self.zone_objects.values() {
for (kind, group) in groups.iter().filter(|(_, g)| g.visible) {
if let Some(model) = self.object_data.get(kind) {
drawer.draw(model, &group.instances);
if !matches!(culling_mode, CullingMode::Underground) {
// Draw LoD objects
let mut drawer = drawer.draw_lod_objects();
for groups in self.zone_objects.values() {
for (kind, group) in groups.iter().filter(|(_, g)| g.visible) {
if let Some(model) = self.object_data.get(kind) {
drawer.draw(model, &group.instances);
}
}
}
}

View File

@ -21,9 +21,9 @@ pub use self::{
use crate::{
audio::{ambient, ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
render::{
create_skybox_mesh, CloudsLocals, Consts, Drawer, GlobalModel, Globals, GlobalsBindGroup,
Light, Model, PointLightMatrix, PostProcessLocals, RainOcclusionLocals, Renderer, Shadow,
ShadowLocals, SkyboxVertex,
create_skybox_mesh, CloudsLocals, Consts, CullingMode, Drawer, GlobalModel, Globals,
GlobalsBindGroup, Light, Model, PointLightMatrix, PostProcessLocals, RainOcclusionLocals,
Renderer, Shadow, ShadowLocals, SkyboxVertex,
},
settings::Settings,
window::{AnalogGameInput, Event},
@ -1246,13 +1246,17 @@ impl Scene {
let focus_pos = self.camera.get_focus_pos();
let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
let is_rain = state.max_weather_near(cam_pos.xy()).rain > RAIN_THRESHOLD;
let is_underground = scene_data
let culling_mode = if scene_data
.state
.terrain()
.get_key(scene_data.state.terrain().pos_key(cam_pos.as_()))
.map_or(false, |c| {
cam_pos.z < c.meta().alt() - (terrain::SHALLOW_ALT + terrain::DEEP_ALT) / 2.0
});
cam_pos.z < c.meta().alt() - terrain::UNDERGROUND_ALT
}) {
CullingMode::Underground
} else {
CullingMode::Surface
};
let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
@ -1265,7 +1269,7 @@ impl Scene {
self.terrain.render_shadows(
&mut shadow_pass.draw_terrain_shadows(),
focus_pos,
is_underground,
culling_mode,
);
// Render figure directed shadows.
@ -1316,7 +1320,7 @@ impl Scene {
);
self.terrain
.render(&mut first_pass, focus_pos, is_underground);
.render(&mut first_pass, focus_pos, culling_mode);
self.figure_mgr.render(
&mut first_pass.draw_figures(),
@ -1326,7 +1330,7 @@ impl Scene {
camera_data,
);
self.lod.render(&mut first_pass);
self.lod.render(&mut first_pass, culling_mode);
// Render the skybox.
first_pass.draw_skybox(&self.skybox.model);
@ -1337,7 +1341,7 @@ impl Scene {
focus_pos,
cam_pos,
scene_data.sprite_render_distance,
is_underground,
culling_mode,
);
// Render particle effects.

View File

@ -10,9 +10,10 @@ use crate::{
},
render::{
pipelines::{self, ColLights},
AltIndices, ColLightInfo, FirstPassDrawer, FluidVertex, GlobalModel, Instances, LodData,
Mesh, Model, RenderError, Renderer, SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex,
SpriteVerts, TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE,
AltIndices, ColLightInfo, CullingMode, FirstPassDrawer, FluidVertex, GlobalModel,
Instances, LodData, Mesh, Model, RenderError, Renderer, SpriteGlobalsBindGroup,
SpriteInstance, SpriteVertex, SpriteVerts, TerrainLocals, TerrainShadowDrawer,
TerrainVertex, SPRITE_VERT_PAGE_SIZE,
},
};
@ -103,8 +104,19 @@ pub struct TerrainChunkData {
alt_indices: AltIndices,
}
/// The depth at which the intermediate zone between underground and surface
/// begins
pub const SHALLOW_ALT: f32 = 24.0;
/// The depth at which the intermediate zone between underground and surface
/// ends
pub const DEEP_ALT: f32 = 96.0;
/// The depth below the surface altitude at which the camera switches from
/// displaying surface elements to underground elements
pub const UNDERGROUND_ALT: f32 = (SHALLOW_ALT + DEEP_ALT) * 0.5;
// The distance (in chunks) within which all levels of the chunks will be drawn
// to minimise cull-related popping.
const NEVER_CULL_DIST: i32 = 3;
#[derive(Copy, Clone)]
struct ChunkMeshState {
@ -861,6 +873,10 @@ impl<V: RectRasterableVol> Terrain<V> {
}
/// Maintain terrain data. To be called once per tick.
///
/// The returned visible bounding volumes take into account the current
/// camera position (i.e: when underground, surface structures will be
/// culled from the volume).
pub fn maintain(
&mut self,
renderer: &mut Renderer,
@ -901,6 +917,7 @@ impl<V: RectRasterableVol> Terrain<V> {
span!(_guard, "maintain", "Terrain::maintain");
let current_tick = scene_data.tick;
let current_time = scene_data.state.get_time();
// The visible bounding box of all chunks, not including culled regions
let mut visible_bounding_box: Option<Aabb<f32>> = None;
// Add any recently created or changed chunks to the list of chunks to be
@ -1327,7 +1344,7 @@ impl<V: RectRasterableVol> Terrain<V> {
let chunk_max = [
chunk_pos.x + chunk_sz,
chunk_pos.y + chunk_sz,
chunk.z_bounds.1,
chunk.sun_occluder_z_bounds.1,
];
let (in_frustum, last_plane_index) = AABB::new(chunk_min, chunk_max)
@ -1335,15 +1352,15 @@ impl<V: RectRasterableVol> Terrain<V> {
chunk.frustum_last_plane_index = last_plane_index;
chunk.visible.in_frustum = in_frustum;
let chunk_box = Aabb {
min: Vec3::from(chunk_min),
max: Vec3::from(chunk_max),
let chunk_area = Aabr {
min: chunk_pos,
max: chunk_pos + chunk_sz,
};
if in_frustum {
let visible_box = Aabb {
min: chunk_box.min.xy().with_z(chunk.sun_occluder_z_bounds.0),
max: chunk_box.max.xy().with_z(chunk.sun_occluder_z_bounds.1),
min: chunk_area.min.with_z(chunk.sun_occluder_z_bounds.0),
max: chunk_area.max.with_z(chunk.sun_occluder_z_bounds.1),
};
visible_bounding_box = visible_bounding_box
.map(|e| e.union(visible_box))
@ -1538,7 +1555,7 @@ impl<V: RectRasterableVol> Terrain<V> {
&'a self,
drawer: &mut TerrainShadowDrawer<'_, 'a>,
focus_pos: Vec3<f32>,
is_underground: bool,
culling_mode: CullingMode,
) {
span!(_guard, "render_shadows", "Terrain::render_shadows");
let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
@ -1568,7 +1585,7 @@ impl<V: RectRasterableVol> Terrain<V> {
))
})
.for_each(|(model, locals, alt_indices)| {
drawer.draw(model, locals, alt_indices, Some(is_underground))
drawer.draw(model, locals, alt_indices, culling_mode)
});
}
@ -1598,7 +1615,7 @@ impl<V: RectRasterableVol> Terrain<V> {
&chunk.locals,
&chunk.alt_indices,
)))
.for_each(|(model, locals, alt_indices)| drawer.draw(model, locals, alt_indices, None));
.for_each(|(model, locals, alt_indices)| drawer.draw(model, locals, alt_indices, CullingMode::None));
}
pub fn chunks_for_point_shadows(
@ -1640,7 +1657,7 @@ impl<V: RectRasterableVol> Terrain<V> {
&'a self,
drawer: &mut FirstPassDrawer<'a>,
focus_pos: Vec3<f32>,
is_underground: bool,
culling_mode: CullingMode,
) {
span!(_guard, "render", "Terrain::render");
let mut drawer = drawer.draw_terrain();
@ -1667,12 +1684,12 @@ impl<V: RectRasterableVol> Terrain<V> {
})
.for_each(|(rpos, model, col_lights, locals, alt_indices)| {
// Always draw all of close chunks to avoid terrain 'popping'
let is_underground = if rpos.magnitude_squared() < 3i32.pow(2) {
None
let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
CullingMode::None
} else {
Some(is_underground)
culling_mode
};
drawer.draw(model, col_lights, locals, alt_indices, is_underground)
drawer.draw(model, col_lights, locals, alt_indices, culling_mode)
});
}
@ -1682,7 +1699,7 @@ impl<V: RectRasterableVol> Terrain<V> {
focus_pos: Vec3<f32>,
cam_pos: Vec3<f32>,
sprite_render_distance: f32,
is_underground: bool,
culling_mode: CullingMode,
) {
span!(_guard, "render_translucent", "Terrain::render_translucent");
let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
@ -1740,17 +1757,17 @@ impl<V: RectRasterableVol> Terrain<V> {
};
// Always draw all of close chunks to avoid terrain 'popping'
let is_underground = if rpos.magnitude_squared() < 3i32.pow(2) {
None
let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
CullingMode::None
} else {
Some(is_underground)
culling_mode
};
sprite_drawer.draw(
&chunk.locals,
&chunk.sprite_instances[lod_level].0,
&chunk.sprite_instances[lod_level].1,
is_underground,
culling_mode,
);
}
});