diff --git a/client/assets/shaders/chunk_lod.wgsl b/client/assets/shaders/chunk_lod.wgsl new file mode 100644 index 0000000..d19ccc8 --- /dev/null +++ b/client/assets/shaders/chunk_lod.wgsl @@ -0,0 +1,29 @@ +struct Params { + centre: vec3; + max_level: u32; + range_step: f32; + count: u32; + _pad: u32; +}; + +@group(0) @binding(0) +var params: Params; + +@group(0) @binding(1) +var keys_in: array>; + +@group(0) @binding(2) +var lod_out: array; + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) id: vec3) { + let idx = id.x; + if (idx >= params.count) { return; } + let key = keys_in[idx]; + let dx = f32(key.x - params.centre.x); + let dy = f32(key.y - params.centre.y); + let dz = f32(key.z - params.centre.z); + var level = floor(length(vec3(dx, dy, dz)) / params.range_step); + if (level > f32(params.max_level)) { level = f32(params.max_level); } + lod_out[idx] = u32(level); +} diff --git a/client/src/app.rs b/client/src/app.rs index fe4d549..a64ced3 100644 --- a/client/src/app.rs +++ b/client/src/app.rs @@ -4,6 +4,7 @@ use bevy_easy_compute::prelude::{AppComputePlugin, AppComputeWorkerPlugin}; use crate::plugins::environment::systems::voxels::sphere_compute::SphereWorker; use crate::plugins::environment::systems::voxels::visible_chunks_compute::VisibleChunksWorker; use crate::plugins::environment::systems::voxels::chunk_mesh_compute::ChunkMeshWorker; +use crate::plugins::environment::systems::voxels::lod_compute::ChunkLodWorker; use bevy::prelude::*; pub struct AppPlugin; @@ -16,6 +17,7 @@ impl Plugin for AppPlugin { app.add_plugins(AppComputeWorkerPlugin::::default()); app.add_plugins(AppComputeWorkerPlugin::::default()); app.add_plugins(AppComputeWorkerPlugin::::default()); + app.add_plugins(AppComputeWorkerPlugin::::default()); //app.add_plugins(crate::plugins::network::network_plugin::NetworkPlugin); app.add_plugins(crate::plugins::input::input_plugin::InputPlugin); app.add_plugins(WireframePlugin); diff --git a/client/src/plugins/environment/systems/voxels/lod.rs b/client/src/plugins/environment/systems/voxels/lod.rs index df3577d..1f9ad0b 100644 --- a/client/src/plugins/environment/systems/voxels/lod.rs +++ b/client/src/plugins/environment/systems/voxels/lod.rs @@ -1,38 +1,60 @@ use bevy::prelude::*; -use crate::plugins::environment::systems::voxels::helper::chunk_center_world; -use crate::plugins::environment::systems::voxels::structure::{Chunk, ChunkLod, ChunkCullingCfg, SparseVoxelOctree, CHUNK_SIZE}; +use crate::plugins::environment::systems::voxels::helper::world_to_chunk; +use crate::plugins::environment::systems::voxels::structure::{Chunk, ChunkLod, ChunkCullingCfg, SparseVoxelOctree, ChunkKey}; +use super::lod_compute::{ChunkLodWorker, LodParams, MAX_LOD_CHUNKS}; +use super::visible_chunks_compute::IVec3Pod; +use bevy_easy_compute::prelude::AppComputeWorker; /// Update each chunk's LOD level based on its distance from the camera. /// Chunks farther away get a higher LOD value (coarser mesh). pub fn update_chunk_lods( cam_q: Query<&GlobalTransform, With>, - mut chunks: Query<(&Chunk, &mut ChunkLod)>, + chunks: Query<(Entity, &Chunk, &ChunkLod)>, + mut chunks_mut: Query<&mut ChunkLod>, mut tree_q: Query<&mut SparseVoxelOctree>, + mut worker: ResMut>, cfg: Res, ) { let cam_pos = cam_q.single().translation(); - - // Borrow the octree only once to avoid repeated query lookups - let mut tree = tree_q.single_mut(); + let tree = tree_q.single(); let max_depth = tree.max_depth - 1; + let max_level = max_depth; let range_step = cfg.view_distance_chunks as f32 / (max_depth as f32 - 1.0); - let chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth); + let centre = world_to_chunk(tree, cam_pos); + drop(tree); - let mut changed = Vec::new(); - for (chunk, mut lod) in chunks.iter_mut() { - let center = chunk_center_world(&tree, chunk.key); - let dist_chunks = cam_pos.distance(center) / chunk_size; - let mut level = (dist_chunks / range_step).floor() as u32; - if level > max_depth { - level = max_depth; - } - if lod.0 != level { - lod.0 = level; - changed.push(chunk.key); - } + let mut keys: Vec = Vec::new(); + let mut entities: Vec<(Entity, ChunkKey)> = Vec::new(); + for (ent, chunk, _lod) in chunks.iter() { + keys.push(IVec3Pod { x: chunk.key.0, y: chunk.key.1, z: chunk.key.2, _pad: 0 }); + entities.push((ent, chunk.key)); } - for key in changed { - tree.dirty_chunks.insert(key); + let count = keys.len().min(MAX_LOD_CHUNKS); + worker.write_slice("keys_in", &keys[..count]); + let params = LodParams { + centre: IVec3Pod { x: centre.0, y: centre.1, z: centre.2, _pad: 0 }, + max_level, + count: count as u32, + range_step, + _pad0: 0, + }; + worker.write("params", ¶ms); + worker.execute(); + + if !worker.ready() { + return; + } + + let results: Vec = worker.read_vec("lod_out"); + + let mut tree = tree_q.single_mut(); + for ((ent, key), level) in entities.into_iter().zip(results.into_iter().take(count)) { + if let Ok(mut lod) = chunks_mut.get_mut(ent) { + if lod.0 != level { + lod.0 = level; + tree.dirty_chunks.insert(key); + } + } } } diff --git a/client/src/plugins/environment/systems/voxels/lod_compute.rs b/client/src/plugins/environment/systems/voxels/lod_compute.rs new file mode 100644 index 0000000..3d3fbb9 --- /dev/null +++ b/client/src/plugins/environment/systems/voxels/lod_compute.rs @@ -0,0 +1,49 @@ +use bevy::prelude::*; +use bevy_easy_compute::prelude::*; +use bytemuck::{Pod, Zeroable}; + +use super::visible_chunks_compute::IVec3Pod; + +pub const MAX_LOD_CHUNKS: usize = 4096; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +pub struct LodParams { + pub centre: IVec3Pod, + pub max_level: u32, + pub count: u32, + pub range_step: f32, + pub _pad0: u32, +} + +#[derive(TypePath)] +struct LodShader; + +impl ComputeShader for LodShader { + fn shader() -> ShaderRef { + "shaders/chunk_lod.wgsl".into() + } +} + +#[derive(Resource)] +pub struct ChunkLodWorker; + +impl ComputeWorker for ChunkLodWorker { + fn build(world: &mut World) -> AppComputeWorker { + let params = LodParams { + centre: IVec3Pod { x: 0, y: 0, z: 0, _pad: 0 }, + max_level: 0, + count: 0, + range_step: 1.0, + _pad0: 0, + }; + AppComputeWorkerBuilder::new(world) + .add_uniform("params", ¶ms) + .add_staging("keys_in", &[IVec3Pod { x: 0, y: 0, z: 0, _pad: 0 }; MAX_LOD_CHUNKS]) + .add_staging("lod_out", &[0u32; MAX_LOD_CHUNKS]) + .add_pass::([((MAX_LOD_CHUNKS as u32 + 63) / 64), 1, 1], &["params", "keys_in", "lod_out"]) + .one_shot() + .synchronous() + .build() + } +} diff --git a/client/src/plugins/environment/systems/voxels/mod.rs b/client/src/plugins/environment/systems/voxels/mod.rs index 203bcb7..d824469 100644 --- a/client/src/plugins/environment/systems/voxels/mod.rs +++ b/client/src/plugins/environment/systems/voxels/mod.rs @@ -13,3 +13,4 @@ pub mod noise_compute; pub mod sphere_compute; pub mod visible_chunks_compute; pub mod chunk_mesh_compute; +pub mod lod_compute; diff --git a/client/src/plugins/environment/systems/voxels/queue_systems.rs b/client/src/plugins/environment/systems/voxels/queue_systems.rs index 05c6a62..843cd18 100644 --- a/client/src/plugins/environment/systems/voxels/queue_systems.rs +++ b/client/src/plugins/environment/systems/voxels/queue_systems.rs @@ -79,7 +79,7 @@ pub fn apply_visible_chunk_results( .into_iter() .filter_map(|r| { if r.dist2 < 0 { return None; } - let key = ChunkKey(r.key.x, r.key.y, r.key.z); + let key = ChunkKey(r.x, r.y, r.z); if spawned.0.contains_key(&key) { return None; } Some((key, r.dist2)) }) diff --git a/client/src/plugins/environment/systems/voxels/visible_chunks_compute.rs b/client/src/plugins/environment/systems/voxels/visible_chunks_compute.rs index e441339..fffed47 100644 --- a/client/src/plugins/environment/systems/voxels/visible_chunks_compute.rs +++ b/client/src/plugins/environment/systems/voxels/visible_chunks_compute.rs @@ -25,7 +25,9 @@ pub struct VisibleParams { #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable, ShaderType)] pub struct ChunkResult { - pub key: IVec3Pod, + pub x: i32, + pub y: i32, + pub z: i32, pub dist2: i32, } @@ -52,7 +54,7 @@ impl ComputeWorker for VisibleChunksWorker { AppComputeWorkerBuilder::new(world) .add_uniform("params", &default_params) .add_staging("keys_in", &[IVec3Pod { x: 0, y: 0, z: 0, _pad: 0 }; MAX_VISIBLE_CHUNKS]) - .add_staging("results", &[ChunkResult { key: IVec3Pod { x: 0, y: 0, z: 0, _pad: 0 }, dist2: 0 }; MAX_VISIBLE_CHUNKS]) + .add_staging("results", &[ChunkResult { x: 0, y: 0, z: 0, dist2: 0 }; MAX_VISIBLE_CHUNKS]) .add_pass::([((MAX_VISIBLE_CHUNKS as u32 + 63) / 64), 1, 1], &["params", "keys_in", "results"]) .one_shot() .synchronous()