summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/final/shaders/math/sdf_utils.wgsl8
-rw-r--r--assets/final/shaders/render/scene_query.wgsl60
-rw-r--r--assets/final/shaders/renderer_3d.wgsl15
-rw-r--r--src/3d/renderer.cc60
-rw-r--r--src/3d/renderer.h10
-rw-r--r--src/tests/test_3d_physics.cc3
-rw-r--r--src/tests/test_3d_render.cc3
7 files changed, 125 insertions, 34 deletions
diff --git a/assets/final/shaders/math/sdf_utils.wgsl b/assets/final/shaders/math/sdf_utils.wgsl
index ce902bf..502ba5b 100644
--- a/assets/final/shaders/math/sdf_utils.wgsl
+++ b/assets/final/shaders/math/sdf_utils.wgsl
@@ -7,3 +7,11 @@ fn get_normal_basic(p: vec3<f32>, obj_type: f32) -> vec3<f32> {
get_dist(p + e.yyx, obj_type) - get_dist(p - e.yyx, obj_type)
));
}
+
+// Distance to an Axis-Aligned Bounding Box
+fn aabb_sdf(p: vec3<f32>, min_p: vec3<f32>, max_p: vec3<f32>) -> f32 {
+ let center = (min_p + max_p) * 0.5;
+ let extent = (max_p - min_p) * 0.5;
+ let q = abs(p - center) - extent;
+ return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);
+}
diff --git a/assets/final/shaders/render/scene_query.wgsl b/assets/final/shaders/render/scene_query.wgsl
index b58f28c..e44b79c 100644
--- a/assets/final/shaders/render/scene_query.wgsl
+++ b/assets/final/shaders/render/scene_query.wgsl
@@ -1,4 +1,5 @@
#include "math/sdf_shapes"
+#include "math/sdf_utils" // For aabb_sdf helper
fn get_dist(p: vec3<f32>, obj_type: f32) -> f32 {
if (obj_type == 1.0) { return length(p) - 1.0; } // Unit Sphere
@@ -10,24 +11,51 @@ fn get_dist(p: vec3<f32>, obj_type: f32) -> f32 {
fn map_scene(p: vec3<f32>, skip_idx: u32) -> f32 {
var d = 1000.0;
- let count = u32(globals.params.x);
-
- for (var i = 0u; i < count; i = i + 1u) {
- if (i == skip_idx) { continue; }
- let obj = object_data.objects[i];
- let obj_type = obj.params.x;
- // Skip rasterized objects (like the floor) in the SDF map
- if (obj_type <= 0.0) { continue; }
+ let use_bvh = (globals.params.y > 0.5);
- let q = (obj.inv_model * vec4<f32>(p, 1.0)).xyz;
+ if (use_bvh) {
+ var stack: array<i32, 32>;
+ var stack_ptr = 0;
- let scale_x = length(obj.model[0].xyz);
- let scale_y = length(obj.model[1].xyz);
- let scale_z = length(obj.model[2].xyz);
- // Use conservative minimum scale to avoid overstepping the distance field
- let s = min(scale_x, min(scale_y, scale_z));
-
- d = min(d, get_dist(q, obj_type) * s);
+ if (arrayLength(&bvh_nodes) > 0u) {
+ stack[stack_ptr] = 0;
+ stack_ptr++;
+ }
+
+ while (stack_ptr > 0) {
+ stack_ptr--;
+ let node_idx = stack[stack_ptr];
+ let node = bvh_nodes[node_idx];
+
+ if (aabb_sdf(p, node.min, node.max) < d) {
+ if (node.left_idx < 0) { // Leaf
+ let obj_idx = u32(node.obj_idx_or_right);
+ if (obj_idx == skip_idx) { continue; }
+ let obj = object_data.objects[obj_idx];
+ let q = (obj.inv_model * vec4<f32>(p, 1.0)).xyz;
+ let s = min(length(obj.model[0].xyz), min(length(obj.model[1].xyz), length(obj.model[2].xyz)));
+ d = min(d, get_dist(q, obj.params.x) * s);
+ } else { // Internal
+ if (stack_ptr < 31) {
+ stack[stack_ptr] = node.left_idx;
+ stack_ptr++;
+ stack[stack_ptr] = node.obj_idx_or_right;
+ stack_ptr++;
+ }
+ }
+ }
+ }
+ } else { // Fallback to linear scan
+ let count = u32(globals.params.x);
+ for (var i = 0u; i < count; i = i + 1u) {
+ if (i == skip_idx) { continue; }
+ let obj = object_data.objects[i];
+ if (obj.params.x <= 0.0) { continue; } // Skip raster objects
+ let q = (obj.inv_model * vec4<f32>(p, 1.0)).xyz;
+ let s = min(length(obj.model[0].xyz), min(length(obj.model[1].xyz), length(obj.model[2].xyz)));
+ d = min(d, get_dist(q, obj.params.x) * s);
+ }
}
+
return d;
}
diff --git a/assets/final/shaders/renderer_3d.wgsl b/assets/final/shaders/renderer_3d.wgsl
index b39525d..723aeb0 100644
--- a/assets/final/shaders/renderer_3d.wgsl
+++ b/assets/final/shaders/renderer_3d.wgsl
@@ -2,9 +2,18 @@
@group(0) @binding(0) var<uniform> globals: GlobalUniforms;
@group(0) @binding(1) var<storage, read> object_data: ObjectsBuffer;
-@group(0) @binding(2) var noise_tex: texture_2d<f32>;
-@group(0) @binding(3) var noise_sampler: sampler;
-@group(0) @binding(4) var sky_tex: texture_2d<f32>;
+
+struct BVHNode {
+ min: vec3<f32>,
+ left_idx: i32,
+ max: vec3<f32>,
+ obj_idx_or_right: i32,
+};
+@group(0) @binding(2) var<storage, read> bvh_nodes: array<BVHNode>;
+
+@group(0) @binding(3) var noise_tex: texture_2d<f32>;
+@group(0) @binding(4) var noise_sampler: sampler;
+@group(0) @binding(5) var sky_tex: texture_2d<f32>;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
diff --git a/src/3d/renderer.cc b/src/3d/renderer.cc
index 684fda9..cf1a067 100644
--- a/src/3d/renderer.cc
+++ b/src/3d/renderer.cc
@@ -12,6 +12,7 @@
#if !defined(STRIP_ALL)
bool Renderer3D::s_debug_enabled_ = false;
+bool Renderer3D::s_bvh_enabled_ = true; // Enabled by default
#endif
void Renderer3D::init(WGPUDevice device, WGPUQueue queue,
@@ -143,6 +144,8 @@ void Renderer3D::shutdown() {
wgpuBufferRelease(global_uniform_buffer_);
if (object_storage_buffer_)
wgpuBufferRelease(object_storage_buffer_);
+ if (bvh_storage_buffer_)
+ wgpuBufferRelease(bvh_storage_buffer_);
if (depth_view_)
wgpuTextureViewRelease(depth_view_);
if (depth_texture_)
@@ -189,6 +192,10 @@ void Renderer3D::create_default_resources() {
WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst,
nullptr)
.buffer;
+ bvh_storage_buffer_ = gpu_create_buffer(
+ device_, sizeof(BVHNode) * kMaxObjects * 2, // Capacity for a full tree
+ WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst, nullptr)
+ .buffer;
}
void Renderer3D::set_noise_texture(WGPUTextureView noise_view) {
@@ -207,33 +214,44 @@ void Renderer3D::add_debug_aabb(const vec3& min, const vec3& max,
}
void Renderer3D::create_pipeline() {
- WGPUBindGroupLayoutEntry entries[5] = {};
+ WGPUBindGroupLayoutEntry entries[6] = {};
+ // Binding 0: Global Uniforms
entries[0].binding = 0;
entries[0].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
entries[0].buffer.type = WGPUBufferBindingType_Uniform;
entries[0].buffer.minBindingSize = sizeof(GlobalUniforms);
+ // Binding 1: Object Data
entries[1].binding = 1;
entries[1].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
entries[1].buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
entries[1].buffer.minBindingSize = sizeof(ObjectData) * kMaxObjects;
+ // Binding 2: BVH Nodes
entries[2].binding = 2;
entries[2].visibility = WGPUShaderStage_Fragment;
- entries[2].texture.sampleType = WGPUTextureSampleType_Float;
- entries[2].texture.viewDimension = WGPUTextureViewDimension_2D;
+ entries[2].buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
+ entries[2].buffer.minBindingSize = sizeof(BVHNode) * kMaxObjects * 2;
+ // Binding 3: Noise Texture
entries[3].binding = 3;
entries[3].visibility = WGPUShaderStage_Fragment;
- entries[3].sampler.type = WGPUSamplerBindingType_Filtering;
+ entries[3].texture.sampleType = WGPUTextureSampleType_Float;
+ entries[3].texture.viewDimension = WGPUTextureViewDimension_2D;
+ // Binding 4: Default Sampler
entries[4].binding = 4;
entries[4].visibility = WGPUShaderStage_Fragment;
- entries[4].texture.sampleType = WGPUTextureSampleType_Float;
- entries[4].texture.viewDimension = WGPUTextureViewDimension_2D;
+ entries[4].sampler.type = WGPUSamplerBindingType_Filtering;
+
+ // Binding 5: Sky Texture
+ entries[5].binding = 5;
+ entries[5].visibility = WGPUShaderStage_Fragment;
+ entries[5].texture.sampleType = WGPUTextureSampleType_Float;
+ entries[5].texture.viewDimension = WGPUTextureViewDimension_2D;
WGPUBindGroupLayoutDescriptor bgl_desc = {};
- bgl_desc.entryCount = 5;
+ bgl_desc.entryCount = 6;
bgl_desc.entries = entries;
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc);
@@ -311,8 +329,8 @@ void Renderer3D::update_uniforms(const Scene& scene, const Camera& camera,
globals.camera_pos_time =
vec4(camera.position.x, camera.position.y, camera.position.z, time);
globals.params =
- vec4((float)std::min((size_t)kMaxObjects, scene.objects.size()), 0.0f,
- 0.0f, 0.0f);
+ vec4((float)std::min((size_t)kMaxObjects, scene.objects.size()),
+ s_bvh_enabled_ ? 1.0f : 0.0f, 0.0f, 0.0f);
globals.resolution = vec2((float)width_, (float)height_);
globals.padding = vec2(0.0f, 0.0f);
@@ -346,6 +364,16 @@ void Renderer3D::update_uniforms(const Scene& scene, const Camera& camera,
wgpuQueueWriteBuffer(queue_, object_storage_buffer_, 0, obj_data.data(),
obj_data.size() * sizeof(ObjectData));
}
+
+ // Build and upload BVH if enabled
+ if (s_bvh_enabled_) {
+ BVHBuilder::build(cpu_bvh_, scene.objects);
+ if (!cpu_bvh_.nodes.empty()) {
+ wgpuQueueWriteBuffer(queue_, bvh_storage_buffer_, 0,
+ cpu_bvh_.nodes.data(),
+ cpu_bvh_.nodes.size() * sizeof(BVHNode));
+ }
+ }
}
void Renderer3D::draw(WGPURenderPassEncoder pass, const Scene& scene,
@@ -357,7 +385,7 @@ void Renderer3D::draw(WGPURenderPassEncoder pass, const Scene& scene,
if (bind_group_)
wgpuBindGroupRelease(bind_group_);
- WGPUBindGroupEntry bg_entries[5] = {};
+ WGPUBindGroupEntry bg_entries[6] = {};
bg_entries[0].binding = 0;
bg_entries[0].buffer = global_uniform_buffer_;
@@ -368,18 +396,22 @@ void Renderer3D::draw(WGPURenderPassEncoder pass, const Scene& scene,
bg_entries[1].size = sizeof(ObjectData) * kMaxObjects;
bg_entries[2].binding = 2;
- bg_entries[2].textureView = noise_texture_view_;
+ bg_entries[2].buffer = bvh_storage_buffer_;
+ bg_entries[2].size = sizeof(BVHNode) * kMaxObjects * 2;
bg_entries[3].binding = 3;
- bg_entries[3].sampler = default_sampler_;
+ bg_entries[3].textureView = noise_texture_view_;
bg_entries[4].binding = 4;
- bg_entries[4].textureView =
+ bg_entries[4].sampler = default_sampler_;
+
+ bg_entries[5].binding = 5;
+ bg_entries[5].textureView =
sky_texture_view_ ? sky_texture_view_ : noise_texture_view_; // Fallback
WGPUBindGroupDescriptor bg_desc = {};
bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0);
- bg_desc.entryCount = 5;
+ bg_desc.entryCount = 6;
bg_desc.entries = bg_entries;
bind_group_ = wgpuDeviceCreateBindGroup(device_, &bg_desc);
diff --git a/src/3d/renderer.h b/src/3d/renderer.h
index 8068bdc..b468423 100644
--- a/src/3d/renderer.h
+++ b/src/3d/renderer.h
@@ -6,6 +6,7 @@
#include "3d/camera.h"
#include "3d/scene.h"
+#include "3d/bvh.h"
#include "gpu/gpu.h"
#include <vector>
@@ -18,7 +19,7 @@ struct GlobalUniforms {
mat4 view_proj;
mat4 inv_view_proj; // Added for skybox/raymarching
vec4 camera_pos_time; // xyz = camera_pos, w = time
- vec4 params; // x = num_objects, yzw = padding
+ vec4 params; // x = num_objects, y = use_bvh, zw = padding
vec2 resolution;
vec2 padding;
};
@@ -40,6 +41,9 @@ class Renderer3D {
static void SetDebugEnabled(bool enabled) {
s_debug_enabled_ = enabled;
}
+ static void SetBvhEnabled(bool enabled) {
+ s_bvh_enabled_ = enabled;
+ }
#endif
// Renders the scene to the given texture view (Convenience: creates a pass)
@@ -83,6 +87,9 @@ class Renderer3D {
WGPUBindGroup skybox_bind_group_ = nullptr;
WGPUBuffer global_uniform_buffer_ = nullptr;
WGPUBuffer object_storage_buffer_ = nullptr;
+ WGPUBuffer bvh_storage_buffer_ = nullptr;
+
+ BVH cpu_bvh_; // Keep a CPU-side copy for building/uploading
WGPUTextureView noise_texture_view_ = nullptr;
WGPUTextureView sky_texture_view_ = nullptr;
@@ -100,5 +107,6 @@ class Renderer3D {
#if !defined(STRIP_ALL)
VisualDebug visual_debug_;
static bool s_debug_enabled_;
+ static bool s_bvh_enabled_;
#endif
};
diff --git a/src/tests/test_3d_physics.cc b/src/tests/test_3d_physics.cc
index 84be333..010d245 100644
--- a/src/tests/test_3d_physics.cc
+++ b/src/tests/test_3d_physics.cc
@@ -194,6 +194,9 @@ int main(int argc, char** argv) {
if (strcmp(argv[i], "--debug") == 0) {
Renderer3D::SetDebugEnabled(true);
}
+ if (strcmp(argv[i], "--no-bvh") == 0) {
+ Renderer3D::SetBvhEnabled(false);
+ }
}
#else
(void)argc;
diff --git a/src/tests/test_3d_render.cc b/src/tests/test_3d_render.cc
index 002cb55..8b2b2c1 100644
--- a/src/tests/test_3d_render.cc
+++ b/src/tests/test_3d_render.cc
@@ -183,6 +183,9 @@ int main(int argc, char** argv) {
if (strcmp(argv[i], "--debug") == 0) {
Renderer3D::SetDebugEnabled(true);
}
+ if (strcmp(argv[i], "--no-bvh") == 0) {
+ Renderer3D::SetBvhEnabled(false);
+ }
}
#else
(void)argc;