fastish build of SAH BVH

This commit is contained in:
Anton Ljungdahl 2025-05-11 14:42:33 +02:00
parent d875d1130b
commit 738c765557
2 changed files with 169 additions and 51 deletions

View File

@ -51,7 +51,8 @@ Viewport :: struct {
Ray :: struct {
origin : Vec3,
direction : Vec3
direction : Vec3,
inv_dir : Vec3
}
HitRecord :: struct {
@ -108,6 +109,7 @@ ray_get :: proc(x : f32, y : f32) -> Ray {
out.direction = ray_direction
out.origin = camera.center
out.inv_dir = Vec3{1.0/out.direction.x, 1.0/out.direction.y, 1.0/out.direction.z}
return out
}
@ -117,29 +119,6 @@ rayt_cpu_main :: proc() {
load_triangles()
// Random triangles
//{
// center_shift := Vec3{5.0, 5.0, 5.0}
// // Generate triangles inside a box
// for i in 0..<MAX_NUM_ENTITIES {
// r0 := vec3_rand_uniform();
// r1 := vec3_rand_uniform();
// r2 := vec3_rand_uniform();
//
// // Put the first vertex within a 10x10x10 cube centered at the origin
// v0 := 9.0*r0
// v0 = v0 - center_shift
//
// entities[i].kind = EntityKind.Tri
// entities[i].v0 = v0
// entities[i].v1 = v0 + r1
// entities[i].v2 = v0 + r2
// entities[i].center = 0.3333*(entities[i].v0 + entities[i].v1 + entities[i].v2)
// tri_indices[i] = cast(u32)i
// //fmt.printf("Triangle idx %i \n", tri_indices[i])
// }
//}
// Set up scene globals
{
image.width = IMAGE_WIDTH
@ -178,7 +157,11 @@ rayt_cpu_main :: proc() {
// build bvh
fmt.println("Building BVH")
time.stopwatch_start(&stopwatch)
bvh_build()
time.stopwatch_stop(&stopwatch)
elapsed_bvh_ms := elapsed_time_ms()
fmt.printf("Build BVH in %.4f ms \n", elapsed_bvh_ms)
bvh_stats()
fmt.printf("Starting CPU raytracing")
@ -251,8 +234,7 @@ triangle_intersection :: proc(ray : ^Ray, rec : ^HitRecord, triangle : ^Entity)
}
}
// If we have an intersection closer than the last, we fill out the hit record
if rec.t < closest_so_far {
intersection_point := ray_point(rec.t, ray)
v0_normal := linalg.cross(edge1, edge2)

View File

@ -5,7 +5,7 @@ import "core:math"
import "core:math/linalg"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Struct definitions
BVHNode :: struct {
aabb_min : Vec3,
aabb_max : Vec3,
@ -13,37 +13,52 @@ BVHNode :: struct {
tri_count : u32
}
// Helper structure used to define a number of primtiives inside a given non-leaf BVH node,
// and their bounding box. This is used to compute the cost of a given split of that node.
BVHBin :: struct {
aabb_min : Vec3,
aabb_max : Vec3,
tri_count : u32,
}
BVH :: struct {
nodes : []BVHNode,
used_nodes : u32,
root_index : u32,
max_num_nodes : u32,
num_leaf_nodes : u32,
num_leaf_entities : u32
num_leaf_entities : u32,
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Globals
bvh : BVH
BVH_NUM_BINS :: 8
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
bvh_bins_init :: proc(bins : []BVHBin) {
for i in 0..<BVH_NUM_BINS {
bins[i].aabb_min = Vec3{math.F32_MAX, math.F32_MAX, math.F32_MAX}
bins[i].aabb_max = Vec3{-math.F32_MAX, -math.F32_MAX, -math.F32_MAX}
}
}
intersect_aabb :: proc(ray : ^Ray, bmin : Vec3, bmax : Vec3, closest_so_far : f32) -> b32 {
tx1 : f32 = (bmin.x - ray.origin.x) / ray.direction.x
tx2 : f32 = (bmax.x - ray.origin.x) / ray.direction.x
tx1 : f32 = (bmin.x - ray.origin.x) * ray.inv_dir.x
tx2 : f32 = (bmax.x - ray.origin.x) * ray.inv_dir.x
tmin : f32 = math.min(tx1, tx2)
tmax : f32 = math.max(tx1, tx2)
ty1 : f32 = (bmin.y - ray.origin.y) / ray.direction.y
ty2 : f32 = (bmax.y - ray.origin.y) / ray.direction.y
ty1 : f32 = (bmin.y - ray.origin.y) * ray.inv_dir.y
ty2 : f32 = (bmax.y - ray.origin.y) * ray.inv_dir.y
tmin = math.max(tmin, math.min(ty1, ty2))
tmax = math.min(tmax, math.max(ty1, ty2))
tz1 : f32 = (bmin.z - ray.origin.z) / ray.direction.z
tz2 : f32 = (bmax.z - ray.origin.z) / ray.direction.z
tz1 : f32 = (bmin.z - ray.origin.z) * ray.inv_dir.z
tz2 : f32 = (bmax.z - ray.origin.z) * ray.inv_dir.z
tmin = math.max(tmin, math.min(tz1, tz2))
tmax = math.min(tmax, math.max(tz1, tz2))
@ -51,6 +66,40 @@ intersect_aabb :: proc(ray : ^Ray, bmin : Vec3, bmax : Vec3, closest_so_far : f3
return out
}
aabb_area :: proc(aabb_min : Vec3, aabb_max : Vec3) -> f32 {
e := aabb_max - aabb_min
return e.x * e.y + e.y * e.z + e.z * e.x
}
aabb_grow :: proc(aabb_min : Vec3, aabb_max : Vec3, p_min : Vec3, p_max : Vec3) -> (Vec3, Vec3) {
out_aabb_min := aabb_min
out_aabb_max := aabb_max
if(p_min.x < math.F32_MAX) {
out_aabb_min = linalg.min(out_aabb_min, p_min)
out_aabb_max = linalg.max(out_aabb_max, p_min)
out_aabb_min = linalg.min(out_aabb_min, p_max)
out_aabb_max = linalg.max(out_aabb_max, p_max)
}
return out_aabb_min, out_aabb_max
}
aabb_min_triangle :: proc(aabb_min : Vec3, tri : ^Entity) -> Vec3 {
out_aabb_min := aabb_min
out_aabb_min = linalg.min(out_aabb_min, tri.v0)
out_aabb_min = linalg.min(out_aabb_min, tri.v1)
out_aabb_min = linalg.min(out_aabb_min, tri.v2)
return out_aabb_min
}
aabb_max_triangle :: proc(aabb_max : Vec3, tri : ^Entity) -> Vec3 {
out_aabb_max := aabb_max
out_aabb_max = linalg.max(out_aabb_max, tri.v0)
out_aabb_max = linalg.max(out_aabb_max, tri.v1)
out_aabb_max = linalg.max(out_aabb_max, tri.v2)
return out_aabb_max
}
bvh_update_bounds :: proc(node_idx : u32) {
node : ^BVHNode = &bvh.nodes[node_idx]
@ -61,27 +110,112 @@ bvh_update_bounds :: proc(node_idx : u32) {
for i in 0..<node.tri_count {
leaf_tri_idx := tri_indices[first_tri_idx + i]
triangle : ^Entity = &entities[leaf_tri_idx]
node.aabb_min = linalg.min(node.aabb_min, triangle.v0)
node.aabb_min = linalg.min(node.aabb_min, triangle.v1)
node.aabb_min = linalg.min(node.aabb_min, triangle.v2)
node.aabb_max = linalg.max(node.aabb_max, triangle.v0)
node.aabb_max = linalg.max(node.aabb_max, triangle.v1)
node.aabb_max = linalg.max(node.aabb_max, triangle.v2)
node.aabb_min = aabb_min_triangle(node.aabb_min, triangle)
node.aabb_max = aabb_max_triangle(node.aabb_max, triangle)
}
}
find_best_split_plane :: proc(node : ^BVHNode, out_axis : ^u32, out_split_pos : ^f32) -> f32 {
best_cost : f32 = math.F32_MAX
// Loop over each axis
for axis in 0..<3 {
bounds_min : f32 = math.F32_MAX
bounds_max : f32 = -math.F32_MAX
// Find the bounds of all the primitive centers in the node
for i in 0..<node.tri_count {
tri : ^Entity = &entities[tri_indices[node.left_first + i]]
bounds_min = math.min(bounds_min, tri.center[axis])
bounds_max = math.max(bounds_max, tri.center[axis])
}
if bounds_min == bounds_max { continue }
bins : [BVH_NUM_BINS]BVHBin
bvh_bins_init(bins[:])
bin_scale : f32 = cast(f32)BVH_NUM_BINS / (bounds_max - bounds_min)
// We put all the primitives in the node in any one of the binds,
// depending on the primitive's centroid pos. What we are doing is really
// just splitting the node with BVH_NUM_BINS-1 number of planes.
for i in 0..<node.tri_count {
tri : ^Entity = &entities[tri_indices[node.left_first + i]]
primitive_index : u32 = cast(u32)((tri.center[axis] - bounds_min) * bin_scale)
bin_idx : u32 = math.min(BVH_NUM_BINS - 1, primitive_index)
bins[bin_idx].tri_count += 1
bins[bin_idx].aabb_min = aabb_min_triangle(bins[bin_idx].aabb_min, tri)
bins[bin_idx].aabb_max = aabb_max_triangle(bins[bin_idx].aabb_min, tri)
}
// Gather data for all BVH_NUM_BINS-1 planes
left_area : [BVH_NUM_BINS - 1]f32
right_area : [BVH_NUM_BINS - 1]f32
left_count : [BVH_NUM_BINS - 1]u32
right_count : [BVH_NUM_BINS - 1]u32
left_box_aabb_min : Vec3 = Vec3{math.F32_MAX, math.F32_MAX, math.F32_MAX}
left_box_aabb_max : Vec3 = Vec3{-math.F32_MAX, -math.F32_MAX, -math.F32_MAX}
right_box_aabb_min : Vec3 = Vec3{math.F32_MAX, math.F32_MAX, math.F32_MAX}
right_box_aabb_max : Vec3 = Vec3{-math.F32_MAX, -math.F32_MAX, -math.F32_MAX}
left_sum : u32 = 0
right_sum : u32 = 0
// Loop from both sides simultaneously
for i in 0..<BVH_NUM_BINS-1 {
left_sum += bins[i].tri_count
left_count[i] = left_sum
left_box_aabb_min, left_box_aabb_max = aabb_grow(left_box_aabb_min, left_box_aabb_max,
bins[i].aabb_min, bins[i].aabb_max)
left_area[i] = aabb_area(left_box_aabb_min, left_box_aabb_max)
right_idx := BVH_NUM_BINS - 1 - i
right_sum += bins[right_idx].tri_count
right_count[right_idx-1] = right_sum
right_box_aabb_min, right_box_aabb_max = aabb_grow(right_box_aabb_min,
right_box_aabb_max,
bins[right_idx].aabb_min,
bins[right_idx].aabb_max)
right_area[right_idx-1] = aabb_area(right_box_aabb_min, right_box_aabb_max)
}
plane_scale : f32 = (bounds_max - bounds_min) / cast(f32)BVH_NUM_BINS
// Compute the Surface area heuristic (SAH) cost for each plane
for i in 0..<BVH_NUM_BINS-1 {
plane_cost : f32 = 0.0
plane_cost += cast(f32)left_count[i] * left_area[i]
plane_cost += cast(f32)right_count[i] * right_area[i]
if(plane_cost < best_cost) {
out_axis^ = cast(u32)axis
out_split_pos^ = bounds_min + plane_scale * cast(f32)(i + 1)
best_cost = plane_cost
}
}
}
return best_cost
}
bvh_subdivide :: proc(node_idx : u32) {
node : ^BVHNode = &bvh.nodes[node_idx]
if node.tri_count < bvh.num_leaf_entities {
return
}
if(node.tri_count <= bvh.num_leaf_entities) {
axis : u32 = 0
split_pos : f32
split_cost := find_best_split_plane(node, &axis, &split_pos)
node_split_cost : f32 = 0.0
{
e : Vec3 = node.aabb_max - node.aabb_min
surface_area := e.x * e.y + e.y * e.z + e.z * e.x
node_split_cost = cast(f32)node.tri_count * surface_area
}
if(split_cost >= node_split_cost) {
return;
}
extent := node.aabb_max - node.aabb_min
axis : u32 = 0
if(extent.y > extent.x) {axis = 1}
if(extent.z > extent[axis]) {axis = 2}
split_pos : f32 = node.aabb_min[axis] + extent[axis] * 0.5
i : u32 = node.left_first
j : u32 = node.tri_count + i - 1
@ -150,8 +284,9 @@ bvh_build :: proc() {
num_triangles : u32 = cast(u32)len(tri_indices)
bvh.max_num_nodes = 2 * num_triangles - 1
bvh.nodes = make([]BVHNode, bvh.max_num_nodes)
bvh.used_nodes = 2 // We skip first two nodes, for some reason. TODO comment this, read the tutorial
bvh.num_leaf_entities = 2
bvh.used_nodes = 2 // We skip first two nodes, for some reason.
//TODO comment this, read the tutorial
bvh.num_leaf_entities = 4
bvh.root_index = 0
// Init root node
@ -184,5 +319,6 @@ bvh_stats :: proc() {
fmt.printf("Total number of leaf nodes: %i \n", num_leaf_nodes)
fmt.printf("Total number of triangles in BVH: %i \n", total_triangles_in_bvh)
}
assert(cast(int)total_triangles_in_bvh == len(tri_indices))
bvh.num_leaf_nodes = num_leaf_nodes
}