mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/culling' into 'master'
Added basic culling of underground terrain and sprites See merge request veloren/veloren!3794
This commit is contained in:
commit
2fb20782e5
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Lantern glow for dropped lanterns.
|
||||
- Suggests commands when an invalid one is entered in chat and added Client-side commands to /help.
|
||||
- Moderator badge in the chat.
|
||||
- More aggressive scene culling based on camera position to improve performance.
|
||||
|
||||
### Changed
|
||||
- Bats move slower and use a simple proportional controller to maintain altitude
|
||||
|
@ -791,7 +791,7 @@
|
||||
),
|
||||
Apple: (
|
||||
bone0: (
|
||||
offset: (-5.5, -5.5, 0.0),
|
||||
offset: (-3.5, -3.5, 0.0),
|
||||
central: ("sprite.fruit.apple"),
|
||||
),
|
||||
bone1: (
|
||||
|
@ -496,7 +496,7 @@ Apple: Some((
|
||||
variations: [
|
||||
(
|
||||
model: "voxygen.voxel.sprite.fruit.apple",
|
||||
offset: (-4.0, -4.0, 2.0),
|
||||
offset: (-3.5, -3.5, 0.0),
|
||||
lod_axes: (1.0, 1.0, 1.0),
|
||||
),
|
||||
],
|
||||
|
@ -868,7 +868,7 @@ pub fn create_quad<O: Vertex, M>(
|
||||
draw_dim: Vec2<Vec3<f32>>,
|
||||
norm: Vec3<f32>,
|
||||
meta: &M,
|
||||
create_vertex: impl Fn(Vec2<u16>, Vec3<f32>, Vec3<f32>, &M) -> O,
|
||||
mut create_vertex: impl FnMut(Vec2<u16>, Vec3<f32>, Vec3<f32>, &M) -> O,
|
||||
) -> Quad<O> {
|
||||
Quad::new(
|
||||
create_vertex(atlas_pos, origin, norm, meta),
|
||||
|
@ -5,8 +5,8 @@ use crate::{
|
||||
greedy::{self, GreedyConfig, GreedyMesh},
|
||||
MeshGen,
|
||||
},
|
||||
render::{ColLightInfo, FluidVertex, Mesh, TerrainVertex},
|
||||
scene::terrain::BlocksOfInterest,
|
||||
render::{AltIndices, ColLightInfo, FluidVertex, Mesh, TerrainVertex, Vertex},
|
||||
scene::terrain::{BlocksOfInterest, DEEP_ALT, SHALLOW_ALT},
|
||||
};
|
||||
use common::{
|
||||
terrain::{Block, TerrainChunk},
|
||||
@ -238,6 +238,8 @@ pub fn generate_mesh<'a>(
|
||||
ColLightInfo,
|
||||
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
|
||||
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
|
||||
AltIndices,
|
||||
(f32, f32),
|
||||
),
|
||||
> {
|
||||
span!(
|
||||
@ -275,6 +277,12 @@ pub fn generate_mesh<'a>(
|
||||
let light = calc_light(true, SUNLIGHT, range, vol, core::iter::empty());
|
||||
let glow = calc_light(false, 0, range, vol, glow_blocks.into_iter());
|
||||
|
||||
let (underground_alt, deep_alt) = vol
|
||||
.get_key(vol.pos_key((range.min + range.max) / 2))
|
||||
.map_or((0.0, 0.0), |c| {
|
||||
(c.meta().alt() - SHALLOW_ALT, c.meta().alt() - DEEP_ALT)
|
||||
});
|
||||
|
||||
let mut opaque_limits = None::<Limits>;
|
||||
let mut fluid_limits = None::<Limits>;
|
||||
let mut air_limits = None::<Limits>;
|
||||
@ -424,7 +432,9 @@ pub fn generate_mesh<'a>(
|
||||
|
||||
let mut greedy =
|
||||
GreedyMesh::<guillotiere::SimpleAtlasAllocator>::new(max_size, greedy::general_config());
|
||||
let mut opaque_mesh = Mesh::new();
|
||||
let mut opaque_deep = Vec::new();
|
||||
let mut opaque_shallow = Vec::new();
|
||||
let mut opaque_surface = Vec::new();
|
||||
let mut fluid_mesh = Mesh::new();
|
||||
greedy.push(GreedyConfig {
|
||||
data: (),
|
||||
@ -438,15 +448,31 @@ pub fn generate_mesh<'a>(
|
||||
should_draw,
|
||||
push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta {
|
||||
FaceKind::Opaque(meta) => {
|
||||
opaque_mesh.push_quad(greedy::create_quad(
|
||||
let mut max_z = None;
|
||||
let mut min_z = None;
|
||||
let quad = greedy::create_quad(
|
||||
atlas_origin,
|
||||
dim,
|
||||
origin,
|
||||
draw_dim,
|
||||
norm,
|
||||
meta,
|
||||
|atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
|
||||
));
|
||||
|atlas_pos, pos, norm, &meta| {
|
||||
max_z = Some(max_z.map_or(pos.z, |z: f32| z.max(pos.z)));
|
||||
min_z = Some(min_z.map_or(pos.z, |z: f32| z.min(pos.z)));
|
||||
create_opaque(atlas_pos, pos, norm, meta)
|
||||
},
|
||||
);
|
||||
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);
|
||||
} else if min_alt > underground_alt {
|
||||
opaque_surface.push(quad);
|
||||
} else {
|
||||
opaque_shallow.push(quad);
|
||||
}
|
||||
},
|
||||
FaceKind::Fluid => {
|
||||
fluid_mesh.push_quad(greedy::create_quad(
|
||||
@ -472,8 +498,30 @@ pub fn generate_mesh<'a>(
|
||||
};
|
||||
let (col_lights, col_lights_size) = greedy.finalize();
|
||||
|
||||
let deep_end = opaque_deep.len()
|
||||
* if TerrainVertex::QUADS_INDEX.is_some() {
|
||||
4
|
||||
} else {
|
||||
6
|
||||
};
|
||||
let alt_indices = AltIndices {
|
||||
deep_end,
|
||||
underground_end: deep_end
|
||||
+ opaque_shallow.len()
|
||||
* if TerrainVertex::QUADS_INDEX.is_some() {
|
||||
4
|
||||
} else {
|
||||
6
|
||||
},
|
||||
};
|
||||
let sun_occluder_z_bounds = (underground_alt.max(bounds.min.z), bounds.max.z);
|
||||
|
||||
(
|
||||
opaque_mesh,
|
||||
opaque_deep
|
||||
.into_iter()
|
||||
.chain(opaque_shallow.into_iter())
|
||||
.chain(opaque_surface.into_iter())
|
||||
.collect(),
|
||||
fluid_mesh,
|
||||
Mesh::new(),
|
||||
(
|
||||
@ -481,6 +529,8 @@ pub fn generate_mesh<'a>(
|
||||
(col_lights, col_lights_size),
|
||||
Arc::new(light),
|
||||
Arc::new(glow),
|
||||
alt_indices,
|
||||
sun_occluder_z_bounds,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,25 @@
|
||||
use super::buffer::DynamicBuffer;
|
||||
use bytemuck::Pod;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Represents a set of instances that has been sent to the GPU.
|
||||
pub struct SubInstances<'a, T: Copy + Pod> {
|
||||
pub inst_range: Range<u32>,
|
||||
buf: &'a wgpu::Buffer,
|
||||
phantom_data: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Copy + Pod> SubInstances<'a, T> {
|
||||
pub(super) fn buf(&self) -> wgpu::BufferSlice<'a> {
|
||||
let start = self.inst_range.start as wgpu::BufferAddress
|
||||
* std::mem::size_of::<T>() as wgpu::BufferAddress;
|
||||
let end = self.inst_range.end as wgpu::BufferAddress
|
||||
* std::mem::size_of::<T>() as wgpu::BufferAddress;
|
||||
self.buf.slice(start..end)
|
||||
}
|
||||
|
||||
pub fn count(&self) -> u32 { self.inst_range.end - self.inst_range.start }
|
||||
}
|
||||
|
||||
/// Represents a mesh that has been sent to the GPU.
|
||||
pub struct Instances<T: Copy + Pod> {
|
||||
@ -15,6 +35,16 @@ impl<T: Copy + Pod> Instances<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a set of instances with a slice of a portion of these instances
|
||||
/// to send to the renderer.
|
||||
pub fn subinstances(&self, inst_range: Range<u32>) -> SubInstances<T> {
|
||||
SubInstances {
|
||||
inst_range,
|
||||
buf: self.buf(),
|
||||
phantom_data: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: count vs len naming scheme??
|
||||
pub fn count(&self) -> usize { self.buf.len() }
|
||||
|
||||
|
@ -124,7 +124,11 @@ impl<V: Vertex> IntoIterator for Mesh<V> {
|
||||
|
||||
impl<V: Vertex> FromIterator<Tri<V>> for Mesh<V> {
|
||||
fn from_iter<I: IntoIterator<Item = Tri<V>>>(tris: I) -> Self {
|
||||
tris.into_iter().fold(Self::new(), |mut this, tri| {
|
||||
let mut this = Self::new();
|
||||
let tris = tris.into_iter();
|
||||
let (lower, upper) = tris.size_hint();
|
||||
this.verts.reserve(3 * upper.unwrap_or(lower));
|
||||
tris.fold(this, |mut this, tri| {
|
||||
this.push_tri(tri);
|
||||
this
|
||||
})
|
||||
@ -133,7 +137,12 @@ impl<V: Vertex> FromIterator<Tri<V>> for Mesh<V> {
|
||||
|
||||
impl<V: Vertex> FromIterator<Quad<V>> for Mesh<V> {
|
||||
fn from_iter<I: IntoIterator<Item = Quad<V>>>(quads: I) -> Self {
|
||||
quads.into_iter().fold(Self::new(), |mut this, quad| {
|
||||
let mut this = Self::new();
|
||||
let quads = quads.into_iter();
|
||||
let (lower, upper) = quads.size_hint();
|
||||
this.verts
|
||||
.reserve(if V::QUADS_INDEX.is_some() { 4 } else { 6 } * upper.unwrap_or(lower));
|
||||
quads.fold(this, |mut this, quad| {
|
||||
this.push_quad(quad);
|
||||
this
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ pub use self::{
|
||||
TerrainDrawer, TerrainShadowDrawer, ThirdPassDrawer, TrailDrawer,
|
||||
TransparentPassDrawer, UiDrawer, VolumetricPassDrawer,
|
||||
},
|
||||
ColLightInfo, Renderer,
|
||||
AltIndices, ColLightInfo, CullingMode, Renderer,
|
||||
},
|
||||
texture::Texture,
|
||||
};
|
||||
|
@ -1552,3 +1552,32 @@ fn create_quad_index_buffer_u32(device: &wgpu::Device, vert_length: usize) -> Bu
|
||||
|
||||
Buffer::new(device, wgpu::BufferUsage::INDEX, &indices)
|
||||
}
|
||||
|
||||
/// 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 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,
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use super::{
|
||||
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, shadow,
|
||||
skybox, sprite, terrain, trail, ui, ColLights, GlobalsBindGroup,
|
||||
},
|
||||
AltIndices, CullingMode,
|
||||
},
|
||||
rain_occlusion_map::{RainOcclusionMap, RainOcclusionMapRenderer},
|
||||
Renderer, ShadowMap, ShadowMapRenderer,
|
||||
@ -794,11 +795,28 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainShadowDrawer<'pass_ref, 'pass> {
|
||||
&mut self,
|
||||
model: &'data Model<terrain::Vertex>,
|
||||
locals: &'data terrain::BoundLocals,
|
||||
alt_indices: &'data AltIndices,
|
||||
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 index_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let submodel = model.submodel(index_range);
|
||||
|
||||
self.render_pass.set_bind_group(1, &locals.bind_group, &[]);
|
||||
self.render_pass.set_vertex_buffer(0, model.buf().slice(..));
|
||||
self.render_pass.set_vertex_buffer(0, submodel.buf());
|
||||
self.render_pass
|
||||
.draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1);
|
||||
.draw_indexed(0..submodel.len() / 4 * 6, 0, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -975,7 +993,22 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
|
||||
model: &'data Model<terrain::Vertex>,
|
||||
col_lights: &'data Arc<ColLights<terrain::Locals>>,
|
||||
locals: &'data terrain::BoundLocals,
|
||||
alt_indices: &'data AltIndices,
|
||||
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 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
|
||||
@ -988,9 +1021,10 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
|
||||
};
|
||||
|
||||
self.render_pass.set_bind_group(3, &locals.bind_group, &[]);
|
||||
self.render_pass.set_vertex_buffer(0, model.buf().slice(..));
|
||||
|
||||
self.render_pass.set_vertex_buffer(0, submodel.buf());
|
||||
self.render_pass
|
||||
.draw_indexed(0..model.len() as u32 / 4 * 6, 0, 0..1);
|
||||
.draw_indexed(0..submodel.len() / 4 * 6, 0, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1027,16 +1061,30 @@ impl<'pass_ref, 'pass: 'pass_ref> SpriteDrawer<'pass_ref, 'pass> {
|
||||
&mut self,
|
||||
terrain_locals: &'data terrain::BoundLocals,
|
||||
instances: &'data Instances<sprite::Instance>,
|
||||
alt_indices: &'data AltIndices,
|
||||
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 instance_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.render_pass
|
||||
.set_bind_group(3, &terrain_locals.bind_group, &[]);
|
||||
|
||||
self.render_pass
|
||||
.set_vertex_buffer(0, instances.buf().slice(..));
|
||||
let subinstances = instances.subinstances(instance_range);
|
||||
|
||||
self.render_pass.set_vertex_buffer(0, subinstances.buf());
|
||||
self.render_pass.draw_indexed(
|
||||
0..sprite::VERT_PAGE_SIZE / 4 * 6,
|
||||
0,
|
||||
0..instances.count() as u32,
|
||||
0..subinstances.count(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +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 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::UNDERGROUND_ALT
|
||||
}) {
|
||||
CullingMode::Underground
|
||||
} else {
|
||||
CullingMode::Surface
|
||||
};
|
||||
|
||||
let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
|
||||
|
||||
@ -1255,8 +1266,11 @@ impl Scene {
|
||||
prof_span!("directed shadows");
|
||||
if let Some(mut shadow_pass) = drawer.shadow_pass() {
|
||||
// Render terrain directed shadows.
|
||||
self.terrain
|
||||
.render_shadows(&mut shadow_pass.draw_terrain_shadows(), focus_pos);
|
||||
self.terrain.render_shadows(
|
||||
&mut shadow_pass.draw_terrain_shadows(),
|
||||
focus_pos,
|
||||
culling_mode,
|
||||
);
|
||||
|
||||
// Render figure directed shadows.
|
||||
self.figure_mgr.render_shadows(
|
||||
@ -1305,7 +1319,8 @@ impl Scene {
|
||||
camera_data,
|
||||
);
|
||||
|
||||
self.terrain.render(&mut first_pass, focus_pos);
|
||||
self.terrain
|
||||
.render(&mut first_pass, focus_pos, culling_mode);
|
||||
|
||||
self.figure_mgr.render(
|
||||
&mut first_pass.draw_figures(),
|
||||
@ -1315,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);
|
||||
@ -1326,6 +1341,7 @@ impl Scene {
|
||||
focus_pos,
|
||||
cam_pos,
|
||||
scene_data.sprite_render_distance,
|
||||
culling_mode,
|
||||
);
|
||||
|
||||
// Render particle effects.
|
||||
|
@ -10,9 +10,10 @@ use crate::{
|
||||
},
|
||||
render::{
|
||||
pipelines::{self, ColLights},
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
@ -89,7 +90,7 @@ pub struct TerrainChunkData {
|
||||
col_lights: Arc<ColLights<pipelines::terrain::Locals>>,
|
||||
light_map: LightMapFn,
|
||||
glow_map: LightMapFn,
|
||||
sprite_instances: [Instances<SpriteInstance>; SPRITE_LOD_LEVELS],
|
||||
sprite_instances: [(Instances<SpriteInstance>, AltIndices); SPRITE_LOD_LEVELS],
|
||||
locals: pipelines::terrain::BoundLocals,
|
||||
pub blocks_of_interest: BlocksOfInterest,
|
||||
|
||||
@ -97,9 +98,26 @@ pub struct TerrainChunkData {
|
||||
can_shadow_point: bool,
|
||||
can_shadow_sun: bool,
|
||||
z_bounds: (f32, f32),
|
||||
sun_occluder_z_bounds: (f32, f32),
|
||||
frustum_last_plane_index: u8,
|
||||
|
||||
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 {
|
||||
pos: Vec2<i32>,
|
||||
@ -112,18 +130,20 @@ struct ChunkMeshState {
|
||||
/// Just the mesh part of a mesh worker response.
|
||||
pub struct MeshWorkerResponseMesh {
|
||||
z_bounds: (f32, f32),
|
||||
sun_occluder_z_bounds: (f32, f32),
|
||||
opaque_mesh: Mesh<TerrainVertex>,
|
||||
fluid_mesh: Mesh<FluidVertex>,
|
||||
col_lights_info: ColLightInfo,
|
||||
light_map: LightMapFn,
|
||||
glow_map: LightMapFn,
|
||||
alt_indices: AltIndices,
|
||||
}
|
||||
|
||||
/// A type produced by mesh worker threads corresponding to the position and
|
||||
/// mesh of a chunk.
|
||||
struct MeshWorkerResponse {
|
||||
pos: Vec2<i32>,
|
||||
sprite_instances: [Vec<SpriteInstance>; SPRITE_LOD_LEVELS],
|
||||
sprite_instances: [(Vec<SpriteInstance>, AltIndices); SPRITE_LOD_LEVELS],
|
||||
/// If None, this update was requested without meshing.
|
||||
mesh: Option<MeshWorkerResponseMesh>,
|
||||
started_tick: u64,
|
||||
@ -244,23 +264,29 @@ fn mesh_worker(
|
||||
mesh = None;
|
||||
(&**light_map, &**glow_map)
|
||||
} else {
|
||||
let (opaque_mesh, fluid_mesh, _shadow_mesh, (bounds, col_lights_info, light_map, glow_map)) =
|
||||
generate_mesh(
|
||||
&volume,
|
||||
(
|
||||
range,
|
||||
Vec2::new(max_texture_size, max_texture_size),
|
||||
&blocks_of_interest,
|
||||
),
|
||||
);
|
||||
let (
|
||||
opaque_mesh,
|
||||
fluid_mesh,
|
||||
_shadow_mesh,
|
||||
(bounds, col_lights_info, light_map, glow_map, alt_indices, sun_occluder_z_bounds),
|
||||
) = generate_mesh(
|
||||
&volume,
|
||||
(
|
||||
range,
|
||||
Vec2::new(max_texture_size, max_texture_size),
|
||||
&blocks_of_interest,
|
||||
),
|
||||
);
|
||||
mesh = Some(MeshWorkerResponseMesh {
|
||||
// TODO: Take sprite bounds into account somehow?
|
||||
z_bounds: (bounds.min.z, bounds.max.z),
|
||||
sun_occluder_z_bounds,
|
||||
opaque_mesh,
|
||||
fluid_mesh,
|
||||
col_lights_info,
|
||||
light_map,
|
||||
glow_map,
|
||||
alt_indices,
|
||||
});
|
||||
// Pointer juggling so borrows work out.
|
||||
let mesh = mesh.as_ref().unwrap();
|
||||
@ -272,7 +298,19 @@ fn mesh_worker(
|
||||
// Extract sprite locations from volume
|
||||
sprite_instances: {
|
||||
prof_span!("extract sprite_instances");
|
||||
let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| Vec::new());
|
||||
let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| {
|
||||
(
|
||||
Vec::new(), // Deep
|
||||
Vec::new(), // Shallow
|
||||
Vec::new(), // Surface
|
||||
)
|
||||
});
|
||||
|
||||
let (underground_alt, deep_alt) = volume
|
||||
.get_key(volume.pos_key((range.min + range.max) / 2))
|
||||
.map_or((0.0, 0.0), |c| {
|
||||
(c.meta().alt() - SHALLOW_ALT, c.meta().alt() - DEEP_ALT)
|
||||
});
|
||||
|
||||
for x in 0..TerrainChunk::RECT_SIZE.x as i32 {
|
||||
for y in 0..TerrainChunk::RECT_SIZE.y as i32 {
|
||||
@ -304,7 +342,7 @@ fn mesh_worker(
|
||||
let light = light_map(wpos);
|
||||
let glow = glow_map(wpos);
|
||||
|
||||
for (lod_level, sprite_data) in
|
||||
for ((deep_level, shallow_level, surface_level), sprite_data) in
|
||||
instances.iter_mut().zip(&sprite_data[&key])
|
||||
{
|
||||
let mat = Mat4::identity()
|
||||
@ -332,7 +370,13 @@ fn mesh_worker(
|
||||
page,
|
||||
matches!(sprite, SpriteKind::Door),
|
||||
);
|
||||
lod_level.push(instance);
|
||||
if (wpos.z as f32) < deep_alt {
|
||||
deep_level.push(instance);
|
||||
} else if wpos.z as f32 > underground_alt {
|
||||
surface_level.push(instance);
|
||||
} else {
|
||||
shallow_level.push(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -340,7 +384,21 @@ fn mesh_worker(
|
||||
}
|
||||
}
|
||||
|
||||
instances
|
||||
instances.map(|(deep_level, shallow_level, surface_level)| {
|
||||
let deep_end = deep_level.len();
|
||||
let alt_indices = AltIndices {
|
||||
deep_end,
|
||||
underground_end: deep_end + shallow_level.len(),
|
||||
};
|
||||
(
|
||||
deep_level
|
||||
.into_iter()
|
||||
.chain(shallow_level.into_iter())
|
||||
.chain(surface_level.into_iter())
|
||||
.collect(),
|
||||
alt_indices,
|
||||
)
|
||||
})
|
||||
},
|
||||
mesh,
|
||||
blocks_of_interest,
|
||||
@ -815,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,
|
||||
@ -855,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
|
||||
@ -1141,11 +1204,15 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
Some(todo) if response.started_tick <= todo.started_tick => {
|
||||
let started_tick = todo.started_tick;
|
||||
|
||||
let sprite_instances = response.sprite_instances.map(|instances| {
|
||||
renderer
|
||||
.create_instances(&instances)
|
||||
.expect("Failed to upload chunk sprite instances to the GPU!")
|
||||
});
|
||||
let sprite_instances =
|
||||
response.sprite_instances.map(|(instances, alt_indices)| {
|
||||
(
|
||||
renderer
|
||||
.create_instances(&instances)
|
||||
.expect("Failed to upload chunk sprite instances to the GPU!"),
|
||||
alt_indices,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(mesh) = response.mesh {
|
||||
// Full update, insert the whole chunk.
|
||||
@ -1225,7 +1292,9 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
can_shadow_sun: false,
|
||||
blocks_of_interest: response.blocks_of_interest,
|
||||
z_bounds: mesh.z_bounds,
|
||||
sun_occluder_z_bounds: mesh.sun_occluder_z_bounds,
|
||||
frustum_last_plane_index: 0,
|
||||
alt_indices: mesh.alt_indices,
|
||||
});
|
||||
} else if let Some(chunk) = self.chunks.get_mut(&response.pos) {
|
||||
// There was an update that didn't require a remesh (probably related to
|
||||
@ -1275,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)
|
||||
@ -1283,13 +1352,16 @@ 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 = chunk_box;
|
||||
let visible_box = Aabb {
|
||||
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))
|
||||
.or(Some(visible_box));
|
||||
@ -1483,6 +1555,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
&'a self,
|
||||
drawer: &mut TerrainShadowDrawer<'_, 'a>,
|
||||
focus_pos: Vec3<f32>,
|
||||
culling_mode: CullingMode,
|
||||
) {
|
||||
span!(_guard, "render_shadows", "Terrain::render_shadows");
|
||||
let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
|
||||
@ -1505,12 +1578,15 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
.filter(|chunk| chunk.can_shadow_sun())
|
||||
.chain(self.shadow_chunks.iter().map(|(_, chunk)| chunk))
|
||||
.filter_map(|chunk| {
|
||||
chunk
|
||||
.opaque_model
|
||||
.as_ref()
|
||||
.map(|model| (model, &chunk.locals))
|
||||
Some((
|
||||
chunk.opaque_model.as_ref()?,
|
||||
&chunk.locals,
|
||||
&chunk.alt_indices,
|
||||
))
|
||||
})
|
||||
.for_each(|(model, locals)| drawer.draw(model, locals));
|
||||
.for_each(|(model, locals, alt_indices)| {
|
||||
drawer.draw(model, locals, alt_indices, culling_mode)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render_rain_occlusion<'a>(
|
||||
@ -1532,13 +1608,14 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
chunk_iter
|
||||
// Find a way to keep this?
|
||||
// .filter(|chunk| chunk.can_shadow_sun())
|
||||
.filter_map(|chunk| {
|
||||
.filter_map(|chunk| Some((
|
||||
chunk
|
||||
.opaque_model
|
||||
.as_ref()
|
||||
.map(|model| (model, &chunk.locals))
|
||||
})
|
||||
.for_each(|(model, locals)| drawer.draw(model, locals));
|
||||
.as_ref()?,
|
||||
&chunk.locals,
|
||||
&chunk.alt_indices,
|
||||
)))
|
||||
.for_each(|(model, locals, alt_indices)| drawer.draw(model, locals, alt_indices, CullingMode::None));
|
||||
}
|
||||
|
||||
pub fn chunks_for_point_shadows(
|
||||
@ -1576,7 +1653,12 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>, focus_pos: Vec3<f32>) {
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
drawer: &mut FirstPassDrawer<'a>,
|
||||
focus_pos: Vec3<f32>,
|
||||
culling_mode: CullingMode,
|
||||
) {
|
||||
span!(_guard, "render", "Terrain::render");
|
||||
let mut drawer = drawer.draw_terrain();
|
||||
|
||||
@ -1587,17 +1669,28 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
Spiral2d::new()
|
||||
.filter_map(|rpos| {
|
||||
let pos = focus_chunk + rpos;
|
||||
self.chunks.get(&pos)
|
||||
Some((rpos, self.chunks.get(&pos)?))
|
||||
})
|
||||
.take(self.chunks.len())
|
||||
.filter(|chunk| chunk.visible.is_visible())
|
||||
.filter_map(|chunk| {
|
||||
chunk
|
||||
.opaque_model
|
||||
.as_ref()
|
||||
.map(|model| (model, &chunk.col_lights, &chunk.locals))
|
||||
.filter(|(_, chunk)| chunk.visible.is_visible())
|
||||
.filter_map(|(rpos, chunk)| {
|
||||
Some((
|
||||
rpos,
|
||||
chunk.opaque_model.as_ref()?,
|
||||
&chunk.col_lights,
|
||||
&chunk.locals,
|
||||
&chunk.alt_indices,
|
||||
))
|
||||
})
|
||||
.for_each(|(model, col_lights, locals)| drawer.draw(model, col_lights, locals));
|
||||
.for_each(|(rpos, model, col_lights, locals, alt_indices)| {
|
||||
// Always draw all of close chunks to avoid terrain 'popping'
|
||||
let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
|
||||
CullingMode::None
|
||||
} else {
|
||||
culling_mode
|
||||
};
|
||||
drawer.draw(model, col_lights, locals, alt_indices, culling_mode)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render_translucent<'a>(
|
||||
@ -1606,6 +1699,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
focus_pos: Vec3<f32>,
|
||||
cam_pos: Vec3<f32>,
|
||||
sprite_render_distance: f32,
|
||||
culling_mode: CullingMode,
|
||||
) {
|
||||
span!(_guard, "render_translucent", "Terrain::render_translucent");
|
||||
let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
|
||||
@ -1616,7 +1710,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
let chunk_iter = Spiral2d::new()
|
||||
.filter_map(|rpos| {
|
||||
let pos = focus_chunk + rpos;
|
||||
self.chunks.get(&pos).map(|c| (pos, c))
|
||||
Some((rpos, pos, self.chunks.get(&pos)?))
|
||||
})
|
||||
.take(self.chunks.len());
|
||||
|
||||
@ -1633,10 +1727,10 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
let mut sprite_drawer = drawer.draw_sprites(&self.sprite_globals, &self.sprite_col_lights);
|
||||
chunk_iter
|
||||
.clone()
|
||||
.filter(|(_, c)| c.visible.is_visible())
|
||||
.for_each(|(pos, chunk)| {
|
||||
.filter(|(_, _, c)| c.visible.is_visible())
|
||||
.for_each(|(rpos, pos, chunk)| {
|
||||
// Skip chunk if it has no sprites
|
||||
if chunk.sprite_instances[0].count() == 0 {
|
||||
if chunk.sprite_instances[0].0.count() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1662,7 +1756,19 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
4
|
||||
};
|
||||
|
||||
sprite_drawer.draw(&chunk.locals, &chunk.sprite_instances[lod_level]);
|
||||
// Always draw all of close chunks to avoid terrain 'popping'
|
||||
let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
|
||||
CullingMode::None
|
||||
} else {
|
||||
culling_mode
|
||||
};
|
||||
|
||||
sprite_drawer.draw(
|
||||
&chunk.locals,
|
||||
&chunk.sprite_instances[lod_level].0,
|
||||
&chunk.sprite_instances[lod_level].1,
|
||||
culling_mode,
|
||||
);
|
||||
}
|
||||
});
|
||||
drop(sprite_drawer);
|
||||
@ -1672,8 +1778,8 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
span!(guard, "Fluid chunks");
|
||||
let mut fluid_drawer = drawer.draw_fluid();
|
||||
chunk_iter
|
||||
.filter(|(_, chunk)| chunk.visible.is_visible())
|
||||
.filter_map(|(_, chunk)| {
|
||||
.filter(|(_, _, chunk)| chunk.visible.is_visible())
|
||||
.filter_map(|(_, _, chunk)| {
|
||||
chunk
|
||||
.fluid_model
|
||||
.as_ref()
|
||||
|
Loading…
Reference in New Issue
Block a user