3 Commits

Author SHA1 Message Date
5183e73ca0 Fixe bug in pbr 2026-02-19 19:08:41 +09:00
b41ea60c02 Refactor codebase. Add punctual light support 2026-01-01 21:52:41 +09:00
acaaa2a86e Added handle system and improve resource management. 2025-12-31 23:50:22 +09:00
44 changed files with 1333 additions and 1042 deletions

View File

@@ -43,7 +43,7 @@ If you have powershell installed, you can run the build.ps1 script in project ro
### Multiple Importance Sampling (MIS)
- Balance heuristic weights BSDF and light sampling PDFs.
- Supports both directional lights and sky light via next-event estimation. Produce noise-free renders with low sample counts.
- Supports directional lights, punctual lgiths, and sky light via next-event estimation. Produce noise-free renders with low sample counts.
### HDR Sky Lighting
- Supports importance sampling of environment maps using a hierarchical CDF over solid angles.
@@ -54,7 +54,7 @@ If you have powershell installed, you can run the build.ps1 script in project ro
- Faster convergence and lower variance than pure RNG at 64 spp.
### Materials (BSDFs)
- Lambertian, and specular supported.
- Oren-Naray and Multi-Scattered GGX support
- Designed to align with the OpenPBR material model.
### Path Tracing
@@ -68,9 +68,9 @@ If you have powershell installed, you can run the build.ps1 script in project ro
- [ ] Standardize light unit
- [ ] General speed improvements
- [ ] Add GPU backend with CUDA
- [ ] Support for glossy microfacet models (GGX)
- [ ] Support for better diffuse models (Oren-Nayar)
- [x] Support for glossy microfacet models (GGX)
- [x] Support for better diffuse models (Oren-Nayar)
- [ ] Support for volumetric scattering (homogeneous media)
- [ ] Light hierarchy for large emitter sets
- [ ] Better multithreaded rendering
- [x] Better multithreaded rendering
- [ ] GUI frontend with real-time control

View File

@@ -81,9 +81,10 @@ internal static unsafe class SrtNative
[StructLayout(LayoutKind.Sequential)]
internal struct TextureHandle
{
public ushort id;
public uint id;
public uint generation;
public static TextureHandle Invalid() => new() { id = 0xFFFF };
public static TextureHandle Invalid() => new() { id = 0xFFFFFFFF, generation = 0 };
}
[StructLayout(LayoutKind.Sequential)]

View File

@@ -1,7 +1,7 @@
CompileFlags:
Add:
- -IF:/c/SimpleRayTracer/external
- -IF:/c/SimpleRayTracer/header
- -LF:/c/SimpleRayTracer/lib
- -IF:/c/SimpleRayTracer/native/external
- -IF:/c/SimpleRayTracer/native/header
- -LF:/c/SimpleRayTracer/native/lib
- -lcglm
- -lassimp-vc143-mt

View File

@@ -1,7 +1,7 @@
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
cmake_minimum_required(VERSION 3.10)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD 23)
set(PROJECT_NAME SimpleRayTracer)
project(${PROJECT_NAME} LANGUAGES C)

Binary file not shown.

View File

@@ -11,23 +11,23 @@
typedef struct
{
aabb_t bounds;
uint64_t start_index;
uint64_t primitive_count;
uint64_t left_child_offset;
uint64_t right_child_offset;
uint32_t start_index;
uint32_t primitive_count;
uint32_t left_child_offset;
uint32_t right_child_offset;
} bvh_node_t;
typedef struct
typedef struct bvh_tree_t
{
uint64_t node_count; // Current number of nodes used
uint64_t node_capacity; // Allocated capacity for nodes
uint64_t primitive_count; // Total number of primitives (should match triangles->count)
bvh_node_t* nodes;
uint64_t* primitive_indices; // Buffer of indices mapping to the original triangle_collection_t buffer
const triangle_collection_t* triangles; // Pointer to the original triangle data (read-only access)
uint32_t* primitive_indices; // Buffer of indices mapping to the original triangle_collection_t buffer
const triangle_list_t* triangles; // Pointer to the original triangle data (read-only access)
uint32_t node_count; // Current number of nodes used
uint32_t node_capacity; // Allocated capacity for nodes
uint32_t primitive_count; // Total number of primitives (should match triangles->count)
} bvh_tree_t;
bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_collection_t* triangles);
bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_list_t* triangles);
bool bvh_tree_build(bvh_tree_t* bvh_tree);
void bvh_tree_free(bvh_tree_t* bvh_tree);

View File

@@ -9,6 +9,7 @@
float ggx_distribution(float n_dot_h, float roughness);
float ggx_g1(float n_dot_v, float roughness);
float ggx_g_smith(float n_dot_v, float n_dot_l, float roughness);
float ggx_visibility(float n_dot_v, float n_dot_l, float roughness);
// GGX VNDF sampling (Heitz 2018) for isotropic GGX.
// Returns a half-vector (H) sampled from the distribution of visible normals.

View File

@@ -21,13 +21,14 @@ typedef struct
typedef struct
{
vec3s point;
vec3s normal; // Should we remove normal, tangent, and uv from here and output u, v, w instead?
vec3s normal;
vec3s geometric_normal;
vec3s tangent;
vec2s uv;
uint64_t triangle_id;
uint32_t model_id;
uint32_t instance_id;
uint8_t material_id;
mesh_model_handle_t model;
mesh_instance_handle_t instance;
material_handle_t material;
uint32_t triangle_id;
float distance;
bool hit;
} hit_result_t;
@@ -38,9 +39,9 @@ vec3s offset_ray_origin(vec3s point, vec3s normal, vec3s w);
hit_result_t ray_intersect_triangle(const ray_t* ray, const triangle_t* triangle);
bool ray_intersect_aabb(const ray_t* ray, aabb_t aabb, float* enter_out, float* exit_out);
void ray_intersect_bvh_closest(const ray_t* ray, const bvh_node_t* bvh_nodes,
const uint64_t* primitive_indices, const triangle_collection_t* all_triangles, uint64_t node_index,
const uint32_t* primitive_indices, const triangle_list_t* all_triangles, uint32_t node_index,
float* closest_out, hit_result_t* best_hit_out);
void ray_intersect_bvh_any(const ray_t* ray, const bvh_node_t* bvh_nodes, const uint64_t* primitive_indices, const triangle_collection_t* all_triangles, uint64_t node_index, hit_result_t* any_hit_out);
void ray_intersect_bvh_any(const ray_t* ray, const bvh_node_t* bvh_nodes, const uint32_t* primitive_indices, const triangle_list_t* all_triangles, uint32_t node_index, hit_result_t* any_hit_out);
hit_result_t ray_intersect_scene_closest(const ray_t* ray, const scene_t* scene);
hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene);

View File

@@ -11,17 +11,17 @@
typedef struct
{
uint64_t node_count;
uint64_t node_capacity;
uint64_t primitive_count;
uint32_t node_count;
uint32_t node_capacity;
uint32_t primitive_count;
bvh_node_t* nodes;
uint64_t* primitive_indices;
uint32_t* primitive_indices;
const aabb_t* instance_bounds; // array of primitive_count bounds
} tlas_tree_t;
bool tlas_tree_build(tlas_tree_t* tlas, const uint64_t* instance_indices, uint64_t instance_count, const aabb_t* all_instance_bounds);
bool tlas_tree_build(tlas_tree_t* tlas, const uint32_t* instance_indices, uint32_t instance_count, const aabb_t* all_instance_bounds);
void tlas_tree_free(tlas_tree_t* tlas);
#endif // TLAS_H

View File

@@ -21,6 +21,14 @@
#define INV_PI 0.318309886183790f // 1 / PI
#define INV_TWO_PI 0.159154943091895f // 1 / (2 * PI)
typedef int32_t result_t;
#define RESULT_SUCCESS 0x00000000
#define RESULT_FAILURE 0x10001001
#define RESULT_NOT_FOUND 0x10001002
#define RESULT_OUT_OF_MEMORY 0x10001003
#define RESULT_INVALID_ARGUMENT 0x10001004
typedef enum
{
PS_INVALID = 0,
@@ -32,12 +40,11 @@ typedef enum
typedef struct
{
vec3s wi;
vec3s direct_lighting;
vec3s bsdf;
vec3s lighting;
float pdf;
float spread_angle;
path_state state;
bool is_delta;
} path_output;
inline float random_float()

View File

@@ -0,0 +1,392 @@
#ifndef COLLECTIONS_H
#define COLLECTIONS_H
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define INVALID_HANDLE(T) (T){ .id = UINT32_MAX, .generation = 0 }
#define IS_VALID_HANDLE(h) ((h).id != UINT32_MAX)
#define HANDLE_DEF(name) \
typedef struct \
{ \
uint32_t id; \
uint32_t generation; \
} name##_handle_t;
#define ARRAY_DEF(T, name) \
typedef struct \
{ \
uint32_t length; \
T* buffer; \
} name##_array_t;
#define array_init(array, size) \
do \
{ \
array.length = size; \
array.buffer = malloc(sizeof(*array.buffer) * size); \
} while (0)
#define array_free(array) \
do \
{ \
if (array.buffer != NULL) \
{ \
free(array.buffer); \
array.buffer = NULL; \
array.length = 0; \
} \
} while (0)
#define array_get(array, index) \
((index) >= (array).length ? NULL : &(array).buffer[index])
#define list_init(list, capacity) \
do \
{ \
capacity = capacity == 0 ? 1 : capacity; \
list.count = 0; \
list.capacity = capacity; \
list.buffer = (T*)malloc(sizeof(T) * capacity); \
} while (0)
#define LIST_DEF(T, name) \
typedef struct \
{ \
uint32_t count; \
uint32_t capacity; \
T* buffer; \
} name##_list_t; \
static inline result_t name##_list_init(name##_list_t* list, uint32_t capacity) \
{ \
capacity = capacity == 0 ? 1 : capacity; \
list->count = 0; \
list->capacity = capacity; \
T* temp = (T*)malloc(sizeof(T) * capacity); \
if (temp == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
list->buffer = temp; \
return RESULT_SUCCESS; \
} \
static inline void name##_list_free(name##_list_t* list) \
{ \
if (list->buffer != NULL) \
{ \
free(list->buffer); \
list->buffer = NULL; \
list->count = 0; \
list->capacity = 0; \
} \
} \
static inline result_t name##_list_add(name##_list_t* list, T item) \
{ \
if (list->count >= list->capacity) \
{ \
uint32_t new_capacity = list->capacity == 0 ? 1 : list->capacity * 2; \
T* resized = (T*)realloc(list->buffer, sizeof(T) * new_capacity); \
if (resized == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
list->buffer = resized; \
list->capacity = new_capacity; \
} \
list->buffer[list->count++] = item; \
return RESULT_SUCCESS; \
} \
static inline void name##_list_remove_at(name##_list_t* list, uint32_t index) \
{ \
if (index >= list->count) \
{ \
return; \
} \
for (uint32_t i = index; i < list->count - 1; i++) \
{ \
list->buffer[i] = list->buffer[i + 1]; \
} \
list->count--; \
} \
static inline T* name##_list_get(name##_list_t* list, uint32_t index) \
{ \
if (index >= list->count) \
{ \
return NULL; \
} \
return &list->buffer[index]; \
} \
static inline result_t name##_list_index_of(name##_list_t* list, const T* item, uint32_t* index) \
{ \
for (uint32_t i = 0; i < list->count; i++) \
{ \
if (&list->buffer[i] == item) \
{ \
*index = i; \
return RESULT_SUCCESS; \
} \
} \
return RESULT_NOT_FOUND; \
}
#define QUEUE_DEF(T, name) \
typedef struct \
{ \
uint32_t head; \
uint32_t tail; \
uint32_t capacity; \
T* buffer; \
} name##_queue_t; \
static inline result_t name##_queue_init(name##_queue_t* queue, uint32_t capacity) \
{ \
capacity = capacity == 0 ? 1 : capacity; \
queue->head = 0; \
queue->tail = 0; \
queue->capacity = capacity; \
T* temp = (T*)malloc(sizeof(T) * capacity); \
if (temp == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
queue->buffer = temp; \
return RESULT_SUCCESS; \
} \
static inline void name##_queue_free(name##_queue_t* queue) \
{ \
if (queue->buffer != NULL) \
{ \
free(queue->buffer); \
queue->buffer = NULL; \
queue->head = 0; \
queue->tail = 0; \
queue->capacity = 0; \
} \
} \
static inline result_t name##_queue_enqueue(name##_queue_t* queue, T item) \
{ \
uint32_t next_tail = (queue->tail + 1) % queue->capacity; \
if (next_tail == queue->head) \
{ \
uint32_t new_capacity = queue->capacity * 2; \
T* resized = (T*)malloc(sizeof(T) * new_capacity); \
if (resized == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
uint32_t i = 0; \
while (queue->head != queue->tail) \
{ \
resized[i++] = queue->buffer[queue->head]; \
queue->head = (queue->head + 1) % queue->capacity; \
} \
free(queue->buffer); \
queue->buffer = resized; \
queue->head = 0; \
queue->tail = i; \
queue->capacity = new_capacity; \
next_tail = (queue->tail + 1) % queue->capacity; \
} \
queue->buffer[queue->tail] = item; \
queue->tail = next_tail; \
return RESULT_SUCCESS; \
} \
static inline result_t name##_queue_dequeue(name##_queue_t* queue, T* item_out) \
{ \
if (queue->head == queue->tail) \
{ \
return RESULT_NOT_FOUND; \
} \
*item_out = queue->buffer[queue->head]; \
queue->head = (queue->head + 1) % queue->capacity; \
return RESULT_SUCCESS; \
} \
static inline uint32_t name##_queue_size(const name##_queue_t* queue) \
{ \
if (queue->tail >= queue->head) \
{ \
return queue->tail - queue->head; \
} \
else \
{ \
return queue->capacity - queue->head + queue->tail; \
} \
}
#define SLOT_MAP_DEF(T, name) \
QUEUE_DEF(uint32_t, internal_##name##_slot_map_free_slots) \
typedef struct \
{ \
uint32_t count; \
uint32_t capacity; \
T* buffer; \
uint8_t* occupied; \
uint32_t* generation; \
internal_##name##_slot_map_free_slots_queue_t free_slots; \
} name##_slot_map_t; \
static inline result_t name##_slot_map_init(name##_slot_map_t* slot_map, uint32_t capacity) \
{ \
capacity = capacity == 0 ? 1 : capacity; \
slot_map->count = 0; \
slot_map->capacity = capacity; \
T* temp_buffer = (T*)malloc(sizeof(T) * capacity); \
if (temp_buffer == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
slot_map->buffer = temp_buffer; \
slot_map->occupied = (uint8_t*)calloc(capacity, sizeof(uint8_t)); \
if (slot_map->occupied == NULL) \
{ \
free(slot_map->buffer); \
return RESULT_OUT_OF_MEMORY; \
} \
slot_map->generation = (uint32_t*)calloc(capacity, sizeof(uint32_t)); \
if (slot_map->generation == NULL) \
{ \
free(slot_map->buffer); \
free(slot_map->occupied); \
return RESULT_OUT_OF_MEMORY; \
} \
result_t res = internal_##name##_slot_map_free_slots_queue_init(&slot_map->free_slots, capacity); \
if (res != RESULT_SUCCESS) \
{ \
free(slot_map->buffer); \
free(slot_map->occupied); \
free(slot_map->generation); \
return res; \
} \
for (uint32_t i = 0; i < capacity; i++) \
{ \
internal_##name##_slot_map_free_slots_queue_enqueue(&slot_map->free_slots, i); \
} \
return RESULT_SUCCESS; \
} \
static inline void name##_slot_map_free(name##_slot_map_t* slot_map) \
{ \
if (slot_map->buffer != NULL) \
{ \
free(slot_map->buffer); \
slot_map->buffer = NULL; \
} \
if (slot_map->occupied != NULL) \
{ \
free(slot_map->occupied); \
slot_map->occupied = NULL; \
} \
if (slot_map->generation != NULL) \
{ \
free(slot_map->generation); \
slot_map->generation = NULL; \
} \
internal_##name##_slot_map_free_slots_queue_free(&slot_map->free_slots); \
slot_map->count = 0; \
slot_map->capacity = 0; \
} \
static inline result_t name##_slot_map_add(name##_slot_map_t* slot_map, T item,uint32_t* out_id, uint32_t* out_generation)\
{ \
if (out_id == NULL || out_generation == NULL) \
{ \
return RESULT_INVALID_ARGUMENT; \
} \
uint32_t slot; \
result_t res = internal_##name##_slot_map_free_slots_queue_dequeue(&slot_map->free_slots, &slot); \
if (res != RESULT_SUCCESS) \
{ \
uint32_t old_capacity = slot_map->capacity; \
uint32_t new_capacity = slot_map->capacity * 2; \
T* resized_buffer = (T*)realloc(slot_map->buffer, sizeof(T) * new_capacity); \
if (resized_buffer == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
uint8_t* resized_occupied = (uint8_t*)realloc(slot_map->occupied, sizeof(uint8_t) * new_capacity);\
if (resized_occupied == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
uint32_t* resized_generation = (uint32_t*)realloc(slot_map->generation, sizeof(uint32_t) * new_capacity);\
if (resized_generation == NULL) \
{ \
return RESULT_OUT_OF_MEMORY; \
} \
slot_map->buffer = resized_buffer; \
slot_map->occupied = resized_occupied; \
slot_map->generation = resized_generation; \
memset(slot_map->occupied + old_capacity, 0, (size_t)(new_capacity - old_capacity) * sizeof(uint8_t)); \
memset(slot_map->generation + old_capacity, 0, (size_t)(new_capacity - old_capacity) * sizeof(uint32_t)); \
for (uint32_t i = slot_map->capacity; i < new_capacity; i++) \
{ \
internal_##name##_slot_map_free_slots_queue_enqueue(&slot_map->free_slots, i); \
} \
slot_map->capacity = new_capacity; \
res = internal_##name##_slot_map_free_slots_queue_dequeue(&slot_map->free_slots, &slot); \
if (res != RESULT_SUCCESS) \
{ \
return res; \
} \
} \
slot_map->buffer[slot] = item; \
slot_map->occupied[slot] = 1; \
slot_map->count++; \
*out_id = slot; \
*out_generation = slot_map->generation[slot]; \
return RESULT_SUCCESS; \
} \
static inline result_t name##_slot_map_remove(name##_slot_map_t* slot_map, uint32_t id, uint32_t generation) \
{ \
if (id >= slot_map->capacity) \
{ \
return RESULT_INVALID_ARGUMENT; \
} \
if (slot_map->occupied[id] == 0) \
{ \
return RESULT_NOT_FOUND; \
} \
if (slot_map->generation[id] != generation) \
{ \
return RESULT_INVALID_ARGUMENT; \
} \
slot_map->occupied[id] = 0; \
slot_map->generation[id]++; \
slot_map->count--; \
internal_##name##_slot_map_free_slots_queue_enqueue(&slot_map->free_slots, id); \
return RESULT_SUCCESS; \
} \
static inline T* name##_slot_map_get(name##_slot_map_t* slot_map, uint32_t id, uint32_t generation) \
{ \
if (id >= slot_map->capacity) \
{ \
return NULL; \
} \
if (slot_map->occupied[id] == 0) \
{ \
return NULL; \
} \
if (slot_map->generation[id] != generation) \
{ \
return NULL; \
} \
return &slot_map->buffer[id]; \
} \
static inline bool name##_slot_map_contains(name##_slot_map_t* slot_map, uint32_t id, uint32_t generation) \
{ \
if (id >= slot_map->capacity) \
{ \
return false; \
} \
if (slot_map->occupied[id] == 0) \
{ \
return false; \
} \
if (slot_map->generation[id] != generation) \
{ \
return false; \
} \
return true; \
}
#endif // COLLECTIONS_H

View File

@@ -0,0 +1,11 @@
#ifndef TYPES_H
#define TYPES_H
#include "Common/Collections.h"
HANDLE_DEF(mesh_model)
HANDLE_DEF(mesh_instance)
HANDLE_DEF(texture)
HANDLE_DEF(material)
#endif // TYPES_H

View File

@@ -2,8 +2,9 @@
#define GEOMETRY_UTILITIES_H
#include "Triangle.h"
#include "../Material/Material.h"
inline void quad_create(vec3s center, vec3s forward, vec3s up, float size, uint8_t material_id, triangle_collection_t* collection)
inline void quad_create(vec3s center, vec3s forward, vec3s up, float size, material_handle_t material, triangle_list_t* list)
{
float half_size = size / 2.0f;
vec3s right = glms_vec3_cross(forward, up);
@@ -37,8 +38,28 @@ inline void quad_create(vec3s center, vec3s forward, vec3s up, float size, uint8
vertex_t vertex_2 = {.position = bottom_right, .normal = forward, .tangent = tangent, .uv = {1.0f, 1.0f}};
vertex_t vertex_3 = {.position = bottom_left, .normal = forward, .tangent = tangent, .uv = {0.0f, 1.0f}};
triangle_create(vertex_0, vertex_1, vertex_3, material_id, collection);
triangle_create(vertex_1, vertex_2, vertex_3, material_id, collection);
triangle_create(vertex_0, vertex_1, vertex_3, material, list);
triangle_create(vertex_1, vertex_2, vertex_3, material, list);
}
inline void cube_create(vec3s center, float size, material_handle_t material, triangle_list_t* list)
{
vec3s forward = {0.0f, 0.0f, 1.0f};
vec3s up = {0.0f, 1.0f, 0.0f};
float half_size = size / 2.0f;
// Front face
quad_create(glms_vec3_add(center, (vec3s){0.0f, 0.0f, half_size}), forward, up, size, material, list);
// Back face
quad_create(glms_vec3_sub(center, (vec3s){0.0f, 0.0f, half_size}), glms_vec3_negate(forward), up, size, material, list);
// Left face
quad_create(glms_vec3_sub(center, (vec3s){half_size, 0.0f, 0.0f}), glms_vec3_negate(glms_vec3_cross(forward, up)), up, size, material, list);
// Right face
quad_create(glms_vec3_add(center, (vec3s){half_size, 0.0f, 0.0f}), glms_vec3_cross(forward, up), up, size, material, list);
// Top face
quad_create(glms_vec3_add(center, (vec3s){0.0f, half_size, 0.0f}), up, glms_vec3_negate(forward), size, material, list);
// Bottom face
quad_create(glms_vec3_sub(center, (vec3s){0.0f, half_size, 0.0f}), glms_vec3_negate(up), forward, size, material, list);
}
#endif // GEOMETRY_UTILITIES_H

View File

@@ -7,26 +7,6 @@
#include <stdint.h>
// TODO: Currently transformation does not work because we store every triangle in to the same buffer and when bulding the bvh, we only consider that triangle buffer.
// One solution for this is we can have two levels of bvh, one for scene and one for each mesh. The mesh level bvh will apply the transformation to the triangles.
// The scene level bvh only tells the ray which mesh to check and the mesh level bvh will tell the ray which triangle to check.
// For instancing, we may need another mesh_model_t struct to store the actual bvh and triangle data(like id and size), and each mesh_entity_t will have a transformation matrix and the id to that mesh_model_t.
// This way we can share the same triangle and bvh for multiple instances of the same mesh, and we can also apply different transformations to each instance.
typedef struct
{
mat4s local_to_world;
// New TLAS/BLAS identifiers.
uint32_t model_id;
uint32_t instance_id;
// Legacy fields (kept for now).
uint64_t triangle_id;
uint64_t triangle_count;
uint16_t material_id;
uint16_t material_count;
} mesh_handle_t;
mesh_handle_t mesh_load(const char* filename, scene_t* scene);
mesh_model_handle_t mesh_load(const char* filename, scene_t* scene);
#endif // MESH_H

View File

@@ -2,6 +2,8 @@
#define TRIANGLE_H
#include "Common.h"
#include "Common/Collections.h"
#include "Common/Types.h"
#include "Geometry/AABB.h"
#include <stdint.h>
@@ -18,24 +20,25 @@ typedef struct
{
vertex_t vertices[3];
vec3s face_normal;
uint8_t material_id;
material_handle_t material;
} triangle_t;
// TODO: Handle material remove, we can use a lookup array.
typedef struct
LIST_DEF(triangle_t, triangle)
inline void triangle_create(vertex_t v1, vertex_t v2, vertex_t v3, material_handle_t material, triangle_list_t* collection)
{
uint64_t count;
uint64_t size;
vec3s edge1 = glms_vec3_sub(v2.position, v1.position);
vec3s edge2 = glms_vec3_sub(v3.position, v1.position);
vec3s normal = glms_vec3_normalize(glms_vec3_cross(edge1, edge2));
triangle_t* buffer;
} triangle_collection_t;
triangle_t triangle = {
.vertices = {v1, v2, v3},
.face_normal = normal,
.material = material,
};
bool triangle_collection_init(size_t size, triangle_collection_t* triangles);
void triangle_collection_resize(triangle_collection_t* collection, size_t size);
void triangle_collection_free(triangle_collection_t* collection);
void triangle_create(vertex_t v1, vertex_t v2, vertex_t v3, uint8_t material_id, triangle_collection_t* collection);
triangle_list_add(collection, triangle);
}
inline vec3s triangle_centroid(const triangle_t triangle)
{
@@ -44,9 +47,12 @@ inline vec3s triangle_centroid(const triangle_t triangle)
inline aabb_t compute_bounds_triangle(triangle_t triangle)
{
aabb_t bounds;
bounds.min = glms_vec3_minv(triangle.vertices[0].position, glms_vec3_minv(triangle.vertices[1].position, triangle.vertices[2].position));
bounds.max = glms_vec3_maxv(triangle.vertices[0].position, glms_vec3_maxv(triangle.vertices[1].position, triangle.vertices[2].position));
aabb_t bounds =
{
.min = glms_vec3_minv(triangle.vertices[0].position, glms_vec3_minv(triangle.vertices[1].position, triangle.vertices[2].position)),
.max = glms_vec3_maxv(triangle.vertices[0].position, glms_vec3_maxv(triangle.vertices[1].position, triangle.vertices[2].position))
};
return bounds;
}

View File

@@ -94,7 +94,8 @@ typedef struct srt_directional_light_t
/* Textures/materials */
typedef struct srt_texture_handle_t
{
uint16_t id;
uint32_t id;
uint32_t generation;
} srt_texture_handle_t;
typedef enum srt_texture_wrap_mode_e

View File

@@ -1,14 +1,10 @@
#ifndef LIGHT_H
#define LIGHT_H
#include "Algorithm/BVH.h"
#include "Algorithm/Sobol.h"
#include "Geometry/Triangle.h"
#include "Rendering/Texture.h"
#include "cglm/struct/vec3.h"
struct scene_t;
typedef struct
{
vec3s position;
@@ -21,9 +17,9 @@ typedef struct
uint16_t bounce_depth;
const struct scene_t* scene;
const struct bvh_tree_t* bvh_tree;
const bvh_tree_t* bvh_tree;
const texture_collection_t* textures;
const texture_slot_map_t* textures;
float spread_angle;
} light_shading_context_t;
@@ -63,100 +59,81 @@ typedef struct
float angular_diameter;
} directional_light_t;
SLOT_MAP_DEF(punctual_light_t, punctual_light)
SLOT_MAP_DEF(directional_light_t, directional_light)
typedef struct
{
uint32_t punctual_light_count;
uint32_t directional_light_count;
uint32_t max_punctual_lights;
uint32_t max_directional_lights;
sky_light_t sky_light;
punctual_light_t* punctual_lights;
directional_light_t* directional_lights;
punctual_light_slot_map_t punctual_lights;
directional_light_slot_map_t directional_lights;
} light_collection_t;
typedef struct
{
uint32_t id;
uint32_t generation;
bool is_punctual;
} light_entity_t;
} light_handle_t;
inline bool light_collection_create(uint32_t max_punctual_lights, uint32_t max_directional_lights, light_collection_t* collection)
inline result_t light_collection_create(light_collection_t* collection, uint32_t punctual_lights_capacity, uint32_t directional_lights_capacity)
{
if (max_punctual_lights == 0 || max_directional_lights == 0)
result_t r = punctual_light_slot_map_init(&collection->punctual_lights, punctual_lights_capacity);
if (r != RESULT_SUCCESS)
{
return false;
return r;
}
collection->max_punctual_lights = max_punctual_lights;
collection->max_directional_lights = max_directional_lights;
collection->punctual_light_count = 0;
collection->directional_light_count = 0;
collection->punctual_lights = (punctual_light_t*)malloc(max_punctual_lights * sizeof(punctual_light_t));
if (collection->punctual_lights == NULL)
r = directional_light_slot_map_init(&collection->directional_lights, directional_lights_capacity);
if (r != RESULT_SUCCESS)
{
return false;
punctual_light_slot_map_free(&collection->punctual_lights);
return r;
}
collection->directional_lights = (directional_light_t*)malloc(max_directional_lights * sizeof(directional_light_t));
if (collection->directional_lights == NULL)
{
free(collection->punctual_lights);
return false;
collection->sky_light.data = NULL;
collection->sky_light.data_size = 0;
collection->sky_light.evaluate_bsdf_sky = NULL;
collection->sky_light.free_sky_data = NULL;
return RESULT_SUCCESS;
}
return true;
inline light_handle_t light_add_punctual_light(light_collection_t* collection, punctual_light_t* light_data)
{
light_handle_t handle;
handle.is_punctual = true;
if (punctual_light_slot_map_add(&collection->punctual_lights, *light_data, &handle.id, &handle.generation) != RESULT_SUCCESS)
{
return INVALID_HANDLE(light_handle_t);
}
inline light_entity_t light_create_punctual_light(light_collection_t* collection)
{
if (collection->punctual_light_count >= collection->max_punctual_lights)
{
return (light_entity_t){0};
return handle;
}
punctual_light_t light = {0};
collection->punctual_lights[collection->punctual_light_count] = light;
light_entity_t entity = {.id = collection->punctual_light_count, .is_punctual = true};
collection->punctual_light_count++;
return entity;
inline light_handle_t light_add_directional_light(light_collection_t* collection, directional_light_t* light_data)
{
light_handle_t handle;
handle.is_punctual = false;
if (directional_light_slot_map_add(&collection->directional_lights, *light_data, &handle.id, &handle.generation) != RESULT_SUCCESS)
{
return INVALID_HANDLE(light_handle_t);
}
inline light_entity_t light_create_directional_light(light_collection_t* collection)
{
if (collection->directional_light_count >= collection->max_directional_lights)
{
return (light_entity_t){0};
}
directional_light_t light = {0};
collection->directional_lights[collection->directional_light_count] = light;
light_entity_t entity = {.id = collection->directional_light_count, .is_punctual = false};
collection->directional_light_count++;
return entity;
return handle;
}
inline void light_collection_free(light_collection_t* collection)
{
free(collection->punctual_lights);
free(collection->directional_lights);
punctual_light_slot_map_free(&collection->punctual_lights);
directional_light_slot_map_free(&collection->directional_lights);
if (collection->sky_light.free_sky_data != NULL)
{
collection->sky_light.free_sky_data(collection->sky_light.data);
}
free(collection->sky_light.data);
collection->max_punctual_lights = 0;
collection->max_directional_lights = 0;
collection->punctual_light_count = 0;
collection->directional_light_count = 0;
collection->punctual_lights = NULL;
collection->directional_lights = NULL;
collection->sky_light.data = NULL;
collection->sky_light.data_size = 0;
}
}
#endif // LIGHT_H

View File

@@ -7,6 +7,7 @@
#include "Rendering/Scene.h"
path_output evaluate_bsdf_directional( directional_light_t light, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index);
path_output evaluate_bsdf_punctual_light(punctual_light_t light, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index);
inline path_output evaluate_bsdf_sky(const light_collection_t* lights, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
{

View File

@@ -31,7 +31,7 @@ typedef struct
sky_light_t sky_create_constant_sky(const constant_sky_data_t* data);
path_output evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index);
sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_handle_t hdri_entity, float intensity);
sky_light_t sky_create_hdr_sky(const texture_slot_map_t* textures, texture_handle_t hdri_entity, float intensity);
path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index);
void hdr_sky_free(hdr_sky_data_t* data);

View File

@@ -8,9 +8,6 @@
#include "Rendering/AOV.h"
#include "Rendering/Texture.h"
#define PROPERTY_SIZE 64
#define INVALID_MATERIAL_ID 255
typedef struct
{
vec4s screen_size; // w, h, 1/w, 1/h
@@ -20,6 +17,7 @@ typedef struct
vec3s position;
vec3s normal;
vec3s geometric_normal;
vec3s tangent;
vec3s wo;
vec3s throughput;
@@ -30,11 +28,11 @@ typedef struct
uint32_t bounce_depth;
const struct scene_t* scene;
const struct bvh_tree_t* bvh_tree;
const bvh_tree_t* bvh_tree;
const triangle_collection_t* triangles;
const triangle_list_t* triangles;
const light_collection_t* lights;
const texture_collection_t* textures;
const texture_slot_map_t* textures;
uint64_t triangle_id;
float cone_width;
@@ -56,38 +54,7 @@ typedef struct
material_render_aov_f render_aov;
} material_t;
typedef struct
{
uint8_t id;
} material_handle_t;
// TODO: Handle material remove, we can use a lookup array.
// NOTE: 255 is invalid
typedef struct
{
uint8_t count;
uint8_t size;
material_t* buffer;
} material_collection_t;
bool material_collection_init(uint8_t size, material_collection_t* materials);
void material_collection_resize(material_collection_t* materials, size_t size);
void material_collection_free(material_collection_t* materials);
material_handle_t material_create(const void* properties, size_t properties_size, material_render_loop_f render_loop, material_render_aov_f render_aov, material_collection_t* collection);
// void material_free(material_entity_t entity, material_collection_t* collection);
inline material_handle_t invalid_material_entity()
{
return (material_handle_t){.id = INVALID_MATERIAL_ID};
}
inline bool is_material_entity_valid(material_handle_t entity)
{
return entity.id != INVALID_MATERIAL_ID;
}
SLOT_MAP_DEF(material_t, material)
inline path_output render_material(const material_t* material, const shading_context_t* context)
{
@@ -109,4 +76,18 @@ inline void render_material_aov(const material_t* material, const shading_contex
material->render_aov(material->properties, context, aov_output);
}
inline void material_free(material_t* material)
{
if (material == NULL)
{
return;
}
if (material->properties != NULL)
{
free(material->properties);
material->properties = NULL;
}
}
#endif // MATERIAL_H

View File

@@ -1,6 +0,0 @@
#ifndef SHADING_CONTEXT_H
#define SHADING_CONTEXT_H
#endif // SHADING_CONTEXT_H

View File

@@ -34,9 +34,32 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
void standard_lit_render_aov(const standard_lit_properties_t* properties, const shading_context_t* context, aov_output_t* aov_output);
inline material_handle_t material_create_standard_lit_default(const standard_lit_properties_t* properties, material_collection_t* collection)
inline material_handle_t material_create_standard_lit_default(const standard_lit_properties_t* properties, material_slot_map_t* collection)
{
return material_create(properties, sizeof(standard_lit_properties_t), (material_render_loop_f)standard_lit_render_loop, (material_render_aov_f)standard_lit_render_aov, collection);
material_t material = {
.emission = glms_vec3_zero(),
.properties_size = sizeof(standard_lit_properties_t),
.properties = malloc(sizeof(standard_lit_properties_t)),
.render_loop = (material_render_loop_f)standard_lit_render_loop,
.render_aov = (material_render_aov_f)standard_lit_render_aov,
};
if (material.properties == NULL)
{
return INVALID_HANDLE(material_handle_t);
}
memcpy(material.properties, properties, sizeof(standard_lit_properties_t));
material_handle_t handle;
if (material_slot_map_add(collection, material, &handle.id, &handle.generation) != RESULT_SUCCESS)
{
free(material.properties);
return INVALID_HANDLE(material_handle_t);
}
return handle;
}
#endif // STANDARd_LIT_H

View File

@@ -16,27 +16,27 @@ typedef struct
uint32_t width;
uint32_t height;
uint32_t sample_count;
uint32_t bucket_size;
uint8_t max_depth;
uint32_t bucket_size;
rendering_mode_t rendering_mode;
aov_flags_t aov_flags;
} rendering_config_t;
typedef struct
{
scene_t* scene;
render_target_t** aov_target;
const rendering_config_t* config;
rendering_config_t config;
rendering_mode_t rendering_mode;
aov_flags_t aov_flags;
bool is_done;
const scene_t* scene;
render_target_t** aov_target;
// Progressive rendering state (0-based count of completed samples per pixel).
// In tile-based mode this remains 0.
uint32_t progressive_sample_index;
bool is_done;
} render_job_t;
bool renderer_aov_target_init(render_job_t* job, aov_flags_t aov_flags);
bool renderer_aov_target_init(render_job_t* job);
void renderer_start(render_job_t* job);
void render_job_free(render_job_t* job);

View File

@@ -4,6 +4,8 @@
#include "Algorithm/BVH.h"
#include "Algorithm/TLAS.h"
#include "Camera.h"
#include "Common/Collections.h"
#include "Common/Types.h"
#include "Lighting/Light.h"
#include "Material/Material.h"
#include "Geometry/Triangle.h"
@@ -15,67 +17,81 @@
#include <stdbool.h>
typedef struct mesh_model_t
typedef struct
{
bool active;
triangle_collection_t triangles; // local-space triangles
triangle_list_t triangles; // local-space triangles
bvh_tree_t blas; // BVH over local-space triangles
aabb_t local_bounds;
} mesh_model_t;
typedef struct
{
bool active;
uint32_t model_id;
mat4s local_to_world;
mat4s world_to_local;
mat3s normal_matrix; // transpose(inverse(mat3(local_to_world)))
mesh_model_handle_t model;
aabb_t world_bounds;
} mesh_instance_t;
typedef struct
{
uint32_t count;
uint32_t capacity;
mesh_model_t* buffer;
} mesh_model_collection_t;
SLOT_MAP_DEF(mesh_model_t, mesh_model)
SLOT_MAP_DEF(mesh_instance_t, mesh_instance)
typedef struct
{
uint32_t count;
uint32_t capacity;
mesh_instance_t* buffer;
} mesh_instance_collection_t;
typedef struct scene_t
{
camera_t camera;
// Legacy: single big triangle array (kept for now for procedural geometry).
bvh_tree_t bvh_tree;
triangle_collection_t triangles;
// New: TLAS/BLAS mesh system.
mesh_model_collection_t mesh_models;
mesh_instance_collection_t mesh_instances;
mesh_model_slot_map_t mesh_models;
mesh_instance_slot_map_t mesh_instances;
tlas_tree_t tlas;
bool tlas_dirty;
material_collection_t materials;
texture_collection_t textures;
material_slot_map_t materials;
texture_slot_map_t textures;
light_collection_t lights;
} scene_t;
bool scene_init(scene_t* scene, uint64_t triangle_count, uint16_t texture_count, uint8_t material_count, uint32_t punctual_light_count);
bool scene_init(scene_t* scene, uint32_t triangle_count, uint32_t texture_count, uint32_t material_count, uint32_t punctual_light_count);
bool scene_build_bvh(scene_t* scene);
void scene_free(scene_t* scene);
// Mesh model/instance API (simple first, flexible later).
uint32_t scene_add_mesh_model(scene_t* scene, uint64_t triangle_reserve);
uint32_t scene_add_mesh_instance(scene_t* scene, uint32_t model_id, mat4s local_to_world);
void scene_remove_mesh_instance(scene_t* scene, uint32_t instance_id);
void scene_set_mesh_instance_transform(scene_t* scene, uint32_t instance_id, mat4s local_to_world);
mesh_model_handle_t scene_add_mesh_model(scene_t* scene, uint32_t triangle_reserve);
mesh_instance_handle_t scene_add_mesh_instance(scene_t* scene, mesh_model_handle_t model, mat4s local_to_world);
void scene_remove_mesh_instance(scene_t* scene, mesh_instance_handle_t instance);
void scene_set_mesh_instance_transform(scene_t* scene, mesh_instance_handle_t instance, mat4s local_to_world);
inline void commit_mesh_model_handle(scene_t* scene, mesh_model_handle_t model)
{
mesh_model_t* mesh_model = mesh_model_slot_map_get(&scene->mesh_models, model.id, model.generation);
if (mesh_model != NULL && mesh_model->triangles.count > 0)
{
bvh_tree_free(&mesh_model->blas);
if (bvh_tree_init(&mesh_model->blas, &mesh_model->triangles))
{
(void)bvh_tree_build(&mesh_model->blas);
if (mesh_model->blas.nodes != NULL && mesh_model->blas.node_count > 0)
{
mesh_model->local_bounds = mesh_model->blas.nodes[0].bounds;
}
}
}
}
inline void commit_mesh_model(mesh_model_t* mesh_model)
{
if (mesh_model != NULL && mesh_model->triangles.count > 0)
{
bvh_tree_free(&mesh_model->blas);
if (bvh_tree_init(&mesh_model->blas, &mesh_model->triangles))
{
(void)bvh_tree_build(&mesh_model->blas);
if (mesh_model->blas.nodes != NULL && mesh_model->blas.node_count > 0)
{
mesh_model->local_bounds = mesh_model->blas.nodes[0].bounds;
}
}
}
}
#endif // SCENE_H

View File

@@ -2,13 +2,13 @@
#define TEXTURE_H
#include "Common.h"
#include "Common/Types.h"
#include "Common/Collections.h"
#include "cglm/struct/vec4.h"
#include <stdint.h>
#include <stdbool.h>
#define INVALID_TEXTURE_ID 0xffff
typedef enum
{
WM_REPEAT,
@@ -56,17 +56,7 @@ typedef struct
texture_t texture;
} texture_asset_t;
typedef struct
{
uint16_t count;
uint16_t size;
texture_asset_t* buffer;
} texture_collection_t;
typedef struct
{
uint16_t id;
} texture_handle_t;
SLOT_MAP_DEF(texture_asset_t, texture)
typedef struct
{
@@ -80,36 +70,32 @@ typedef struct
float distance;
} texture_sample_context_t;
bool texture_collection_init(uint16_t size, texture_collection_t* textures);
void texture_collection_resize(texture_collection_t* textures, uint16_t size);
void texture_collection_free(texture_collection_t* textures);
texture_handle_t texture_load(const char* filename, bool srgb, bool mipmap, stride_t stride, texture_collection_t* textures);
texture_handle_t texture_load(const char* filename, bool srgb, bool mipmap, stride_t stride, texture_slot_map_t* textures);
void texture_free(texture_t* texture);
vec4s texture_get_pixel(const texture_t* texture, vec2s uv, uint8_t lod);
float texture_get_sample_lod(const texture_t* texture, const texture_sample_context_t* sample_context);
vec4s texture_sample(const texture_t* texture, const texture_sample_context_t* sample_context, vec2s uv);
vec4s texture_sample_lod(const texture_t* texture, vec2s uv, float lod);
void texture_free(texture_t* texture);
inline texture_handle_t invalid_texture_handle()
{
return (texture_handle_t){.id = INVALID_TEXTURE_ID};
}
inline bool is_texture_entity_valid(texture_handle_t entity)
{
return entity.id != INVALID_TEXTURE_ID;
return IS_VALID_HANDLE(entity);
}
inline texture_t* get_texture(const texture_collection_t* textures, texture_handle_t entity)
inline texture_t* get_texture(const texture_slot_map_t* textures, texture_handle_t handle)
{
if (entity.id >= textures->count || !is_texture_entity_valid(entity))
if (textures == NULL)
{
return NULL;
}
return &textures->buffer[entity.id].texture;
texture_asset_t* asset = texture_slot_map_get((texture_slot_map_t*)textures, handle.id, handle.generation);
if (asset == NULL)
{
return NULL;
}
return &asset->texture;
}
inline size_t texture_get_pixel_index(texture_t* texture, uint32_t x, uint32_t y)

View File

@@ -10,7 +10,7 @@ typedef struct
uint32_t count;
} bin_t;
bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_collection_t* triangles)
bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_list_t* triangles)
{
if (bvh_tree == NULL || triangles == NULL || triangles->count == 0)
{
@@ -22,14 +22,14 @@ bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_collection_t* triangles)
temp.primitive_count = triangles->count;
// Allocate primitive indices buffer
temp.primitive_indices = (uint64_t*)malloc(temp.primitive_count * sizeof(uint64_t));
temp.primitive_indices = (uint32_t*)malloc(temp.primitive_count * sizeof(uint32_t));
if (!temp.primitive_indices)
{
return false;
}
// Initialize primitive indices
for (uint64_t i = 0; i < temp.primitive_count; ++i)
for (uint32_t i = 0; i < temp.primitive_count; ++i)
{
temp.primitive_indices[i] = i;
}
@@ -49,17 +49,17 @@ bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_collection_t* triangles)
return true;
}
static inline aabb_t compute_primitives_aabb(const triangle_collection_t* triangles, const uint64_t* primitive_indices, uint64_t start, uint64_t count)
static inline aabb_t compute_primitives_aabb(const triangle_list_t* triangles, const uint32_t* primitive_indices, uint32_t start, uint32_t count)
{
if (count == 0)
{
return (aabb_t){0};
}
uint64_t triangle_index = primitive_indices[start];
uint32_t triangle_index = primitive_indices[start];
aabb_t bounds = compute_bounds_triangle(triangles->buffer[triangle_index]);
for (uint64_t i = start + 1; i < start + count; ++i)
for (uint32_t i = start + 1; i < start + count; ++i)
{
triangle_index = primitive_indices[i];
aabb_growth(&bounds, triangles->buffer[triangle_index].vertices[0].position);
@@ -72,10 +72,10 @@ static inline aabb_t compute_primitives_aabb(const triangle_collection_t* triang
// TODO: For BVH generation, we can use a stack-based approach to avoid recursion and improve performance.
// TODO: We can generate a cheap LBVH first, and then refine it with SAH in parallel. Doing this even allows us to migrate BVH generation to the GPU.
static uint64_t build_bvh_node(bvh_node_t* bvh_nodes, uint64_t* next_node_index, uint64_t* primitive_indices, const triangle_collection_t* triangles,
uint64_t prim_start, uint64_t prim_count)
static uint32_t build_bvh_node(bvh_node_t* bvh_nodes, uint32_t* next_node_index, uint32_t* primitive_indices, const triangle_list_t* triangles,
uint32_t prim_start, uint32_t prim_count)
{
uint64_t node_index = (*next_node_index)++;
uint32_t node_index = (*next_node_index)++;
// We assume that the node_index is always less than the node_capacity, may need better error handling later
bvh_node_t* node = &bvh_nodes[node_index];
@@ -94,7 +94,7 @@ static uint64_t build_bvh_node(bvh_node_t* bvh_nodes, uint64_t* next_node_index,
int best_axis = -1;
int best_split_bin_index = -1;
// NOTE: This is global index, not the local index in the node.
uint64_t best_split_index = 0;
uint32_t best_split_index = 0;
float best_sah_cost = SAH_INTERSECTION_COST * prim_count; // TODO: Replace with actual SAH cost calculation
aabb_t current_aabb = node->bounds;
@@ -139,9 +139,9 @@ static uint64_t build_bvh_node(bvh_node_t* bvh_nodes, uint64_t* next_node_index,
}
// Populate bins
for (uint64_t i = 0; i < prim_count; i++)
for (uint32_t i = 0; i < prim_count; i++)
{
uint64_t triangle_index = primitive_indices[prim_start + i];
uint32_t triangle_index = primitive_indices[prim_start + i];
vec3s centroid = triangle_centroid(triangles->buffer[triangle_index]);
float position_on_axis = centroid.raw[axis];
@@ -156,9 +156,9 @@ static uint64_t build_bvh_node(bvh_node_t* bvh_nodes, uint64_t* next_node_index,
// Calculate prefix and suffix AABBs and counts.
aabb_t left_aabb[SAH_BIN_COUNT];
uint32_t left_count[SAH_BIN_COUNT] = {0};
uint32_t left_count[SAH_BIN_COUNT];
aabb_t right_aabb[SAH_BIN_COUNT];
uint32_t right_count[SAH_BIN_COUNT] = {0};
uint32_t right_count[SAH_BIN_COUNT];
aabb_t current_left_aabb = invalid_aabb();
uint32_t current_left_count = 0;
@@ -224,7 +224,7 @@ static uint64_t build_bvh_node(bvh_node_t* bvh_nodes, uint64_t* next_node_index,
// --- Decide whether to split or make leaf ---
perform_split_check:
bool perform_split = false;
uint64_t actual_partition_index = prim_start; // Default if no split
uint32_t actual_partition_index = prim_start; // Default if no split
if (parent_sah == 0.0f && prim_count > 1) {
// Flat AABB case with more than one primitive: Force median split
@@ -251,14 +251,14 @@ perform_split_check:
if (parent_sah <= 0.0f)
{
uint64_t threshold_original_index = primitive_indices[prim_start + (prim_count / 2)]; // Arbitrary index threshold
uint64_t i = prim_start; // Pointer for the left side
for (uint64_t j = prim_start; j < prim_start + prim_count; ++j) {
uint32_t threshold_original_index = primitive_indices[prim_start + (prim_count / 2)]; // Arbitrary index threshold
uint32_t i = prim_start; // Pointer for the left side
for (uint32_t j = prim_start; j < prim_start + prim_count; ++j) {
// Swap if original index is "less than" threshold
if (primitive_indices[j] < threshold_original_index)
{
// Arbitrary criterion
uint64_t temp = primitive_indices[i];
uint32_t temp = primitive_indices[i];
primitive_indices[i] = primitive_indices[j];
primitive_indices[j] = temp;
i++;
@@ -275,10 +275,10 @@ perform_split_check:
float split_axis_extent = node->bounds.max.raw[best_axis] - split_axis_min;
float split_bin_size = split_axis_extent / SAH_BIN_COUNT; // Use best_axis's properties
uint64_t i = prim_start;
for (uint64_t j = prim_start; j < prim_start + prim_count; ++j)
uint32_t i = prim_start;
for (uint32_t j = prim_start; j < prim_start + prim_count; ++j)
{
uint64_t original_tri_idx = primitive_indices[j];
uint32_t original_tri_idx = primitive_indices[j];
vec3s centroid = triangle_centroid(triangles->buffer[original_tri_idx]);
float pos_on_axis = ((float*)&centroid)[best_axis];
@@ -289,7 +289,7 @@ perform_split_check:
// If the primitive belongs to the left child's partition (bin <= best_split_bin_index)
if (bin_idx <= best_split_bin_index) {
// Swap it into the current position of the left partition
uint64_t temp = primitive_indices[i];
uint32_t temp = primitive_indices[i];
primitive_indices[i] = primitive_indices[j];
primitive_indices[j] = temp;
i++; // Move the left partition boundary forward
@@ -300,16 +300,16 @@ perform_split_check:
actual_partition_index = i;
} // End partition type branch
uint64_t left_count = actual_partition_index - prim_start;
uint64_t right_count = (prim_start + prim_count) - actual_partition_index;
uint32_t left_count = actual_partition_index - prim_start;
uint32_t right_count = (prim_start + prim_count) - actual_partition_index;
if (left_count == 0 || right_count == 0)
{
goto force_leaf;
}
uint64_t left_child_idx = build_bvh_node(bvh_nodes, next_node_index, primitive_indices, triangles, prim_start, left_count);
uint64_t right_child_idx = build_bvh_node(bvh_nodes, next_node_index, primitive_indices, triangles, actual_partition_index, right_count);
uint32_t left_child_idx = build_bvh_node(bvh_nodes, next_node_index, primitive_indices, triangles, prim_start, left_count);
uint32_t right_child_idx = build_bvh_node(bvh_nodes, next_node_index, primitive_indices, triangles, actual_partition_index, right_count);
node->left_child_offset = left_child_idx;
node->right_child_offset = right_child_idx;
@@ -328,14 +328,14 @@ bool bvh_tree_build(bvh_tree_t* bvh_tree)
return false;
}
uint64_t triangle_count = bvh_tree->triangles->count;
uint32_t triangle_count = bvh_tree->triangles->count;
if (triangle_count == 0)
{
return true;
}
uint64_t next_node_index = 0;
uint64_t root_node_index = build_bvh_node(bvh_tree->nodes, &next_node_index,
uint32_t next_node_index = 0;
uint32_t root_node_index = build_bvh_node(bvh_tree->nodes, &next_node_index,
bvh_tree->primitive_indices, bvh_tree->triangles,
0, triangle_count);

View File

@@ -127,7 +127,12 @@ void ggx_ms_init_lut_once(void)
// For F=1, importance sampling with VNDF yields a simple estimator:
// rho_ss(v) = E[ G1(NoL) ].
sum += ggx_g1(NoL, roughness);
float alpha_lut = roughness * roughness;
float alpha2_lut = alpha_lut * alpha_lut;
float lambda_v = sqrtf(alpha2_lut + (1.0f - alpha2_lut) * NoV * NoV);
float lambda_l = sqrtf(alpha2_lut + (1.0f - alpha2_lut) * NoL * NoL);
sum += NoL * (NoV + lambda_v) / fmaxf(NoL * lambda_v + NoV * lambda_l, 1e-7f);
valid++;
}

View File

@@ -35,6 +35,17 @@ float ggx_g_smith(float n_dot_v, float n_dot_l, float roughness)
return ggx_g1(n_dot_v, roughness) * ggx_g1(n_dot_l, roughness);
}
float ggx_visibility(float n_dot_v, float n_dot_l, float roughness)
{
float alpha = roughness * roughness;
float alpha2 = alpha * alpha;
float ggx_v = n_dot_l * sqrtf(alpha2 + (1.0f - alpha2) * n_dot_v * n_dot_v);
float ggx_l = n_dot_v * sqrtf(alpha2 + (1.0f - alpha2) * n_dot_l * n_dot_l);
return 0.5f / fmaxf(ggx_v + ggx_l, FLT_EPSILON);
}
vec3s ggx_sample_vndf(vec3s n, vec3s v, float roughness, float u1, float u2)
{
// Build local frame around n.

View File

@@ -35,15 +35,15 @@ static inline shading_context_t make_shading_context(const scene_t* scene,
float cone_width,
float spread_angle)
{
const triangle_collection_t* triangles = &scene->triangles;
const bvh_tree_t* bvh_tree = &scene->bvh_tree;
if (scene->tlas.nodes != NULL && hit->instance_id != UINT32_MAX && hit->instance_id < scene->mesh_instances.capacity)
const triangle_list_t* triangles = NULL;
const bvh_tree_t* bvh_tree = NULL;
if (scene->tlas.nodes != NULL && IS_VALID_HANDLE(hit->instance))
{
const mesh_instance_t* inst = &scene->mesh_instances.buffer[hit->instance_id];
if (inst->active && inst->model_id < scene->mesh_models.capacity)
const mesh_instance_t* inst = mesh_instance_slot_map_get(&scene->mesh_instances, hit->instance.id, hit->instance.generation);
if (inst != NULL)
{
const mesh_model_t* model = &scene->mesh_models.buffer[inst->model_id];
if (model->active)
const mesh_model_t* model = mesh_model_slot_map_get(&scene->mesh_models, inst->model.id, inst->model.generation);
if (model != NULL)
{
triangles = &model->triangles;
bvh_tree = &model->blas;
@@ -57,6 +57,7 @@ static inline shading_context_t make_shading_context(const scene_t* scene,
.position = hit->point,
.normal = hit->normal,
.geometric_normal = hit->geometric_normal,
.tangent = hit->tangent,
.uv = hit->uv,
.wo = wo,
@@ -90,7 +91,11 @@ static void trace_surface_aovs_only(const scene_t* scene,
return;
}
const material_t* hit_material = &scene->materials.buffer[closest_hit.material_id];
const material_t* hit_material = material_slot_map_get(&scene->materials, closest_hit.material.id, closest_hit.material.generation);
if (hit_material == NULL)
{
return;
}
float cone_width = ray.width + closest_hit.distance * ray.spread_angle;
shading_context_t shading_context = make_shading_context(scene,
@@ -174,19 +179,33 @@ static void trace_lighting_aovs(const scene_t* scene,
{
float pdf_env = sky_output.pdf;
float pdf_bsdf = last_bsdf_pdf;
if (pdf_env > 0.0f && pdf_bsdf > 0.0f)
if (pdf_env > 0.0f)
{
w = power_heuristic(pdf_bsdf, pdf_env);
}
}
vec3s env_contrib = glms_vec3_scale(sky_output.direct_lighting, w);
vec3s env_contrib = glms_vec3_scale(sky_output.lighting, w);
if (depth > 0)
{
float max_contrib = 10.0f; // Tune this
float m = glms_vec3_max(env_contrib);
if (m > max_contrib)
{
env_contrib = glms_vec3_scale(env_contrib, max_contrib / m);
}
}
aov_accumulate_nee(out, aov_flags, env_contrib, depth);
break;
}
uint8_t material_id = closest_hit.material_id;
const material_t* hit_material = &scene->materials.buffer[material_id];
const material_t* hit_material = material_slot_map_get(&scene->materials, closest_hit.material.id, closest_hit.material.generation);
if (hit_material == NULL)
{
break;
}
float current_cone_width = active_ray.width + closest_hit.distance * active_ray.spread_angle;
shading_context_t shading_context = make_shading_context(scene,
active_ray.direction,
@@ -215,12 +234,12 @@ static void trace_lighting_aovs(const scene_t* scene,
}
path_output material_output = render_material(hit_material, &shading_context);
if (glms_vec3_isinf(material_output.direct_lighting) || glms_vec3_isnan(material_output.direct_lighting))
if (glms_vec3_isinf(material_output.lighting) || glms_vec3_isnan(material_output.lighting))
{
break;
}
aov_accumulate_nee(out, aov_flags, material_output.direct_lighting, depth);
aov_accumulate_nee(out, aov_flags, material_output.lighting, depth);
if (material_output.pdf < FLT_EPSILON)
{

View File

@@ -151,6 +151,7 @@ hit_result_t ray_intersect_triangle(const ray_t* ray, const triangle_t* triangle
normal = glms_vec3_add(normal, glms_vec3_scale(triangle->vertices[2].normal, v));
normal = glms_vec3_dot(normal, direction) < 0.0f ? normal : glms_vec3_negate(normal);
result.normal = glms_vec3_normalize(normal);
result.geometric_normal = triangle->face_normal;
vec3s tangent = glms_vec3_scale(triangle->vertices[0].tangent, w);
tangent = glms_vec3_add(tangent, glms_vec3_scale(triangle->vertices[1].tangent, u));
@@ -219,7 +220,7 @@ static inline float distance_to_aabb(vec3s point, aabb_t aabb)
}
// TODO: Use a stack to avoid recursion.
void ray_intersect_bvh_closest(const ray_t* ray, const bvh_node_t* bvh_nodes, const uint64_t* primitive_indices, const triangle_collection_t* all_triangles, uint64_t node_index,
void ray_intersect_bvh_closest(const ray_t* ray, const bvh_node_t* bvh_nodes, const uint32_t* primitive_indices, const triangle_list_t* all_triangles, uint32_t node_index,
float* closest_out, hit_result_t* best_hit_out)
{
const bvh_node_t* node = &bvh_nodes[node_index];
@@ -240,7 +241,7 @@ void ray_intersect_bvh_closest(const ray_t* ray, const bvh_node_t* bvh_nodes, co
{
for (uint32_t i = 0; i < node->primitive_count; i++)
{
uint64_t triangle_index = primitive_indices[node->start_index + i];
uint32_t triangle_index = primitive_indices[node->start_index + i];
hit_result_t hit_result = ray_intersect_triangle(ray, &all_triangles->buffer[triangle_index]);
if (hit_result.hit && hit_result.distance < *closest_out)
{
@@ -252,8 +253,8 @@ void ray_intersect_bvh_closest(const ray_t* ray, const bvh_node_t* bvh_nodes, co
}
else // Internal node (primitive_count == 0 implies internal)
{
uint64_t left_child_index = node->left_child_offset;
uint64_t right_child_index = node->right_child_offset;
uint32_t left_child_index = node->left_child_offset;
uint32_t right_child_index = node->right_child_offset;
const bvh_node_t* left_child = &bvh_nodes[left_child_index];
const bvh_node_t* right_child = &bvh_nodes[right_child_index];
@@ -293,7 +294,7 @@ void ray_intersect_bvh_closest(const ray_t* ray, const bvh_node_t* bvh_nodes, co
}
}
void ray_intersect_bvh_any(const ray_t* ray, const bvh_node_t* bvh_nodes, const uint64_t* primitive_indices, const triangle_collection_t* all_triangles, uint64_t node_index, hit_result_t* any_hit_out)
void ray_intersect_bvh_any(const ray_t* ray, const bvh_node_t* bvh_nodes, const uint32_t* primitive_indices, const triangle_list_t* all_triangles, uint32_t node_index, hit_result_t* any_hit_out)
{
const bvh_node_t* node = &bvh_nodes[node_index];
@@ -308,7 +309,7 @@ void ray_intersect_bvh_any(const ray_t* ray, const bvh_node_t* bvh_nodes, const
{
for (uint32_t i = 0; i < node->primitive_count; i++)
{
uint64_t triangle_index = primitive_indices[node->start_index + i];
uint32_t triangle_index = primitive_indices[node->start_index + i];
hit_result_t hit_result = ray_intersect_triangle(ray, &all_triangles->buffer[triangle_index]);
if (hit_result.hit)
{
@@ -322,8 +323,8 @@ void ray_intersect_bvh_any(const ray_t* ray, const bvh_node_t* bvh_nodes, const
else
{
// Internal node: traverse children in nearfirst order
uint64_t left_child_index = node->left_child_offset;
uint64_t right_child_index = node->right_child_offset;
uint32_t left_child_index = node->left_child_offset;
uint32_t right_child_index = node->right_child_offset;
const bvh_node_t* left_child = &bvh_nodes[left_child_index];
const bvh_node_t* right_child = &bvh_nodes[right_child_index];
@@ -372,12 +373,11 @@ hit_result_t ray_intersect_scene_closest(const ray_t* ray, const scene_t* scene)
return result;
}
// TLAS/BLAS path (may coexist with legacy geometry).
if (scene->tlas.nodes != NULL && scene->tlas.node_count > 0)
{
typedef struct
{
uint64_t node_index;
uint32_t node_index;
float enter;
} tlas_stack_entry_t;
@@ -406,32 +406,27 @@ hit_result_t ray_intersect_scene_closest(const ray_t* ray, const scene_t* scene)
{
for (uint32_t i = 0; i < node->primitive_count; ++i)
{
uint64_t instance_id = scene->tlas.primitive_indices[node->start_index + i];
uint32_t instance_id = scene->tlas.primitive_indices[node->start_index + i];
if (instance_id >= scene->mesh_instances.capacity)
{
continue;
}
const mesh_instance_t* inst = &scene->mesh_instances.buffer[instance_id];
if (!inst->active)
if (inst == NULL || !scene->mesh_instances.occupied[instance_id])
{
continue;
}
float inst_enter, inst_exit;
if (!ray_intersect_aabb(ray, inst->world_bounds, &inst_enter, &inst_exit) || inst_enter > closest)
if (!ray_intersect_aabb(ray, inst->world_bounds, &inst_enter, &inst_exit))
{
continue;
}
uint32_t model_id = inst->model_id;
if (model_id >= scene->mesh_models.capacity)
{
continue;
}
const mesh_model_t* model = &scene->mesh_models.buffer[model_id];
if (!model->active || model->blas.nodes == NULL || model->blas.node_count == 0)
mesh_model_handle_t model_handle = inst->model;
const mesh_model_t* model = mesh_model_slot_map_get(&scene->mesh_models, model_handle.id, model_handle.generation);
if (model == NULL || model->blas.nodes == NULL || model->blas.node_count == 0)
{
continue;
}
@@ -450,7 +445,7 @@ hit_result_t ray_intersect_scene_closest(const ray_t* ray, const scene_t* scene)
vec3s world_point = mat4_mul_point(inst->local_to_world, local_hit.point);
float world_distance = glms_vec3_dot(glms_vec3_sub(world_point, ray->origin), ray->direction);
if (world_distance <= ray->esp || world_distance >= closest)
if (world_distance <= ray->esp)
{
continue;
}
@@ -461,20 +456,20 @@ hit_result_t ray_intersect_scene_closest(const ray_t* ray, const scene_t* scene)
result = local_hit;
result.hit = true;
result.point = world_point;
result.distance = world_distance;
result.normal = world_normal;
result.tangent = world_tangent;
result.distance = world_distance;
result.model_id = model_id;
result.instance_id = (uint32_t)instance_id;
result.material_id = model->triangles.buffer[local_hit.triangle_id].material_id;
closest = world_distance;
result.model = model_handle;
result.instance = (mesh_instance_handle_t){ .id = instance_id, .generation = scene->mesh_instances.generation[instance_id] };
result.material = model->triangles.buffer[local_hit.triangle_id].material;
return result;
}
continue;
}
uint64_t left = node->left_child_offset;
uint64_t right = node->right_child_offset;
uint32_t left = node->left_child_offset;
uint32_t right = node->right_child_offset;
float left_enter, left_exit, right_enter, right_exit;
bool hit_left = ray_intersect_aabb(ray, scene->tlas.nodes[left].bounds, &left_enter, &left_exit);
@@ -503,24 +498,6 @@ hit_result_t ray_intersect_scene_closest(const ray_t* ray, const scene_t* scene)
if (stack_size < stack_capacity) stack[stack_size++] = (tlas_stack_entry_t){.node_index = right, .enter = right_enter};
}
}
return result;
}
// Legacy path (or mixed scenes).
if (scene->bvh_tree.nodes != NULL && scene->triangles.count > 0 && scene->bvh_tree.node_count > 0 && scene->bvh_tree.primitive_count > 0)
{
hit_result_t legacy_hit = (hit_result_t){0};
float legacy_closest = closest;
ray_intersect_bvh_closest(ray, scene->bvh_tree.nodes, scene->bvh_tree.primitive_indices, &scene->triangles, 0, &legacy_closest, &legacy_hit);
if (legacy_hit.hit)
{
legacy_hit.material_id = scene->triangles.buffer[legacy_hit.triangle_id].material_id;
legacy_hit.model_id = UINT32_MAX;
legacy_hit.instance_id = UINT32_MAX;
result = legacy_hit;
closest = legacy_closest;
}
}
return result;
@@ -539,14 +516,14 @@ hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene)
if (scene->tlas.nodes != NULL && scene->tlas.node_count > 0)
{
uint64_t stack[128];
uint32_t stack[128];
const int32_t stack_capacity = (int32_t)(sizeof(stack) / sizeof(stack[0]));
int32_t stack_size = 0;
stack[stack_size++] = 0;
while (stack_size > 0)
{
uint64_t node_index = stack[--stack_size];
uint32_t node_index = stack[--stack_size];
const bvh_node_t* node = &scene->tlas.nodes[node_index];
float enter, exit;
@@ -559,14 +536,14 @@ hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene)
{
for (uint32_t i = 0; i < node->primitive_count; ++i)
{
uint64_t instance_id = scene->tlas.primitive_indices[node->start_index + i];
uint32_t instance_id = scene->tlas.primitive_indices[node->start_index + i];
if (instance_id >= scene->mesh_instances.capacity)
{
continue;
}
const mesh_instance_t* inst = &scene->mesh_instances.buffer[instance_id];
if (!inst->active)
if (inst == NULL || !scene->mesh_instances.occupied[instance_id])
{
continue;
}
@@ -577,14 +554,9 @@ hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene)
continue;
}
uint32_t model_id = inst->model_id;
if (model_id >= scene->mesh_models.capacity)
{
continue;
}
const mesh_model_t* model = &scene->mesh_models.buffer[model_id];
if (!model->active || model->blas.nodes == NULL || model->blas.node_count == 0)
mesh_model_handle_t model_handle = inst->model;
const mesh_model_t* model = mesh_model_slot_map_get(&scene->mesh_models, model_handle.id, model_handle.generation);
if (model == NULL || model->blas.nodes == NULL || model->blas.node_count == 0)
{
continue;
}
@@ -611,9 +583,9 @@ hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene)
result.hit = true;
result.point = world_point;
result.distance = world_distance;
result.model_id = model_id;
result.instance_id = (uint32_t)instance_id;
result.material_id = model->triangles.buffer[local_hit.triangle_id].material_id;
result.model = model_handle;
result.instance = (mesh_instance_handle_t){ .id = instance_id, .generation = scene->mesh_instances.generation[instance_id] };
result.material = model->triangles.buffer[local_hit.triangle_id].material;
return result;
}
@@ -623,24 +595,6 @@ hit_result_t ray_intersect_scene_any(const ray_t* ray, const scene_t* scene)
if (stack_size < stack_capacity) stack[stack_size++] = node->left_child_offset;
if (stack_size < stack_capacity) stack[stack_size++] = node->right_child_offset;
}
if (result.hit)
{
return result;
}
}
if (scene->bvh_tree.nodes == NULL || scene->triangles.count == 0 || scene->bvh_tree.node_count == 0 || scene->bvh_tree.primitive_count == 0)
{
return result;
}
ray_intersect_bvh_any(ray, scene->bvh_tree.nodes, scene->bvh_tree.primitive_indices, &scene->triangles, 0, &result);
if (result.hit)
{
result.material_id = scene->triangles.buffer[result.triangle_id].material_id;
result.model_id = UINT32_MAX;
result.instance_id = UINT32_MAX;
}
return result;

View File

@@ -3,7 +3,7 @@
#include <stdlib.h>
#include <string.h>
static inline aabb_t compute_bounds_range(const uint64_t* primitive_indices, uint64_t start, uint64_t count, const aabb_t* all_bounds)
static inline aabb_t compute_bounds_range(const uint32_t* primitive_indices, uint32_t start, uint32_t count, const aabb_t* all_bounds)
{
if (count == 0)
{
@@ -11,7 +11,7 @@ static inline aabb_t compute_bounds_range(const uint64_t* primitive_indices, uin
}
aabb_t bounds = all_bounds[primitive_indices[start]];
for (uint64_t i = start + 1; i < start + count; ++i)
for (uint32_t i = start + 1; i < start + count; ++i)
{
bounds = aabb_union(bounds, all_bounds[primitive_indices[i]]);
}
@@ -24,14 +24,14 @@ static inline vec3s bounds_centroid(aabb_t b)
return glms_vec3_scale(glms_vec3_add(b.min, b.max), 0.5f);
}
static uint64_t build_tlas_node(bvh_node_t* nodes,
uint64_t* next_node_index,
uint64_t* primitive_indices,
static uint32_t build_tlas_node(bvh_node_t* nodes,
uint32_t* next_node_index,
uint32_t* primitive_indices,
const aabb_t* all_bounds,
uint64_t prim_start,
uint64_t prim_count)
uint32_t prim_start,
uint32_t prim_count)
{
uint64_t node_index = (*next_node_index)++;
uint32_t node_index = (*next_node_index)++;
bvh_node_t* node = &nodes[node_index];
node->start_index = prim_start;
@@ -47,7 +47,7 @@ static uint64_t build_tlas_node(bvh_node_t* nodes,
// Choose split axis based on centroid bounds extent.
aabb_t centroid_bounds = invalid_aabb();
for (uint64_t i = prim_start; i < prim_start + prim_count; ++i)
for (uint32_t i = prim_start; i < prim_start + prim_count; ++i)
{
aabb_t b = all_bounds[primitive_indices[i]];
vec3s c = bounds_centroid(b);
@@ -62,27 +62,27 @@ static uint64_t build_tlas_node(bvh_node_t* nodes,
float mid = (centroid_bounds.min.raw[axis] + centroid_bounds.max.raw[axis]) * 0.5f;
// Partition by centroid along axis.
uint64_t i = prim_start;
for (uint64_t j = prim_start; j < prim_start + prim_count; ++j)
uint32_t i = prim_start;
for (uint32_t j = prim_start; j < prim_start + prim_count; ++j)
{
aabb_t b = all_bounds[primitive_indices[j]];
vec3s c = bounds_centroid(b);
if (c.raw[axis] < mid)
{
uint64_t tmp = primitive_indices[i];
uint32_t tmp = primitive_indices[i];
primitive_indices[i] = primitive_indices[j];
primitive_indices[j] = tmp;
i++;
}
}
uint64_t left_count = i - prim_start;
uint64_t right_count = prim_count - left_count;
uint32_t left_count = i - prim_start;
uint32_t right_count = prim_count - left_count;
// Fallback to median if partition failed.
if (left_count == 0 || right_count == 0)
{
uint64_t median = prim_start + prim_count / 2;
uint32_t median = prim_start + prim_count / 2;
left_count = median - prim_start;
right_count = prim_count - left_count;
i = median;
@@ -94,8 +94,8 @@ static uint64_t build_tlas_node(bvh_node_t* nodes,
}
}
uint64_t left_child = build_tlas_node(nodes, next_node_index, primitive_indices, all_bounds, prim_start, left_count);
uint64_t right_child = build_tlas_node(nodes, next_node_index, primitive_indices, all_bounds, i, right_count);
uint32_t left_child = build_tlas_node(nodes, next_node_index, primitive_indices, all_bounds, prim_start, left_count);
uint32_t right_child = build_tlas_node(nodes, next_node_index, primitive_indices, all_bounds, i, right_count);
node->left_child_offset = left_child;
node->right_child_offset = right_child;
@@ -103,7 +103,7 @@ static uint64_t build_tlas_node(bvh_node_t* nodes,
return node_index;
}
bool tlas_tree_build(tlas_tree_t* tlas, const uint64_t* instance_indices, uint64_t instance_count, const aabb_t* all_instance_bounds)
bool tlas_tree_build(tlas_tree_t* tlas, const uint32_t* instance_indices, uint32_t instance_count, const aabb_t* all_instance_bounds)
{
if (tlas == NULL)
{
@@ -120,14 +120,14 @@ bool tlas_tree_build(tlas_tree_t* tlas, const uint64_t* instance_indices, uint64
tlas->primitive_count = instance_count;
tlas->instance_bounds = all_instance_bounds;
tlas->primitive_indices = (uint64_t*)malloc(sizeof(uint64_t) * instance_count);
tlas->primitive_indices = (uint32_t*)malloc(sizeof(uint32_t) * instance_count);
if (tlas->primitive_indices == NULL)
{
tlas_tree_free(tlas);
return false;
}
memcpy(tlas->primitive_indices, instance_indices, sizeof(uint64_t) * instance_count);
memcpy(tlas->primitive_indices, instance_indices, sizeof(uint32_t) * instance_count);
tlas->node_capacity = instance_count * 2 - 1;
tlas->nodes = (bvh_node_t*)malloc(sizeof(bvh_node_t) * tlas->node_capacity);
@@ -137,7 +137,7 @@ bool tlas_tree_build(tlas_tree_t* tlas, const uint64_t* instance_indices, uint64
return false;
}
uint64_t next_node = 0;
uint32_t next_node = 0;
(void)build_tlas_node(tlas->nodes, &next_node, tlas->primitive_indices, all_instance_bounds, 0, instance_count);
tlas->node_count = next_node;

View File

@@ -22,26 +22,20 @@ static texture_handle_t load_material_texture(const struct aiMaterial* material,
return texture_load(path.data, true, true, UINT_8, &scene->textures);
}
return invalid_texture_handle();
return INVALID_HANDLE(texture_handle_t);
}
mesh_handle_t mesh_load(const char* filename, scene_t* scene)
mesh_model_handle_t mesh_load(const char* filename, scene_t* scene)
{
mesh_handle_t entity = {0};
entity.model_id = UINT32_MAX;
entity.instance_id = UINT32_MAX;
const struct aiScene* mesh_scene = aiImportFile(filename, aiProcessPreset_TargetRealtime_Quality);
if (mesh_scene == NULL)
{
perror(aiGetErrorString());
return entity;
return INVALID_HANDLE(mesh_model_handle_t);
}
entity.material_id = scene->materials.count;
// Reserve a model sized for the imported geometry.
uint64_t triangle_reserve = 0;
uint32_t triangle_reserve = 0;
for (uint32_t i = 0; i < mesh_scene->mNumMeshes; i++)
{
const struct aiMesh* mesh = mesh_scene->mMeshes[i];
@@ -51,14 +45,23 @@ mesh_handle_t mesh_load(const char* filename, scene_t* scene)
}
// Each face is expected to be a triangle; we still validate per face below.
triangle_reserve += (uint64_t)mesh->mNumFaces;
triangle_reserve += (uint32_t)mesh->mNumFaces;
}
entity.model_id = scene_add_mesh_model(scene, triangle_reserve);
mesh_model_t* model = NULL;
if (entity.model_id != UINT32_MAX && entity.model_id < scene->mesh_models.capacity)
mesh_model_handle_t handle = scene_add_mesh_model(scene, triangle_reserve);
mesh_model_t* model = mesh_model_slot_map_get(&scene->mesh_models, handle.id, handle.generation);
if (model == NULL)
{
model = &scene->mesh_models.buffer[entity.model_id];
aiReleaseImport(mesh_scene);
return INVALID_HANDLE(mesh_model_handle_t);
}
// TODO: Custom memory allocator.
material_handle_t* material_handles = malloc(sizeof(material_handle_t) * mesh_scene->mNumMaterials);
if (material_handles == NULL)
{
aiReleaseImport(mesh_scene);
return INVALID_HANDLE(mesh_model_handle_t);
}
for (uint32_t i = 0; i < mesh_scene->mNumMaterials; i++)
@@ -74,7 +77,7 @@ mesh_handle_t mesh_load(const char* filename, scene_t* scene)
float metallic = 0.0f;
aiGetMaterialFloat(src, AI_MATKEY_METALLIC_FACTOR, &metallic);
texture_handle_t albedo_entity = load_material_texture(src, aiTextureType_DIFFUSE, filename, scene);
texture_handle_t albedo_entity = load_material_texture(src, aiTextureType_BASE_COLOR, filename, scene);
texture_handle_t normal_entity = load_material_texture(src, aiTextureType_NORMALS, filename, scene);
texture_handle_t roughness_entity = load_material_texture(src, aiTextureType_DIFFUSE_ROUGHNESS, filename, scene);
texture_handle_t metallic_entity = load_material_texture(src, aiTextureType_METALNESS, filename, scene);
@@ -91,8 +94,7 @@ mesh_handle_t mesh_load(const char* filename, scene_t* scene)
.metallic_texture = metallic_entity,
};
material_create_standard_lit_default(&prop, &scene->materials);
entity.material_count++;
material_handles[i] = material_create_standard_lit_default(&prop, &scene->materials);
}
for (uint32_t i = 0; i < mesh_scene->mNumMeshes; i++)
@@ -133,29 +135,14 @@ mesh_handle_t mesh_load(const char* filename, scene_t* scene)
if (model != NULL)
{
triangle_create(vertices[0], vertices[1], vertices[2], (uint8_t)(entity.material_id + mesh->mMaterialIndex), &model->triangles);
triangle_create(vertices[0], vertices[1], vertices[2], material_handles[mesh->mMaterialIndex], &model->triangles);
}
entity.triangle_count++;
}
}
if (model != NULL && model->triangles.count > 0)
{
bvh_tree_free(&model->blas);
if (bvh_tree_init(&model->blas, &model->triangles))
{
(void)bvh_tree_build(&model->blas);
if (model->blas.nodes != NULL && model->blas.node_count > 0)
{
model->local_bounds = model->blas.nodes[0].bounds;
}
}
mat4s identity = glms_mat4_identity();
entity.local_to_world = identity;
entity.instance_id = scene_add_mesh_instance(scene, entity.model_id, identity);
}
commit_mesh_model(model);
aiReleaseImport(mesh_scene);
return entity;
free(material_handles);
return handle;
}

View File

@@ -1,67 +0,0 @@
#include "Geometry/Triangle.h"
bool triangle_collection_init(size_t size, triangle_collection_t* triangles)
{
if (size > UINT64_MAX)
{
size = UINT64_MAX;
}
triangle_collection_t temp = {0};
temp.buffer = (triangle_t*)malloc(size * sizeof(triangle_t));
if (temp.buffer == NULL)
{
return false;
}
temp.size = (uint64_t)size;
temp.count = 0;
*triangles = temp;
return true;
}
void triangle_collection_resize(triangle_collection_t* collection, size_t size)
{
if (size > UINT64_MAX)
{
size = UINT64_MAX;
}
triangle_t* temp = (triangle_t*)realloc(collection->buffer, size * sizeof(triangle_t));
if (temp != NULL)
{
collection->buffer = temp;
collection->size = (uint64_t)size;
}
}
void triangle_collection_free(triangle_collection_t* collection)
{
if (collection->buffer != NULL)
{
free(collection->buffer);
collection->buffer = NULL;
}
}
void triangle_create(vertex_t v1, vertex_t v2, vertex_t v3, uint8_t material_id, triangle_collection_t* collection)
{
vec3s edge1 = glms_vec3_sub(v2.position, v1.position);
vec3s edge2 = glms_vec3_sub(v3.position, v1.position);
vec3s normal = glms_vec3_normalize(glms_vec3_cross(edge1, edge2));
triangle_t triangle = {
.vertices = {v1, v2, v3},
.face_normal = normal,
.material_id = material_id,
};
if (collection->count >= collection->size)
{
triangle_collection_resize(collection, collection->size * 2);
}
collection->buffer[collection->count] = triangle;
collection->count++;
}

View File

@@ -308,7 +308,7 @@ SRT_API srt_result_e srt_scene_add_directional_light(srt_scene_t* scene, const s
return SRT_OUT_OF_CAPACITY;
}
light_entity_t e = light_create_directional_light(L);
light_handle_t e = light_create_directional_light(L);
uint32_t id = e.id;
directional_light_t* dst = &L->directional_lights[id];
@@ -414,7 +414,7 @@ SRT_API srt_result_e srt_scene_add_punctual_light(srt_scene_t* scene, const srt_
return SRT_OUT_OF_CAPACITY;
}
light_entity_t e = light_create_punctual_light(L);
light_handle_t e = light_create_punctual_light(L);
uint32_t id = e.id;
punctual_light_t* dst = &L->punctual_lights[id];
@@ -637,7 +637,7 @@ SRT_API srt_result_e srt_scene_texture_load(srt_scene_t* scene,
return SRT_ERROR;
}
*out_texture = (srt_texture_handle_t){.id = h.id};
*out_texture = (srt_texture_handle_t){.id = h.id, .generation = h.generation};
return SRT_OK;
}
@@ -651,7 +651,7 @@ SRT_API srt_result_e srt_scene_texture_set_sampler(srt_scene_t* scene,
return SRT_INVALID_ARGUMENT;
}
texture_handle_t h = {.id = texture.id};
texture_handle_t h = {.id = texture.id, .generation = texture.generation};
texture_t* tex = get_texture(&scene->scene.textures, h);
if (tex == NULL)
{
@@ -706,7 +706,7 @@ SRT_API srt_result_e srt_scene_set_sky(srt_scene_t* scene, const srt_sky_desc_t*
}
const srt_sky_hdr_desc_t* d = (const srt_sky_hdr_desc_t*)desc->data;
texture_handle_t h = {.id = d->hdri.id};
texture_handle_t h = {.id = d->hdri.id, .generation = d->hdri.generation};
if (get_texture(&scene->scene.textures, h) == NULL)
{
return SRT_NOT_FOUND;

View File

@@ -51,8 +51,61 @@ path_output evaluate_bsdf_directional(directional_light_t light, const light_sha
vec3s light_radiance = glms_vec3_scale(light.color, light.intensity);
vec3s light_contribute = glms_vec3_scale( throughput, fmaxf(0.0f, n_dot_l)); // we always assume pdf = 1.0f for directional light
output.direct_lighting = glms_vec3_mul(light_radiance, light_contribute);
output.lighting = glms_vec3_mul(light_radiance, light_contribute);
output.wi = wi;
output.state = PS_SUCCESS;
return output;
}
// TODO: Support light size and soft shadows
// TODO: Support different light types (spot, area)
path_output evaluate_bsdf_punctual_light(punctual_light_t light, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
{
path_output output = {0.0f};
output.state = PS_TERMINATE;
output.pdf = 1.0f;
if (context == NULL)
{
return output;
}
if (light.intensity <= 0.0f)
{
return output;
}
vec3s light_dir = glms_vec3_normalize(glms_vec3_sub(light.position, context->position));
float distance_squared = glms_vec3_dot(glms_vec3_sub(light.position, context->position), glms_vec3_sub(light.position, context->position));
float n_dot_l = glms_vec3_dot(context->normal, light_dir);
if (n_dot_l <= 0.0f)
{
return output;
}
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), light_dir, 0.0f, 0.0f);
hit_result_t shadow_hit = {0};
if (context->scene != NULL)
{
shadow_hit = ray_intersect_scene_any(&shadow_ray, context->scene);
}
else if (context->bvh_tree != NULL)
{
ray_intersect_bvh_any(&shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &shadow_hit);
}
if (shadow_hit.hit && shadow_hit.distance * shadow_hit.distance < distance_squared - FLT_EPSILON)
{
return output;
}
vec3s light_radiance = glms_vec3_scale(light.color, light.intensity / distance_squared);
vec3s light_contribute = glms_vec3_scale(throughput, fmaxf(0.0f, n_dot_l)); // we always assume pdf = 1.0f for punctual light
output.lighting = glms_vec3_mul(light_radiance, light_contribute);
output.wi = light_dir;
output.state = PS_SUCCESS;
return output;
}

View File

@@ -40,7 +40,7 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
// cosine-weighted hemisphere around the last shading normal.
if (context == NULL || (context->scene == NULL && context->bvh_tree == NULL))
{
output.direct_lighting = glms_vec3_mul(sky_light, throughput);
output.lighting = glms_vec3_mul(sky_light, throughput);
// If a normal wasn't provided, fall back to a normal-independent uniform sphere PDF.
float n2 = (context != NULL) ? glms_vec3_norm2(context->normal) : 0.0f;
@@ -89,7 +89,7 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
return output;
}
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), cos_theta / pdf);
output.lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), cos_theta / pdf);
output.wi = wi;
output.state = PS_SUCCESS;
@@ -118,7 +118,7 @@ static uint32_t lower_bound_float(const float* arr, uint32_t n, float target)
// HDR Sky
sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_handle_t hdri_entity, float intensity)
sky_light_t sky_create_hdr_sky(const texture_slot_map_t* textures, texture_handle_t hdri_entity, float intensity)
{
sky_light_t light = {
.data_size = sizeof(hdr_sky_data_t),
@@ -175,7 +175,7 @@ sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_han
for (uint32_t j = 0; j < data.width; ++j)
{
vec2s uv = {((float)j + 0.5f) / (float)data.width, ((float)i + 0.5f) / (float)data.height};
vec4s pixel = texture_get_pixel(hdri, uv, 0);
vec4s pixel = texture_sample_lod(hdri, uv, 0);
float lum = luminance(glms_vec3(pixel)) * sin_theta;
p_xy_original_pdf[i * data.width + j] = lum;
@@ -308,14 +308,27 @@ static inline float hdr_sky_pdf_direction(const hdr_sky_data_t* sky_data, vec3s
{
vec2s uv = direction_to_equirectangular(wi);
uint32_t x, y;
uv_to_index(uv, sky_data->width, sky_data->height, &x, &y);
// Continuous texel coordinates (matching bilinear filtering)
float fx = uv.x * sky_data->width - 0.5f;
float fy = uv.y * sky_data->height - 0.5f;
float mass = 0.0f;
if (sky_data->pdf_uv_mass != NULL)
{
mass = sky_data->pdf_uv_mass[y * sky_data->width + x];
}
int32_t x0 = (int32_t)floorf(fx);
int32_t y0 = (int32_t)floorf(fy);
float tx = fx - x0;
float ty = fy - y0;
// Clamp to valid range
uint32_t x0c = (uint32_t)glm_clamp(x0, 0, (int32_t)sky_data->width - 1);
uint32_t y0c = (uint32_t)glm_clamp(y0, 0, (int32_t)sky_data->height - 1);
uint32_t x1c = (uint32_t)glm_clamp(x0 + 1, 0, (int32_t)sky_data->width - 1);
uint32_t y1c = (uint32_t)glm_clamp(y0 + 1, 0, (int32_t)sky_data->height - 1);
float m00 = sky_data->pdf_uv_mass[y0c * sky_data->width + x0c];
float m10 = sky_data->pdf_uv_mass[y0c * sky_data->width + x1c];
float m01 = sky_data->pdf_uv_mass[y1c * sky_data->width + x0c];
float m11 = sky_data->pdf_uv_mass[y1c * sky_data->width + x1c];
float mass = glm_lerp(glm_lerp(m00, m10, tx), glm_lerp(m01, m11, tx), ty);
return hdr_pdf_from_uv_mass(mass, uv.y, sky_data->height, sky_data->width);
}
@@ -348,7 +361,7 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
}
vec4s sky_light = texture_sample_lod(get_texture(context->textures, sky_data->texture), uv, lod);
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(glms_vec3(sky_light), throughput), sky_data->intensity);
output.lighting = glms_vec3_scale(glms_vec3_mul(glms_vec3(sky_light), throughput), sky_data->intensity);
// Return the correct environment PDF for MIS when the BSDF-sampled ray escapes to the sky.
output.pdf = hdr_sky_pdf_direction(sky_data, context->wo);
return output;
@@ -400,9 +413,11 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
return output;
}
// TODO: Both texture sampling and PDF calculation can be bilinear interpolated for better quality
// This requires us to change the uv from always being at the center of the texel to being continuous
vec2s uv = (vec2s){u, v};
const texture_t* hdri = get_texture(context->textures, sky_data->texture);
vec4s pixel = texture_get_pixel(hdri, uv, 0);
vec4s pixel = texture_sample_lod(hdri, uv, 0);
vec3s sky_light = glms_vec3_scale(glms_vec3(pixel), sky_data->intensity);
float mass = sky_data->pdf_uv_mass[y_idx * sky_data->width + x_idx];
@@ -412,7 +427,7 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
return output;
}
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), n_dot_l / pdf);
output.lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), n_dot_l / pdf);
output.wi = wi;
output.pdf = pdf;
output.state = PS_SUCCESS;

View File

@@ -1,106 +0,0 @@
#include "Material/Material.h"
#include <string.h>
bool material_collection_init(uint8_t size, material_collection_t* materials)
{
if (size > 254)
{
size = 254;
}
material_collection_t temp = {0};
temp.buffer = (material_t*)malloc(size * sizeof(material_t));
if (temp.buffer == NULL)
{
return false;
}
temp.size = (uint8_t)size;
temp.count = 0;
*materials = temp;
return true;
}
void material_collection_resize(material_collection_t* materials, size_t size)
{
if (size == INVALID_MATERIAL_ID)
{
size = INVALID_MATERIAL_ID - 1;
}
if (size == materials->size)
{
return;
}
material_t* temp = (material_t*)realloc(materials->buffer, size * sizeof(material_t));
if (temp != NULL)
{
materials->buffer = temp;
materials->size = (uint8_t)size;
}
}
void material_collection_free(material_collection_t* materials)
{
if (materials->buffer != NULL)
{
for (uint8_t i = 0; i < materials->count; i++)
{
free(materials->buffer[i].properties);
materials->buffer[i].properties = NULL;
}
free(materials->buffer);
materials->buffer = NULL;
}
}
material_handle_t material_create(const void* properties, size_t properties_size, material_render_loop_f render_loop, material_render_aov_f render_aov, material_collection_t* collection)
{
material_t material = {0};
if (collection->count >= collection->size)
{
material_collection_resize(collection, collection->size * 2);
}
void* temp = malloc(properties_size);
if (temp == NULL)
{
return invalid_material_entity();
}
memcpy(temp, properties, properties_size);
material.properties = temp;
material.properties_size = properties_size;
material.render_loop = render_loop;
material.render_aov = render_aov;
material_handle_t entity = {.id = collection->count};
collection->buffer[collection->count] = material;
collection->count++;
return entity;
}
// void material_free(material_entity_t entity, material_collection_t* collection)
// {
// if (entity.id >= collection->count || !is_material_entity_valid(entity))
// {
// return;
// }
//
// free(collection->buffer[entity.id].properties);
// collection->buffer[entity.id].properties = NULL;
//
// for (uint8_t i = entity.id; i < collection->count - 1; i++)
// {
// collection->buffer[i] = collection->buffer[i + 1];
// }
//
// collection->count--;
// }

View File

@@ -4,40 +4,6 @@
#include "Algorithm/GGXMultiScatter.h"
#include "Lighting/LightEvaluation.h"
static float oren_nayar_eval(vec3s l, vec3s v, vec3s n, float roughness, float n_dot_l, float n_dot_v)
{
// Full Qualitative Oren-Nayar
float sigma2 = roughness * roughness;
float A = 1.0f - (sigma2 / (2.0f * (sigma2 + 0.33f)));
float B = 0.45f * sigma2 / (sigma2 + 0.09f);
// Cosine of azimuth difference (phi)
// Project V and L onto the tangent plane
vec3s v_plane = glms_vec3_normalize(glms_vec3_sub(v, glms_vec3_scale(n, n_dot_v)));
vec3s l_plane = glms_vec3_normalize(glms_vec3_sub(l, glms_vec3_scale(n, n_dot_l)));
float cos_phi_diff = fmaxf(0.0f, glms_vec3_dot(v_plane, l_plane));
// Sine and Tangent terms
// alpha = max(theta_l, theta_v), beta = min(theta_l, theta_v)
// We use sin/tan relationships: sin(x) = sqrt(1-cos^2(x))
float sin_theta_l = sqrtf(fmaxf(0.0f, 1.0f - n_dot_l * n_dot_l));
float sin_theta_v = sqrtf(fmaxf(0.0f, 1.0f - n_dot_v * n_dot_v));
float sin_alpha, tan_beta;
if (n_dot_v > n_dot_l)
{
sin_alpha = sin_theta_l;
tan_beta = sin_theta_v / fmaxf(n_dot_v, FLT_EPSILON);
}
else
{
sin_alpha = sin_theta_v;
tan_beta = sin_theta_l / fmaxf(n_dot_l, FLT_EPSILON);
}
return (A + B * cos_phi_diff * sin_alpha * tan_beta) * INV_PI;
}
static void get_surface_data(const shading_context_t* context, const standard_lit_properties_t* properties, standard_lit_surface_data_t* data_out)
{
// Use the ray cone width (footprint) instead of simple distance for mip selection
@@ -46,17 +12,14 @@ static void get_surface_data(const shading_context_t* context, const standard_li
// Fetch geometry data for LOD calculation
const triangle_t* triangle = &context->triangles->buffer[context->triangle_id];
texture_sample_context_t sample_context =
{
.view_direction = view,
texture_sample_context_t sample_context = {.view_direction = view,
.normal = context->normal,
.edge1 = glms_vec3_sub(triangle->vertices[1].position, triangle->vertices[0].position),
.edge2 = glms_vec3_sub(triangle->vertices[2].position, triangle->vertices[0].position),
.uv1 = glms_vec2_sub(triangle->vertices[1].uv, triangle->vertices[0].uv),
.uv2 = glms_vec2_sub(triangle->vertices[2].uv, triangle->vertices[0].uv),
.ray_width = context->cone_width,
.distance = distance
};
.distance = distance};
data_out->albedo = properties->albedo;
const texture_t* albedo_texture = get_texture(context->textures, properties->albedo_texture);
@@ -93,7 +56,41 @@ static void get_surface_data(const shading_context_t* context, const standard_li
}
}
static vec3s evaluate_bsdf_standard_lit(const shading_context_t* context, standard_lit_surface_data_t* surface_data, vec3s wi)
static float oren_nayar_eval(vec3s l, vec3s v, vec3s n, float roughness, float n_dot_l, float n_dot_v)
{
// Full Qualitative Oren-Nayar
float sigma2 = roughness * roughness;
float A = 1.0f - (sigma2 / (2.0f * (sigma2 + 0.33f)));
float B = 0.45f * sigma2 / (sigma2 + 0.09f);
// Cosine of azimuth difference (phi)
// Project V and L onto the tangent plane
vec3s v_plane = glms_vec3_normalize(glms_vec3_sub(v, glms_vec3_scale(n, n_dot_v)));
vec3s l_plane = glms_vec3_normalize(glms_vec3_sub(l, glms_vec3_scale(n, n_dot_l)));
float cos_phi_diff = fmaxf(0.0f, glms_vec3_dot(v_plane, l_plane));
// Sine and Tangent terms
// alpha = max(theta_l, theta_v), beta = min(theta_l, theta_v)
// We use sin/tan relationships: sin(x) = sqrt(1-cos^2(x))
float sin_theta_l = sqrtf(fmaxf(0.0f, 1.0f - n_dot_l * n_dot_l));
float sin_theta_v = sqrtf(fmaxf(0.0f, 1.0f - n_dot_v * n_dot_v));
float sin_alpha, tan_beta;
if (n_dot_v > n_dot_l)
{
sin_alpha = sin_theta_l;
tan_beta = sin_theta_v / fmaxf(n_dot_v, FLT_EPSILON);
}
else
{
sin_alpha = sin_theta_v;
tan_beta = sin_theta_l / fmaxf(n_dot_l, FLT_EPSILON);
}
return (A + B * cos_phi_diff * sin_alpha * tan_beta) * INV_PI;
}
static vec3s evaluate_bsdf_standard_lit(const shading_context_t* context, const standard_lit_surface_data_t* surface_data, vec3s wi)
{
vec3s n = surface_data->normal;
vec3s v = glms_vec3_negate(context->wo);
@@ -116,21 +113,21 @@ static vec3s evaluate_bsdf_standard_lit(const shading_context_t* context, standa
// Specular (GGX)
float D = ggx_distribution(n_dot_h, surface_data->roughness);
float G = ggx_g_smith(n_dot_v, n_dot_l, surface_data->roughness);
vec3s spec = glms_vec3_scale(glms_vec3_mul(F, (vec3s){D * G, D * G, D * G}),
1.0f / fmaxf(4.0f * n_dot_v * n_dot_l, FLT_EPSILON));
float V = ggx_visibility(n_dot_v, n_dot_l, surface_data->roughness);
vec3s spec = glms_vec3_scale(F, D * V);
// Multi-scatter GGX (broad lobe)
vec3s ms = ggx_multi_scatter_lambert(f0, n_dot_v, n_dot_l, surface_data->roughness);
// Diffuse (Oren-Nayar)
vec3s kD = glms_vec3_sub(glms_vec3_one(), F);
// Diffuse (Oren-Nayar) — use F(NdotV) * E(NdotV) as hemispherical specular reflectance
vec3s F_v = fresnel_schlick_vec3(f0, n_dot_v);
float Eo = ggx_ms_E(n_dot_v, surface_data->roughness);
vec3s kD = glms_vec3_sub(glms_vec3_one(), glms_vec3_scale(F_v, Eo));
kD = glms_vec3_scale(kD, (1.0f - surface_data->metallic));
float on_val = oren_nayar_eval(l, v, n, surface_data->diffuse_roughness, n_dot_l, n_dot_v);
vec3s diff = glms_vec3_scale(glms_vec3_mul(surface_data->albedo, kD), on_val);
// return (vec3s){n_dot_h, n_dot_h, n_dot_h};
return glms_vec3_add(glms_vec3_add(diff, spec), ms);
}
@@ -188,6 +185,14 @@ static float sample_bsdf_pdf(const standard_lit_surface_data_t* surface_data, ve
return w_ss * pdf_spec + w_cos * pdf_cos;
}
static void accumulate_lighting(path_output* output, const path_output* light_output, const shading_context_t* context, vec3s V, const standard_lit_surface_data_t* surface_data)
{
vec3s bsdf_dir_light = evaluate_bsdf_standard_lit(context, surface_data, light_output->wi);
float pdf_bsdf = sample_bsdf_pdf(surface_data, V, light_output->wi);
vec3s light_contribute = weight_nee_light(bsdf_dir_light, light_output->lighting, pdf_bsdf, light_output->pdf);
output->lighting = glms_vec3_add(output->lighting, light_contribute);
}
path_output standard_lit_render_loop(const standard_lit_properties_t* properties, const shading_context_t* context)
{
standard_lit_surface_data_t surface_data; // Assuming you reuse your struct
@@ -238,15 +243,22 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
// Running the light loop.
// TODO: Implementing other light types.
for (uint32_t i = 0; i < context->lights->directional_light_count; i++)
for (uint32_t i = 0; i < context->lights->directional_lights.count; i++)
{
path_output light_output = evaluate_bsdf_directional(context->lights->directional_lights[i], &light_context, context->throughput, context->sample_index);
path_output light_output = evaluate_bsdf_directional(context->lights->directional_lights.buffer[i], &light_context, context->throughput, context->sample_index);
if (light_output.state == PS_SUCCESS)
{
vec3s bsdf_dir_light = evaluate_bsdf_standard_lit(context, &surface_data, light_output.wi);
float pdf_bsdf = sample_bsdf_pdf(&surface_data, V, light_output.wi);
vec3s light_contribute = weight_nee_light(bsdf_dir_light, light_output.direct_lighting, pdf_bsdf, light_output.pdf);
output.direct_lighting = glms_vec3_add(output.direct_lighting, light_contribute);
accumulate_lighting(&output, &light_output, context, V, &surface_data);
}
}
// Punctual Lights
for (uint32_t i = 0; i < context->lights->punctual_lights.count; i++)
{
path_output light_output = evaluate_bsdf_punctual_light(context->lights->punctual_lights.buffer[i], &light_context, context->throughput, context->sample_index);
if (light_output.state == PS_SUCCESS)
{
accumulate_lighting(&output, &light_output, context, V, &surface_data);
}
}
@@ -254,10 +266,7 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
path_output sky_output = evaluate_bsdf_sky(context->lights, &light_context, context->throughput, context->sample_index);
if (sky_output.state == PS_SUCCESS)
{
vec3s bsdf_sky_light = evaluate_bsdf_standard_lit(context, &surface_data, sky_output.wi);
float pdf_bsdf = sample_bsdf_pdf(&surface_data, V, sky_output.wi);
vec3s sky_light = weight_nee_light(bsdf_sky_light, sky_output.direct_lighting, pdf_bsdf, sky_output.pdf);
output.direct_lighting = glms_vec3_add(output.direct_lighting, sky_light);
accumulate_lighting(&output, &sky_output, context, V, &surface_data);
}
// ----------------------------------------------------

View File

@@ -1,15 +1,15 @@
#include "Rendering/Debug.h"
#include "Algorithm/RayIntersection.h"
static void ray_intersect_bvh_count(ray_t ray, bvh_tree_t bvh_tree, uint64_t node_index, uint32_t* count_out)
static void ray_intersect_tlas_count(ray_t ray, const tlas_tree_t* tlas_tree, uint32_t node_index, uint32_t* count_out)
{
const float _MAX_DIST = 1e6f;
if (bvh_tree.nodes == NULL || bvh_tree.primitive_indices == NULL || count_out == NULL)
if (tlas_tree->nodes == NULL || tlas_tree->primitive_indices == NULL || count_out == NULL)
{
return;
}
const bvh_node_t* node = &bvh_tree.nodes[node_index];
const bvh_node_t* node = &tlas_tree->nodes[node_index];
float enter, exit;
if (!ray_intersect_aabb(&ray, node->bounds, &enter, &exit))
@@ -26,8 +26,8 @@ static void ray_intersect_bvh_count(ray_t ray, bvh_tree_t bvh_tree, uint64_t nod
if (node->primitive_count == 0)
{
// Internal node
ray_intersect_bvh_count(ray, bvh_tree, node->left_child_offset, count_out);
ray_intersect_bvh_count(ray, bvh_tree, node->right_child_offset, count_out);
ray_intersect_tlas_count(ray, tlas_tree, node->left_child_offset, count_out);
ray_intersect_tlas_count(ray, tlas_tree, node->right_child_offset, count_out);
}
}
@@ -42,7 +42,7 @@ vec4s render_debug(scene_t* scene, ray_t ray, uint16_t sample_index, int flag)
{
case DEBUG_BVH:
uint32_t count = 0;
ray_intersect_bvh_count(ray, scene->bvh_tree, 0, &count);
ray_intersect_tlas_count(ray, &scene->tlas, 0, &count);
vec4s result = glms_vec4_zero();
for (uint32_t i = 0; i < count; i++)

View File

@@ -20,7 +20,7 @@ static inline void create_target_if_required(aov_flags_t aov_flags, aov_flags_t
*render_target = temp;
}
bool renderer_aov_target_init(render_job_t* job, aov_flags_t aov_flags)
bool renderer_aov_target_init(render_job_t* job)
{
job->aov_target = (render_target_t**)malloc(sizeof(render_target_t*) * MAX_AOV_TARGET);
if (job->aov_target == NULL)
@@ -28,14 +28,14 @@ bool renderer_aov_target_init(render_job_t* job, aov_flags_t aov_flags)
return false;
}
create_target_if_required(aov_flags, AOV_BEAUTY, &job->aov_target[AOV_BEAUTY_INDEX], job->config->width, job->config->height);
create_target_if_required(aov_flags, AOV_AlBEDO, &job->aov_target[AOV_AlBEDO_INDEX], job->config->width, job->config->height);
create_target_if_required(aov_flags, AOV_NORMAL, &job->aov_target[AOV_NORMAL_INDEX], job->config->width, job->config->height);
create_target_if_required(aov_flags, AOV_DEPTH, &job->aov_target[AOV_DEPTH_INDEX], job->config->width, job->config->height);
create_target_if_required(aov_flags, AOV_POSITION, &job->aov_target[AOV_POSITION_INDEX], job->config->width, job->config->height);
create_target_if_required(job->config.aov_flags, AOV_BEAUTY, &job->aov_target[AOV_BEAUTY_INDEX], job->config.width, job->config.height);
create_target_if_required(job->config.aov_flags, AOV_AlBEDO, &job->aov_target[AOV_AlBEDO_INDEX], job->config.width, job->config.height);
create_target_if_required(job->config.aov_flags, AOV_NORMAL, &job->aov_target[AOV_NORMAL_INDEX], job->config.width, job->config.height);
create_target_if_required(job->config.aov_flags, AOV_DEPTH, &job->aov_target[AOV_DEPTH_INDEX], job->config.width, job->config.height);
create_target_if_required(job->config.aov_flags, AOV_POSITION, &job->aov_target[AOV_POSITION_INDEX], job->config.width, job->config.height);
create_target_if_required(aov_flags, AOV_DIRECT, &job->aov_target[AOV_DIRECT_INDEX], job->config->width, job->config->height);
create_target_if_required(aov_flags, AOV_INDIRECT, &job->aov_target[AOV_INDIRECT_INDEX], job->config->width, job->config->height);
create_target_if_required(job->config.aov_flags, AOV_DIRECT, &job->aov_target[AOV_DIRECT_INDEX], job->config.width, job->config.height);
create_target_if_required(job->config.aov_flags, AOV_INDIRECT, &job->aov_target[AOV_INDIRECT_INDEX], job->config.width, job->config.height);
return true;
}
@@ -67,17 +67,34 @@ static inline bool aov_needs_lighting_samples(aov_flags_t flags)
return has_flag(flags, AOV_BEAUTY) || has_flag(flags, AOV_DIRECT) || has_flag(flags, AOV_INDIRECT);
}
static inline uint32_t get_minimal_sample_count(render_job_t* job)
{
if (aov_needs_lighting_samples(job->config.aov_flags))
{
if (job->config.aov_flags == AOV_DIRECT)
{
return 1;
}
return job->config.sample_count;
}
else
{
return 1;
}
}
static inline vec4s running_average_vec4(vec4s prev_avg, vec4s sample, uint32_t prev_count)
{
float n = (float)prev_count;
float inv = 1.0f / (n + 1.0f);
vec4s out;
out.x = (prev_avg.x * n + sample.x) * inv;
out.y = (prev_avg.y * n + sample.y) * inv;
out.z = (prev_avg.z * n + sample.z) * inv;
out.w = 1.0f;
return out;
return (vec4s){
.x = (prev_avg.x * n + sample.x) * inv,
.y = (prev_avg.y * n + sample.y) * inv,
.z = (prev_avg.z * n + sample.z) * inv,
.w = 1.0f
};
}
static inline void clear_render_target(render_target_t* target)
@@ -108,7 +125,13 @@ static inline void clear_aov_targets(render_job_t* job)
}
}
static void render_pixel(const rendering_config_t* config, scene_t* scene, vec3s coord, uint32_t x, uint32_t y, aov_flags_t aov_flags, aov_output_t* pixel_output)
static void render_pixel(const rendering_config_t* config,
const scene_t* scene,
vec3s coord,
uint32_t x,
uint32_t y,
aov_flags_t aov_flags,
aov_output_t* pixel_output)
{
aov_output_t accumulated_color = {0};
@@ -155,12 +178,11 @@ static void render_pixel(const rendering_config_t* config, scene_t* scene, vec3s
}
static void render_pixel_one_sample(const rendering_config_t* config,
scene_t* scene,
const scene_t* scene,
vec3s coord,
uint32_t x,
uint32_t y,
uint32_t sample_index,
aov_flags_t aov_flags,
aov_output_t* pixel_output)
{
uint32_t pixel_id = y * config->width + x;
@@ -192,7 +214,7 @@ static void render_pixel_one_sample(const rendering_config_t* config,
spread_angle);
aov_output_t out = {0};
path_trace_aov(scene, ray, sobol_idx, config->max_depth, aov_flags, &out);
path_trace_aov(scene, ray, sobol_idx, config->max_depth, config->aov_flags, &out);
*pixel_output = out;
}
@@ -220,23 +242,24 @@ static inline void update_aov(render_target_t** target, const aov_output_t* aov,
void renderer_start(render_job_t* job)
{
ensure_camera_aspect_ratio(&job->scene->camera, job->config);
ensure_camera_aspect_ratio(&job->scene->camera, &job->config);
job->config.sample_count = get_minimal_sample_count(job);
// Reset progressive state whenever we (re)start.
job->progressive_sample_index = 0;
vec3s coord = glms_vec3_add(job->scene->camera.position, glms_vec3_scale(quat_get_forward(job->scene->camera.rotation), job->scene->camera.focal_length));
if (job->rendering_mode == RENDER_PROGRESSIVE)
if (job->config.rendering_mode == RENDER_PROGRESSIVE)
{
// Progressive mode: accumulate 1 spp per pass until sample_count or stop requested.
clear_aov_targets(job);
job->is_done = false;
uint32_t width = job->config->width;
uint32_t height = job->config->height;
uint32_t width = job->config.width;
uint32_t height = job->config.height;
for (uint32_t s = 0; s < job->config->sample_count; ++s)
for (uint32_t s = 0; s < job->config.sample_count; ++s)
{
if (job->is_done)
{
@@ -257,10 +280,10 @@ void renderer_start(render_job_t* job)
}
aov_output_t pixel = {0};
render_pixel_one_sample(job->config, job->scene, coord, (uint32_t)x, (uint32_t)y, s, job->aov_flags, &pixel);
render_pixel_one_sample(&job->config, job->scene, coord, (uint32_t)x, (uint32_t)y, s, &pixel);
// Accumulate lighting AOVs; write non-stochastic AOVs once.
if (has_flag(job->aov_flags, AOV_BEAUTY))
if (has_flag(job->config.aov_flags, AOV_BEAUTY))
{
vec4s prev = render_target_get_pixel(job->aov_target[AOV_BEAUTY_INDEX], (uint32_t)x, (uint32_t)y);
vec4s avg = running_average_vec4(prev, pixel.beauty, s);
@@ -275,14 +298,14 @@ void renderer_start(render_job_t* job)
update_aov_pixel_if_exist(&job->aov_target[AOV_POSITION_INDEX], pixel.position, (uint32_t)x, (uint32_t)y);
}
if (has_flag(job->aov_flags, AOV_DIRECT))
if (has_flag(job->config.aov_flags, AOV_DIRECT))
{
vec4s prev = render_target_get_pixel(job->aov_target[AOV_DIRECT_INDEX], (uint32_t)x, (uint32_t)y);
vec4s avg = running_average_vec4(prev, pixel.direct, s);
render_target_set_pixel(job->aov_target[AOV_DIRECT_INDEX], (uint32_t)x, (uint32_t)y, avg);
}
if (has_flag(job->aov_flags, AOV_INDIRECT))
if (has_flag(job->config.aov_flags, AOV_INDIRECT))
{
vec4s prev = render_target_get_pixel(job->aov_target[AOV_INDIRECT_INDEX], (uint32_t)x, (uint32_t)y);
vec4s avg = running_average_vec4(prev, pixel.indirect, s);
@@ -299,8 +322,8 @@ void renderer_start(render_job_t* job)
}
else
{
uint32_t tile_count_x = (job->config->width + job->config->bucket_size - 1) / job->config->bucket_size;
uint32_t tile_count_y = (job->config->height + job->config->bucket_size - 1) / job->config->bucket_size;
uint32_t tile_count_x = (job->config.width + job->config.bucket_size - 1) / job->config.bucket_size;
uint32_t tile_count_y = (job->config.height + job->config.bucket_size - 1) / job->config.bucket_size;
uint32_t tile_count = tile_count_x * tile_count_y;
int64_t x, y, tile_index; // OpenMP requires these to be declared outside the parallel region.
@@ -309,10 +332,10 @@ void renderer_start(render_job_t* job)
private(x, y, tile_index)
for (tile_index = 0; tile_index < tile_count; tile_index++)
{
uint32_t tile_x_0 = (uint32_t)tile_index % tile_count_x * job->config->bucket_size;
uint32_t tile_y_0 = (uint32_t)tile_index / tile_count_x * job->config->bucket_size;
uint32_t tile_x_1 = (uint32_t)fmin(tile_x_0 + job->config->bucket_size, job->config->width);
uint32_t tile_y_1 = (uint32_t)fmin(tile_y_0 + job->config->bucket_size, job->config->height);
uint32_t tile_x_0 = (uint32_t)tile_index % tile_count_x * job->config.bucket_size;
uint32_t tile_y_0 = (uint32_t)tile_index / tile_count_x * job->config.bucket_size;
uint32_t tile_x_1 = (uint32_t)fmin(tile_x_0 + job->config.bucket_size, job->config.width);
uint32_t tile_y_1 = (uint32_t)fmin(tile_y_0 + job->config.bucket_size, job->config.height);
for (y = tile_y_0; y < tile_y_1; y++)
{
@@ -324,7 +347,7 @@ void renderer_start(render_job_t* job)
}
aov_output_t pixel_output = {0};
render_pixel(job->config, job->scene, coord, (uint32_t)x, (uint32_t)y, job->aov_flags, &pixel_output);
render_pixel(&job->config, job->scene, coord, (uint32_t)x, (uint32_t)y, job->config.aov_flags, &pixel_output);
update_aov(job->aov_target, &pixel_output, (uint32_t)x, (uint32_t)y);
}
}

View File

@@ -3,56 +3,6 @@
#include <stdlib.h>
#include <string.h>
static inline void mesh_model_collection_init(mesh_model_collection_t* models, uint32_t capacity)
{
models->count = capacity;
models->capacity = capacity;
models->buffer = (mesh_model_t*)calloc(capacity, sizeof(mesh_model_t));
}
static inline void mesh_instance_collection_init(mesh_instance_collection_t* instances, uint32_t capacity)
{
instances->count = capacity;
instances->capacity = capacity;
instances->buffer = (mesh_instance_t*)calloc(capacity, sizeof(mesh_instance_t));
}
static inline void mesh_model_collection_free(mesh_model_collection_t* models)
{
if (models == NULL || models->buffer == NULL)
{
return;
}
for (uint32_t i = 0; i < models->capacity; ++i)
{
mesh_model_t* m = &models->buffer[i];
if (m->active)
{
bvh_tree_free(&m->blas);
triangle_collection_free(&m->triangles);
}
}
free(models->buffer);
models->buffer = NULL;
models->count = 0;
models->capacity = 0;
}
static inline void mesh_instance_collection_free(mesh_instance_collection_t* instances)
{
if (instances == NULL)
{
return;
}
free(instances->buffer);
instances->buffer = NULL;
instances->count = 0;
instances->capacity = 0;
}
static inline vec3s mat4_mul_point(mat4s m, vec3s p)
{
return glms_mat4_mulv3(m, p, 1.0f);
@@ -98,10 +48,10 @@ static bool scene_rebuild_tlas(scene_t* scene)
}
// Build list of active instances.
uint64_t active_count = 0;
uint32_t active_count = 0;
for (uint32_t i = 0; i < scene->mesh_instances.capacity; ++i)
{
if (scene->mesh_instances.buffer[i].active)
if (scene->mesh_instances.occupied[i])
{
active_count++;
}
@@ -114,16 +64,16 @@ static bool scene_rebuild_tlas(scene_t* scene)
return true;
}
uint64_t* indices = (uint64_t*)malloc(sizeof(uint64_t) * active_count);
uint32_t* indices = (uint32_t*)malloc(sizeof(uint32_t) * active_count);
if (indices == NULL)
{
return false;
}
uint64_t cursor = 0;
uint32_t cursor = 0;
for (uint32_t i = 0; i < scene->mesh_instances.capacity; ++i)
{
if (scene->mesh_instances.buffer[i].active)
if (scene->mesh_instances.occupied[i])
{
indices[cursor++] = i;
}
@@ -165,26 +115,23 @@ static bool scene_rebuild_tlas(scene_t* scene)
return true;
}
bool scene_init(scene_t* scene, uint64_t triangle_count, uint16_t texture_count, uint8_t material_count, uint32_t punctual_light_count)
ARRAY_DEF(int, test)
bool scene_init(scene_t* scene, uint32_t triangle_count, uint32_t texture_count, uint32_t material_count, uint32_t punctual_light_count)
{
scene_t temp = {0};
if (!triangle_collection_init(triangle_count, &temp.triangles))
{
goto triangle_failed;
}
if (!texture_collection_init(texture_count, &temp.textures))
if (texture_slot_map_init(&temp.textures, texture_count) != RESULT_SUCCESS)
{
goto texture_failed;
}
if (!material_collection_init(material_count, &temp.materials))
if (material_slot_map_init(&temp.materials, material_count) != RESULT_SUCCESS)
{
goto material_failed;
}
if (!light_collection_create(punctual_light_count, 16, &temp.lights)) // NOTE: We just fixed the max directional light count to 16.
if (light_collection_create( &temp.lights, punctual_light_count, 16) != RESULT_SUCCESS) // NOTE: We just fixed the max directional light count to 16.
{
goto light_failed;
}
@@ -199,20 +146,20 @@ bool scene_init(scene_t* scene, uint64_t triangle_count, uint16_t texture_count,
// New mesh system: start with small default capacities (simple first).
(void)triangle_count;
mesh_model_collection_init(&temp.mesh_models, 64);
mesh_instance_collection_init(&temp.mesh_instances, 128);
mesh_model_slot_map_init(&temp.mesh_models, 64);
memset(temp.mesh_models.buffer, 0, sizeof(mesh_model_t) * temp.mesh_models.capacity);
mesh_instance_slot_map_init(&temp.mesh_instances, 128);
temp.tlas_dirty = true;
*scene = temp;
return true;
light_failed:
material_collection_free(&temp.materials);
material_slot_map_free(&temp.materials);
material_failed:
texture_collection_free(&temp.textures);
texture_slot_map_free(&temp.textures);
texture_failed:
triangle_collection_free(&temp.triangles);
triangle_failed:
return false;
}
@@ -223,7 +170,6 @@ bool scene_build_bvh(scene_t* scene)
return false;
}
// Prefer TLAS if any mesh instances exist.
if (scene->tlas_dirty)
{
if (!scene_rebuild_tlas(scene))
@@ -232,21 +178,6 @@ bool scene_build_bvh(scene_t* scene)
}
}
// Legacy BVH build if triangles are present.
if (scene->triangles.count > 0)
{
bvh_tree_free(&scene->bvh_tree);
bvh_tree_t bvh_tree = {0};
if (!bvh_tree_init(&bvh_tree, &scene->triangles))
{
return false;
}
bvh_tree_build(&bvh_tree);
scene->bvh_tree = bvh_tree;
}
return true;
}
@@ -257,148 +188,138 @@ void scene_free(scene_t* scene)
return;
}
bvh_tree_free(&scene->bvh_tree);
triangle_collection_free(&scene->triangles);
// Mesh system
tlas_tree_free(&scene->tlas);
free((void*)scene->tlas.instance_bounds);
scene->tlas.instance_bounds = NULL;
mesh_instance_collection_free(&scene->mesh_instances);
mesh_model_collection_free(&scene->mesh_models);
texture_collection_free(&scene->textures);
material_collection_free(&scene->materials);
for (size_t i = 0; i < scene->mesh_models.capacity; ++i)
{
if (scene->mesh_instances.occupied[i])
{
mesh_model_t* m = &scene->mesh_models.buffer[i];
bvh_tree_free(&m->blas);
triangle_list_free(&m->triangles);
}
}
for (uint32_t i = 0; i < scene->textures.capacity; i++)
{
if (scene->textures.occupied[i] != 0)
{
texture_asset_t* asset = &scene->textures.buffer[i];
texture_free(&asset->texture);
if (asset->full_name != NULL)
{
free(asset->full_name);
asset->full_name = NULL;
}
}
}
for (uint32_t i = 0; i < scene->materials.capacity; i++)
{
if (scene->materials.occupied[i] != 0)
{
material_t* material = &scene->materials.buffer[i];
material_free(material);
}
}
mesh_instance_slot_map_free(&scene->mesh_instances);
mesh_model_slot_map_free(&scene->mesh_models);
texture_slot_map_free(&scene->textures);
material_slot_map_free(&scene->materials);
light_collection_free(&scene->lights);
}
static uint32_t find_free_mesh_model_slot(scene_t* scene)
{
for (uint32_t i = 0; i < scene->mesh_models.capacity; ++i)
{
if (!scene->mesh_models.buffer[i].active)
{
return i;
}
}
uint32_t old_capacity = scene->mesh_models.capacity;
uint32_t new_capacity = old_capacity == 0 ? 64 : old_capacity * 2;
mesh_model_t* resized = (mesh_model_t*)realloc(scene->mesh_models.buffer, sizeof(mesh_model_t) * new_capacity);
if (resized == NULL)
{
return UINT32_MAX;
}
memset(resized + old_capacity, 0, sizeof(mesh_model_t) * (new_capacity - old_capacity));
scene->mesh_models.buffer = resized;
scene->mesh_models.capacity = new_capacity;
scene->mesh_models.count = new_capacity;
return old_capacity;
}
static uint32_t find_free_mesh_instance_slot(scene_t* scene)
{
for (uint32_t i = 0; i < scene->mesh_instances.capacity; ++i)
{
if (!scene->mesh_instances.buffer[i].active)
{
return i;
}
}
uint32_t old_capacity = scene->mesh_instances.capacity;
uint32_t new_capacity = old_capacity == 0 ? 128 : old_capacity * 2;
mesh_instance_t* resized = (mesh_instance_t*)realloc(scene->mesh_instances.buffer, sizeof(mesh_instance_t) * new_capacity);
if (resized == NULL)
{
return UINT32_MAX;
}
memset(resized + old_capacity, 0, sizeof(mesh_instance_t) * (new_capacity - old_capacity));
scene->mesh_instances.buffer = resized;
scene->mesh_instances.capacity = new_capacity;
scene->mesh_instances.count = new_capacity;
return old_capacity;
}
uint32_t scene_add_mesh_model(scene_t* scene, uint64_t triangle_reserve)
mesh_model_handle_t scene_add_mesh_model(scene_t* scene, uint32_t triangle_reserve)
{
if (scene == NULL)
{
return UINT32_MAX;
return INVALID_HANDLE(mesh_model_handle_t);
}
uint32_t slot = find_free_mesh_model_slot(scene);
if (slot == UINT32_MAX)
mesh_model_t model =
{
return UINT32_MAX;
}
.local_bounds = invalid_aabb()
};
mesh_model_t* model = &scene->mesh_models.buffer[slot];
*model = (mesh_model_t){0};
model->active = true;
model->local_bounds = invalid_aabb();
if (!triangle_collection_init((size_t)(triangle_reserve > 0 ? triangle_reserve : 1), &model->triangles))
if (triangle_list_init(&model.triangles, triangle_reserve) != RESULT_SUCCESS)
{
model->active = false;
return UINT32_MAX;
return INVALID_HANDLE(mesh_model_handle_t);
}
return slot;
}
uint32_t scene_add_mesh_instance(scene_t* scene, uint32_t model_id, mat4s local_to_world)
mesh_model_handle_t handle;
if (mesh_model_slot_map_add(&scene->mesh_models, model, &handle.id, &handle.generation) != RESULT_SUCCESS)
{
if (scene == NULL || model_id >= scene->mesh_models.capacity || !scene->mesh_models.buffer[model_id].active)
{
return UINT32_MAX;
return INVALID_HANDLE(mesh_model_handle_t);
}
uint32_t slot = find_free_mesh_instance_slot(scene);
if (slot == UINT32_MAX)
{
return UINT32_MAX;
return handle;
}
mesh_instance_t* inst = &scene->mesh_instances.buffer[slot];
*inst = (mesh_instance_t){0};
inst->active = true;
inst->model_id = model_id;
inst->local_to_world = local_to_world;
inst->world_to_local = glms_mat4_inv(local_to_world);
inst->normal_matrix = compute_normal_matrix(local_to_world);
inst->world_bounds = aabb_transform(local_to_world, scene->mesh_models.buffer[model_id].local_bounds);
mesh_instance_handle_t scene_add_mesh_instance(scene_t* scene, mesh_model_handle_t model, mat4s local_to_world)
{
if (scene == NULL)
{
return INVALID_HANDLE(mesh_instance_handle_t);
}
mesh_model_t* mesh_model = mesh_model_slot_map_get(&scene->mesh_models, model.id, model.generation);
if (mesh_model == NULL)
{
return INVALID_HANDLE(mesh_instance_handle_t);
}
mesh_instance_t instance =
{
.model = model,
.local_to_world = local_to_world,
.world_to_local = glms_mat4_inv(local_to_world),
.normal_matrix = compute_normal_matrix(local_to_world),
.world_bounds = aabb_transform(local_to_world, mesh_model->local_bounds)
};
mesh_instance_handle_t handle;
if (mesh_instance_slot_map_add(&scene->mesh_instances, instance, &handle.id, &handle.generation) != RESULT_SUCCESS)
{
return INVALID_HANDLE(mesh_instance_handle_t);
}
scene->tlas_dirty = true;
return slot;
return handle;
}
void scene_remove_mesh_instance(scene_t* scene, uint32_t instance_id)
void scene_remove_mesh_instance(scene_t* scene, mesh_instance_handle_t instance)
{
if (scene == NULL || instance_id >= scene->mesh_instances.capacity)
if (scene == NULL)
{
return;
}
if (!scene->mesh_instances.buffer[instance_id].active)
if (mesh_instance_slot_map_remove(&scene->mesh_instances, instance.id, instance.generation) == RESULT_SUCCESS)
{
return;
}
scene->mesh_instances.buffer[instance_id].active = false;
scene->tlas_dirty = true;
}
}
void scene_set_mesh_instance_transform(scene_t* scene, uint32_t instance_id, mat4s local_to_world)
void scene_set_mesh_instance_transform(scene_t* scene, mesh_instance_handle_t instance, mat4s local_to_world)
{
if (scene == NULL || instance_id >= scene->mesh_instances.capacity)
if (scene == NULL)
{
return;
}
mesh_instance_t* inst = &scene->mesh_instances.buffer[instance_id];
if (!inst->active)
mesh_instance_t* inst = mesh_instance_slot_map_get(&scene->mesh_instances, instance.id, instance.generation);
if (inst == NULL)
{
return;
}
mesh_model_t* mesh_model = mesh_model_slot_map_get(&scene->mesh_models, inst->model.id, inst->model.generation);
if (mesh_model == NULL)
{
return;
}
@@ -406,10 +327,7 @@ void scene_set_mesh_instance_transform(scene_t* scene, uint32_t instance_id, mat
inst->local_to_world = local_to_world;
inst->world_to_local = glms_mat4_inv(local_to_world);
inst->normal_matrix = compute_normal_matrix(local_to_world);
if (inst->model_id < scene->mesh_models.capacity && scene->mesh_models.buffer[inst->model_id].active)
{
inst->world_bounds = aabb_transform(local_to_world, scene->mesh_models.buffer[inst->model_id].local_bounds);
}
inst->world_bounds = aabb_transform(local_to_world, mesh_model->local_bounds);
scene->tlas_dirty = true;
}

View File

@@ -6,65 +6,6 @@
#define GET_CHANNEL_DATA(pixel, channel, channel_count, default, max) (channel < channel_count ? pixel[channel] : default) / max
bool texture_collection_init(uint16_t size, texture_collection_t* textures)
{
texture_collection_t temp = {0};
temp.buffer = (texture_asset_t*)malloc(size * sizeof(texture_asset_t));
if (temp.buffer == NULL)
{
return false;
}
temp.size = size;
temp.count = 0;
*textures = temp;
return true;
}
void texture_collection_resize(texture_collection_t* textures, uint16_t size)
{
if (size == INVALID_TEXTURE_ID)
{
size = INVALID_TEXTURE_ID - 1;
}
if (size == textures->size)
{
return;
}
texture_asset_t* temp = (texture_asset_t*)realloc(textures->buffer, size * sizeof(texture_asset_t));
if (temp != NULL)
{
textures->buffer = temp;
textures->size = size;
}
}
void texture_collection_free(texture_collection_t* textures)
{
if (textures == NULL)
{
return;
}
for (uint16_t i = 0; i < textures->count; i++)
{
texture_free(&textures->buffer[i].texture);
char* full_name = textures->buffer[i].full_name;
if (full_name != NULL)
{
free(full_name);
}
}
free(textures->buffer);
textures->buffer = NULL;
}
static inline void read_pixel_raw(const char* data, uint32_t x, uint32_t y, uint32_t width, uint8_t channel_count, stride_t stride, char* out_pixel_data)
{
size_t pixel_offset = (size_t)(y * width + x) * channel_count * stride;
@@ -253,7 +194,7 @@ static void generate_mipmap(char* raw_data, mipmap_t* texture_data, uint32_t wid
#endif
}
texture_handle_t texture_load(const char* filename, bool srgb, bool mipmap, stride_t stride, texture_collection_t* textures)
texture_handle_t texture_load(const char* filename, bool srgb, bool mipmap, stride_t stride, texture_slot_map_t* textures)
{
// TODO: This hurts performance, consider using a hash map or similar structure for faster lookups
@@ -284,7 +225,7 @@ texture_handle_t texture_load(const char* filename, bool srgb, bool mipmap, stri
if (raw_data == NULL)
{
return invalid_texture_handle();
return INVALID_HANDLE(texture_handle_t);
}
uint8_t max_mip_level = mipmap ? (uint8_t)log2f(fmaxf((float)width, (float)height)) : 0;
@@ -292,7 +233,7 @@ texture_handle_t texture_load(const char* filename, bool srgb, bool mipmap, stri
if (temp_texture_data == NULL)
{
stbi_image_free(raw_data);
return invalid_texture_handle();
return INVALID_HANDLE(texture_handle_t);
}
generate_mipmap(raw_data, temp_texture_data, (uint32_t)width, (uint32_t)height, (uint8_t)channels, max_mip_level, stride);
@@ -311,17 +252,19 @@ texture_handle_t texture_load(const char* filename, bool srgb, bool mipmap, stri
texture.wrap_mode = WM_REPEAT;
texture.filter_mode = FM_LINEAR;
if (textures->count >= textures->size)
texture_handle_t handle;
texture_asset_t asset = {.full_name = string_copy(filename), .texture = texture};
if (texture_slot_map_add(textures, asset, &handle.id, &handle.generation) != RESULT_SUCCESS)
{
texture_collection_resize(textures, textures->size * 2);
texture_free(&texture);
if (asset.full_name != NULL)
{
free(asset.full_name);
}
return INVALID_HANDLE(texture_handle_t);
}
texture_handle_t entity = {.id = textures->count};
textures->buffer[textures->count] = (texture_asset_t){.full_name = string_copy(filename), .texture = texture};
textures->count++;
return entity;
return handle;
}
static inline void warp_uv(wrap_mode_t mode, vec2s* uv)
@@ -419,7 +362,7 @@ float texture_get_sample_lod(const texture_t* texture, const texture_sample_cont
// 4. Convert to LOD
// LOD 0 = 1 texel. LOD 1 = 2 texels. LOD 2 = 4 texels.
// log2(texels_covered) gives the mip level.
return log2f(texels_covered) * 0.5f;
return log2f(texels_covered) * 0.5f; // TODO: Apply EWA for better quality
}
static vec4s nearest_filter(const texture_t* texture, vec2s uv, uint8_t lod)

View File

@@ -1,7 +1,6 @@
#include <time.h>
#include <omp.h>
#include <stdint.h>
#include <svpng.inc>
#include "Algorithm/Sobol.h"
#include "Geometry/GeometryUtilities.h"
@@ -13,9 +12,27 @@
#include "Rendering/Scene.h"
#include "Window.h"
//#define SAVE_OUTPUT
#define TEST_SCENE
#define TITLE "Path Tracing"
#ifdef TEST_SCENE
#define SCENE_PATH "./assets/bunny.obj"
#else
#define SCENE_PATH "./assets/sponza.fbx"
#define HDRI_PATH "C:/Users/Misaki/Downloads/shanghai_bund_1k.hdr"
#endif
#define HDRI_PATH "./assets/hdri/hansaplatz_1k.hdr"
#define CONCAT(x, y) x##y
#define EVALUATOR(x, y) CONCAT(x, y)
#define OUTPUT_AOV AOV_BEAUTY
#define OUTPUT_AOV_INDEX EVALUATOR(OUTPUT_AOV, _INDEX)
#ifdef SAVE_OUTPUT
#include <svpng.inc>
#define OUTPUT_PATH "./output.png"
#endif
static bool scene_setup(scene_t* scene)
{
@@ -24,21 +41,57 @@ static bool scene_setup(scene_t* scene)
return false;
}
#if 0
#ifdef TEST_SCENE
scene->camera.position = (vec3s){0.0f, 0.5f, 5.0f};
scene->camera.rotation = euler_to_quat(0.0f, 0.0f, 0.0f);
#else
scene->camera.position = (vec3s){-7.5f, 2.5f, 0.0f};
scene->camera.rotation = euler_to_quat(10.0f, -90.0f, 0.0f);
#else
scene->camera.position = (vec3s){0.0f, 0.0f, 5.0f};
scene->camera.rotation = glms_quat_identity();
#endif
// TODO: Standardize light unit
light_entity_t sun = light_create_directional_light(&scene->lights);
directional_light_t* sun_light = &scene->lights.directional_lights[sun.id];
sun_light->direction = glms_vec3_normalize((vec3s){0.6f, 1.0f, 0.25f});
sun_light->color = (vec3s){1.0f, 0.93f, 0.87f};
sun_light->intensity = 0.0f;
sun_light->angular_diameter = 0.53f;
// directional_light_t sun_light =
// {
// .direction = glms_vec3_normalize((vec3s){0.6f, 1.0f, 0.25f}),
// .color = (vec3s){1.0f, 0.93f, 0.87f},
// .intensity = 1.0f,
// .angular_diameter = 0.53f
// };
// light_handle_t sun = light_add_directional_light(&scene->lights, &sun_light);
// punctual_light_t point_light1 =
// {
// .position = (vec3s){1.0f, 5.0f, 0.0f},
// .color = (vec3s){1.0f, 1.0f, 1.0f},
// .intensity = 10.0f,
// };
// punctual_light_t point_light2 =
// {
// .position = (vec3s){1.0f, 5.0f, 2.5f},
// .color = (vec3s){1.0f, 1.0f, 1.0f},
// .intensity = 50.0f,
// };
// punctual_light_t point_light3 =
// {
// .position = (vec3s){1.0f, 5.0f, 2.5f},
// .color = (vec3s){1.0f, 1.0f, 1.0f},
// .intensity = 50.0f,
// };
// punctual_light_t point_light4 =
// {
// .position = (vec3s){1.0f, 5.0f, 2.5f},
// .color = (vec3s){1.0f, 1.0f, 1.0f},
// .intensity = 50.0f,
// };
// light_add_punctual_light(&scene->lights, &point_light1);
// light_add_punctual_light(&scene->lights, &point_light2);
// light_add_punctual_light(&scene->lights, &point_light3);
// light_add_punctual_light(&scene->lights, &point_light4);
#if 0
scene->lights.sky_light = sky_create_constant_sky(&(constant_sky_data_t)
@@ -48,8 +101,12 @@ static bool scene_setup(scene_t* scene)
});
#else
texture_handle_t hdri = texture_load(HDRI_PATH, false, false, FLOAT_32, &scene->textures);
scene->textures.buffer[hdri.id].texture.wrap_mode = WM_CLAMP;
scene->textures.buffer[hdri.id].texture.filter_mode = FM_LINEAR;
texture_t* hdri_tex = get_texture(&scene->textures, hdri);
if (hdri_tex != NULL)
{
hdri_tex->wrap_mode = WM_CLAMP;
hdri_tex->filter_mode = FM_LINEAR;
}
scene->lights.sky_light = sky_create_hdr_sky(&scene->textures, hdri, 1.0f);
#endif
@@ -58,42 +115,67 @@ static bool scene_setup(scene_t* scene)
static bool load_assets(scene_t* scene)
{
#if 0
mesh_load(SCENE_PATH, scene);
#else
material_handle_t floor_material = material_create_standard_lit_default(&(standard_lit_properties_t)
#ifdef TEST_SCENE
material_handle_t white_material = material_create_standard_lit_default(&(standard_lit_properties_t)
{
.albedo = (vec3s){1.0f, 1.0f, 1.0f},
.roughness = 1.0f,
.roughness = 0.0f,
.diffuse_roughness = 0.0f,
.metallic = 0.0f,
.albedo_texture = invalid_texture_handle(),
.metallic_texture = invalid_texture_handle(),
.roughness_texture = invalid_texture_handle(),
.normal_texture = invalid_texture_handle(),
.albedo_texture = INVALID_HANDLE(texture_handle_t),
.metallic_texture = INVALID_HANDLE(texture_handle_t),
.roughness_texture = INVALID_HANDLE(texture_handle_t),
.normal_texture = INVALID_HANDLE(texture_handle_t),
}, &scene->materials);
material_handle_t quad_material = material_create_standard_lit_default(
material_handle_t red_material = material_create_standard_lit_default(
&(standard_lit_properties_t){
.albedo = (vec3s){0.8f, 0.0f, 0.0f},
.roughness = 0.95f,
.diffuse_roughness = 0.0f,
.metallic = 0.0f,
.albedo_texture = invalid_texture_handle(),
.metallic_texture = invalid_texture_handle(),
.roughness_texture = invalid_texture_handle(),
.normal_texture = invalid_texture_handle(),
.albedo_texture = INVALID_HANDLE(texture_handle_t),
.metallic_texture = INVALID_HANDLE(texture_handle_t),
.roughness_texture = INVALID_HANDLE(texture_handle_t),
.normal_texture = INVALID_HANDLE(texture_handle_t),
},
&scene->materials);
quad_create((vec3s){0.0f, -0.5f, 0.0f}, (vec3s){0.0f, 1.0f, 0.0f}, (vec3s){1.0f, 0.0f, 0.0f}, 10.0f, floor_material.id, &scene->triangles);
quad_create((vec3s){0.0f, 0.0f, 0.0f}, (vec3s){0.0f, 0.0f, 1.0f}, (vec3s){0.0f, 1.0f, 0.0f}, 1.0f, floor_material.id, &scene->triangles);
mesh_model_handle_t model_handle = scene_add_mesh_model(scene, 12);
if (!IS_VALID_HANDLE(model_handle))
{
return false;
}
mesh_model_t* model = mesh_model_slot_map_get(&scene->mesh_models, model_handle.id, model_handle.generation);
if (model == NULL)
{
return false;
}
quad_create((vec3s){0.0f, 0.0f, 0.0f}, (vec3s){0.0f, 1.0f, 0.0f}, (vec3s){0.0f, 0.0f, 1.0f}, 10.0f, white_material, &model->triangles);
commit_mesh_model(model);
mesh_model_handle_t mesh_model_handle = mesh_load(SCENE_PATH, scene);
if (!IS_VALID_HANDLE(mesh_model_handle))
{
return false;
}
scene_add_mesh_instance(scene, mesh_model_handle, glms_mat4_identity());
#else
mesh_model_handle_t model_handle = mesh_load(SCENE_PATH, scene);
if (!IS_VALID_HANDLE(model_handle))
{
return false;
}
#endif
scene_add_mesh_instance(scene, model_handle, glms_mat4_identity());
return scene_build_bvh(scene);
}
static bool initialize_renderer(const rendering_config_t* config, aov_flags_t aov_flags, render_job_t** outJob, scene_t* outScene)
static bool initialize_renderer(const rendering_config_t* config, render_job_t** outJob, scene_t* outScene)
{
if (!scene_setup(outScene)
|| !load_assets(outScene))
@@ -109,14 +191,11 @@ static bool initialize_renderer(const rendering_config_t* config, aov_flags_t ao
*job = (render_job_t){
.scene = outScene,
.config = config,
.rendering_mode = RENDER_PROGRESSIVE,
.aov_flags = aov_flags,
.config = *config,
.is_done = false,
};
if (!renderer_aov_target_init(job, job->aov_flags))
if (!renderer_aov_target_init(job))
{
return false;
}
@@ -190,7 +269,46 @@ static int run_main_loop(render_job_t* job, uint8_t aov_index)
return 0;
}
// int main()
static void save_render_output_png(const render_target_t* render_target, const char* output_path)
{
#ifdef SAVE_OUTPUT
if (render_target == NULL || render_target->buffer == NULL)
{
return;
}
uint8_t* png_buffer = (uint8_t*)malloc(render_target->width * render_target->height * 3);
if (png_buffer == NULL)
{
return;
}
for (uint32_t y = 0; y < render_target->height; y++)
{
for (uint32_t x = 0; x < render_target->width; x++)
{
vec4s pixel = render_target_get_pixel(render_target, x, y);
pixel = aces_tone_map(pixel);
pixel = gamma_correct(pixel, 2.2f);
size_t index = (size_t)(y * render_target->width + x) * 3;
png_buffer[index + 0] = (uint8_t)(fminf(fmaxf(pixel.x, 0.0f), 1.0f) * 255.0f);
png_buffer[index + 1] = (uint8_t)(fminf(fmaxf(pixel.y, 0.0f), 1.0f) * 255.0f);
png_buffer[index + 2] = (uint8_t)(fminf(fmaxf(pixel.z, 0.0f), 1.0f) * 255.0f);
}
}
FILE* fp = fopen(output_path, "wb");
if (fp != NULL)
{
svpng(fp, render_target->width, render_target->height, png_buffer, 0);
fclose(fp);
}
free(png_buffer);
#endif
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR pCmdLine, _In_ int nCmdShow)
{
int num_threads = omp_get_num_procs() - 1;
@@ -205,21 +323,31 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
render_job_t* job = NULL;
rendering_config_t config = {
.width = 1920 / 2,
.height = 1080 / 2,
.width = 1920 / 1,
.height = 1080 / 1,
.sample_count = 16 * 4,
.max_depth = 4,
.bucket_size = 64,
.bucket_size = 128,
#ifdef TEST_SCENE
.rendering_mode = RENDER_PROGRESSIVE,
#else
.rendering_mode = RENDER_TILE_BASED,
#endif
.aov_flags = OUTPUT_AOV,
};
if (!initialize_renderer(&config, AOV_BEAUTY, &job, &scene)
if (!initialize_renderer(&config, &job, &scene)
|| !window_create(TITLE, hInstance, config.width, config.height, job))
{
shutdown_renderer(job, &scene);
return -1;
}
int result = run_main_loop(job, AOV_BEAUTY_INDEX);
int result = run_main_loop(job, OUTPUT_AOV_INDEX);
#ifdef SAVE_OUTPUT
save_render_output_png(job->aov_target[OUTPUT_AOV_INDEX], OUTPUT_PATH);
#endif
window_close();
shutdown_renderer(job, &scene);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 MiB

After

Width:  |  Height:  |  Size: 5.9 MiB