Add compute shader for voxel generation

This commit is contained in:
Elias Stepanik 2025-06-11 12:13:19 +02:00
parent cdef1618ce
commit db4fc07f49
9 changed files with 213 additions and 4 deletions

View File

@ -21,4 +21,6 @@ smallvec = "1.14.0"
once_cell = "1.21.3"
rayon = "1.10.0"
bincode = "1.3"
bevy_easy_compute = "0.15"
bytemuck = { version = "1", features = ["derive"] }

View File

@ -0,0 +1,42 @@
struct Params {
frequency: f32,
amplitude: f32,
width: u32,
depth: u32,
};
@group(0) @binding(0)
var<uniform> params: Params;
@group(0) @binding(1)
var<storage, read_write> heights: array<f32>;
fn hash(p: vec2<i32>) -> f32 {
let dot_val = f32(p.x * 1271 + p.y * 3117);
return fract(sin(dot_val) * 43758.5453);
}
fn noise(p: vec2<f32>) -> f32 {
let i = vec2<i32>(floor(p));
let f = fract(p);
let a = hash(i);
let b = hash(i + vec2<i32>(1,0));
let c = hash(i + vec2<i32>(0,1));
let d = hash(i + vec2<i32>(1,1));
let u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
@compute @workgroup_size(8,8,1)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
if (id.x >= params.width || id.y >= params.depth) {
return;
}
let index = id.y * params.width + id.x;
let pos = vec2<f32>(f32(id.x), f32(id.y)) * params.frequency;
let n = noise(pos);
heights[index] = n * params.amplitude;
}

View File

@ -0,0 +1,24 @@
struct Params {
radius: u32,
diameter: u32,
};
@group(0) @binding(0)
var<uniform> params: Params;
@group(0) @binding(1)
var<storage, read_write> voxels: array<u32>;
@compute @workgroup_size(8,8,8)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
if (id.x >= params.diameter || id.y >= params.diameter || id.z >= params.diameter) {
return;
}
let r = f32(params.radius);
let cx = f32(id.x) - r;
let cy = f32(id.y) - r;
let cz = f32(id.z) - r;
let inside = select(0u, 1u, cx * cx + cy * cy + cz * cz <= r * r);
let index = id.z * params.diameter * params.diameter + id.y * params.diameter + id.x;
voxels[index] = inside;
}

View File

@ -1,5 +1,7 @@
use bevy::pbr::wireframe::WireframePlugin;
use crate::helper::debug_gizmos::debug_gizmos;
use bevy_easy_compute::prelude::{AppComputePlugin, AppComputeWorkerPlugin};
use crate::plugins::environment::systems::voxels::sphere_compute::SphereWorker;
use bevy::prelude::*;
pub struct AppPlugin;
@ -8,6 +10,8 @@ impl Plugin for AppPlugin {
app.add_plugins(crate::plugins::ui::ui_plugin::UiPlugin);
app.add_plugins(crate::plugins::big_space::big_space_plugin::BigSpaceIntegrationPlugin);
app.add_plugins(crate::plugins::environment::environment_plugin::EnvironmentPlugin);
app.add_plugins(AppComputePlugin);
app.add_plugins(AppComputeWorkerPlugin::<SphereWorker>::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,5 +1,7 @@
use bevy::app::{App, Plugin, PreStartup, PreUpdate, Startup};
use bevy::prelude::*;
use bevy_easy_compute::prelude::*;
use crate::plugins::environment::systems::voxels::sphere_compute::{SphereWorker, SphereParams, SphereGenerated, execute_sphere_once, apply_sphere_result};
use crate::plugins::environment::systems::voxels::debug::{draw_grid, visualize_octree_system};
use crate::plugins::environment::systems::voxels::queue_systems;
use crate::plugins::environment::systems::voxels::queue_systems::{enqueue_visible_chunks, process_chunk_queue};
@ -16,8 +18,8 @@ impl Plugin for EnvironmentPlugin {
(
crate::plugins::environment::systems::camera_system::setup,
crate::plugins::environment::systems::environment_system::setup.after(crate::plugins::environment::systems::camera_system::setup),
crate::plugins::environment::systems::voxel_system::setup
crate::plugins::environment::systems::voxel_system::setup,
execute_sphere_once.after(crate::plugins::environment::systems::voxel_system::setup),
),
);
@ -25,6 +27,7 @@ impl Plugin for EnvironmentPlugin {
app.insert_resource(ChunkCullingCfg { view_distance_chunks });
app.insert_resource(ChunkBudget { per_frame: 20 });
app.init_resource::<PrevCameraChunk>();
app.init_resource::<SphereGenerated>();
app.add_systems(Update, log_mesh_count);
app
// ------------------------------------------------------------------------
@ -42,7 +45,8 @@ impl Plugin for EnvironmentPlugin {
enqueue_visible_chunks,
process_chunk_queue.after(enqueue_visible_chunks),
update_chunk_lods.after(process_chunk_queue),
rebuild_dirty_chunks .after(process_chunk_queue), // 4. (re)mesh dirty chunks
rebuild_dirty_chunks .after(process_chunk_queue),
apply_sphere_result.after(rebuild_dirty_chunks), // 4. (re)mesh dirty chunks
/* ---------- optional debug drawing ------- */
visualize_octree_system

View File

@ -47,7 +47,7 @@ pub fn setup(
generate_voxel_sphere_parallel(&mut tree, center, radius, color);
}*/
generate_voxel_sphere(&mut tree, 200, color);
// voxel sphere is generated asynchronously via compute shader
tree
};

View File

@ -9,3 +9,5 @@ pub mod render_chunks;
pub mod culling;
pub mod queue_systems;
pub mod lod;
pub mod noise_compute;
pub mod sphere_compute;

View File

@ -0,0 +1,41 @@
use bevy::prelude::*;
use bevy_easy_compute::prelude::*;
#[derive(ShaderType, Clone, Copy)]
pub struct NoiseParams {
pub frequency: f32,
pub amplitude: f32,
pub width: u32,
pub depth: u32,
}
#[derive(TypePath)]
struct NoiseShader;
impl ComputeShader for NoiseShader {
fn shader() -> ShaderRef {
"shaders/noise.wgsl".into()
}
}
#[derive(Resource)]
pub struct NoiseWorker;
impl ComputeWorker for NoiseWorker {
fn build(world: &mut World) -> AppComputeWorker<Self> {
let params = NoiseParams {
frequency: 0.1,
amplitude: 1.0,
width: 0,
depth: 0,
};
AppComputeWorkerBuilder::new(world)
.add_uniform("params", &params)
.add_staging("heights", &[0.0_f32; 1])
.add_pass::<NoiseShader>([1, 1, 1], &["params", "heights"])
.one_shot()
.build()
}
}

View File

@ -0,0 +1,90 @@
use bevy::prelude::*;
use bevy_easy_compute::prelude::*;
use bytemuck::{Pod, Zeroable};
use super::structure::{SparseVoxelOctree, Voxel};
#[repr(C)]
#[derive(ShaderType, Clone, Copy, Pod, Zeroable)]
pub struct SphereParams {
pub radius: u32,
pub diameter: u32,
}
#[derive(TypePath)]
struct SphereShader;
impl ComputeShader for SphereShader {
fn shader() -> ShaderRef {
"shaders/sphere.wgsl".into()
}
}
#[derive(Resource)]
pub struct SphereWorker;
impl ComputeWorker for SphereWorker {
fn build(world: &mut World) -> AppComputeWorker<Self> {
let radius = 200u32;
let diameter = radius * 2 + 1;
let params = SphereParams { radius, diameter };
let buffer = vec![0u32; (diameter * diameter * diameter) as usize];
AppComputeWorkerBuilder::new(world)
.add_uniform("params", &params)
.add_staging("voxels", &buffer)
.add_pass::<SphereShader>([
(diameter + 7) / 8,
(diameter + 7) / 8,
(diameter + 7) / 8,
], &["params", "voxels"])
.synchronous()
.one_shot()
.build()
}
}
#[derive(Resource, Default)]
pub struct SphereGenerated(pub bool);
pub fn execute_sphere_once(
mut worker: ResMut<AppComputeWorker<SphereWorker>>,
mut generated: ResMut<SphereGenerated>,
) {
if generated.0 {
return;
}
worker.execute();
generated.0 = true;
}
pub fn apply_sphere_result(
mut worker: ResMut<AppComputeWorker<SphereWorker>>,
mut octree_q: Query<&mut SparseVoxelOctree>,
mut generated: ResMut<SphereGenerated>,
) {
if !generated.0 || !worker.ready() {
return;
}
let params: SphereParams = worker.read("params");
let voxels: Vec<u32> = worker.read_vec("voxels");
let mut octree = octree_q.single_mut();
let step = octree.get_spacing_at_depth(octree.max_depth);
let radius = params.radius as i32;
let diameter = params.diameter as i32;
for x in 0..diameter {
for y in 0..diameter {
for z in 0..diameter {
let idx = (z * diameter * diameter + y * diameter + x) as usize;
if voxels[idx] != 0 {
let wx = (x - radius) as f32 * step;
let wy = (y - radius) as f32 * step;
let wz = (z - radius) as f32 * step;
octree.insert(Vec3::new(wx, wy, wz), Voxel { color: Color::rgb(0.2, 0.8, 0.2) });
}
}
}
}
generated.0 = false;
}