working bvh raytrace of the unity model

This commit is contained in:
Anton Ljungdahl 2025-05-07 15:29:28 +02:00
parent d68f740c10
commit d875d1130b
4 changed files with 12811 additions and 102 deletions

12582
assets/unity.tri Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,12 @@ package main
import rl "vendor:raylib"
import "core:fmt"
import "core:math"
////////////////////////////////////////////////////////////////////////////////////////////////////
WINDOW_WIDTH :: 1280
WINDOW_HEIGHT :: 720
WINDOW_HEIGHT : i32
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -16,24 +17,38 @@ rl_window_loop :: proc() {
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Rayt");
defer rl.CloseWindow()
do_debug_elements := false
do_debug_model := false
rl_image := rl.Image {
data = raw_data(pixelbuffer_rgb),
width = cast(i32)image.width,
height = cast(i32)image.height,
mipmaps = 1,
format = .UNCOMPRESSED_R8G8B8
}
defer rl.UnloadImage(rl_image)
fmt.println("Created raylib image from rgb data")
rl_image := rl.Image {
data = raw_data(pixelbuffer_rgb),
width = cast(i32)image.width,
height = cast(i32)image.height,
mipmaps = 1,
format = .UNCOMPRESSED_R8G8B8
}
defer rl.UnloadImage(rl_image)
fmt.println("Created raylib image from rgb data")
texture := rl.LoadTextureFromImage(rl_image)
defer rl.UnloadTexture(texture)
fmt.println("Loaded texture from image")
//rl_window_loop(texture)
texture := rl.LoadTextureFromImage(rl_image)
defer rl.UnloadTexture(texture)
fmt.println("Loaded texture from image")
rl_camera := rl.Camera3D {
position = {-2.0, 0.0, 6.0},
target = {0.0, 0.0, 0.0},
up = {0.0, 1.0, 0.0},
fovy = 45,
projection = .PERSPECTIVE
}
mesh : rl.Mesh
model : rl.Model
if do_debug_model {
mesh = create_mesh_from_triangles()
model = rl.LoadModelFromMesh(mesh)
}
for !rl.WindowShouldClose() {
@ -42,20 +57,33 @@ rl_window_loop :: proc() {
rl.BeginDrawing()
rl.ClearBackground(rl.BLUE)
// Display raytraced image
rl.DrawTexture(texture, 0, 0, rl.WHITE)
rl.DrawCircle(400, 300, 50, rl.GREEN)
rl.DrawLine(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, rl.BLUE)
// Debug draw model
if do_debug_model {
rl.BeginMode3D(rl_camera)
rl.DrawModel(model, {0.0, 0.0, 0.0}, 1, rl.RED)
rl.DrawGrid(10, 1.0)
rl.EndMode3D()
}
if do_debug_elements {
rl.DrawCircle(400, 300, 50, rl.GREEN)
rl.DrawLine(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, rl.BLUE)
rl.DrawCircle(100, 100, 120, rl.RED)
rl.DrawCircle(100, 100, 120, rl.RED)
}
rl.EndDrawing()
}
if do_debug_model {
rl.UnloadMesh(mesh)
rl.UnloadModel(model)
}
}
@ -63,7 +91,10 @@ rl_window_loop :: proc() {
main :: proc() {
rl.SetTraceLogLevel(rl.TraceLogLevel.ERROR)
// Fill pixelbuffer with raytraced image.
WINDOW_HEIGHT = cast(i32)math.ceil((cast(f32)WINDOW_WIDTH/1.7778))
fmt.printf("Window dimensions %i x %i \n", WINDOW_WIDTH, WINDOW_HEIGHT)
// Fill pixelbuffer with raytraced image.
rayt_cpu_main()
@ -71,3 +102,40 @@ main :: proc() {
rl_window_loop()
}
create_mesh_from_triangles :: proc() -> rl.Mesh {
vertex_count := len(tri_indices) * 3
vertices := make([]f32, vertex_count * 3)
indices := make([]u16, vertex_count)
for tri, i in entities {
base_idx := i * 3
// Vertex 0
vertices[base_idx * 3 + 0] = tri.v0.x
vertices[base_idx * 3 + 1] = tri.v0.y
vertices[base_idx * 3 + 2] = tri.v0.z
// Vertex 1
vertices[base_idx * 3 + 3] = tri.v1.x
vertices[base_idx * 3 + 4] = tri.v1.y
vertices[base_idx * 3 + 5] = tri.v1.z
// Vertex 2
vertices[base_idx * 3 + 6] = tri.v2.x
vertices[base_idx * 3 + 7] = tri.v2.y
vertices[base_idx * 3 + 8] = tri.v2.z
// Indices (simple sequential indices since each triangle is independent)
indices[base_idx + 0] = cast(u16)(base_idx + 0)
indices[base_idx + 1] = cast(u16)(base_idx + 1)
indices[base_idx + 2] = cast(u16)(base_idx + 2)
}
mesh: rl.Mesh
mesh.vertexCount = cast(i32)vertex_count
mesh.triangleCount = cast(i32)len(tri_indices)
mesh.vertices = &vertices[0]
mesh.indices = &indices[0]
rl.UploadMesh(&mesh, false)
return mesh
}

View File

@ -1,6 +1,9 @@
package main
import "core:os"
import "core:fmt"
import "core:strings"
import "core:strconv"
import "core:math/rand"
import "core:math/linalg"
import "core:math"
@ -17,9 +20,8 @@ COLOR_WHITE :: Vec3{1.0, 1.0, 1.0}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Global program parameters
IMAGE_WIDTH :: 1280
IMAGE_WIDTH :: WINDOW_WIDTH
ASPECT_RATIO :: 1.7778 // 16:9
MAX_NUM_ENTITIES :: 64
////////////////////////////////////////////////////////////////////////////////////////////////////
// Struct defs
@ -56,7 +58,6 @@ HitRecord :: struct {
point : Vec3,
normal : Vec3,
t : f32,
hit : b32,
front_face : b32,
}
@ -75,13 +76,14 @@ Entity :: struct {
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Main global variables
stopwatch : time.Stopwatch
use_bvh := true
image: Image
camera: Camera
viewport: Viewport
entities: [MAX_NUM_ENTITIES]Entity
tri_indices: [MAX_NUM_ENTITIES]u32
entities: []Entity
tri_indices: []u32
pixelbuffer: []Vec3
@ -113,28 +115,30 @@ ray_get :: proc(x : f32, y : f32) -> Ray {
rayt_cpu_main :: proc() {
rand.reset(RAND_SEED)
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])
}
}
//{
// 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
{
@ -145,7 +149,7 @@ rayt_cpu_main :: proc() {
image.width, image.height, image.aspect_ratio)
camera.focal_length = 3.0
camera.center = Vec3{0.0, 0.0, 18.0}
camera.center = Vec3{-2.0, 0.0, 4.0}
viewport.height = 2.0
viewport.width = viewport.height * cast(f32)(image.width)/cast(f32)(image.height)
@ -178,8 +182,8 @@ rayt_cpu_main :: proc() {
bvh_stats()
fmt.printf("Starting CPU raytracing")
if bvh.num_leaf_nodes > 1 {
fmt.printf(" - WITH BVH!")
if !use_bvh {
fmt.printf(" - NB NB NB! NO BVH! WITHOUT BVH!")
}
fmt.printf("\n")
@ -222,15 +226,15 @@ ray_point :: proc(t : f32, ray : ^Ray) -> Vec3 {
triangle_intersection :: proc(ray : ^Ray, rec : ^HitRecord, triangle : ^Entity) {
edge1 := triangle.v1-triangle.v0
edge2 := triangle.v2-triangle.v0
rec.hit = false
// Moller-Trumbore intersection algorithm
closest_so_far : f32 = rec.t
{
h := linalg.cross(ray.direction, edge2)
closest_so_far : f32 = rec.t
a := linalg.dot(edge1, h)
if a <= -0.001 || a >= 0.001 {
if a <= -0.0001 || a >= 0.0001 {
f := 1.0/a
s := ray.origin-triangle.v0
u := f * linalg.dot(s, h)
@ -239,9 +243,8 @@ triangle_intersection :: proc(ray : ^Ray, rec : ^HitRecord, triangle : ^Entity)
v := f * linalg.dot(ray.direction, q)
if v >= 0.0 && (u + v) <= 1.0 {
t := f * linalg.dot(edge2, q)
if t > 0.0001 && t <= closest_so_far {
rec.hit = true
rec.t = t
if t > 0.0001 {
rec.t = math.min(t, rec.t)
}
}
}
@ -249,7 +252,8 @@ triangle_intersection :: proc(ray : ^Ray, rec : ^HitRecord, triangle : ^Entity)
}
if rec.hit {
if rec.t < closest_so_far {
intersection_point := ray_point(rec.t, ray)
v0_normal := linalg.cross(edge1, edge2)
v0_normal = linalg.normalize(v0_normal)
@ -266,7 +270,7 @@ triangle_intersection :: proc(ray : ^Ray, rec : ^HitRecord, triangle : ^Entity)
}
cpu_raytracing :: proc() {
do_trace_without_bvh := false
num_hits : u32 = 0
last_ray : Ray
// Temp fill pixels
@ -281,42 +285,28 @@ cpu_raytracing :: proc() {
ray := ray_get(cast(f32)x, cast(f32)y)
hit_rec.hit = false
hit_rec.t = math.F32_MAX
temp_hit_rec : HitRecord
temp_hit_rec.hit = false
temp_hit_rec.t = hit_rec.t
{
hit_rec.t = math.F32_MAX
if use_bvh {
bvh_intersect(&ray, &hit_rec, bvh.root_index)
} else if do_trace_without_bvh {
for i in 0..<len(entities) {
tri_ref := &entities[i]
triangle_intersection(&ray, &hit_rec, tri_ref)
}
}
//{
// 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 {
if hit_rec.t < math.F32_MAX {
// Color triangle
sample_pixel_color = 0.5*(hit_rec.normal + COLOR_WHITE)
//sample_pixel_color = Vec3{0.7, 0.2, 0.2}
} else {
// Background gradient
unit_dir := linalg.normalize(ray.direction)
blend : f32 = 0.5*(unit_dir.y + 1.0)
sample_pixel_color = vec3_lerp(blend, COLOR_WHITE, COLOR_LIGHT_BLUE)
//sample_pixel_color = Vec3{0.0, 0.0, 0.0}
}
@ -330,7 +320,81 @@ cpu_raytracing :: proc() {
}
elapsed_time_ms :: proc() -> f64 {
return time.duration_milliseconds(time.stopwatch_duration(stopwatch))
}
load_triangles :: proc() {
do_debug_print := false
file_path := "W:/rayt/assets/unity.tri"
data, ok := os.read_entire_file(file_path)
if !ok {
fmt.println("Error reading file: ", file_path)
os.exit(1);
}
defer delete(data)
content := string(data)
lines := strings.split(content, "\n")
num_triangles := len(lines)
entities = make([]Entity, num_triangles)
tri_indices = make([]u32, num_triangles)
entity_idx : u32 = 0
for line in lines {
trimmed := strings.trim_space(line)
if len(trimmed) == 0 {
continue
}
fields := strings.split(trimmed, " ")
defer delete(fields)
if len(fields) != 9 {
fmt.printf("Warning, line '%s' does not contain 9 values \n", trimmed)
continue
}
values: [9]f32
valid := true
for field, i in fields {
if num, ok := strconv.parse_f32(field); ok {
values[i] = num
} else {
fmt.printf("Error: could not prase '%s' as f32 in line '%s' \n", field, trimmed)
valid = false
break
}
}
if !valid {
os.exit(1)
} else {
if do_debug_print {
fmt.printf("Creating triangle %i, ", entity_idx)
}
entities[entity_idx].v0 = Vec3{values[0], values[1], values[2]}
entities[entity_idx].v1 = Vec3{values[3], values[4], values[5]}
entities[entity_idx].v2 = Vec3{values[6], values[7], values[8]}
entities[entity_idx].center = 0.3333*
(entities[entity_idx].v0
+ entities[entity_idx].v1 + entities[entity_idx].v2)
tri_indices[entity_idx] = entity_idx
if do_debug_print {
fmt.printf("added to tri_indices[%i] = %i", entity_idx, tri_indices[entity_idx])
}
entity_idx += 1
if do_debug_print {
fmt.printf("\n")
}
}
}
fmt.printf("Parsed %i triangles from file %s \n", len(tri_indices), file_path)
assert(num_triangles == len(tri_indices))
assert(num_triangles == len(entities))
assert(num_triangles == int(entity_idx))
}

View File

@ -121,9 +121,9 @@ bvh_subdivide :: proc(node_idx : u32) {
node.tri_count = 0
bvh_update_bounds(left_child_idx)
bvh_update_bounds(right_child_idx)
bvh_subdivide(left_child_idx)
bvh_update_bounds(right_child_idx)
bvh_subdivide(right_child_idx)
}
@ -131,16 +131,12 @@ bvh_subdivide :: proc(node_idx : u32) {
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)
@ -148,24 +144,20 @@ bvh_intersect :: proc(ray : ^Ray, rec : ^HitRecord, node_idx : u32) {
}
}
if !rec.hit {
rec.hit = any_hit
}
}
bvh_build :: proc() {
bvh.max_num_nodes = 2 * MAX_NUM_ENTITIES - 1
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 = 8
bvh.num_leaf_entities = 2
bvh.root_index = 0
// Init root node
root : ^BVHNode = &bvh.nodes[bvh.root_index]
root.left_first = 0
root.tri_count = MAX_NUM_ENTITIES
root.tri_count = num_triangles
bvh_update_bounds(bvh.root_index)
@ -175,19 +167,22 @@ bvh_build :: proc() {
}
bvh_stats :: proc() {
do_print : b32 = false
do_print : b32 = true
num_leaf_nodes : u32 = 0
total_triangles_in_bvh : 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)
//fmt.printf("Node %i is leaf node with %i triangles \n", i, node.tri_count)
}
num_leaf_nodes += 1
total_triangles_in_bvh += node.tri_count
}
}
if do_print {
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)
}
bvh.num_leaf_nodes = num_leaf_nodes
}