From e14902e3adc4ae56c3bb37fa202997c273e1d981 Mon Sep 17 00:00:00 2001 From: Siirko Date: Sun, 5 Jan 2025 15:09:46 +0100 Subject: [PATCH 1/4] update with model loading --- src/lib.rs | 3 +- src/object/aabb.rs | 10 +++++ src/object/mod.rs | 3 ++ src/object/sphere.rs | 14 +++++++ src/render_context.rs | 9 ++++ src/scene/mod.rs | 86 +++++++++++++++++++++++++++++++++++++- src/shader/raytracing.wgsl | 43 +++++++++++-------- 7 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 src/object/aabb.rs diff --git a/src/lib.rs b/src/lib.rs index 824c8c1..75918cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ mod scene; extern crate nalgebra_glm as glm; mod object; + struct MyUserEvent; struct State<'a> { @@ -156,7 +157,7 @@ fn init( #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] pub async fn run() { info!("Starting up"); - let scale = 2.2; + let scale = 2.0; let width = 900 * scale as u32; let height = 450 * scale as u32; let (window, event_loop) = init(width, height); diff --git a/src/object/aabb.rs b/src/object/aabb.rs new file mode 100644 index 0000000..850b2f3 --- /dev/null +++ b/src/object/aabb.rs @@ -0,0 +1,10 @@ +use glm::Vec3; + +#[repr(C)] +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct AABB { + pub min: Vec3, + pub max: Vec3, + pub left_child: u32, + pub right_child: u32, +} diff --git a/src/object/mod.rs b/src/object/mod.rs index 605c50f..df83db3 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -4,6 +4,9 @@ pub use sphere::Sphere; mod mesh; pub use mesh::Mesh; +mod aabb; +pub use aabb::AABB; + #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] pub struct Object { diff --git a/src/object/sphere.rs b/src/object/sphere.rs index bb692eb..2bd5c34 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -1,3 +1,5 @@ +use super::aabb; + #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] pub struct Sphere { @@ -27,4 +29,16 @@ impl Sphere { _padding: [0; 2], } } + + pub fn get_bounding_box(&self) -> aabb::AABB { + let radius = glm::vec3(self.radius, self.radius, self.radius); + let center = self.center.xyz(); + + aabb::AABB { + min: center - radius, + max: center + radius, + left_child: 0, + right_child: 0, + } + } } diff --git a/src/render_context.rs b/src/render_context.rs index e81e8b2..7ee9712 100644 --- a/src/render_context.rs +++ b/src/render_context.rs @@ -218,6 +218,13 @@ impl<'a> RenderContext<'a> { Some("surfaces buffer"), ); + let aabb_buffer = StorageBuffer::new_from_bytes( + &device, + bytemuck::cast_slice(scene.get_bvh().as_slice()), + 5_u32, + Some("aabb buffer"), + ); + let scene_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ @@ -226,6 +233,7 @@ impl<'a> RenderContext<'a> { material_buffer.layout(wgpu::ShaderStages::FRAGMENT, true), texture_buffer.layout(wgpu::ShaderStages::FRAGMENT, true), surfaces_buffer.layout(wgpu::ShaderStages::FRAGMENT, true), + aabb_buffer.layout(wgpu::ShaderStages::FRAGMENT, true), ], label: Some("scene layout"), }); @@ -238,6 +246,7 @@ impl<'a> RenderContext<'a> { material_buffer.binding(), texture_buffer.binding(), surfaces_buffer.binding(), + aabb_buffer.binding(), ], label: Some("scene bind group"), }); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index a23dc5c..045375e 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -4,7 +4,7 @@ pub use camera::{Camera, CameraController, GpuCamera}; mod material; pub use material::{GpuMaterial, Material, Texture}; -use crate::object::{self, Mesh, Object, ObjectType, Sphere}; +use crate::object::{self, Mesh, Object, ObjectType, Sphere, AABB}; #[derive(Clone, Debug)] pub struct Scene { @@ -160,6 +160,90 @@ impl Scene { camera_controller: CameraController::new(4.0, 0.4), } } + + pub fn get_bvh(&self) -> Vec { + let mut bvh = Vec::new(); + + let axis = rand::random::() % 3; + let mut spheres = self.spheres.clone(); + + spheres.sort_by(|a, b| { + let a = a.get_bounding_box(); + let b = b.get_bounding_box(); + + a.min[axis as usize] + .partial_cmp(&b.min[axis as usize]) + .unwrap() + }); + + let mut stack = Vec::new(); + stack.push((0, spheres.len(), 0)); + + while let Some((start, end, _)) = stack.pop() { + let mut min = glm::vec3(std::f32::INFINITY, std::f32::INFINITY, std::f32::INFINITY); + let mut max = glm::vec3( + std::f32::NEG_INFINITY, + std::f32::NEG_INFINITY, + std::f32::NEG_INFINITY, + ); + + for i in start..end { + let sphere = &spheres[i]; + let aabb = sphere.get_bounding_box(); + + min = glm::vec3( + min.x.min(aabb.min.x), + min.y.min(aabb.min.y), + min.z.min(aabb.min.z), + ); + + max = glm::vec3( + max.x.max(aabb.max.x), + max.y.max(aabb.max.y), + max.z.max(aabb.max.z), + ); + } + + let mid = (start + end) / 2; + + let left_child = if mid - start == 1 { + start as u32 + } else { + bvh.len() as u32 + 1 + }; + + let right_child = if end - mid == 1 { + mid as u32 + } else { + bvh.len() as u32 + 2 + }; + + bvh.push(AABB { + min, + max, + left_child, + right_child, + }); + + if mid - start > 1 { + stack.push((start, mid, bvh.len() as u32 - 1)); + } + + if end - mid > 1 { + stack.push((mid, end, bvh.len() as u32 - 1)); + } + } + + // DEBUG: traverse bvh and print a tree + for (i, aabb) in bvh.iter().enumerate() { + println!( + "Node: {} min: {:?} max: {:?} left: {} right: {}", + i, aabb.min, aabb.max, aabb.left_child, aabb.right_child + ); + } + + bvh + } } #[repr(C)] diff --git a/src/shader/raytracing.wgsl b/src/shader/raytracing.wgsl index d1c9cd8..aa5ab72 100644 --- a/src/shader/raytracing.wgsl +++ b/src/shader/raytracing.wgsl @@ -27,7 +27,7 @@ const MAX_T = 1000f; @group(1) @binding(3) var textures: array>; // TODO: for now, surfaces will represent a single Mesh @group(1) @binding(4) var surfaces: array; - +@group(1) @binding(5) var aabbs: array; @vertex fn vs_main( @@ -166,6 +166,13 @@ struct Ray { direction: vec3, }; +struct AABB { + min: vec3, + max: vec3, + left_child: u32, + right_child: u32, +}; + struct Sphere { center: vec4, radius: f32, @@ -350,22 +357,6 @@ fn get_ray(rngState: ptr, x: f32, y: f32) -> Ray { -// pub fn from_linear_rgb(c: [f32; 3]) -> Color { -// let f = |x: f32| -> u32 { -// let y = if x > 0.0031308 { -// let a = 0.055; -// (1.0 + a) * x.powf(-2.4) - a -// } else { -// 12.92 * x -// }; -// (y * 255.0).round() as u32 -// }; -// f(c[0]) << 16 | f(c[1]) << 8 | f(c[2]) -// } - - - - fn ray_color(first_ray: Ray, rngState: ptr) -> vec3 { var ray = first_ray; var sky_color = vec3(0.0); @@ -390,6 +381,24 @@ fn ray_color(first_ray: Ray, rngState: ptr) -> vec3 { } +fn rayIntersectBV(ray: ptr, aabb: ptr) -> bool { + let t0 = ((*aabb).min - (*ray).origin) / (*ray).direction; + let t1 = ((*aabb).max - (*ray).origin) / (*ray).direction; + let tmin = min(t0, t1); + let tmax = max(t0, t1); + let maxMinT = max(tmin.x, max(tmin.y, tmin.z)); + let minMaxT = min(tmax.x, min(tmax.y, tmax.z)); + return maxMinT < minMaxT; +} + +fn rayIntersectBVH( + ray: Ray, + hit: ptr, +) -> bool { + // TODO: Implement BVH traversal + return true; +} + fn scatter(ray: Ray, hit: HitRecord, material: Material, rngState: ptr) -> Scatter { switch (material.id) From 4cc6f1a50b449835e06305459334c976674acf18 Mon Sep 17 00:00:00 2001 From: Siirko Date: Sun, 5 Jan 2025 17:24:47 +0100 Subject: [PATCH 2/4] progress needs to remove duplicate and plot graphs to be sure that we are alright before moving into wgsl code --- src/lib.rs | 6 +-- src/object/bvh.rs | 104 ++++++++++++++++++++++++++++++++++++++++++ src/object/mesh.rs | 20 ++++++++ src/object/mod.rs | 13 ++++++ src/object/sphere.rs | 4 +- src/render_context.rs | 5 +- src/scene/mod.rs | 86 +--------------------------------- 7 files changed, 147 insertions(+), 91 deletions(-) create mode 100644 src/object/bvh.rs diff --git a/src/lib.rs b/src/lib.rs index 75918cb..0318440 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,11 +169,11 @@ pub async fn run() { last_time: instant::Instant::now(), render_context: RenderContext::new( &window, - &Scene::teapot_scene( + &Scene::raytracing_scene_oneweek( scene::RenderParam { samples_per_pixel: 1, - max_depth: 2, - samples_max_per_pixel: 1, + max_depth: 7, + samples_max_per_pixel: 1000, total_samples: 0, clear_samples: 0, }, diff --git a/src/object/bvh.rs b/src/object/bvh.rs new file mode 100644 index 0000000..123fe75 --- /dev/null +++ b/src/object/bvh.rs @@ -0,0 +1,104 @@ +use super::{Mesh, Object, ObjectType, Sphere, AABB}; + +fn build_aabbs(objects: &Vec, spheres: &Vec, meshes: &Vec) -> Vec { + let mut aabbs = Vec::new(); + for object in objects { + let aabb = match ObjectType::from_u32(object.obj_type) { + ObjectType::Sphere => spheres[object.id as usize].get_aabb(), + ObjectType::Mesh => meshes[object.id as usize].get_aabb(), + }; + aabbs.push(aabb); + } + aabbs +} + +pub fn bvh_print(bvh: &Vec, index: u32, depth: u32) { + let node = &bvh[index as usize]; + for _ in 0..depth { + print!(" "); + } + if depth > 0 { + print!("└── "); + } + // vec3 to string + println!("Min: {} {} {}", node.min.x, node.min.y, node.min.z); + if node.left_child != u32::MAX { + bvh_print(bvh, node.left_child, depth + 1); + } + if node.right_child != u32::MAX { + bvh_print(bvh, node.right_child, depth + 1); + } +} + +pub fn get_bvh(objects: &Vec, spheres: &Vec, meshes: &Vec) -> Vec { + fn build_bvh_recursive( + aabbs: &mut [AABB], + indices: &[usize], + axis: usize, + bvh: &mut Vec, + ) -> u32 { + if indices.is_empty() { + return u32::MAX; + } + + if indices.len() == 1 { + let leaf_index = indices[0]; + let node_index = bvh.len() as u32; + bvh.push(AABB { + min: aabbs[leaf_index].min, + max: aabbs[leaf_index].max, + left_child: u32::MAX, + right_child: u32::MAX, + }); + return node_index; + } + + let mut sorted_indices = indices.to_vec(); + sorted_indices.sort_by(|&i1, &i2| { + aabbs[i1].min[axis] + .partial_cmp(&aabbs[i2].min[axis]) + .unwrap_or(std::cmp::Ordering::Equal) + .then_with(|| { + aabbs[i1].max[axis] + .partial_cmp(&aabbs[i2].max[axis]) + .unwrap_or(std::cmp::Ordering::Equal) + }) + }); + + let mid = sorted_indices.len() / 2; + let (left_indices, right_indices) = sorted_indices.split_at(mid); + + let mut combined_aabb = AABB { + min: glm::vec3(f32::MAX, f32::MAX, f32::MAX), + max: glm::vec3(f32::MIN, f32::MIN, f32::MIN), + left_child: u32::MAX, + right_child: u32::MAX, + }; + for &index in indices { + combined_aabb.min = glm::min2(&combined_aabb.min, &aabbs[index].min); + combined_aabb.max = glm::max2(&combined_aabb.max, &aabbs[index].max); + } + + let node_index = bvh.len() as u32; + bvh.push(combined_aabb); + + let next_axis = (axis + 1) % 3; + bvh[node_index as usize].left_child = + build_bvh_recursive(aabbs, left_indices, next_axis, bvh); + bvh[node_index as usize].right_child = + build_bvh_recursive(aabbs, right_indices, next_axis, bvh); + + node_index + } + + let mut aabbs = build_aabbs(objects, spheres, meshes); + + let mut bvh = Vec::new(); + let indices: Vec = (0..aabbs.len()).collect(); + build_bvh_recursive(&mut aabbs, &indices, 0, &mut bvh); + + // print bvh as a tree for debugging + bvh_print(&bvh, 0, 0); + println!("BVH: {:?}", bvh); + bvh +} diff --git a/src/object/mesh.rs b/src/object/mesh.rs index 88b5dca..7732f72 100644 --- a/src/object/mesh.rs +++ b/src/object/mesh.rs @@ -1,3 +1,5 @@ +use super::{ObjectType, AABB}; + #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] // TODO: For the moment, vec4 for padding, include manually @@ -63,4 +65,22 @@ impl Mesh { }); indices.collect() } + + pub fn get_aabb(&self) -> AABB { + let mut min = glm::vec3(self.vertices[0].x, self.vertices[0].y, self.vertices[0].z); + let mut max = glm::vec3(self.vertices[0].x, self.vertices[0].y, self.vertices[0].z); + + for vertex in &self.vertices { + let vertex_pos = glm::vec3(vertex.x, vertex.y, vertex.z); + min = glm::min2(&min, &vertex_pos); + max = glm::max2(&max, &vertex_pos); + } + + AABB { + min, + max, + left_child: 0, + right_child: 0, + } + } } diff --git a/src/object/mod.rs b/src/object/mod.rs index df83db3..b7881b0 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -7,6 +7,9 @@ pub use mesh::Mesh; mod aabb; pub use aabb::AABB; +mod bvh; +pub use bvh::get_bvh; + #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] pub struct Object { @@ -29,3 +32,13 @@ pub enum ObjectType { Sphere = 0, Mesh = 1, } + +impl ObjectType { + pub fn from_u32(value: u32) -> Self { + match value { + 0 => ObjectType::Sphere, + 1 => ObjectType::Mesh, + _ => panic!("Unknown object type"), + } + } +} diff --git a/src/object/sphere.rs b/src/object/sphere.rs index 2bd5c34..00760fc 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -1,4 +1,4 @@ -use super::aabb; +use super::{aabb, ObjectType}; #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] @@ -30,7 +30,7 @@ impl Sphere { } } - pub fn get_bounding_box(&self) -> aabb::AABB { + pub fn get_aabb(&self) -> aabb::AABB { let radius = glm::vec3(self.radius, self.radius, self.radius); let center = self.center.xyz(); diff --git a/src/render_context.rs b/src/render_context.rs index 7ee9712..d1a12a2 100644 --- a/src/render_context.rs +++ b/src/render_context.rs @@ -6,6 +6,7 @@ use winit::{ }; use crate::{ + object::get_bvh, scene::{GpuCamera, GpuMaterial, Scene}, utils::{EguiRenderer, StorageBuffer, UniformBuffer, Vertex}, }; @@ -220,7 +221,9 @@ impl<'a> RenderContext<'a> { let aabb_buffer = StorageBuffer::new_from_bytes( &device, - bytemuck::cast_slice(scene.get_bvh().as_slice()), + bytemuck::cast_slice( + get_bvh(&scene.objects, &scene.spheres, &scene.meshes).as_slice(), + ), 5_u32, Some("aabb buffer"), ); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 045375e..a23dc5c 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -4,7 +4,7 @@ pub use camera::{Camera, CameraController, GpuCamera}; mod material; pub use material::{GpuMaterial, Material, Texture}; -use crate::object::{self, Mesh, Object, ObjectType, Sphere, AABB}; +use crate::object::{self, Mesh, Object, ObjectType, Sphere}; #[derive(Clone, Debug)] pub struct Scene { @@ -160,90 +160,6 @@ impl Scene { camera_controller: CameraController::new(4.0, 0.4), } } - - pub fn get_bvh(&self) -> Vec { - let mut bvh = Vec::new(); - - let axis = rand::random::() % 3; - let mut spheres = self.spheres.clone(); - - spheres.sort_by(|a, b| { - let a = a.get_bounding_box(); - let b = b.get_bounding_box(); - - a.min[axis as usize] - .partial_cmp(&b.min[axis as usize]) - .unwrap() - }); - - let mut stack = Vec::new(); - stack.push((0, spheres.len(), 0)); - - while let Some((start, end, _)) = stack.pop() { - let mut min = glm::vec3(std::f32::INFINITY, std::f32::INFINITY, std::f32::INFINITY); - let mut max = glm::vec3( - std::f32::NEG_INFINITY, - std::f32::NEG_INFINITY, - std::f32::NEG_INFINITY, - ); - - for i in start..end { - let sphere = &spheres[i]; - let aabb = sphere.get_bounding_box(); - - min = glm::vec3( - min.x.min(aabb.min.x), - min.y.min(aabb.min.y), - min.z.min(aabb.min.z), - ); - - max = glm::vec3( - max.x.max(aabb.max.x), - max.y.max(aabb.max.y), - max.z.max(aabb.max.z), - ); - } - - let mid = (start + end) / 2; - - let left_child = if mid - start == 1 { - start as u32 - } else { - bvh.len() as u32 + 1 - }; - - let right_child = if end - mid == 1 { - mid as u32 - } else { - bvh.len() as u32 + 2 - }; - - bvh.push(AABB { - min, - max, - left_child, - right_child, - }); - - if mid - start > 1 { - stack.push((start, mid, bvh.len() as u32 - 1)); - } - - if end - mid > 1 { - stack.push((mid, end, bvh.len() as u32 - 1)); - } - } - - // DEBUG: traverse bvh and print a tree - for (i, aabb) in bvh.iter().enumerate() { - println!( - "Node: {} min: {:?} max: {:?} left: {} right: {}", - i, aabb.min, aabb.max, aabb.left_child, aabb.right_child - ); - } - - bvh - } } #[repr(C)] From 41f1f120500168bbd71e42020312ed6e994af6f3 Mon Sep 17 00:00:00 2001 From: Siirko Date: Sun, 5 Jan 2025 17:26:36 +0100 Subject: [PATCH 3/4] removed warnings --- src/object/mesh.rs | 2 +- src/object/sphere.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/object/mesh.rs b/src/object/mesh.rs index 7732f72..c95d024 100644 --- a/src/object/mesh.rs +++ b/src/object/mesh.rs @@ -1,4 +1,4 @@ -use super::{ObjectType, AABB}; +use super::AABB; #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] diff --git a/src/object/sphere.rs b/src/object/sphere.rs index 00760fc..d17fda2 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -1,4 +1,4 @@ -use super::{aabb, ObjectType}; +use super::aabb; #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)] From db62a1a43edfdcc13b723e4244f6a0f733d7caa9 Mon Sep 17 00:00:00 2001 From: Siirko Date: Sun, 5 Jan 2025 23:30:38 +0100 Subject: [PATCH 4/4] array ok but wgsl not ok --- src/object/aabb.rs | 50 ++++++- src/object/bvh.rs | 264 +++++++++++++++++++++++++------------ src/object/mesh.rs | 7 +- src/object/mod.rs | 2 +- src/object/sphere.rs | 10 +- src/shader/raytracing.wgsl | 133 ++++++++++++++----- 6 files changed, 342 insertions(+), 124 deletions(-) diff --git a/src/object/aabb.rs b/src/object/aabb.rs index 850b2f3..afc882b 100644 --- a/src/object/aabb.rs +++ b/src/object/aabb.rs @@ -2,9 +2,53 @@ use glm::Vec3; #[repr(C)] #[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct AABB { +pub struct Bounds { pub min: Vec3, pub max: Vec3, - pub left_child: u32, - pub right_child: u32, +} + +impl Bounds { + pub fn new(min: Vec3, max: Vec3) -> Self { + Self { min, max } + } + + pub fn empty() -> Self { + Self { + min: Vec3::new(0.0, 0.0, 0.0), + max: Vec3::new(0.0, 0.0, 0.0), + } + } + + pub fn union(b1: Bounds, b2: Bounds) -> Bounds { + let min = Vec3::new( + b1.min.x.min(b2.min.x), + b1.min.y.min(b2.min.y), + b1.min.z.min(b2.min.z), + ); + let max = Vec3::new( + b1.max.x.max(b2.max.x), + b1.max.y.max(b2.max.y), + b1.max.z.max(b2.max.z), + ); + Bounds { min, max } + } + + pub fn maximum_extent(&self) -> usize { + let diag = self.max - self.min; + if diag.x > diag.y && diag.x > diag.z { + 0 + } else if diag.y > diag.z { + 1 + } else { + 2 + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct AABB { + pub bounds: Bounds, + pub centroid: Vec3, + pub type_: u32, } diff --git a/src/object/bvh.rs b/src/object/bvh.rs index 123fe75..da5c1ed 100644 --- a/src/object/bvh.rs +++ b/src/object/bvh.rs @@ -1,4 +1,137 @@ -use super::{Mesh, Object, ObjectType, Sphere, AABB}; +#![allow(dead_code)] +use std::rc::Rc; + +use glm::{Vec3, Vec4}; + +use super::{Bounds, Mesh, Object, ObjectType, Sphere, AABB}; + +#[derive(Debug, Clone)] +struct BVHBuildNode { + bounds: Bounds, + left: Option>, + right: Option>, + split_axis: u32, + first_obj_offset: u32, + n_obj: u32, + obj_type: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct LinearBVHNode { + min: Vec4, + max: Vec3, + offset: u32, + n_obj: u32, + axis: u32, + obj_type: u32, + obj_offset: u32, +} + +#[derive(Debug, Clone)] +pub struct BVH { + root: Rc, + pub total_nodes: u32, +} + +impl BVHBuildNode { + pub fn default() -> Self { + BVHBuildNode { + bounds: Bounds { + min: Vec3::new(0.0, 0.0, 0.0), + max: Vec3::new(0.0, 0.0, 0.0), + }, + left: None, + right: None, + split_axis: 0, + first_obj_offset: 0, + n_obj: 0, + obj_type: 0, + } + } + pub fn init_leaf(first: u32, n: u32, bounds: Bounds) -> BVHBuildNode { + BVHBuildNode { + bounds, + first_obj_offset: first, + n_obj: n, + left: None, + right: None, + split_axis: 0, + obj_type: 0, + } + } + + pub fn init_interior(axis: u32, c0: Rc, c1: Rc) -> BVHBuildNode { + let c0_bounds = c0.bounds; + let c1_bounds = c1.bounds; + + let bounds = Bounds::union(c0_bounds, c1_bounds); + + BVHBuildNode { + bounds, + first_obj_offset: 0, + n_obj: 0, + left: Some(c0), + right: Some(c1), + split_axis: axis, + obj_type: 0, + } + } +} + +fn recursive_build( + aabb: &Vec, + start: usize, + end: usize, + total_nodes: &mut u32, + ordered_objects: &mut Vec, +) -> Rc { + *total_nodes += 1; + let mut bounds = Bounds::empty(); + for i in start..end { + bounds = Bounds::union(bounds, aabb[i].bounds); + } + + let n_obj = end - start; + if n_obj == 1 { + // create leaf node + let first_obj_offset = ordered_objects.len() as u32; + for i in start..end { + ordered_objects.push(aabb[i].type_); + } + return Rc::new(BVHBuildNode::init_leaf( + first_obj_offset, + n_obj as u32, + bounds, + )); + } else { + let mut centroid_bounds = Bounds::empty(); + for i in start..end { + centroid_bounds = Bounds::union(centroid_bounds, aabb[i].bounds); + } + let dim = centroid_bounds.maximum_extent(); + let mid = start + (end - start) / 2; + if centroid_bounds.max[dim] == centroid_bounds.min[dim] { + // create leaf node + let first_obj_offset = ordered_objects.len() as u32; + for i in start..end { + ordered_objects.push(aabb[i].type_); + } + return Rc::new(BVHBuildNode::init_leaf( + first_obj_offset, + n_obj as u32, + bounds, + )); + } else { + // partition objects based on the midpoint + return Rc::new(BVHBuildNode::init_interior( + dim as u32, + recursive_build(aabb, start, mid, total_nodes, ordered_objects), + recursive_build(aabb, mid, end, total_nodes, ordered_objects), + )); + } + } +} fn build_aabbs(objects: &Vec, spheres: &Vec, meshes: &Vec) -> Vec { let mut aabbs = Vec::new(); @@ -12,93 +145,62 @@ fn build_aabbs(objects: &Vec, spheres: &Vec, meshes: &Vec) aabbs } -pub fn bvh_print(bvh: &Vec, index: u32, depth: u32) { - let node = &bvh[index as usize]; +fn print_tree(node: &Rc, depth: u32) { for _ in 0..depth { - print!(" "); - } - if depth > 0 { - print!("└── "); + print!(" "); } - // vec3 to string - println!("Min: {} {} {}", node.min.x, node.min.y, node.min.z); - if node.left_child != u32::MAX { - bvh_print(bvh, node.left_child, depth + 1); - } - if node.right_child != u32::MAX { - bvh_print(bvh, node.right_child, depth + 1); + println!( + "Bounds: {:?}, n_obj: {}, split_axis: {}, first_obj_offset: {}, n_obj: {}", + node.bounds, node.n_obj, node.split_axis, node.first_obj_offset, node.n_obj, + ); + if node.n_obj == 0 { + print_tree(&node.left.as_ref().unwrap(), depth + 1); + print_tree(&node.right.as_ref().unwrap(), depth + 1); } } -pub fn get_bvh(objects: &Vec, spheres: &Vec, meshes: &Vec) -> Vec { - fn build_bvh_recursive( - aabbs: &mut [AABB], - indices: &[usize], - axis: usize, - bvh: &mut Vec, - ) -> u32 { - if indices.is_empty() { - return u32::MAX; - } - - if indices.len() == 1 { - let leaf_index = indices[0]; - let node_index = bvh.len() as u32; - bvh.push(AABB { - min: aabbs[leaf_index].min, - max: aabbs[leaf_index].max, - left_child: u32::MAX, - right_child: u32::MAX, - }); - return node_index; - } - - let mut sorted_indices = indices.to_vec(); - sorted_indices.sort_by(|&i1, &i2| { - aabbs[i1].min[axis] - .partial_cmp(&aabbs[i2].min[axis]) - .unwrap_or(std::cmp::Ordering::Equal) - .then_with(|| { - aabbs[i1].max[axis] - .partial_cmp(&aabbs[i2].max[axis]) - .unwrap_or(std::cmp::Ordering::Equal) - }) - }); - - let mid = sorted_indices.len() / 2; - let (left_indices, right_indices) = sorted_indices.split_at(mid); - - let mut combined_aabb = AABB { - min: glm::vec3(f32::MAX, f32::MAX, f32::MAX), - max: glm::vec3(f32::MIN, f32::MIN, f32::MIN), - left_child: u32::MAX, - right_child: u32::MAX, - }; - for &index in indices { - combined_aabb.min = glm::min2(&combined_aabb.min, &aabbs[index].min); - combined_aabb.max = glm::max2(&combined_aabb.max, &aabbs[index].max); - } - - let node_index = bvh.len() as u32; - bvh.push(combined_aabb); - - let next_axis = (axis + 1) % 3; - bvh[node_index as usize].left_child = - build_bvh_recursive(aabbs, left_indices, next_axis, bvh); - bvh[node_index as usize].right_child = - build_bvh_recursive(aabbs, right_indices, next_axis, bvh); - - node_index +fn linearize_bvh(node: &Rc, linear_nodes: &mut Vec, offset: &mut u32) { + let linear_node = LinearBVHNode { + min: Vec4::new(node.bounds.min.x, node.bounds.min.y, node.bounds.min.z, 0.0), + max: node.bounds.max, + offset: *offset, + n_obj: node.n_obj, + axis: node.split_axis, + obj_type: node.obj_type, + obj_offset: node.first_obj_offset, + }; + *offset += 1; + linear_nodes.push(linear_node); + if node.n_obj == 0 { + linearize_bvh(node.left.as_ref().unwrap(), linear_nodes, offset); + linearize_bvh(node.right.as_ref().unwrap(), linear_nodes, offset); } +} +// https://pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies#LinearBVHNode::secondChildOffset +pub fn get_bvh( + objects: &Vec, + spheres: &Vec, + meshes: &Vec, +) -> Vec { + let aabbs = build_aabbs(objects, spheres, meshes); - let mut aabbs = build_aabbs(objects, spheres, meshes); + let mut ordered_objects = Vec::new(); + let mut total_nodes = 0; + let root = recursive_build( + &aabbs, + 0, + aabbs.len(), + &mut total_nodes, + &mut ordered_objects, + ); - let mut bvh = Vec::new(); - let indices: Vec = (0..aabbs.len()).collect(); - build_bvh_recursive(&mut aabbs, &indices, 0, &mut bvh); + // print_tree(&root, 0); + let mut linear_nodes = Vec::new(); + let mut offset = 0; + linearize_bvh(&root, &mut linear_nodes, &mut offset); - // print bvh as a tree for debugging - bvh_print(&bvh, 0, 0); - println!("BVH: {:?}", bvh); - bvh + for node in &linear_nodes { + println!("{:?}", node); + } + linear_nodes } diff --git a/src/object/mesh.rs b/src/object/mesh.rs index c95d024..18613c8 100644 --- a/src/object/mesh.rs +++ b/src/object/mesh.rs @@ -77,10 +77,9 @@ impl Mesh { } AABB { - min, - max, - left_child: 0, - right_child: 0, + bounds: super::Bounds { min, max }, + centroid: (min + max) / 2.0, + type_: 1, } } } diff --git a/src/object/mod.rs b/src/object/mod.rs index b7881b0..159c709 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -5,7 +5,7 @@ mod mesh; pub use mesh::Mesh; mod aabb; -pub use aabb::AABB; +pub use aabb::{Bounds, AABB}; mod bvh; pub use bvh::get_bvh; diff --git a/src/object/sphere.rs b/src/object/sphere.rs index d17fda2..314ebc9 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -35,10 +35,12 @@ impl Sphere { let center = self.center.xyz(); aabb::AABB { - min: center - radius, - max: center + radius, - left_child: 0, - right_child: 0, + bounds: aabb::Bounds { + min: center - radius, + max: center + radius, + }, + centroid: center, + type_: 0, } } } diff --git a/src/shader/raytracing.wgsl b/src/shader/raytracing.wgsl index aa5ab72..2024280 100644 --- a/src/shader/raytracing.wgsl +++ b/src/shader/raytracing.wgsl @@ -166,11 +166,19 @@ struct Ray { direction: vec3, }; +struct Bouding { + min: vec4, + max: vec3, +}; + struct AABB { - min: vec3, + min: vec4, max: vec3, - left_child: u32, - right_child: u32, + offset: u32, + n_obj: u32, + axis: u32, + obj_type: u32, + obj_offset: u32, }; struct Sphere { @@ -302,35 +310,107 @@ fn hit_triangle( return false; } +fn hit_object( + object_index: u32, + ray: Ray, + ray_min: f32, + ray_max: f32, + hit: ptr, +) -> bool { + switch objects[object_index].obj_type { + case OBJECT_SPHERE: { + return hit_sphere(object_index, ray, ray_min, ray_max, hit); + } + case OBJECT_MESHES: { + return hit_triangle(object_index, ray, ray_min, ray_max, hit); + } + default: { + return false; + } + } +} + fn check_intersection(ray: Ray, intersection: ptr) -> bool { var closest_so_far = MAX_T; var hit_anything = false; var tmp_rec = HitRecord(); for (var i = 0u; i < arrayLength(&objects); i += 1u) { - switch (objects[i].obj_type) { - case OBJECT_SPHERE: { - if hit_sphere(objects[i].id, ray, MIN_T, closest_so_far, &tmp_rec) { + if hit_object(i, ray, MIN_T, closest_so_far, &tmp_rec) { + hit_anything = true; + closest_so_far = tmp_rec.t; + *intersection = tmp_rec; + } + } + + return hit_anything; +} + +// https://pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies#LinearBVHNode::secondChildOffset +fn check_intersection_bvh(ray: Ray, intersection: ptr) -> bool { + var closest_so_far = MAX_T; + var hit_anything = false; + var tmp_rec = HitRecord(); + + let invD = 1.0 / ray.direction; + + var to_visit_offset = 0u; + var current_node_index = 0u; + var nodes_to_visit = array(); + + var ray_ = ray; + + var node = aabbs[10]; + let bounds = Bouding(node.min, node.max); + if rayIntersectBV(&ray_, bounds) { + if node.n_obj > 0 { + for (var j = 0u; j < node.n_obj; j += 1u) { + if hit_object(node.obj_offset - 1 + j, ray_, MIN_T, closest_so_far, &tmp_rec) { hit_anything = true; closest_so_far = tmp_rec.t; *intersection = tmp_rec; } } - case OBJECT_MESHES: { - for (var j = 0u; j < arrayLength(&surfaces); j += 1u) { - if hit_triangle(j, ray, MIN_T, closest_so_far, &tmp_rec) { - hit_anything = true; - closest_so_far = tmp_rec.t; - *intersection = tmp_rec; - } - } - } - default: { - // Do nothing - } } } + var max_iterations = 64u; + // for (var i = 0u; i < max_iterations; i += 1u) { + // var node = aabbs[current_node_index]; + // let bounds = Bouding(node.min, node.max); + // if rayIntersectBV(&ray_, bounds) { + // if node.n_obj > 0 { + // for (var j = 0u; j < node.n_obj; j += 1u) { + // if hit_object(node.obj_offset - 1 + j, ray_, MIN_T, closest_so_far, &tmp_rec) { + // hit_anything = true; + // closest_so_far = tmp_rec.t; + // *intersection = tmp_rec; + // } + // } + // if to_visit_offset == 0u { + // break; + // } + // to_visit_offset -= 1u; + // current_node_index = nodes_to_visit[to_visit_offset]; + // } else { + // if ray_.direction[node.axis] < 0.0 { + // nodes_to_visit[to_visit_offset] = current_node_index + 1u; + // current_node_index = node.offset; + // } else { + // nodes_to_visit[to_visit_offset] = node.offset; + // current_node_index = current_node_index + 1u; + // } + // to_visit_offset += 1u; + // } + // } else { + // if to_visit_offset == 0u { + // break; + // } + // to_visit_offset -= 1u; + // current_node_index = nodes_to_visit[to_visit_offset]; + // } + // } + return hit_anything; } @@ -364,7 +444,7 @@ fn ray_color(first_ray: Ray, rngState: ptr) -> vec3 { for (var i = 0u; i < render_param.max_depth; i += 1u) { var intersection = HitRecord(); - if check_intersection(ray, &intersection) { + if check_intersection_bvh(ray, &intersection) { let material = materials[intersection.material_index]; let scattered = scatter(ray, intersection, material, rngState); color *= scattered.attenuation; @@ -381,9 +461,9 @@ fn ray_color(first_ray: Ray, rngState: ptr) -> vec3 { } -fn rayIntersectBV(ray: ptr, aabb: ptr) -> bool { - let t0 = ((*aabb).min - (*ray).origin) / (*ray).direction; - let t1 = ((*aabb).max - (*ray).origin) / (*ray).direction; +fn rayIntersectBV(ray: ptr, aabb: Bouding) -> bool { + let t0 = ((aabb).min.xyz - (*ray).origin) / (*ray).direction; + let t1 = ((aabb).max.xyz - (*ray).origin) / (*ray).direction; let tmin = min(t0, t1); let tmax = max(t0, t1); let maxMinT = max(tmin.x, max(tmin.y, tmin.z)); @@ -391,15 +471,6 @@ fn rayIntersectBV(ray: ptr, aabb: ptr) -> bool { return maxMinT < minMaxT; } -fn rayIntersectBVH( - ray: Ray, - hit: ptr, -) -> bool { - // TODO: Implement BVH traversal - return true; -} - - fn scatter(ray: Ray, hit: HitRecord, material: Material, rngState: ptr) -> Scatter { switch (material.id) {