Add compute-based LOD update and fix culling output

This commit is contained in:
Elias Stepanik 2025-06-12 01:28:05 +02:00
parent 95c088cabb
commit 0401b6076f
7 changed files with 129 additions and 24 deletions

View File

@ -0,0 +1,29 @@
struct Params {
centre: vec3<i32>;
max_level: u32;
range_step: f32;
count: u32;
_pad: u32;
};
@group(0) @binding(0)
var<uniform> params: Params;
@group(0) @binding(1)
var<storage, read> keys_in: array<vec3<i32>>;
@group(0) @binding(2)
var<storage, read_write> lod_out: array<u32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
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<f32>(dx, dy, dz)) / params.range_step);
if (level > f32(params.max_level)) { level = f32(params.max_level); }
lod_out[idx] = u32(level);
}

View File

@ -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::<SphereWorker>::default());
app.add_plugins(AppComputeWorkerPlugin::<VisibleChunksWorker>::default());
app.add_plugins(AppComputeWorkerPlugin::<ChunkMeshWorker>::default());
app.add_plugins(AppComputeWorkerPlugin::<ChunkLodWorker>::default());
//app.add_plugins(crate::plugins::network::network_plugin::NetworkPlugin);
app.add_plugins(crate::plugins::input::input_plugin::InputPlugin);
app.add_plugins(WireframePlugin);

View File

@ -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<Camera>>,
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<AppComputeWorker<ChunkLodWorker>>,
cfg: Res<ChunkCullingCfg>,
) {
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<IVec3Pod> = 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", &params);
worker.execute();
if !worker.ready() {
return;
}
let results: Vec<u32> = 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);
}
}
}
}

View File

@ -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<Self> {
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", &params)
.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::<LodShader>([((MAX_LOD_CHUNKS as u32 + 63) / 64), 1, 1], &["params", "keys_in", "lod_out"])
.one_shot()
.synchronous()
.build()
}
}

View File

@ -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;

View File

@ -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))
})

View File

@ -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::<VisibleShader>([((MAX_VISIBLE_CHUNKS as u32 + 63) / 64), 1, 1], &["params", "keys_in", "results"])
.one_shot()
.synchronous()