working first bvh with odin and raylib

This commit is contained in:
Anton Ljungdahl 2025-05-07 11:13:26 +02:00
parent 6603f27c90
commit d68f740c10
3 changed files with 262 additions and 23 deletions

View File

@ -3,10 +3,15 @@ package main
import rl "vendor:raylib"
import "core:fmt"
////////////////////////////////////////////////////////////////////////////////////////////////////
WINDOW_WIDTH :: 1280
WINDOW_HEIGHT :: 720
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
rl_window_loop :: proc() {
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Rayt");
defer rl.CloseWindow()

View File

@ -4,36 +4,36 @@ import "core:fmt"
import "core:math/rand"
import "core:math/linalg"
import "core:math"
import "core:time"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Global defines
RAND_SEED :: 1984
// Global program parameters
IMAGE_WIDTH :: 1280
ASPECT_RATIO :: 1.7778 // 16:9
MAX_NUM_ENTITIES :: 64
Vec3 :: distinct [3]f32
// Global colors
COLOR_LIGHT_BLUE :: Vec3{0.5, 0.7, 1.0}
COLOR_WHITE :: Vec3{1.0, 1.0, 1.0}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Global program parameters
IMAGE_WIDTH :: 1280
ASPECT_RATIO :: 1.7778 // 16:9
MAX_NUM_ENTITIES :: 64
////////////////////////////////////////////////////////////////////////////////////////////////////
// Struct defs
Image :: struct {
width : u32,
height : u32,
aspect_ratio : f32
}
image: Image
Camera :: struct {
center : Vec3,
up : Vec3,
focal_length : f32,
}
camera: Camera
Viewport :: struct {
width : f32,
@ -46,7 +46,6 @@ Viewport :: struct {
pixel_delta_u : Vec3,
pixel_delta_v : Vec3,
}
viewport: Viewport
Ray :: struct {
origin : Vec3,
@ -72,11 +71,26 @@ Entity :: struct {
v1: Vec3,
v2: Vec3,
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Main global variables
stopwatch : time.Stopwatch
image: Image
camera: Camera
viewport: Viewport
entities: [MAX_NUM_ENTITIES]Entity
tri_indices: [MAX_NUM_ENTITIES]u32
pixelbuffer: []Vec3
pixelbuffer_rgb: []u8
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
vec3_rand_uniform :: proc() -> Vec3 {
return Vec3{rand.float32(), rand.float32(), rand.float32()}
}
@ -117,6 +131,8 @@ rayt_cpu_main :: proc() {
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])
}
}
@ -156,8 +172,23 @@ rayt_cpu_main :: proc() {
pixelbuffer = make([]Vec3, num_pixels);
pixelbuffer_rgb = make([]u8, num_pixels * 3) // rgb values for each pixel
fmt.println("Starting CPU raytracing")
// build bvh
fmt.println("Building BVH")
bvh_build()
bvh_stats()
fmt.printf("Starting CPU raytracing")
if bvh.num_leaf_nodes > 1 {
fmt.printf(" - WITH BVH!")
}
fmt.printf("\n")
time.stopwatch_start(&stopwatch)
cpu_raytracing()
time.stopwatch_stop(&stopwatch)
elapsed_ms := elapsed_time_ms()
fmt.printf("Elapsed for CPU raytracing: %.4f ms \n", elapsed_ms)
// Translate pixelbuffer with colors from 0 to 1, to rgb 0..255
{
@ -255,18 +286,23 @@ cpu_raytracing :: proc() {
temp_hit_rec : HitRecord
temp_hit_rec.hit = false
temp_hit_rec.t = hit_rec.t
for i in 0..<MAX_NUM_ENTITIES {
tri_ref := &entities[i]
triangle_intersection(&ray, &temp_hit_rec, tri_ref)
if temp_hit_rec.hit {
hit_rec.hit = true
hit_rec.t = temp_hit_rec.t
hit_rec.normal = temp_hit_rec.normal
num_hits += 1
}
{
bvh_intersect(&ray, &hit_rec, bvh.root_index)
}
//{
// for i in 0..<MAX_NUM_ENTITIES {
// tri_ref := &entities[i]
// triangle_intersection(&ray, &temp_hit_rec, tri_ref)
// if temp_hit_rec.hit {
// hit_rec.hit = true
// hit_rec.t = temp_hit_rec.t
// hit_rec.normal = temp_hit_rec.normal
// num_hits += 1
// }
// }
//}
if hit_rec.hit {
@ -292,4 +328,9 @@ cpu_raytracing :: proc() {
}
//fmt.printf("Num hits on triangles: %i \n", num_hits)
}
elapsed_time_ms :: proc() -> f64 {
return time.duration_milliseconds(time.stopwatch_duration(stopwatch))
}

193
src/rayt_bvh.odin Normal file
View File

@ -0,0 +1,193 @@
package main
import "core:fmt"
import "core:math"
import "core:math/linalg"
////////////////////////////////////////////////////////////////////////////////////////////////////
BVHNode :: struct {
aabb_min : Vec3,
aabb_max : Vec3,
left_first : u32,
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
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bvh : BVH
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
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
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
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
tmin = math.max(tmin, math.min(tz1, tz2))
tmax = math.min(tmax, math.max(tz1, tz2))
out : b32 = tmax >= tmin && tmin < closest_so_far && tmax > 0.0
return out
}
bvh_update_bounds :: proc(node_idx : u32) {
node : ^BVHNode = &bvh.nodes[node_idx]
node.aabb_min = Vec3{math.F32_MAX, math.F32_MAX, math.F32_MAX}
node.aabb_max = Vec3{-math.F32_MAX, -math.F32_MAX, -math.F32_MAX}
first_tri_idx := node.left_first
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)
}
}
bvh_subdivide :: proc(node_idx : u32) {
node : ^BVHNode = &bvh.nodes[node_idx]
if(node.tri_count <= bvh.num_leaf_entities) {
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
// Sort indices into partitions depending on split pos
for i <= j {
//fmt.printf("BVH node idx %i \n", node_idx)
//fmt.printf("(i, j) = (%i, %i) \n", i, j)
tri_idx : u32 = tri_indices[i]
if entities[tri_idx].center[axis] < split_pos {
i += 1
} else {
tri_indices[i] = tri_indices[j]
tri_indices[j] = tri_idx
j -= 1
}
}
left_count : u32 = i - node.left_first
if left_count == 0 || left_count == node.tri_count {
// One of the partitions is empty, stop subdividing
return
}
// Create child nodes and subdivide
left_child_idx := bvh.used_nodes
bvh.used_nodes += 1
right_child_idx := bvh.used_nodes
bvh.used_nodes += 1
bvh.nodes[left_child_idx].left_first = node.left_first
bvh.nodes[left_child_idx].tri_count = left_count
bvh.nodes[right_child_idx].left_first = i
bvh.nodes[right_child_idx].tri_count = node.tri_count - left_count
// Set the current node to not be a leaf node
node.left_first = left_child_idx
node.tri_count = 0
bvh_update_bounds(left_child_idx)
bvh_update_bounds(right_child_idx)
bvh_subdivide(left_child_idx)
bvh_subdivide(right_child_idx)
}
bvh_intersect :: proc(ray : ^Ray, rec : ^HitRecord, node_idx : u32) {
node : ^BVHNode = &bvh.nodes[node_idx]
any_hit : b32 = false
if intersect_aabb(ray, node.aabb_min, node.aabb_max, rec.t) {
if node.tri_count > 0 {
for i in 0..<node.tri_count {
tri_idx := tri_indices[node.left_first + i]
triangle : ^Entity = &entities[tri_idx]
triangle_intersection(ray, rec, triangle)
if rec.hit {
any_hit = true
}
}
} else {
bvh_intersect(ray, rec, node.left_first)
bvh_intersect(ray, rec, node.left_first + 1)
}
}
if !rec.hit {
rec.hit = any_hit
}
}
bvh_build :: proc() {
bvh.max_num_nodes = 2 * MAX_NUM_ENTITIES - 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 = 8
bvh.root_index = 0
// Init root node
root : ^BVHNode = &bvh.nodes[bvh.root_index]
root.left_first = 0
root.tri_count = MAX_NUM_ENTITIES
bvh_update_bounds(bvh.root_index)
bvh_subdivide(bvh.root_index)
}
bvh_stats :: proc() {
do_print : b32 = false
num_leaf_nodes : u32 = 0
for i in 0..<bvh.max_num_nodes {
node : ^BVHNode = &bvh.nodes[i]
if node.tri_count > 0 {
if do_print {
fmt.printf("Node %i is leaf node with %i triangles \n", i, node.tri_count)
}
num_leaf_nodes += 1
}
}
if do_print {
fmt.printf("Total number of leaf nodes: %i \n", num_leaf_nodes)
}
bvh.num_leaf_nodes = num_leaf_nodes
}