Update project structure and improve performance

Added new files for BVH, AABB, and Debug functionalities.
Added new utility functions in Common.h.
Added gamma correction function in PostProcessing.h.
Changed the return type of path_trace to vec4s for alpha blending.
Changed BSDF function signatures to include sample index and bounce.
Changed the BSDF.h to replace inline functions with declarations.
Changed the Light and SkyLight evaluation functions to include throughput and sample index.
Changed the sphere creation function in GeometryUtilities.h for better quality.
Changed the scene structure to include a BVH tree for improved ray intersection.
Changed the scene initialization parameters for better performance.
Created new Debug functions for ray intersection counting.
Created new functions for triangle collection management in Triangle.c.
Improved pixel updating logic in Window.c.
Improved ray intersection performance with new BVH implementation.
Removed unused includes from Common.h.
Removed old library linking methods in CMakeLists.txt.
This commit is contained in:
2025-04-21 15:56:19 +09:00
parent 1162575545
commit 6800810369
36 changed files with 1590 additions and 579 deletions

View File

@@ -1,5 +1,5 @@
BasedOnStyle: Microsoft BasedOnStyle: Microsoft
IndentWidth: 4 IndentWidth: 4
ColumnLimit: 120 ColumnLimit: 200
BreakBeforeBraces: Allman BreakBeforeBraces: Allman
PointerAlignment: Left PointerAlignment: Left

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"C_Cpp.errorSquiggles": "disabled"
}

View File

@@ -21,17 +21,48 @@ add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE ${EXTERNAL_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${EXTERNAL_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${HEADER_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${HEADER_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBRARY_PATH}/cglm.lib) # Define imported target for cglm
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBRARY_PATH}/assimp-vc143-mt.lib) add_library(cglm_shared SHARED IMPORTED)
set_target_properties(cglm_shared PROPERTIES
IMPORTED_LOCATION "${LIBRARY_PATH}/cglm.dll"
IMPORTED_IMPLIB "${LIBRARY_PATH}/cglm.lib"
INTERFACE_INCLUDE_DIRECTORIES "${EXTERNAL_DIR}/cglm"
)
# Define imported target for assimp
add_library(assimp_shared SHARED IMPORTED)
set_target_properties(assimp_shared PROPERTIES
IMPORTED_LOCATION "${LIBRARY_PATH}/assimp-vc143-mt.dll"
IMPORTED_IMPLIB "${LIBRARY_PATH}/assimp-vc143-mt.lib"
INTERFACE_INCLUDE_DIRECTORIES "${EXTERNAL_DIR}/assimp"
)
target_link_libraries(${PROJECT_NAME} PRIVATE
cglm_shared
assimp_shared
)
# target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBRARY_PATH}/cglm.lib)
# target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBRARY_PATH}/assimp-vc143-mt.lib)
if(OpenMP_C_FOUND) if(OpenMP_C_FOUND)
target_link_libraries(${PROJECT_NAME} PRIVATE OpenMP::OpenMP_C) target_link_libraries(${PROJECT_NAME} PRIVATE OpenMP::OpenMP_C)
endif() endif()
# Specify the output directory for the EXE file
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
) )
file(COPY ${LIBRARY_PATH}/cglm.dll DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/) if(WIN32)
file(COPY ${LIBRARY_PATH}/assimp-vc143-mt.dll DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/) add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_RUNTIME_DLLS:${PROJECT_NAME}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
VERBATIM
COMMAND_EXPAND_LISTS
)
endif()
# file(COPY ${LIBRARY_PATH}/cglm.dll DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/)
# file(COPY ${LIBRARY_PATH}/assimp-vc143-mt.dll DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/)

View File

@@ -1,68 +1,24 @@
#ifndef BSDF_H #ifndef BSDF_H
#define BSDF_H #define BSDF_H
#include "Algorithm/Sobol.h"
#include "cglm/struct/vec3.h" #include "cglm/struct/vec3.h"
inline float power_heuristic(float pdf_a, float pdf_b) float power_heuristic(float pdf_a, float pdf_b);
{ float roughness_to_blinn_phong_specular_exponent(float roughness);
float a2 = pdf_a * pdf_a; vec3s fresnel_schlick_vec3(vec3s f0, float cos_theta);
float b2 = pdf_b * pdf_b;
return a2 / (a2 + b2);
}
inline float roughness_to_blinn_phong_specular_exponent(float roughness) // BSDF sampling functions
{ float pdf_cosine_weighted_hemisphere(vec3s normal, vec3s wi);
return glm_clamp(2.0f * 1.0f / (fmaxf(roughness * roughness, FLT_EPSILON)) - 2.0f, FLT_EPSILON, 1.0f / FLT_EPSILON); float pdf_blinn_phong_lobe(vec3s normal, vec3s wi, vec3s wo, float roughness);
}
inline vec3s fresnel_schlick_vec3(vec3s f0, float cos_theta) vec3s sample_cosine_weighted_hemisphere_z_angular(float angular, uint32_t index, uint32_t d1, uint32_t d2);
{ vec3s sample_cosine_weighted_hemisphere_z(uint32_t index, uint32_t d1, uint32_t d2);
float x = 1.0f - cos_theta; void create_orthonormal_basis(vec3s direction, vec3s* u, vec3s* v);
float x5 = x * x * x * x * x; vec3s random_cosine_direction_angular(vec3s direction, float angular, uint32_t index, uint32_t d1, uint32_t d2);
return glms_vec3_adds(glms_vec3_scale(f0, (1.0f - x5)), x5); vec3s random_cosine_direction(vec3s direction, uint32_t index, uint32_t d1, uint32_t d2);
}
inline float pdf_cosine_weighted_hemisphere(vec3s normal, vec3s wi) vec3s random_uniform_cdf_direction(vec3s direction, uint32_t index, uint32_t d1, uint32_t d2);
{ vec3s random_uniform_cdf_direction_angular(vec3s direction, uint32_t index, float angular, uint32_t d1, uint32_t d2);
return fmaxf(glms_vec3_dot(wi, normal), 0.0f) / (float)M_PI;
}
inline float pdf_blinn_phong_lobe(vec3s normal, vec3s wi, vec3s wo, float roughness)
{
// Check if wo and wi are on the same side of the surface normal geometry
if (glms_vec3_dot(wo, normal) <= 0.0f || glms_vec3_dot(wi, normal) <= 0.0f)
{
return 0.0f; // Cannot scatter from below horizon to above, or vice versa
}
// Calculate the half-vector h based on input wo and wi
vec3s wo_n = glms_vec3_normalize(wo); // Ensure normalized inputs if not guaranteed
vec3s wi_n = glms_vec3_normalize(wi);
vec3s h = glms_vec3_add(wo_n, wi_n);
float h_len_sq = glms_vec3_norm2(h);
if (h_len_sq < FLT_EPSILON)
{
return 0.0f; // wo and wi are opposite, highly unlikely for reflection
}
h = glms_vec3_scale(h, 1.0f / sqrtf(h_len_sq)); // Normalize h
// Calculate Blinn-Phong specular exponent
float specular_exponent = roughness_to_blinn_phong_specular_exponent(roughness);
// PDF of sampling h (Blinn-Phong distribution)
// D(h) = (specular_exponent + 1) / (2 * PI) * pow(max(0, dot(n, h)), specular_exponent)
float n_dot_h = fmaxf(0.0f, glms_vec3_dot(normal, h));
float pdf_h = (specular_exponent + 1.0f) / (2.0f * (float)M_PI) * powf(n_dot_h, specular_exponent);
// Jacobian of the transformation from h to wi
// jacobian = 1 / (4 * dot(wo, h))
float wo_dot_h = fmaxf(FLT_EPSILON, glms_vec3_dot(wo_n, h)); // Use normalized wo, ensure > 0
float jacobian = 1.0f / (4.0f * wo_dot_h);
// PDF of sampling wi is pdf(h) * jacobian
float pdf_spec = pdf_h * jacobian;
return pdf_spec;
}
#endif // BSDF_H #endif // BSDF_H

34
header/Algorithm/BVH.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef BVH_H
#define BVH_H
#include "Geometry/AABB.h"
#include "Geometry/Triangle.h"
#define SAH_BIN_COUNT 8 // Number of bins for SAH
#define SAH_TRAVERSAL_COST 1.0f // Cost of traversing a node
#define SAH_INTERSECTION_COST 10.0f // Cost of intersecting a triangle
typedef struct
{
aabb_t bounds;
uint64_t start_index;
uint64_t primitive_count;
uint64_t left_child_offset;
uint64_t right_child_offset;
} bvh_node_t;
typedef struct
{
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)
} bvh_tree_t;
bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_collection_t* triangles);
bool bvh_tree_build(bvh_tree_t* bvh_tree);
void bvh_tree_free(bvh_tree_t* bvh_tree);
#endif // BVH_H

View File

@@ -6,6 +6,6 @@
#include "Geometry/Triangle.h" #include "Geometry/Triangle.h"
#include "Rendering/Scene.h" #include "Rendering/Scene.h"
vec3s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, int depth); vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_t depth);
#endif // PATH_TRACING_H #endif // PATH_TRACING_H

View File

@@ -3,6 +3,7 @@
#include "Common.h" #include "Common.h"
#include "Geometry/Triangle.h" #include "Geometry/Triangle.h"
#include "Rendering/Scene.h"
typedef struct typedef struct
{ {
@@ -19,8 +20,11 @@ typedef struct
bool hit; bool hit;
} hit_result_t; } hit_result_t;
hit_result_t ray_intersect( triangle_t triangle, ray_t ray); hit_result_t ray_intersect_triangle(ray_t ray, triangle_t triangle);
hit_result_t ray_intersect_closest(const triangle_collection_t* triangles, ray_t ray); bool ray_intersect_aabb(ray_t ray, aabb_t aabb, float* enter_out, float* exit_out);
hit_result_t ray_intersect_any(const triangle_collection_t* triangles, ray_t ray); void ray_intersect_bvh(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,
float* closest_out, hit_result_t* best_hit_out);
hit_result_t ray_intersect_scene(ray_t ray, const scene_t* scene);
#endif // RAY_INTERSECTION_H #endif // RAY_INTERSECTION_H

View File

@@ -1,39 +1,82 @@
#ifndef SOBOL_H #ifndef SOBOL_H
#define SOBOL_H #define SOBOL_H
#include "Common.h"
#include <stdint.h> #include <stdint.h>
#define SOBOL_BITS 32 #define SOBOL_BITS 32
// NOTE: May need more dimensions for later // NOTE: May need more dimensions for later, most of commercial renderer use more than 1000 dimensions
#define SOBOL_DIMENSIONS 8 #define SOBOL_DIMENSIONS 36
// #define COS_WEIGHTED_HEMISPHERE_R1_INDEX 0 typedef enum
// #define COS_WEIGHTED_HEMISPHERE_R2_INDEX 1 {
PRNG_FILTER_U = 0,
PRNG_FILTER_V = 1,
PRNG_LENS_U = 2,
PRNG_LENS_V = 3,
PRNG_BASE_NUM = 4,
PRNG_BSDF_U = 0,
PRNG_BSDF_V = 1,
PRNG_BSDF = 2,
PRNG_LIGHT = 3,
PRNG_LIGHT_U = 4,
PRNG_LIGHT_V = 5,
PRNG_LIGHT_F = 6,
PRNG_TERMINATE = 7,
PRNG_BOUNCE_NUM = 8
} sampling_dimension_t;
static uint32_t sobol_direction_vectors[SOBOL_DIMENSIONS][SOBOL_BITS]; static uint32_t sobol_direction_vectors[SOBOL_DIMENSIONS][SOBOL_BITS];
// Precomputed table: s, a, and m_i's // Precomputed table: s, a, and m_i's
// https://web.maths.unsw.edu.au/~fkuo/sobol/ // https://web.maths.unsw.edu.au/~fkuo/sobol/
static int s_vals[SOBOL_DIMENSIONS - 1] = {1, 2, 3, 3, 4, 4, 5}; static int s_vals[SOBOL_DIMENSIONS - 1] = {1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6,
static int a_vals[SOBOL_DIMENSIONS - 1] = {0, 1, 1, 2, 1, 4, 2}; 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7};
static int m_vals[SOBOL_DIMENSIONS - 1][5] = {
{1}, // dim 2 static uint32_t a_vals[SOBOL_DIMENSIONS - 1] = {0, 1, 1, 2, 1, 4, 2, 4, 7, 11, 13, 14, 1, 13, 16, 19,
{1, 3}, // dim 3 22, 25, 1, 4, 7, 8, 14, 19, 21, 28, 31, 32, 37, 41, 42, 50, 55, 56, 59};
{1, 3, 1}, // dim 4
{1, 1, 1}, // dim 5 static uint32_t m_vals[SOBOL_DIMENSIONS - 1][7] = {
{1, 1, 3, 3}, // dim 6 {1},
{1 ,1, 5, 13}, // dim 7 {1, 3},
{1, 1, 5, 5, 17} // dim 8 {1, 3, 1},
{1, 1, 1},
{1, 1, 3, 3},
{1 ,1, 5, 13},
{1, 1, 5, 5, 17},
{1, 1, 5, 5, 5},
{1, 1, 7, 11, 19},
{1, 1, 5, 1, 1},
{1, 1, 1, 3, 11},
{1, 3, 5, 5, 31},
{1, 3, 3, 9, 7, 49},
{1, 1, 1, 15, 21, 21},
{1, 3, 1, 13, 27, 49},
{1, 1, 1, 15, 7, 5},
{1, 3, 1, 15, 13, 25},
{1, 1, 5, 5, 19, 61},
{1, 3, 7, 11, 23, 15, 103},
{1, 3, 7, 13, 13, 15, 69},
{1, 1, 3, 13, 7, 35, 63},
{1, 3, 5, 9, 1, 25, 53},
{1, 3, 1, 13, 9, 35, 107},
{1, 3, 1, 5, 27, 61, 31},
{1, 1, 5, 11, 19, 41, 61},
{1, 3, 5, 3, 3, 13, 69},
{1, 1, 7, 13, 1, 19, 1},
{1, 3, 7, 5, 13, 19, 59},
{1, 1, 3, 9, 25, 29, 41},
{1, 3, 5, 13, 23, 1, 55},
{1, 3, 7, 3, 13, 59, 17},
{1, 3, 1, 3, 5, 53, 69},
{1, 1, 5, 5, 23, 33, 13},
{1, 1, 7, 7, 1, 61, 123},
{1, 1, 7, 9, 13, 61, 49}
}; };
typedef struct
{
uint32_t index;
uint32_t dimension;
} sobol_state_t;
void sobol_init(); void sobol_init();
uint16_t sobol_get_dimension(uint16_t bounce, sampling_dimension_t operation_type);
float sobol_sample(uint32_t index, uint32_t dimension); float sobol_sample(uint32_t index, uint32_t dimension);
float sobol_next(sobol_state_t* state);
#endif // SOBOL_H #endif // SOBOL_H

View File

@@ -1,7 +1,6 @@
#ifndef COMMON_H #ifndef COMMON_H
#define COMMON_H #define COMMON_H
#include "Algorithm/Sobol.h"
#include "cglm/struct/vec4.h" #include "cglm/struct/vec4.h"
#include "cglm/struct/vec3.h" #include "cglm/struct/vec3.h"
#include <math.h> #include <math.h>
@@ -13,115 +12,26 @@ inline float random_float()
return (float)rand() / (float)RAND_MAX; return (float)rand() / (float)RAND_MAX;
} }
inline vec3s sample_cosine_weighted_hemisphere_z_angular(sobol_state_t* sobol_state, float angular) inline uint32_t hash_uint32(uint32_t x) {
{ x = ((x >> 16) ^ x) * 0x45d9f3b;
float r1 = sobol_next(sobol_state); x = ((x >> 16) ^ x) * 0x45d9f3b;
float r2 = sobol_next(sobol_state); x = (x >> 16) ^ x;
return x;
float phi = 2.0f * (float)M_PI * r1;
float cos_theta = 1.0f - r2 * (1.0f - cosf(angular));
float sin_theta = sqrtf(1.0f - cos_theta * cos_theta);
// Convert spherical to Cartesian coordinates (relative to +Z axis)
float x = cosf(phi) * sin_theta;
float y = sinf(phi) * sin_theta;
float z = cos_theta;
// Create the vector in local coordinates (Z is up)
vec3s local_dir = {{x, y, z}};
return local_dir;
} }
// Function to generate a direction with cosine weighting around (0, 0, 1) inline bool has_flag(int flags, int flag)
// This is the local coordinate sample.
inline vec3s sample_cosine_weighted_hemisphere_z(sobol_state_t* sobol_state)
{ {
// float r1 = random_float(); return (flags & flag) != 0;
// float r2 = random_float();
float r1 = sobol_next(sobol_state);
float r2 = sobol_next(sobol_state);
// Convert uniform random numbers to spherical coordinates for cosine weighting
// phi = angle around Z-axis (azimuthal)
// theta = angle from Z-axis (polar)
float phi = 2.0f * (float)M_PI * r1;
// cos(theta) = sqrt(r2), so z = sqrt(r2)
// sin(theta) = sqrt(1 - cos^2(theta)) = sqrt(1 - r2)
float sqrt_r2 = sqrtf(r2); // This is z
float sqrt_1_minus_r2 = sqrtf(1.0f - r2); // This is sin(theta)
// Convert spherical to Cartesian coordinates (relative to +Z axis)
float x = cosf(phi) * sqrt_1_minus_r2;
float y = sinf(phi) * sqrt_1_minus_r2;
float z = sqrt_r2; // Direct mapping for cosine weighting
// Create the vector in local coordinates (Z is up)
vec3s local_dir = {{x, y, z}};
return local_dir; // Already normalized by construction
} }
// Function to create an orthonormal basis (coordinate system) from a single vector (normal) inline void angle_to_direction_vector(float yaw, float pitch, vec3s* forward, vec3s* right, vec3s* up)
// w will be aligned with normal, u and v will be perpendicular.
inline void create_orthonormal_basis(vec3s normal, vec3s* u, vec3s* v)
{ {
// Choose a vector 'a' that is not parallel to 'normal' forward->x = cosf(yaw) * cosf(pitch);
vec3s a; forward->y = sinf(pitch);
if (fabsf(normal.x) > 0.9f) forward->z = sinf(yaw) * cosf(pitch);
{ // If w is close to x-axis
a = (vec3s){{0.0f, 1.0f, 0.0f}}; // Use y-axis
}
else
{
a = (vec3s){{1.0f, 0.0f, 0.0f}}; // Use x-axis
}
// Calculate u = normalize(cross(a, w)) - this is the new 'x-axis' *right = glms_vec3_cross(*forward, (vec3s){0.0f, 1.0f, 0.0f});
*u = glms_vec3_normalize(glms_vec3_cross(a, normal)); *up = glms_vec3_cross(*right, *forward);
// Calculate v = cross(w, u) - this is the new 'y-axis'
// Since w and u are orthonormal, v will also be normalized.
*v = glms_vec3_cross(normal, *u);
}
inline vec3s random_cosine_direction_angular(vec3s normal, float angular, sobol_state_t* sobol_state)
{
vec3s local_dir = sample_cosine_weighted_hemisphere_z_angular(sobol_state, angular);
vec3s u, v;
create_orthonormal_basis(normal, &u, &v);
vec3s term_u = glms_vec3_scale(u, local_dir.x);
vec3s term_v = glms_vec3_scale(v, local_dir.y);
vec3s term_w = glms_vec3_scale(normal, local_dir.z);
vec3s world_dir = glms_vec3_add(glms_vec3_add(term_u, term_v), term_w);
return world_dir;
}
// Samples a direction from the hemisphere oriented along 'normal'
// with a cosine-weighted distribution.
inline vec3s random_cosine_direction(vec3s normal, sobol_state_t* sobol_state)
{
// 1. Sample a direction in local coordinates (hemisphere oriented along +Z)
vec3s local_dir = sample_cosine_weighted_hemisphere_z(sobol_state);
// 2. Create an orthonormal basis aligned with the surface normal
vec3s u, v;
create_orthonormal_basis(normal, &u, &v);
// 3. Transform the local direction to world coordinates using the basis
// world_dir = u * local_dir.x + v * local_dir.y + w * local_dir.z
vec3s term_u = glms_vec3_scale(u, local_dir.x);
vec3s term_v = glms_vec3_scale(v, local_dir.y);
vec3s term_w = glms_vec3_scale(normal, local_dir.z);
vec3s world_dir = glms_vec3_add(glms_vec3_add(term_u, term_v), term_w);
// The result should be normalized due to the sampling method and basis transform.
// Is it necessary to be that safe?
// return glms_vec3_normalize(world_dir);
return world_dir;
} }
#endif // COMMON_H #endif // COMMON_H

18
header/Debug.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef DEBUG_H
#define DEBUG_H
#include "Rendering/Scene.h"
#include "Algorithm/RayIntersection.h"
typedef enum
{
DEBUG_NONE = 0,
DEBUG_BVH = 1,
DEBUG_SOBOL = 2,
} debug_flag_t;
static const vec4s DEBUG_COLOR_BVH = {0.0f, 1.0f, 0.0f, 0.005f}; // Green
vec4s render_debug(scene_t* scene, ray_t ray, uint16_t sample_index, int flag);
#endif // DEBUG_H

75
header/Geometry/AABB.h Normal file
View File

@@ -0,0 +1,75 @@
#ifndef AABB_H
#define AABB_H
#include "cglm/struct/vec3.h"
typedef struct
{
vec3s min;
vec3s max;
} aabb_t;
inline aabb_t invalid_aabb()
{
return (aabb_t){
.min = {FLT_MAX, FLT_MAX, FLT_MAX},
.max = {-FLT_MAX, -FLT_MAX, -FLT_MAX},
};
}
inline void aabb_growth(aabb_t* aabb, vec3s point)
{
aabb->min = glms_vec3_minv(aabb->min, point);
aabb->max = glms_vec3_maxv(aabb->max, point);
}
inline void aabb_growth_min_max(aabb_t* aabb, vec3s min, vec3s max)
{
aabb->min = glms_vec3_minv(aabb->min, min);
aabb->max = glms_vec3_maxv(aabb->max, max);
}
inline bool aabb_eq(aabb_t a, aabb_t b)
{
return glms_vec3_eqv(a.min, b.min) && glms_vec3_eqv(a.max, b.max);
}
inline bool aabb_is_valid(aabb_t aabb)
{
return aabb.max.x >= aabb.min.x && aabb.max.y >= aabb.min.y && aabb.max.z >= aabb.min.z;
}
inline aabb_t aabb_union(aabb_t a, aabb_t b)
{
if (!aabb_is_valid(a) && !aabb_is_valid(b))
{
return invalid_aabb();
}
else if (!aabb_is_valid(a))
{
return b;
}
else if (!aabb_is_valid(b))
{
return a;
}
aabb_t result;
result.min = glms_vec3_minv(a.min, b.min);
result.max = glms_vec3_maxv(a.max, b.max);
return result;
}
inline float aabb_surface_area(aabb_t aabb)
{
vec3s extent = glms_vec3_sub(aabb.max, aabb.min);
return 2.0f * (extent.x * extent.y + extent.x * extent.z + extent.y * extent.z);
}
inline float aabb_volume(aabb_t aabb)
{
vec3s extent = glms_vec3_sub(aabb.max, aabb.min);
return extent.x * extent.y * extent.z;
}
#endif // AABB_H

View File

@@ -25,8 +25,8 @@ inline void quad_create(vec3s center, vec3s forward, vec3s up, float size, uint8
inline void sphere_create(vec3s center, float radius, uint8_t material_id, triangle_collection_t* collection) inline void sphere_create(vec3s center, float radius, uint8_t material_id, triangle_collection_t* collection)
{ {
int segments = 8; int segments = 16;
int rings = 8; int rings = 16;
for (int i = 0; i < rings; i++) for (int i = 0; i < rings; i++)
{ {
@@ -59,13 +59,15 @@ inline void sphere_create(vec3s center, float radius, uint8_t material_id, trian
center.z + radius * sinf(theta1) * sinf(phi2) center.z + radius * sinf(theta1) * sinf(phi2)
}; };
vec3s n1 = glms_vec3_normalize(glms_vec3_sub(p1, center)); // vec3s n1 = glms_vec3_normalize(glms_vec3_sub(p1, center));
vec3s n2 = glms_vec3_normalize(glms_vec3_sub(p2, center)); // vec3s n2 = glms_vec3_normalize(glms_vec3_sub(p2, center));
vec3s n3 = glms_vec3_normalize(glms_vec3_sub(p3, center)); // vec3s n3 = glms_vec3_normalize(glms_vec3_sub(p3, center));
vec3s n4 = glms_vec3_normalize(glms_vec3_sub(p4, center)); // vec3s n4 = glms_vec3_normalize(glms_vec3_sub(p4, center));
triangle_create_with_normals(p3, p2, p1, n3, n2, n1, material_id, collection); // triangle_create_with_normals(p3, p2, p1, n3, n2, n1, material_id, collection);
triangle_create_with_normals(p3, p1, p4, n3, n1, n4, material_id, collection); // triangle_create_with_normals(p3, p1, p4, n3, n1, n4, material_id, collection);
triangle_create(p3, p2, p1, material_id, collection);
triangle_create(p3, p1, p4, material_id, collection);
} }
} }
} }

View File

@@ -6,8 +6,14 @@
#include <stdint.h> #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 onlt 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 typedef struct
{ {
mat4s local_to_world;
uint64_t id; uint64_t id;
uint64_t size; uint64_t size;
} mesh_entity_t; } mesh_entity_t;

View File

@@ -2,6 +2,7 @@
#define TRIANGLE_H #define TRIANGLE_H
#include "Common.h" #include "Common.h"
#include "Geometry/AABB.h"
#include <stdint.h> #include <stdint.h>
typedef struct typedef struct
@@ -26,55 +27,26 @@ typedef struct
triangle_t* buffer; triangle_t* buffer;
}triangle_collection_t; }triangle_collection_t;
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_with_normals(vec3s point1, vec3s point2, vec3s point3, void triangle_create_with_normals(vec3s point1, vec3s point2, vec3s point3,
vec3s normal1, vec3s normal2, vec3s normal3, vec3s normal1, vec3s normal2, vec3s normal3,
uint8_t material_id, triangle_collection_t* collection); uint8_t material_id, triangle_collection_t* collection);
void triangle_create(vec3s point1, vec3s point2, vec3s point3, uint8_t material_id, triangle_collection_t* collection); void triangle_create(vec3s point1, vec3s point2, vec3s point3, uint8_t material_id, triangle_collection_t* collection);
inline bool triangle_collection_init(size_t size, triangle_collection_t* triangles) inline vec3s triangle_centroid(const triangle_t triangle)
{ {
if (size > UINT64_MAX) return glms_vec3_scale(glms_vec3_add(glms_vec3_add(triangle.point_1, triangle.point_2), triangle.point_3), 1.0f / 3.0f);
{
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;
} }
inline void triangle_collection_resize(triangle_collection_t* collection, size_t size) inline aabb_t compute_bounds_triangle(triangle_t triangle)
{ {
if (size > UINT64_MAX) aabb_t bounds;
{ bounds.min = glms_vec3_minv(triangle.point_1, glms_vec3_minv(triangle.point_2, triangle.point_3));
size = UINT64_MAX; bounds.max = glms_vec3_maxv(triangle.point_1, glms_vec3_maxv(triangle.point_2, triangle.point_3));
} return bounds;
triangle_t* temp = (triangle_t*)realloc(collection->buffer, size * sizeof(triangle_t));
if (temp != NULL)
{
collection->buffer = temp;
collection->size = (uint64_t)size;
}
} }
inline void triangle_collection_free(triangle_collection_t* collection)
{
if (collection->buffer != NULL)
{
free(collection->buffer);
collection->buffer = NULL;
}
}
#endif // TRIANGLE_H #endif // TRIANGLE_H

View File

@@ -1,6 +1,7 @@
#ifndef LIGHT_H #ifndef LIGHT_H
#define LIGHT_H #define LIGHT_H
#include "Algorithm/BVH.h"
#include "Algorithm/Sobol.h" #include "Algorithm/Sobol.h"
#include "Geometry/Triangle.h" #include "Geometry/Triangle.h"
#include "Material/Material.h" #include "Material/Material.h"
@@ -11,13 +12,14 @@ typedef struct
vec3s normal; vec3s normal;
vec3s hit_point; vec3s hit_point;
vec3s wo; vec3s wo;
vec3s throughput;
const triangle_collection_t* triangles; uint16_t bounce_depth;
const bvh_tree_t* bvh_tree;
const material_t* material; const material_t* material;
} light_shading_context_t; } light_shading_context_t;
typedef vec3s (*evaluate_bsdf_sky_f) (const void* data, const light_shading_context_t* context, sobol_state_t* sobol_state); typedef vec3s (*evaluate_bsdf_sky_f) (const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t index);
typedef struct typedef struct
{ {

View File

@@ -6,14 +6,14 @@
#include "Geometry/Triangle.h" #include "Geometry/Triangle.h"
#include "Rendering/Scene.h" #include "Rendering/Scene.h"
vec3s evaluate_bsdf_directional( directional_light_t light, const light_shading_context_t* context, sobol_state_t* sobol_state); vec3s evaluate_bsdf_directional( directional_light_t light, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index);
inline vec3s evaluate_bsdf_sky(const scene_t* scene, const light_shading_context_t* context, sobol_state_t* sobol_state) inline vec3s evaluate_bsdf_sky(const scene_t* scene, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
{ {
vec3s ske_color = glms_vec3_zero(); vec3s ske_color = glms_vec3_zero();
if (scene->lights.sky_light.evaluate_bsdf_sky != NULL) if (scene->lights.sky_light.evaluate_bsdf_sky != NULL)
{ {
ske_color = scene->lights.sky_light.evaluate_bsdf_sky(scene->lights.sky_light.data, context, sobol_state); ske_color = scene->lights.sky_light.evaluate_bsdf_sky(scene->lights.sky_light.data, context, throughput, sample_index);
} }
return ske_color; return ske_color;

View File

@@ -6,9 +6,10 @@
typedef struct typedef struct
{ {
vec3s color; vec3s color;
float intensity;
} constant_sky_data_t; } constant_sky_data_t;
vec3s evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* context, sobol_state_t* sobol_state); vec3s evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index);
inline sky_light_t sky_create_constant_sky(const constant_sky_data_t* data) inline sky_light_t sky_create_constant_sky(const constant_sky_data_t* data)
{ {

View File

@@ -1,6 +1,7 @@
#ifndef MATERIAL_H #ifndef MATERIAL_H
#define MATERIAL_H #define MATERIAL_H
#include "Algorithm/Sobol.h"
#include "Common.h" #include "Common.h"
typedef struct typedef struct
@@ -11,7 +12,7 @@ typedef struct
vec3s wo; vec3s wo;
}shading_context_t; }shading_context_t;
typedef vec3s (*sample_bsdf_f)(const void* data, vec3s normal, vec3s wo, sobol_state_t* sobol_state, float* pdf_out); typedef vec3s (*sample_bsdf_f)(const void* data, vec3s normal, vec3s wo, uint32_t index, uint32_t bounce, float* pdf_out);
typedef float (*sample_bsdf_pdf_f)(const void* data, vec3s normal, vec3s wo, vec3s wi); typedef float (*sample_bsdf_pdf_f)(const void* data, vec3s normal, vec3s wo, vec3s wi);
typedef vec3s (*evaluate_bsdf_f)(const shading_context_t* context, const void* data); typedef vec3s (*evaluate_bsdf_f)(const shading_context_t* context, const void* data);
@@ -30,7 +31,6 @@ typedef struct
uint8_t id; uint8_t id;
} material_entity_t; } material_entity_t;
//TODO: Handle material remove, we can use something like sparse set. //TODO: Handle material remove, we can use something like sparse set.
typedef struct typedef struct
{ {
@@ -39,104 +39,14 @@ typedef struct
material_t* buffer; material_t* buffer;
} material_collection_t; } material_collection_t;
inline bool material_collection_init(size_t size, material_collection_t* materials) bool material_collection_init(size_t size, material_collection_t* materials);
{ void material_collection_resize(material_collection_t* materials, size_t size);
if (size > 254) void material_collection_free(material_collection_t* materials);
{
size = 254; // Limit the count to 254 to fit in a uint8_t
}
material_collection_t temp = {0}; material_entity_t material_create(sample_bsdf_f sample, sample_bsdf_pdf_f sample_pdf, evaluate_bsdf_f evaluate, void* data, material_collection_t* collection);
temp.buffer = (material_t*)malloc(size * sizeof(material_t));
if (temp.buffer == NULL)
{
return false;
}
temp.size = (uint8_t)size; vec3s sample_material_bsdf(const material_t* material, vec3s normal, vec3s wo, uint32_t sample_index, uint32_t bounce, float* pdf_out);
temp.count = 0; float sample_material_bsdf_pdf(const material_t* material, vec3s normal, vec3s wo, vec3s wi);
*materials = temp; vec3s evaluate_material_bsdf(const material_t* material, const shading_context_t* context);
return true;
}
inline void material_collection_resize(material_collection_t* materials, size_t size)
{
if (size > 254)
{
size = 254; // Limit the count to 254 to fit in a uint8_t
}
material_t* temp = (material_t*)realloc(materials->buffer, size * sizeof(material_t));
if (temp != NULL)
{
materials->buffer = temp;
materials->size = (uint8_t)size;
}
}
inline void material_collection_free(material_collection_t* materials)
{
if (materials->buffer != NULL)
{
free(materials->buffer);
materials->buffer = NULL;
}
}
inline material_entity_t material_create( sample_bsdf_f sample, sample_bsdf_pdf_f sample_pdf, evaluate_bsdf_f evaluate, void* data, material_collection_t* collection)
{
material_t material = {0};
if (collection->count >= collection->size)
{
material_collection_resize(collection, collection->size * 2);
}
material.sample_bsdf = sample;
material.sample_bsdf_pdf = sample_pdf;
material.evaluate_bsdf = evaluate;
material.data = data;
material_entity_t entity = {.id = collection->count};
collection->buffer[collection->count] = material;
collection->count++;
return entity;
}
inline vec3s sample_material_bsdf(const material_t* material, vec3s normal, vec3s wo, sobol_state_t* sobol_state, float* pdf_out)
{
vec3s wi = glms_vec3_zero();
if (material->sample_bsdf != NULL)
{
wi = material->sample_bsdf(material->data, normal, wo, sobol_state, pdf_out);
}
return wi;
}
inline float sample_material_bsdf_pdf(const material_t* material, vec3s normal, vec3s wo, vec3s wi)
{
float pdf = 0.0f;
if (material->sample_bsdf_pdf != NULL)
{
pdf = material->sample_bsdf_pdf(material->data, normal, wo, wi);
}
return pdf;
}
inline vec3s evaluate_material_bsdf(const material_t* material, const shading_context_t* context)
{
vec3s bsdf_color = glms_vec3_zero();
if (material->evaluate_bsdf != NULL)
{
bsdf_color = material->evaluate_bsdf(context, material->data);
}
return bsdf_color;
}
#endif // MATERIAL_H #endif // MATERIAL_H

View File

@@ -11,7 +11,7 @@ typedef struct
float metallic; float metallic;
}simple_lit_data_t; }simple_lit_data_t;
vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, sobol_state_t* sobol_state, float* pdf_out); vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, uint32_t sample_index, uint32_t bounce, float* pdf_out);
vec3s evaluate_bsdf_simple_lit(const shading_context_t* context, const void* data); vec3s evaluate_bsdf_simple_lit(const shading_context_t* context, const void* data);
float sample_bsdf_pdf_simple_lit(const void* data, vec3s normal, vec3s wo, vec3s wi); float sample_bsdf_pdf_simple_lit(const void* data, vec3s normal, vec3s wo, vec3s wi);

View File

@@ -6,6 +6,7 @@
typedef struct typedef struct
{ {
// TODO: Use mat4s instead of vec3s for position, forward, up and right
vec3s position; vec3s position;
vec3s forward; vec3s forward;
vec3s up; vec3s up;

View File

@@ -4,8 +4,17 @@
#include "cglm/struct/vec4.h" #include "cglm/struct/vec4.h"
#include "cglm/struct/mat4.h" #include "cglm/struct/mat4.h"
inline vec4s gamma_correct(vec4s color, float gamma)
{
vec4s corrected_color = color;
corrected_color.x = powf(color.x, 1.0f / gamma);
corrected_color.y = powf(color.y, 1.0f / gamma);
corrected_color.z = powf(color.z, 1.0f / gamma);
return corrected_color;
}
// Maybe full aces later // Maybe full aces later
inline vec4s aces_tone_map( vec4s color) inline vec4s aces_tone_map(vec4s color)
{ {
vec4s mapped_color = color; vec4s mapped_color = color;
float a = 2.51f; float a = 2.51f;

View File

@@ -1,6 +1,7 @@
#ifndef SCENE_H #ifndef SCENE_H
#define SCENE_H #define SCENE_H
#include "Algorithm/BVH.h"
#include "Camera.h" #include "Camera.h"
#include "Lighting/Light.h" #include "Lighting/Light.h"
#include "Material/Material.h" #include "Material/Material.h"
@@ -37,59 +38,18 @@ typedef struct
typedef struct typedef struct
{ {
camera_t camera; camera_t camera;
bvh_tree_t bvh_tree;
triangle_collection_t triangles; triangle_collection_t triangles;
material_collection_t materials; material_collection_t materials;
light_collection_t lights; light_collection_t lights;
} scene_t; } scene_t;
bool scene_render_tile(scene_t* scene, rendering_context_t* ctx, render_target_t* render_target,
rendering_config_t config, uint32_t tile_index, tile_t* tile_out);
bool scene_render(scene_t* scene, rendering_config_t confi, render_target_t* render_targetg);
inline bool scene_init(uint64_t triangle_count, uint8_t material_count, uint32_t punctual_light_count, scene_t* scene) bool scene_init(uint64_t triangle_count, uint8_t material_count, uint32_t punctual_light_count, scene_t* scene);
{ void scene_free(scene_t* scene);
scene_t temp = {0};
if (!triangle_collection_init(triangle_count, &temp.triangles)) bool scene_build_bvh(scene_t* scene);
{ bool scene_render_tile(scene_t* scene, rendering_context_t* ctx, rendering_config_t config, uint32_t tile_index, int rendering_flags, render_target_t* render_target, tile_t* tile_out);
goto triangle_failed; bool scene_render(scene_t* scene, rendering_config_t config, int rendering_flags, render_target_t* render_targetg);
}
if (!material_collection_init(material_count, &temp.materials))
{
goto material_failed;
}
if (!light_collection_create(punctual_light_count, 16, &temp.lights)) // NOTE: We just fixed the max directional light count to 16.
{
goto light_failed;
}
temp.camera = camera_create(
(vec3s){0.0f, 0.0f, 5.0f},
(vec3s){0.0f, 0.0f, -1.0f},
(vec3s){0.0f, 1.0f, 0.0f},
0.025f,
0.036f,
16.0f / 9.0f
);
*scene = temp;
return true;
light_failed:
material_collection_free(&temp.materials);
material_failed:
triangle_collection_free(&temp.triangles);
triangle_failed:
return false;
}
inline void scene_free(scene_t* scene)
{
triangle_collection_free(&scene->triangles);
material_collection_free(&scene->materials);
light_collection_free(&scene->lights);
}
#endif // SCENE_H #endif // SCENE_H

210
source/Algorithm/BSDF.c Normal file
View File

@@ -0,0 +1,210 @@
#include "ALgorithm/BSDF.h"
float power_heuristic(float pdf_a, float pdf_b)
{
float a2 = pdf_a * pdf_a;
float b2 = pdf_b * pdf_b;
return a2 / (a2 + b2);
}
float roughness_to_blinn_phong_specular_exponent(float roughness)
{
return glm_clamp(2.0f * 1.0f / (fmaxf(roughness * roughness, FLT_EPSILON)) - 2.0f, FLT_EPSILON, 1.0f / FLT_EPSILON);
}
vec3s fresnel_schlick_vec3(vec3s f0, float cos_theta)
{
float x = 1.0f - cos_theta;
float x5 = x * x * x * x * x;
return glms_vec3_adds(glms_vec3_scale(f0, (1.0f - x5)), x5);
}
// BSDF sampling functions
float pdf_cosine_weighted_hemisphere(vec3s normal, vec3s wi)
{
return fmaxf(glms_vec3_dot(wi, normal), 0.0f) / (float)M_PI;
}
float pdf_blinn_phong_lobe(vec3s normal, vec3s wi, vec3s wo, float roughness)
{
// Check if wo and wi are on the same side of the surface normal geometry
if (glms_vec3_dot(wo, normal) <= 0.0f || glms_vec3_dot(wi, normal) <= 0.0f)
{
return 0.0f; // Cannot scatter from below horizon to above, or vice versa
}
// Calculate the half-vector h based on input wo and wi
vec3s wo_n = glms_vec3_normalize(wo); // Ensure normalized inputs if not guaranteed
vec3s wi_n = glms_vec3_normalize(wi);
vec3s h = glms_vec3_add(wo_n, wi_n);
float h_len_sq = glms_vec3_norm2(h);
if (h_len_sq < FLT_EPSILON)
{
return 0.0f; // wo and wi are opposite, highly unlikely for reflection
}
h = glms_vec3_scale(h, 1.0f / sqrtf(h_len_sq)); // Normalize h
// Calculate Blinn-Phong specular exponent
float specular_exponent = roughness_to_blinn_phong_specular_exponent(roughness);
// PDF of sampling h (Blinn-Phong distribution)
// D(h) = (specular_exponent + 1) / (2 * PI) * pow(max(0, dot(n, h)), specular_exponent)
float n_dot_h = fmaxf(0.0f, glms_vec3_dot(normal, h));
float pdf_h = (specular_exponent + 1.0f) / (2.0f * (float)M_PI) * powf(n_dot_h, specular_exponent);
// Jacobian of the transformation from h to wi
// jacobian = 1 / (4 * dot(wo, h))
float wo_dot_h = fmaxf(FLT_EPSILON, glms_vec3_dot(wo_n, h)); // Use normalized wo, ensure > 0
float jacobian = 1.0f / (4.0f * wo_dot_h);
// PDF of sampling wi is pdf(h) * jacobian
float pdf_spec = pdf_h * jacobian;
return pdf_spec;
}
vec3s sample_cosine_weighted_hemisphere_z_angular(float angular, uint32_t index, uint32_t d1, uint32_t d2)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float phi = 2.0f * (float)M_PI * r1;
float cos_angular = cosf(angular);
// Correctly sample cos(theta) for cosine weighting within the cone [cos_angular, 1]
// cos_theta = sqrt(cos(angular)^2 + r2 * (1 - cos(angular)^2))
float cos_theta = sqrtf(cos_angular * cos_angular + r2 * (1.0f - cos_angular * cos_angular));
float sin_theta = sqrtf(1.0f - cos_theta * cos_theta);
// Convert spherical coordinates (1, theta, phi) to Cartesian (Z-up)
float x = sin_theta * cosf(phi);
float y = sin_theta * sinf(phi);
float z = cos_theta;
vec3s local_dir = {{x, y, z}};
return local_dir;
}
// Function to generate a direction with cosine weighting around (0, 0, 1)
// This is the local coordinate sample.
vec3s sample_cosine_weighted_hemisphere_z(uint32_t index, uint32_t d1, uint32_t d2)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float r = sqrtf(r1);
float phi = 2.0f * (float)M_PI * r2;
float disk_x = r * cosf(phi);
float disk_y = r * sinf(phi);
// Map point (disk_x, disk_y) on disk to hemisphere (Z-up)
float x = disk_x;
float y = disk_y;
float z = sqrtf(1.0f - disk_x * disk_x - disk_y * disk_y); // z = sqrt(1 - r*r) = sqrt(1 - r1)
vec3s local_dir = {{x, y, z}};
return local_dir;
}
// Function to create an orthonormal basis (coordinate system) from a single vector (normal)
// w will be aligned with normal, u and v will be perpendicular.
void create_orthonormal_basis(vec3s direction, vec3s* u, vec3s* v)
{
vec3s a;
if (fabsf(direction.x) > 0.9f)
{
a = (vec3s){{0.0f, 1.0f, 0.0f}}; // Use y-axis
}
else
{
a = (vec3s){{1.0f, 0.0f, 0.0f}}; // Use x-axis
}
*u = glms_vec3_normalize(glms_vec3_cross(a, direction));
*v = glms_vec3_normalize(glms_vec3_cross(direction, *u));
}
vec3s random_cosine_direction_angular(vec3s direction, float angular, uint32_t index, uint32_t d1, uint32_t d2)
{
vec3s local_dir = sample_cosine_weighted_hemisphere_z_angular(angular, index, d1, d2);
vec3s u, v;
create_orthonormal_basis(direction, &u, &v);
vec3s term_u = glms_vec3_scale(u, local_dir.x);
vec3s term_v = glms_vec3_scale(v, local_dir.y);
vec3s term_w = glms_vec3_scale(direction, local_dir.z);
vec3s world_dir = glms_vec3_add(glms_vec3_add(term_u, term_v), term_w);
return world_dir;
}
// Samples a direction from the hemisphere oriented along 'normal'
// with a cosine-weighted distribution.
vec3s random_cosine_direction(vec3s direction, uint32_t index, uint32_t d1, uint32_t d2)
{
vec3s local_dir = sample_cosine_weighted_hemisphere_z(index, d1, d2);
vec3s u, v;
create_orthonormal_basis(direction, &u, &v);
vec3s term_u = glms_vec3_scale(u, local_dir.x);
vec3s term_v = glms_vec3_scale(v, local_dir.y);
vec3s term_w = glms_vec3_scale(direction, local_dir.z);
vec3s world_dir = glms_vec3_add(glms_vec3_add(term_u, term_v), term_w);
return world_dir;
}
vec3s random_uniform_cdf_direction(vec3s direction, uint32_t index, uint32_t d1, uint32_t d2)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float phi = 2.0f * (float)M_PI * r1;
float cos_theta = 1.0f - r2 * 2.0f;
float sin_theta = sqrtf(fmaxf(0.0f, 1.0f - cos_theta * cos_theta));
float x = sin_theta * cosf(phi);
float y = sin_theta * sinf(phi);
float z = cos_theta;
vec3s u, v;
create_orthonormal_basis(direction, &u, &v);
vec3s term_u = glms_vec3_scale(u, x);
vec3s term_v = glms_vec3_scale(v, y);
vec3s term_w = glms_vec3_scale(direction, z);
vec3s world_dir = glms_vec3_add(glms_vec3_add(term_u, term_v), term_w);
return world_dir;
}
vec3s random_uniform_cdf_direction_angular(vec3s direction, uint32_t index, float angular, uint32_t d1, uint32_t d2)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float cos_alpha = cosf(angular);
float cos_theta = 1.0f - r1 * (1.0f - cos_alpha);
float sin_theta = sqrtf(fmaxf(0.0f, 1.0f - cos_theta * cos_theta));
float phi = 2.0f * (float)M_PI * r2;
float x = sin_theta * cosf(phi);
float y = sin_theta * sinf(phi);
float z = cos_theta;
vec3s u, v;
create_orthonormal_basis(direction, &u, &v);
vec3s term_u = glms_vec3_scale(u, x);
vec3s term_v = glms_vec3_scale(v, y);
vec3s term_w = glms_vec3_scale(direction, z);
vec3s world_dir = glms_vec3_add(glms_vec3_add(term_u, term_v), term_w);
return world_dir;
}

367
source/Algorithm/BVH.c Normal file
View File

@@ -0,0 +1,367 @@
#include "Algorithm/BVH.h"
#include "Geometry/AABB.h"
#include "Geometry/Triangle.h"
#include <math.h>
#include <stdbool.h>
typedef struct
{
aabb_t bounds;
uint32_t count;
} bin_t;
bool bvh_tree_init(bvh_tree_t* bvh_tree, const triangle_collection_t* triangles)
{
if (bvh_tree == NULL || triangles == NULL || triangles->count == 0)
{
return false;
}
bvh_tree_t temp = {0};
temp.triangles = triangles;
temp.primitive_count = triangles->count;
// Allocate primitive indices buffer
temp.primitive_indices = (uint64_t*)malloc(temp.primitive_count * sizeof(uint64_t));
if (!temp.primitive_indices)
{
return false;
}
// Initialize primitive indices
for (uint64_t i = 0; i < temp.primitive_count; ++i)
{
temp.primitive_indices[i] = i;
}
// Allocate nodes buffer (Estimate: 2n-1 nodes for a binary tree)
temp.node_capacity = temp.primitive_count * 2 - 1; // Start with a generous estimate
temp.nodes = (bvh_node_t*)malloc(temp.node_capacity * sizeof(bvh_node_t));
if (!temp.nodes)
{
free(temp.primitive_indices);
temp.primitive_indices = NULL;
return false;
}
temp.node_count = 0;
*bvh_tree = temp;
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)
{
if (count == 0)
{
return (aabb_t){0};
}
uint64_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)
{
triangle_index = primitive_indices[i];
aabb_growth(&bounds, triangles->buffer[triangle_index].point_1);
aabb_growth(&bounds, triangles->buffer[triangle_index].point_2);
aabb_growth(&bounds, triangles->buffer[triangle_index].point_3);
}
return bounds;
}
// 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)
{
uint64_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];
node->start_index = prim_start;
node->primitive_count = 0; // Only leaf nodes have primitive_count > 0
node->bounds = compute_primitives_aabb(triangles, primitive_indices, prim_start, prim_count);
const uint32_t LEAF_THRESHOLD = 4;
if (prim_count <= LEAF_THRESHOLD)
{
node->primitive_count = prim_count;
return node_index;
}
// --- Find the Best Bind ---
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;
float best_sah_cost = SAH_INTERSECTION_COST * prim_count; // TODO: Replace with actual SAH cost calculation
aabb_t current_aabb = node->bounds;
float parent_sah = aabb_surface_area(current_aabb);
if (parent_sah <= 0.0f)
{
// Split down the middle if the parent AABB is flat
if (prim_count <= 1)
{
goto force_leaf;
}
best_axis = 0;
best_split_index = prim_start + (prim_count / 2);
if (best_split_index == prim_start || best_split_index == prim_start + prim_count)
{
best_axis = -1; // Indicate no effective split if it results in empty child
}
goto perform_split_check;
}
// SAH binning evaluation
for (int axis = 0; axis < 3; axis++)
{
bin_t bins[SAH_BIN_COUNT];
for (int i = 0; i < SAH_BIN_COUNT; i++)
{
bins[i].count = 0;
bins[i].bounds = invalid_aabb();
}
float axis_min = current_aabb.min.raw[axis];
float axis_max = current_aabb.max.raw[axis];
float axis_extent = axis_max - axis_min;
float bin_size = axis_extent / SAH_BIN_COUNT;
if (bin_size <= 0.0f)
{
continue; // Skip flat aabb since they need to handle separately;
}
// Populate bins
for (uint64_t i = 0; i < prim_count; i++)
{
uint64_t triangle_index = primitive_indices[prim_start + i];
vec3s centroid = triangle_centroid(triangles->buffer[triangle_index]);
float position_on_axis = centroid.raw[axis];
int bin_index = (int)((position_on_axis - axis_min) / bin_size);
bin_index = (int)fminf((float)bin_index, (float)SAH_BIN_COUNT - 1.0f);
bin_index = (int)fmaxf((float)bin_index, 0.0f);
aabb_t triangle_aabb = compute_bounds_triangle(triangles->buffer[triangle_index]);;
bins[bin_index].bounds = aabb_union(bins[bin_index].bounds, triangle_aabb);
bins[bin_index].count++;
}
// Calculate prefix and suffix AABBs and counts.
aabb_t left_aabb[SAH_BIN_COUNT];
uint32_t left_count[SAH_BIN_COUNT] = {0};
aabb_t right_aabb[SAH_BIN_COUNT];
uint32_t right_count[SAH_BIN_COUNT] = {0};
aabb_t current_left_aabb = invalid_aabb();
uint32_t current_left_count = 0;
for (int i = 0; i < SAH_BIN_COUNT; i++)
{
if (bins[i].count > 0)
{
if (current_left_count == 0)
current_left_aabb = bins[i].bounds;
else
current_left_aabb = aabb_union(current_left_aabb, bins[i].bounds);
}
current_left_count += bins[i].count;
left_aabb[i] = current_left_aabb;
left_count[i] = current_left_count;
}
aabb_t current_right_aabb = invalid_aabb();
uint32_t current_right_count = 0;
for (int i = SAH_BIN_COUNT - 1; i >= 0; i--)
{
if (bins[i].count > 0)
{
if (current_right_count == 0)
current_right_aabb = bins[i].bounds; // First non-empty bin (from right)
else
current_right_aabb = aabb_union(current_right_aabb, bins[i].bounds);
}
current_right_count += bins[i].count;
right_aabb[i] = current_right_aabb;
right_count[i] = current_right_count;
}
// Evaluate splits between bins
for (int i = 0; i < SAH_BIN_COUNT - 1; i++)
{
uint32_t n_left = left_count[i];
uint32_t n_right = right_count[i + 1];
if (n_left == 0 || n_right == 0)
{
continue; // Skip empty bins
}
float sah_cost = SAH_TRAVERSAL_COST +
(aabb_surface_area(left_aabb[i]) / parent_sah) * SAH_INTERSECTION_COST * n_left +
(aabb_surface_area(right_aabb[i+1]) / parent_sah) * SAH_INTERSECTION_COST * n_right;
if (sah_cost < best_sah_cost)
{
best_sah_cost = sah_cost;
best_axis = axis;
best_split_bin_index = i;
// The split point in the primitive indices is after the first "left_count" primitives in the current range.
best_split_index = prim_start + n_left;
}
}
} //End axis loop
// --- 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
if (parent_sah == 0.0f && prim_count > 1) {
// Flat AABB case with more than one primitive: Force median split
perform_split = true;
// The target is mid-point, partition manually based on count
best_split_index = prim_start + prim_count / 2;
best_axis = 0; // Arbitrary axis for the partition loop if needed (though count is primary)
best_split_bin_index = -1; // Not applicable for this type of split
}
else if (best_axis != -1 && best_sah_cost < SAH_INTERSECTION_COST * prim_count)
{
// Non-flat AABB and SAH found a beneficial split
perform_split = true;
}
if (!perform_split)
{
goto force_leaf;
}
// --- Perform Split if beneficial ---
// Rearrange primitive_indices in the range [prim_start ... prim_start + prim_count - 1]
// The criterion depends on whether this was an SAH split or a flat AABB split.
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) {
// Swap if original index is "less than" threshold
if (primitive_indices[j] < threshold_original_index)
{
// Arbitrary criterion
uint64_t temp = primitive_indices[i];
primitive_indices[i] = primitive_indices[j];
primitive_indices[j] = temp;
i++;
}
}
actual_partition_index = i;
}
else
{
// SAH-based partition (uses bin index as criterion)
// The partition criterion is: centroid bin along best_axis <= best_split_bin_index
float split_axis_min = node->bounds.min.raw[best_axis];
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)
{
uint64_t original_tri_idx = primitive_indices[j];
vec3s centroid = triangle_centroid(triangles->buffer[original_tri_idx]);
float pos_on_axis = ((float*)&centroid)[best_axis];
int bin_idx = (int)((pos_on_axis - split_axis_min) / split_bin_size);
bin_idx = (int)fminf((float)bin_idx, (float)SAH_BIN_COUNT - 1.0f);
bin_idx = (int)fmaxf((float)bin_idx, 0.0f);
// 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];
primitive_indices[i] = primitive_indices[j];
primitive_indices[j] = temp;
i++; // Move the left partition boundary forward
}
}
// After this partition loop, 'i' is the index of the first element that belongs
// to the right side's partition. This is the actual partition point.
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;
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);
node->left_child_offset = left_child_idx;
node->right_child_offset = right_child_idx;
return node_index;
force_leaf:
node->primitive_count = prim_count;
return node_index; // Return the index of this leaf node
}
bool bvh_tree_build(bvh_tree_t* bvh_tree)
{
if (bvh_tree ==NULL)
{
return false;
}
uint64_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,
bvh_tree->primitive_indices, bvh_tree->triangles,
0, triangle_count);
bvh_tree->node_count = next_node_index;
if (bvh_tree->node_count < bvh_tree->node_capacity)
{
bvh_node_t* temp_nodes = realloc(bvh_tree->nodes, bvh_tree->node_count * sizeof(bvh_node_t));
if (temp_nodes != NULL)
{
bvh_tree->nodes = temp_nodes;
bvh_tree->node_capacity = bvh_tree->node_count;
}
}
return true;
}
void bvh_tree_free(bvh_tree_t* bvh_tree)
{
if (bvh_tree == NULL)
{
return;
}
free(bvh_tree->nodes);
free(bvh_tree->primitive_indices);
bvh_tree->nodes = NULL;
bvh_tree->primitive_indices = NULL;
}

View File

@@ -1,33 +1,32 @@
#include "Algorithm/PathTracing.h" #include "Algorithm/PathTracing.h"
#include "Algorithm/RayIntersection.h" #include "Algorithm/RayIntersection.h"
#include "Algorithm/BSDF.h" #include "Algorithm/BSDF.h"
#include "Algorithm/Sobol.h"
#include "Lighting/LightEvaluation.h" #include "Lighting/LightEvaluation.h"
// TODO: Implement a faster methods like BVH, KD-Tree or uniform grid acceleration // TODO: Implement a faster methods like BVH, KD-Tree or uniform grid acceleration
// TODO: Split the diffuse and specular into different Monte Carlo, so we can decide the sample count for each one // TODO: Split the diffuse and specular into different Monte Carlo, so we can decide the sample count for each one
vec3s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, int max_depth) vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_t max_depth)
{ {
const triangle_collection_t* triangles = &scene->triangles; const triangle_collection_t* triangles = &scene->triangles;
const bvh_tree_t* bvh_tree = &scene->bvh_tree;
const material_collection_t* materials = &scene->materials; const material_collection_t* materials = &scene->materials;
const light_collection_t* lights = &scene->lights; const light_collection_t* lights = &scene->lights;
vec3s accumulated_color = glms_vec3_zero(); vec4s accumulated_color = glms_vec4_zero();
vec3s throughput = glms_vec3_one(); vec3s throughput = glms_vec3_one();
ray_t active_ray = ray; ray_t active_ray = ray;
vec3s prev_normal = glms_vec3_zero(); vec3s prev_normal = glms_vec3_zero();
float pdf_bsdf = 1.0f; // Even though pdf_bsdf should be avaliable after the first bounce. For seafty, we set it to 1.0f for the first iteration. float pdf_bsdf = 1.0f;
sobol_state_t sobol_state = {sample_index, 0};
int depth = 0; uint16_t depth = 0;
while (depth < max_depth) while (depth < max_depth)
{ {
hit_result_t closest_hit = ray_intersect_closest(triangles, active_ray); hit_result_t closest_hit = ray_intersect_scene(active_ray, scene);
if (!closest_hit.hit) if (!closest_hit.hit)
{ {
vec3s sky_light = evaluate_bsdf_sky(scene, NULL, &sobol_state); vec3s sky_light = evaluate_bsdf_sky(scene, NULL, throughput, sample_index);
if (depth > 0) if (depth > 0)
{ {
// Have to multiply the weight since we evaluate the sky at each bounce // Have to multiply the weight since we evaluate the sky at each bounce
@@ -35,21 +34,23 @@ vec3s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, int
float weight = power_heuristic(pdf_bsdf, pdf_nee); float weight = power_heuristic(pdf_bsdf, pdf_nee);
sky_light = glms_vec3_scale(sky_light, weight); sky_light = glms_vec3_scale(sky_light, weight);
} }
accumulated_color = glms_vec3_add(accumulated_color, sky_light); // TODO: Skybox accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(sky_light, 1.0f)); // TODO: Physical Skybox
break; break;
} }
// Add the emission of the hit material to the accumulated color // Add the emission of the hit material to the accumulated color
material_t* hit_material = &materials->buffer[triangles->buffer[closest_hit.triangle_id].material_id]; material_t* hit_material = &materials->buffer[triangles->buffer[closest_hit.triangle_id].material_id];
vec3s emission = hit_material->emission; vec3s emission = hit_material->emission;
accumulated_color = glms_vec3_add(accumulated_color, glms_vec3_mul(throughput, emission)); accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(glms_vec3_mul(throughput, emission), 1.0f));
light_shading_context_t light_context = { light_shading_context_t light_context = {
.normal = closest_hit.normal, .normal = closest_hit.normal,
.hit_point = closest_hit.point, .hit_point = closest_hit.point,
.wo = active_ray.direction, .wo = active_ray.direction,
.throughput = throughput,
.triangles = triangles, .bounce_depth = depth,
.bvh_tree = bvh_tree,
.material = hit_material .material = hit_material
}; };
@@ -57,16 +58,16 @@ vec3s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, int
// TODO: Implementing other light types. // TODO: Implementing other light types.
for (uint32_t i = 0; i < lights->directional_light_count; i++) for (uint32_t i = 0; i < lights->directional_light_count; i++)
{ {
vec3s l = evaluate_bsdf_directional(lights->directional_lights[i], &light_context, &sobol_state); vec3s l = evaluate_bsdf_directional(lights->directional_lights[i], &light_context, throughput, sample_index);
accumulated_color = glms_vec3_add(accumulated_color, l); accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(l, 1.0f));
} }
vec3s sky_light = evaluate_bsdf_sky(scene, &light_context, &sobol_state); vec3s sky_light = evaluate_bsdf_sky(scene, &light_context, throughput, sample_index);
accumulated_color = glms_vec3_add(accumulated_color, sky_light); accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(sky_light, 1.0f));
// Bounce and prepare for the next iteration // Bounce and prepare for the next iteration
vec3s wo = glms_vec3_negate(active_ray.direction); // We need to negate the direction of the incoming ray vec3s wo = glms_vec3_negate(active_ray.direction); // We need to negate the direction of the incoming ray
vec3s wi = sample_material_bsdf(hit_material, closest_hit.normal, wo, &sobol_state, &pdf_bsdf); vec3s wi = sample_material_bsdf(hit_material, closest_hit.normal, wo, sample_index, depth, &pdf_bsdf);
if (pdf_bsdf <= 0.0f) if (pdf_bsdf <= 0.0f)
{ {
break; break;
@@ -86,7 +87,9 @@ vec3s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, int
if (depth > 2) if (depth > 2)
{ {
float q = fminf(glms_vec3_max(throughput), 0.95f); float q = fminf(glms_vec3_max(throughput), 0.95f);
if (random_float() > q) float rr_sample = sobol_sample(sample_index, sobol_get_dimension(depth, PRNG_TERMINATE));
// float rr_sample = random_float();
if (rr_sample > q)
{ {
break; // Terminate the path break; // Terminate the path
} }
@@ -94,7 +97,7 @@ vec3s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, int
throughput = glms_vec3_scale(throughput, 1.0f / q); throughput = glms_vec3_scale(throughput, 1.0f / q);
} }
active_ray.origin = glms_vec3_add(closest_hit.point, glms_vec3_scale(closest_hit.normal, FLT_EPSILON)); active_ray.origin = glms_vec3_add(closest_hit.point, glms_vec3_scale(closest_hit.normal, 1.192092896e-04F));
active_ray.direction = wi; active_ray.direction = wi;
prev_normal = closest_hit.normal; prev_normal = closest_hit.normal;

View File

@@ -1,6 +1,7 @@
#include "algorithm/RayIntersection.h" #include "algorithm/RayIntersection.h"
#include "Geometry/Triangle.h"
hit_result_t ray_intersect( triangle_t triangle, ray_t ray) hit_result_t ray_intersect_triangle(ray_t ray, triangle_t triangle)
{ {
hit_result_t result = {0}; hit_result_t result = {0};
@@ -80,35 +81,201 @@ hit_result_t ray_intersect( triangle_t triangle, ray_t ray)
return result; return result;
} }
hit_result_t ray_intersect_closest(const triangle_collection_t* triangles, ray_t ray) static hit_result_t intersect_triangle(ray_t ray, triangle_t tri)
{ {
hit_result_t closest_hit = {0}; hit_result_t result = {0};
closest_hit.distance = FLT_MAX;
for (uint64_t i = 0; i < triangles->count; i++) vec3s edge1 = glms_vec3_sub(tri.point_2, tri.point_1);
vec3s edge2 = glms_vec3_sub(tri.point_3, tri.point_1);
vec3s h = glms_vec3_cross(ray.direction, edge2);
float a = glms_vec3_dot(edge1, h);
if (a > -FLT_EPSILON && a < FLT_EPSILON)
{ {
hit_result_t hit_result = ray_intersect(triangles->buffer[i], ray); return result;
if (hit_result.hit && hit_result.distance < closest_hit.distance)
{
closest_hit = hit_result;
closest_hit.triangle_id = i;
}
} }
return closest_hit; float f = 1.0f / a;
} vec3s s = glms_vec3_sub(ray.origin, tri.point_1);
float u = f * glms_vec3_dot(s, h);
hit_result_t ray_intersect_any(const triangle_collection_t* triangles, ray_t ray) if (u < 0.0f || u > 1.0f)
{
for (uint64_t i = 0; i < triangles->count; i++)
{ {
hit_result_t hit_result = ray_intersect(triangles->buffer[i], ray); return result; // Hit outside the triangle's u range.
if (hit_result.hit)
{
hit_result.triangle_id = i;
return hit_result;
}
} }
return (hit_result_t){0}; vec3s q = glms_vec3_cross(s, edge1);
float v = f * glms_vec3_dot(ray.direction, q);
if (v < 0.0f || u + v > 1.0f)
{
return result; // Hit outside the triangle's v range.
}
// At this stage we can compute t to find out where the intersection point is on the line.
float t = f * glms_vec3_dot(edge2, q);
float esp = fmaxf(glms_vec3_max(glms_vec3_abs(edge1)), glms_vec3_max(glms_vec3_abs(edge2))) * 1.192092896e-04F;
if (t > 1.192092896e-04F)
{
result.hit = true;
result.distance = t;
result.point = glms_vec3_add(ray.origin, glms_vec3_scale(ray.direction, t));
result.normal = glms_vec3_cross(edge1, edge2);
if (glms_vec3_dot(result.normal, ray.direction) > 0)
{
result.normal = glms_vec3_scale(result.normal, -1.0f);
}
result.normal = glms_vec3_normalize(result.normal);
return result;
}
return result;
}
bool ray_intersect_aabb(ray_t ray, aabb_t aabb, float* enter_out, float* exit_out)
{
//vec3s inv_dir = glms_vec3_div(glms_vec3_one(), ray.direction);
vec3s inv_dir;
inv_dir.x = ray.direction.x != 0.0f ? 1.0f / ray.direction.x : FLT_MAX;
inv_dir.y = ray.direction.y != 0.0f ? 1.0f / ray.direction.y : FLT_MAX;
inv_dir.z = ray.direction.z != 0.0f ? 1.0f / ray.direction.z : FLT_MAX;
float t_near = -FLT_MIN;
float t_far = FLT_MAX;
// X axis
{
float t1 = (aabb.min.x - ray.origin.x) * inv_dir.x;
float t2 = (aabb.max.x - ray.origin.x) * inv_dir.x;
float tmin = fminf(t1, t2);
float tmax = fmaxf(t1, t2);
t_near = fmaxf(t_near, tmin);
t_far = fminf(t_far, tmax);
}
// Y axis
{
float t1 = (aabb.min.y - ray.origin.y) * inv_dir.y;
float t2 = (aabb.max.y - ray.origin.y) * inv_dir.y;
float tmin = fminf(t1, t2);
float tmax = fmaxf(t1, t2);
t_near = fmaxf(t_near, tmin);
t_far = fminf(t_far, tmax);
}
// Z axis
{
float t1 = (aabb.min.z - ray.origin.z) * inv_dir.z;
float t2 = (aabb.max.z - ray.origin.z) * inv_dir.z;
float tmin = fminf(t1, t2);
float tmax = fmaxf(t1, t2);
t_near = fmaxf(t_near, tmin);
t_far = fminf(t_far, tmax);
}
if (t_near > t_far || t_far < 0.0f)
{
return false;
}
if (enter_out) *enter_out = t_near;
if (exit_out) *exit_out = t_far;
return true;
}
static inline float distance_to_aabb(vec3s point, aabb_t aabb)
{
float dx = fmaxf(aabb.min.x - point.x, 0.0f) + fmaxf(point.x - aabb.max.x, 0.0f);
float dy = fmaxf(aabb.min.y - point.y, 0.0f) + fmaxf(point.y - aabb.max.y, 0.0f);
float dz = fmaxf(aabb.min.z - point.z, 0.0f) + fmaxf(point.z - aabb.max.z, 0.0f);
return sqrtf(dx * dx + dy * dy + dz * dz);
}
// TODO: Use a stack to avoid recursion.
void ray_intersect_bvh(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,
float* closest_out, hit_result_t* best_hit_out)
{
const bvh_node_t* node = &bvh_nodes[node_index];
float enter, exit;
if (!ray_intersect_aabb(ray, node->bounds, &enter, &exit)) {
return;
}
if (enter > *closest_out) {
return;
}
// If primitive_count > 0 implies leaf:
if (node->primitive_count > 0)
{
for (uint32_t i = 0; i < node->primitive_count; i++)
{
uint64_t triangle_index = primitive_indices[node->start_index + i];
hit_result_t hit_result = intersect_triangle(ray, all_triangles->buffer[triangle_index]);
if (hit_result.hit && hit_result.distance < *closest_out )
{
hit_result.triangle_id = triangle_index;
*best_hit_out = hit_result;
*closest_out = hit_result.distance;
}
}
}
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;
const bvh_node_t* left_child = &bvh_nodes[left_child_index];
const bvh_node_t* right_child = &bvh_nodes[right_child_index];
float left_enter, left_exit, right_enter, right_exit;
bool hit_left_aabb = ray_intersect_aabb(ray, left_child->bounds, &left_enter, &left_exit);
bool hit_right_aabb = ray_intersect_aabb(ray, right_child->bounds, &right_enter, &right_exit);
// Traverse children based on closest AABB and current best hit distance (*closest_t)
if (hit_left_aabb && hit_right_aabb)
{
if (left_enter < right_enter)
{
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->left_child_offset, closest_out, best_hit_out);
if (right_enter < *closest_out)
{
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->right_child_offset, closest_out, best_hit_out);
}
}
else
{
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->right_child_offset, closest_out, best_hit_out);
if (left_enter < *closest_out)
{
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->left_child_offset, closest_out, best_hit_out);
}
}
}
else if (hit_left_aabb)
{
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->left_child_offset, closest_out, best_hit_out);
}
else if (hit_right_aabb)
{
ray_intersect_bvh(ray, bvh_nodes, primitive_indices, all_triangles, node->right_child_offset, closest_out, best_hit_out);
}
}
}
hit_result_t ray_intersect_scene(ray_t ray, const scene_t* scene)
{
hit_result_t result = {0};
float closest = FLT_MAX;
if (scene == NULL || 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(ray, scene->bvh_tree.nodes, scene->bvh_tree.primitive_indices, &scene->triangles, 0, &closest, &result);
return result;
} }

View File

@@ -1,5 +1,10 @@
#include "Algorithm/Sobol.h" #include "Algorithm/Sobol.h"
static inline int sobol_get_bit(uint32_t index, uint32_t bit)
{
return (index >> bit) & 1;
}
//NOTE: Maybe precompute the vector table? //NOTE: Maybe precompute the vector table?
void sobol_init() void sobol_init()
{ {
@@ -9,17 +14,15 @@ void sobol_init()
sobol_direction_vectors[0][i] = 1U << (31 - i); sobol_direction_vectors[0][i] = 1U << (31 - i);
} }
// Dimensions 2 to 6
for (int d = 1; d < SOBOL_DIMENSIONS; d++) for (int d = 1; d < SOBOL_DIMENSIONS; d++)
{ {
int s = s_vals[d - 1]; int s = s_vals[d - 1];
int a = a_vals[d - 1]; int a = a_vals[d - 1];
const int* m = m_vals[d - 1];
// Set initial direction numbers // Set initial direction numbers
for (int i = 0; i < s; i++) for (int i = 0; i < s; i++)
{ {
sobol_direction_vectors[d][i] = (uint32_t)m[i] << (31 - i); sobol_direction_vectors[d][i] = m_vals[d - 1][i] << (31 - i);
} }
// Compute remaining direction numbers // Compute remaining direction numbers
@@ -39,9 +42,9 @@ void sobol_init()
} }
} }
static inline uint32_t sobol_get_bit(uint32_t index, uint32_t bit) uint16_t sobol_get_dimension(uint16_t bounce, sampling_dimension_t operation_type)
{ {
return (index >> bit) & 1; return PRNG_BASE_NUM + bounce * PRNG_BOUNCE_NUM + operation_type;
} }
static inline float sobol_uint_to_float(uint32_t x) static inline float sobol_uint_to_float(uint32_t x)
@@ -51,6 +54,7 @@ static inline float sobol_uint_to_float(uint32_t x)
float sobol_sample(uint32_t index, uint32_t dimension) float sobol_sample(uint32_t index, uint32_t dimension)
{ {
// return random_float();
if (dimension >= SOBOL_DIMENSIONS) if (dimension >= SOBOL_DIMENSIONS)
{ {
return 0.0f; // Invalid dimension return 0.0f; // Invalid dimension
@@ -67,23 +71,3 @@ float sobol_sample(uint32_t index, uint32_t dimension)
return sobol_uint_to_float(result); return sobol_uint_to_float(result);
} }
float sobol_next(sobol_state_t* state)
{
uint32_t index = state->index;
uint32_t dimension = state->dimension;
uint32_t result = 0;
for (int i = 0; i < SOBOL_BITS; i++)
{
if (sobol_get_bit(index, i))
{
result ^= sobol_direction_vectors[dimension][i];
}
}
// Increment the index for the next call
state->dimension = (state->dimension + 1) % SOBOL_DIMENSIONS;
return sobol_uint_to_float(result);
}

65
source/Debug.c Normal file
View File

@@ -0,0 +1,65 @@
#include "Debug.h"
#include "Algorithm/RayIntersection.h"
void ray_intersect_bvh_count(ray_t ray, bvh_tree_t bvh_tree, uint64_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)
{
return;
}
const bvh_node_t* node = &bvh_tree.nodes[node_index];
float enter, exit;
if (!ray_intersect_aabb(ray, node->bounds, &enter, &exit))
{
return;
}
if (enter > _MAX_DIST || exit < 0.0f)
{
return;
}
(*count_out)++;
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);
}
}
vec4s render_debug(scene_t* scene, ray_t ray, uint16_t sample_index, int flag)
{
vec4s result = glms_vec4_zero();
if (scene == NULL)
{
return result;
}
switch (flag & 0xFF)
{
case DEBUG_BVH:
uint32_t count = 0;
ray_intersect_bvh_count(ray, scene->bvh_tree, 0, &count);
for (uint32_t i = 0; i < count; i++)
{
result = glms_vec4_add(result, DEBUG_COLOR_BVH);
}
break;
case DEBUG_SOBOL:
uint16_t i = sample_index ^ (sample_index >> 1);
float sobol_sample_value = sobol_sample(i, 1); // Assuming dimension 0 for simplicity
result = glms_vec4_add(result, (vec4s){sobol_sample_value, sobol_sample_value, sobol_sample_value, 1.0f});
break;
default:
break;
}
return result;
}

View File

@@ -1,5 +1,50 @@
#include "Geometry/Triangle.h" #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_with_normals(vec3s point1, vec3s point2, vec3s point3, void triangle_create_with_normals(vec3s point1, vec3s point2, vec3s point3,
vec3s normal1, vec3s normal2, vec3s normal3, vec3s normal1, vec3s normal2, vec3s normal3,
uint8_t material_id, triangle_collection_t* collection) uint8_t material_id, triangle_collection_t* collection)
@@ -34,4 +79,3 @@ void triangle_create(vec3s point1, vec3s point2, vec3s point3, uint8_t material_
vec3s normal = glms_vec3_normalize(glms_vec3_cross(edge1, edge2)); vec3s normal = glms_vec3_normalize(glms_vec3_cross(edge1, edge2));
triangle_create_with_normals(point1, point2, point3, normal, normal, normal, material_id, collection); triangle_create_with_normals(point1, point2, point3, normal, normal, normal, material_id, collection);
} }

View File

@@ -1,25 +1,32 @@
#include "Lighting/LightEvaluation.h" #include "Lighting/LightEvaluation.h"
#include "Algorithm/BSDF.h"
#include "Algorithm/RayIntersection.h" #include "Algorithm/RayIntersection.h"
#include "Algorithm/Sobol.h"
vec3s evaluate_bsdf_directional( directional_light_t light, const light_shading_context_t* context, sobol_state_t* sobol_state) vec3s evaluate_bsdf_directional(directional_light_t light, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
{ {
float angular_radius = glm_rad(light.angular_diameter / 2.0f); float angular_radius = glm_rad(light.angular_diameter / 2.0f);
vec3s wi = random_cosine_direction_angular(light.direction, angular_radius, sobol_state);
ray_t shadow_ray = { uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
.origin = glms_vec3_add(context->hit_point, glms_vec3_scale(context->normal, 0.001f)), uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
.direction = wi,
};
hit_result_t shadow_hit = ray_intersect_any(context->triangles, shadow_ray); vec3s wi = random_uniform_cdf_direction_angular(light.direction, sample_index, angular_radius, d1, d2);
if (shadow_hit.hit)
float n_dot_l = glms_vec3_dot(context->normal, wi);
if (n_dot_l <= 0.0f)
{ {
return glms_vec3_zero(); return glms_vec3_zero();
} }
float n_dot_l = glms_vec3_dot(context->normal, light.direction); ray_t shadow_ray = {
if (n_dot_l <= 0.0f) .origin = glms_vec3_add(context->hit_point, glms_vec3_scale(context->normal, 1.192092896e-04F)),
.direction = wi,
};
float closest = FLT_MAX;
hit_result_t shadow_hit = {1};
ray_intersect_bvh(shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0,
&closest, &shadow_hit);
if (shadow_hit.hit)
{ {
return glms_vec3_zero(); return glms_vec3_zero();
} }
@@ -31,10 +38,7 @@ vec3s evaluate_bsdf_directional( directional_light_t light, const light_shading_
}; };
vec3s bsdf = evaluate_material_bsdf(context->material, &shading_context); vec3s bsdf = evaluate_material_bsdf(context->material, &shading_context);
vec3s light_irradiance = glms_vec3_scale(light.color, light.intensity); vec3s light_radiance = glms_vec3_scale(light.color, light.intensity);
float cos_theta = fmaxf(n_dot_l, 0.0f); vec3s light_contribute = glms_vec3_scale(glms_vec3_mul(throughput, bsdf), fmaxf(0.0f, n_dot_l));
vec3s irradiance_contrib = glms_vec3_scale(light_irradiance, cos_theta); return glms_vec3_mul(light_radiance, light_contribute);
vec3s light_contrib = glms_vec3_scale(glms_vec3_mul(context->throughput, bsdf), cos_theta); // glms_vec3_scale(glms_vec3_mul(throughput, bsdf), cos_theta / 1.0f); pdf for directional light is 1.0f, we can ignore it.
return glms_vec3_mul(irradiance_contrib, light_contrib);
} }

View File

@@ -3,27 +3,34 @@
#include "Algorithm/RayIntersection.h" #include "Algorithm/RayIntersection.h"
#include "Material/Material.h" #include "Material/Material.h"
vec3s evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* context, sobol_state_t* sobol_state) vec3s evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
{ {
constant_sky_data_t sky_data = *(const constant_sky_data_t*)data; constant_sky_data_t sky_data = *(const constant_sky_data_t*)data;
vec3s sky_color = glms_vec3_scale(sky_data.color, sky_data.intensity);
if (context == NULL) if (context == NULL)
{ {
return sky_data.color; // No context, return the sky color directly. return glms_vec3_mul(sky_color, throughput);
// return sky_data.color;
} }
vec3s wi = random_cosine_direction(context->normal, sobol_state); uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
float pdf = pdf_cosine_weighted_hemisphere(context->normal, wi); uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
vec3s wi = random_uniform_cdf_direction(context->normal, sample_index, d1, d2);
float pdf = 1.0f / (4.0f * (float)M_PI);
ray_t shadow_ray = { ray_t shadow_ray = {
.origin = glms_vec3_add(context->hit_point, glms_vec3_scale(context->normal, 0.001f)), .origin = glms_vec3_add(context->hit_point, glms_vec3_scale(context->normal, 1.192092896e-04F)),
.direction = wi, .direction = wi,
}; };
hit_result_t shadow_hit = ray_intersect_any(context->triangles, shadow_ray); float closest = FLT_MAX;
hit_result_t shadow_hit = {1};
ray_intersect_bvh(shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &closest, &shadow_hit);
if (shadow_hit.hit) if (shadow_hit.hit)
{ {
return glms_vec3_zero(); // Skip if the ray hits something return glms_vec3_zero();
} }
shading_context_t shading_context = { shading_context_t shading_context = {
@@ -33,12 +40,12 @@ vec3s evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* c
}; };
vec3s bsdf = evaluate_material_bsdf(context->material, &shading_context); vec3s bsdf = evaluate_material_bsdf(context->material, &shading_context);
bsdf = glms_vec3_mul(bsdf, sky_data.color); bsdf = glms_vec3_mul(bsdf, sky_color);
float cos_theta = fmaxf(glms_vec3_dot(wi, context->normal), 0.0f); float cos_theta = fmaxf(glms_vec3_dot(wi, context->normal), 0.0f);
float pdf_bsdf = sample_material_bsdf_pdf(context->material, shading_context.normal, shading_context.wo, shading_context.wi); float pdf_bsdf = sample_material_bsdf_pdf(context->material, shading_context.normal, shading_context.wo, shading_context.wi);
float weight = power_heuristic(pdf, pdf_bsdf); float weight = power_heuristic(pdf, pdf_bsdf);
vec3s env_contrib = glms_vec3_scale(glms_vec3_mul(context->throughput, bsdf), cos_theta / pdf); vec3s env_contrib = glms_vec3_scale(glms_vec3_mul(throughput, bsdf), cos_theta / pdf);
return glms_vec3_scale(env_contrib, weight); return glms_vec3_scale(env_contrib, weight);
} }

101
source/Material/Material.c Normal file
View File

@@ -0,0 +1,101 @@
#include "Material/Material.h"
bool material_collection_init(size_t size, material_collection_t* materials)
{
if (size > 254)
{
size = 254; // Limit the count to 254 to fit in a uint8_t
}
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 > 254)
{
size = 254; // Limit the count to 254 to fit in a uint8_t
}
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)
{
free(materials->buffer);
materials->buffer = NULL;
}
}
material_entity_t material_create(sample_bsdf_f sample, sample_bsdf_pdf_f sample_pdf, evaluate_bsdf_f evaluate, void* data, material_collection_t* collection)
{
material_t material = {0};
if (collection->count >= collection->size)
{
material_collection_resize(collection, collection->size * 2);
}
material.sample_bsdf = sample;
material.sample_bsdf_pdf = sample_pdf;
material.evaluate_bsdf = evaluate;
material.data = data;
material_entity_t entity = {.id = collection->count};
collection->buffer[collection->count] = material;
collection->count++;
return entity;
}
vec3s sample_material_bsdf(const material_t* material, vec3s normal, vec3s wo, uint32_t sample_index, uint32_t bounce, float* pdf_out)
{
vec3s wi = glms_vec3_zero();
if (material->sample_bsdf != NULL)
{
wi = material->sample_bsdf(material->data, normal, wo, sample_index, bounce, pdf_out);
}
return wi;
}
float sample_material_bsdf_pdf(const material_t* material, vec3s normal, vec3s wo, vec3s wi)
{
float pdf = 0.0f;
if (material->sample_bsdf_pdf != NULL)
{
pdf = material->sample_bsdf_pdf(material->data, normal, wo, wi);
}
return pdf;
}
vec3s evaluate_material_bsdf(const material_t* material, const shading_context_t* context)
{
vec3s bsdf_color = glms_vec3_zero();
if (material->evaluate_bsdf != NULL)
{
bsdf_color = material->evaluate_bsdf(context, material->data);
}
return bsdf_color;
}

View File

@@ -1,22 +1,24 @@
#include "Material/SimpleLit.h" #include "Material/SimpleLit.h"
#include "Algorithm/BSDF.h" #include "Algorithm/BSDF.h"
#include "Algorithm/Sobol.h"
#include <float.h> #include <float.h>
static float DIELECTRIC_REFLECTIVE_F0 = 0.04f; // Standard dielectric reflectivity coef at incident angle (= 4%) static float DIELECTRIC_REFLECTIVE_F0 = 0.04f; // Standard dielectric reflectivity coef at incident angle (= 4%)
static vec3s DIELECTRIC_REFLECTIVE = {0.04f, 0.04f, 0.04f}; // Standard dielectric reflectivity coef at incident angle (= 4%) static vec3s DIELECTRIC_REFLECTIVE = {0.04f, 0.04f, 0.04f}; // Standard dielectric reflectivity coef at incident angle (= 4%)
// Simple lit, but keep it unbiased as much as possible // Simple lit, but keep it unbiased as much as possible
vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, sobol_state_t* sobol_state, float* pdf_out) vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, uint32_t sample_index, uint32_t bounce, float* pdf_out)
{ {
simple_lit_data_t shading_data = *(const simple_lit_data_t*)data; simple_lit_data_t shading_data = *(const simple_lit_data_t*)data;
//TODO: having a bsdf data struct to avoid recomputing the same thing in both sample and evaluate //TODO: having a bsdf data struct to avoid recomputing the same thing in both sample and evaluate
vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, shading_data.albedo, shading_data.metallic); vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, shading_data.albedo, shading_data.metallic);
float cos_theta_0 = fmaxf(glms_vec3_dot(normal, wo), 0.0f); float cos_theta_0 = fmaxf(glms_vec3_dot(normal, wo), 0.0f);
float F = glms_vec3_max(fresnel_schlick_vec3(f0, cos_theta_0)); // We use the max component of the Fresnel term for simplicity vec3s f = fresnel_schlick_vec3(f0, cos_theta_0);
float lum_f = (f.x + f.y + f.z) / 3.0f;
float prob_specular = glm_lerp(F, 1.0f, shading_data.metallic); float prob_specular = glm_lerp(lum_f, 1.0f, shading_data.metallic);
float prob_diffuse = (1.0f - shading_data.metallic) * (1.0f - F); // Diffuse only for non-metals, reduced by reflection float prob_diffuse = (1.0f - shading_data.metallic) * (1.0f - lum_f); // Diffuse only for non-metals, reduced by reflection
float total_prob = prob_diffuse + prob_specular; float total_prob = prob_diffuse + prob_specular;
if (total_prob < FLT_EPSILON) if (total_prob < FLT_EPSILON)
{ {
@@ -30,10 +32,13 @@ vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, sobol_sta
prob_specular /= total_prob; prob_specular /= total_prob;
vec3s wi; vec3s wi;
float lob_sample = sobol_sample(sample_index, sobol_get_dimension(bounce, PRNG_BSDF));
uint16_t d1 = sobol_get_dimension(bounce, PRNG_BSDF_U);
uint16_t d2 = sobol_get_dimension(bounce, PRNG_BSDF_V);
if (random_float() < prob_diffuse) // Diffuse Lobe if (lob_sample < prob_diffuse) // Diffuse Lobe
{ {
wi = random_cosine_direction(normal, sobol_state); wi = random_cosine_direction(normal, sample_index, d1, d2);
} }
else // Specular Lobe else // Specular Lobe
{ {
@@ -42,9 +47,13 @@ vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, sobol_sta
// A common simplification involves sampling spherical coordinates(theta and phi angles) related to normal such that cose(theta) is distributed according to the Blinn-Phong distribution // A common simplification involves sampling spherical coordinates(theta and phi angles) related to normal such that cose(theta) is distributed according to the Blinn-Phong distribution
// We can use a inversion sampling where cos(theta) = powf(random_float(), 1.0f / (specular_exponent + 1.0f)) and phi = 2 * PI * random_float() // We can use a inversion sampling where cos(theta) = powf(random_float(), 1.0f / (specular_exponent + 1.0f)) and phi = 2 * PI * random_float()
float specular_exponent = roughness_to_blinn_phong_specular_exponent(shading_data.roughness); float specular_exponent = roughness_to_blinn_phong_specular_exponent(shading_data.roughness);
float theta = acosf(powf(sobol_next(sobol_state), 1.0f / (specular_exponent + 1.0f)));
float phi = 2.0f * (float)M_PI * sobol_next(sobol_state); float theta = acosf(powf(sobol_sample(sample_index, d1), 1.0f / (specular_exponent + 1.0f)));
vec3s h_ts = (vec3s){ float phi = 2.0f * (float)M_PI * sobol_sample(sample_index, d2);
// float theta = acosf(powf(random_float(), 1.0f / (specular_exponent + 1.0f)));
// float phi = 2.0f * (float)M_PI * random_float();
vec3s h_ts = (vec3s)
{
sinf(theta) * cosf(phi), sinf(theta) * cosf(phi),
sinf(theta) * sinf(phi), sinf(theta) * sinf(phi),
cosf(theta) cosf(theta)
@@ -62,7 +71,7 @@ vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, sobol_sta
vec3s h_ws; vec3s h_ws;
h_ws = glms_vec3_add(scaled_u, scaled_v); h_ws = glms_vec3_add(scaled_u, scaled_v);
h_ws = glms_vec3_add(h_ws, scaled_n); h_ws = glms_vec3_add(h_ws, scaled_n);
h_ws = glms_vec3_normalize(h_ws); // Normalize the half-vector h_ws = glms_vec3_normalize(h_ws);
// wi is simple now, just reflect wo around normal // wi is simple now, just reflect wo around normal
wi = glms_vec3_reflect(glms_vec3_negate(wo), h_ws); wi = glms_vec3_reflect(glms_vec3_negate(wo), h_ws);

View File

@@ -1,8 +1,74 @@
#define FLIP_Y #include "Debug.h"
#include "Rendering/Scene.h" #include "Rendering/Scene.h"
#include "Algorithm/PathTracing.h" #include "Algorithm/PathTracing.h"
#define FLIP_Y
bool scene_init(uint64_t triangle_count, uint8_t material_count, uint32_t punctual_light_count, scene_t* scene)
{
scene_t temp = {0};
if (!triangle_collection_init(triangle_count, &temp.triangles))
{
goto triangle_failed;
}
if (!material_collection_init(material_count, &temp.materials))
{
goto material_failed;
}
if (!light_collection_create(punctual_light_count, 16, &temp.lights)) // NOTE: We just fixed the max directional light count to 16.
{
goto light_failed;
}
temp.camera = camera_create(
(vec3s){0.0f, 0.0f, 5.0f},
(vec3s){0.0f, 0.0f, -1.0f},
(vec3s){0.0f, 1.0f, 0.0f},
0.025f,
0.036f,
16.0f / 9.0f
);
*scene = temp;
return true;
light_failed:
material_collection_free(&temp.materials);
material_failed:
triangle_collection_free(&temp.triangles);
triangle_failed:
return false;
}
void scene_free(scene_t* scene)
{
bvh_tree_free(&scene->bvh_tree);
triangle_collection_free(&scene->triangles);
material_collection_free(&scene->materials);
light_collection_free(&scene->lights);
}
bool scene_build_bvh(scene_t* scene)
{
if (scene == NULL || scene->triangles.count == 0)
{
return false;
}
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;
}
static inline void ensure_camera_aspect_ratio(camera_t* camera, rendering_config_t config) static inline void ensure_camera_aspect_ratio(camera_t* camera, rendering_config_t config)
{ {
if (fabsf((float)config.width / config.height - camera->aspect_ratio) > FLT_EPSILON) if (fabsf((float)config.width / config.height - camera->aspect_ratio) > FLT_EPSILON)
@@ -30,13 +96,29 @@ static inline vec2s compute_ndc(float x, float y, uint32_t width, uint32_t heigh
}; };
} }
static void screne_render_pixel(scene_t* scene, rendering_config_t config, vec3s coord, uint32_t x, uint32_t y, vec4s* pixel_color) static inline uint16_t get_sample_count(uint16_t sample_count, int flag)
{
switch (flag)
{
case DEBUG_BVH:
case DEBUG_SOBOL:
return 1;
default:
return sample_count;
}
return sample_count;
}
static void screen_render_pixel(scene_t* scene, rendering_config_t config, vec3s coord, uint32_t x, uint32_t y, int flag, vec4s* pixel_color)
{ {
vec4s accumulated_color = glms_vec4_zero(); vec4s accumulated_color = glms_vec4_zero();
*pixel_color = accumulated_color; *pixel_color = accumulated_color;
uint32_t pixel_id = y * config.width + x; uint32_t pixel_id = y * config.width + x;
for (uint16_t k = 0; k < config.sample_count; k++) uint16_t sample_count = get_sample_count(config.sample_count, flag);
for (uint16_t k = 0; k < sample_count; k++)
{ {
vec2s position_ndc = compute_ndc((float)x, (float)y, config.width, config.height); vec2s position_ndc = compute_ndc((float)x, (float)y, config.width, config.height);
float screen_x = position_ndc.x * 2.0f - 1.0f; float screen_x = position_ndc.x * 2.0f - 1.0f;
@@ -53,20 +135,27 @@ static void screne_render_pixel(scene_t* scene, rendering_config_t config, vec3
.direction = glms_vec3_normalize(glms_vec3_sub(image_plane_point, scene->camera.position)) .direction = glms_vec3_normalize(glms_vec3_sub(image_plane_point, scene->camera.position))
}; };
// TODO: Hash it vec4s out_color = glms_vec4_zero();
uint32_t sobol_idx = pixel_id * config.sample_count + (k + 1);
vec3s out_color = path_trace(scene, ray, sobol_idx, config.max_depth);
// TODO: Handle alpha if (flag != 0)
vec4s color = {.x = out_color.x, .y = out_color.y, .z = out_color.z, .w = 1.0f}; {
accumulated_color = glms_vec4_add(accumulated_color, color); out_color = render_debug(scene, ray, k, flag);
}
else
{
// TODO: Hash it
uint32_t sobol_idx = pixel_id * config.sample_count + (k + 1);
out_color = path_trace(scene, ray, sobol_idx, config.max_depth);
}
accumulated_color = glms_vec4_add(accumulated_color, out_color);
} }
*pixel_color = glms_vec4_scale(accumulated_color, 1.0f / (float)config.sample_count); *pixel_color = glms_vec4_scale(accumulated_color, 1.0f / (float)sample_count);
} }
bool scene_render_tile(scene_t* scene, rendering_context_t* ctx, render_target_t* render_target,
rendering_config_t config, uint32_t tile_index, tile_t* tile_out) bool scene_render_tile(scene_t* scene, rendering_context_t* ctx, rendering_config_t config, uint32_t tile_index, int rendering_flag, render_target_t* render_target, tile_t* tile_out)
{ {
if (ctx->is_done) if (ctx->is_done)
{ {
@@ -108,7 +197,7 @@ bool scene_render_tile(scene_t* scene, rendering_context_t* ctx, render_target_t
for (x = tile_x_0; x < tile_x_1; x++) for (x = tile_x_0; x < tile_x_1; x++)
{ {
vec4s pixel_color; vec4s pixel_color;
screne_render_pixel(scene, config_copy, ctx->coord, (uint32_t)x, (uint32_t)y, &pixel_color); screen_render_pixel(scene, config_copy, ctx->coord, (uint32_t)x, (uint32_t)y, rendering_flag, &pixel_color);
render_target_set_pixel(render_target, (uint32_t)x, (uint32_t)y, pixel_color); render_target_set_pixel(render_target, (uint32_t)x, (uint32_t)y, pixel_color);
} }
} }
@@ -121,7 +210,7 @@ bool scene_render_tile(scene_t* scene, rendering_context_t* ctx, render_target_t
return true; return true;
} }
bool scene_render(scene_t* scene, rendering_config_t config, render_target_t* render_target) bool scene_render(scene_t* scene, rendering_config_t config, int rendering_flag, render_target_t* render_target)
{ {
ensure_camera_aspect_ratio(&scene->camera, config); ensure_camera_aspect_ratio(&scene->camera, config);
@@ -160,7 +249,7 @@ bool scene_render(scene_t* scene, rendering_config_t config, render_target_t* re
for (x = tile_x_0; x < tile_x_1; x++) for (x = tile_x_0; x < tile_x_1; x++)
{ {
vec4s pixel_color; vec4s pixel_color;
screne_render_pixel(scene, config_copy, coord, (uint32_t)x, (uint32_t)y, &pixel_color); screen_render_pixel(scene, config_copy, coord, (uint32_t)x, (uint32_t)y, rendering_flag, &pixel_color);
render_target_set_pixel(render_target, (uint32_t)x, (uint32_t)y, pixel_color); render_target_set_pixel(render_target, (uint32_t)x, (uint32_t)y, pixel_color);
} }
} }

View File

@@ -87,11 +87,13 @@ void window_update_pixels(vec4s color, int pixel_x, int pixel_y)
{ {
int pixel_index = (pixel_y * window_width + pixel_x) * 4; int pixel_index = (pixel_y * window_width + pixel_x) * 4;
float alpha = fminf(fmaxf(color.w, 0.0f), 1.0f);
// NOTE: The pixel buffer is in BGRA format, so we need to swap the channels // NOTE: The pixel buffer is in BGRA format, so we need to swap the channels
pixel_buffer[pixel_index] = COLOR_CLAMP(color.z * 255.0f); pixel_buffer[pixel_index] = COLOR_CLAMP(color.z * alpha * 255.0f);
pixel_buffer[pixel_index + 1] = COLOR_CLAMP(color.y * 255.0f); pixel_buffer[pixel_index + 1] = COLOR_CLAMP(color.y * alpha * 255.0f);
pixel_buffer[pixel_index + 2] = COLOR_CLAMP(color.x * 255.0f); pixel_buffer[pixel_index + 2] = COLOR_CLAMP(color.x * alpha * 255.0f);
pixel_buffer[pixel_index + 3] = COLOR_CLAMP(color.w * 255.0f); pixel_buffer[pixel_index + 3] = COLOR_CLAMP(alpha * 255.0f);
} }
void window_refresh_region(int pixel_x, int pixel_y, int region_width, int region_height) void window_refresh_region(int pixel_x, int pixel_y, int region_width, int region_height)

View File

@@ -3,10 +3,13 @@
#include <stdint.h> #include <stdint.h>
#include "Algorithm/Sobol.h" #include "Algorithm/Sobol.h"
#include "Debug.h"
#include "Geometry/GeometryUtilities.h"
#include "Geometry/Mesh.h"
#include "Lighting/SkyLight.h" #include "Lighting/SkyLight.h"
#include "Material/SimpleLit.h" #include "Material/SimpleLit.h"
#include "Rendering/Scene.h" #include "Rendering/Scene.h"
#include "Geometry/GeometryUtilities.h" #include "Rendering/PostProcessing.h"
#include "Window.h" #include "Window.h"
static void save_img(render_target_t* source, uint32_t width, uint32_t height, const char* filename) static void save_img(render_target_t* source, uint32_t width, uint32_t height, const char* filename)
@@ -30,8 +33,8 @@ static void save_img(render_target_t* source, uint32_t width, uint32_t height,
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR pCmdLine, _In_ int nCmdShow) int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR pCmdLine, _In_ int nCmdShow)
{ {
rendering_config_t config = { rendering_config_t config = {
.width = 1280 / 2, .width = 1920 / 1,
.height = 720 / 2, .height = 1080 / 1,
.sample_count = 64, .sample_count = 64,
.max_depth = 4, .max_depth = 4,
.bucket_size = 64, .bucket_size = 64,
@@ -49,47 +52,55 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
sobol_init(); sobol_init();
scene_t scene; scene_t scene;
if (!scene_init(64, 8, 4, &scene)) if (!scene_init(67000, 8, 1, &scene))
{ {
return -1; return -1;
} }
scene.camera.position = (vec3s){0.0f, 0.0f, 5.0f}; scene.camera.position = (vec3s){-7.5f, 2.0f, 0.0f};
scene.camera.forward = glms_vec3_normalize((vec3s){1.0f, 0.0f, 0.0f});
light_entity_t sun = light_create_directional_light(&scene.lights); light_entity_t sun = light_create_directional_light(&scene.lights);
directional_light_t* sun_light = &scene.lights.directional_lights[sun.id]; directional_light_t* sun_light = &scene.lights.directional_lights[sun.id];
sun_light->direction = glms_vec3_normalize((vec3s){0.75f, 0.75f, 1.0f}); sun_light->direction = glms_vec3_normalize((vec3s){-0.5f, 1.0f, 0.15f});
sun_light->color = (vec3s){1.0f, 0.93f, 0.87f}; sun_light->color = (vec3s){1.0f, 0.93f, 0.87f};
sun_light->intensity = 3.0f; sun_light->intensity = 2.0f;
sun_light->angular_diameter = 0.53f; sun_light->angular_diameter = 0.53f;
scene.lights.sky_light = sky_create_constant_sky(&(constant_sky_data_t){ scene.lights.sky_light = sky_create_constant_sky(&(constant_sky_data_t)
.color = (vec3s){0.73f, 0.82f, 1.0f} {
.color = (vec3s){0.73f, 0.82f, 1.0f},
.intensity = 1.0f,
}); });
simple_lit_data_t gray_lit_data = { simple_lit_data_t gray_lit_data =
{
.albedo = (vec3s){0.73f, 0.73f, 0.73f}, .albedo = (vec3s){0.73f, 0.73f, 0.73f},
.roughness = 0.5f, .roughness = 0.5f,
.metallic = 0.0f, .metallic = 0.0f,
}; };
simple_lit_data_t blue_lit_data = { simple_lit_data_t blue_lit_data =
{
.albedo = (vec3s){0.0f, 0.0f, 1.0f}, .albedo = (vec3s){0.0f, 0.0f, 1.0f},
.roughness = 0.5f, .roughness = 0.5f,
.metallic = 0.0f, .metallic = 0.0f,
}; };
simple_lit_data_t red_lit_data = { simple_lit_data_t red_lit_data =
{
.albedo = (vec3s){1.0f, 0.0f, 0.0f}, .albedo = (vec3s){1.0f, 0.0f, 0.0f},
.roughness = 0.5f, .roughness = 0.5f,
.metallic = 0.0f, .metallic = 0.0f,
}; };
simple_lit_data_t green_lit_data = { simple_lit_data_t green_lit_data =
{
.albedo = (vec3s){0.0f, 1.0f, 0.0f}, .albedo = (vec3s){0.0f, 1.0f, 0.0f},
.roughness = 0.5f, .roughness = 0.5f,
.metallic = 0.0f, .metallic = 0.0f,
}; };
simple_lit_data_t floor_lit_data = { simple_lit_data_t floor_lit_data =
{
.albedo = (vec3s){1.0f, 1.0f, 1.0f}, .albedo = (vec3s){1.0f, 1.0f, 1.0f},
.roughness = 0.75f, .roughness = 0.95f,
.metallic = 0.0f, .metallic = 0.0f,
}; };
material_entity_t gray_material = material_create_simple_lit(&gray_lit_data, &scene.materials); material_entity_t gray_material = material_create_simple_lit(&gray_lit_data, &scene.materials);
@@ -99,57 +110,67 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
material_entity_t green_material = material_create_simple_lit(&green_lit_data, &scene.materials); material_entity_t green_material = material_create_simple_lit(&green_lit_data, &scene.materials);
material_entity_t floor_material = material_create_simple_lit(&floor_lit_data, &scene.materials); material_entity_t floor_material = material_create_simple_lit(&floor_lit_data, &scene.materials);
// scene.materials.buffer[gray_light_material.id].emission = (vec3s){10.0f, 10.0f, 10.0f}; //scene.materials.buffer[gray_light_material.id].emission = (vec3s){10.0f, 10.0f, 10.0f};
quad_create( mesh_load("F:/c/SimpleRayTracer/assets/sponza.obj", floor_material.id, &scene.triangles, &scene.materials);
(vec3s){0.0f, 1.95f, 0.0f},
(vec3s){0.0f, -1.0f, 0.0f},
(vec3s){0.0f, 0.0f, 1.0f},
1.0f, gray_light_material.id, &scene.triangles
);
// sphere_create( // quad_create(
// (vec3s){0.0f, 0.0f, 0.0f}, // (vec3s){0.0f, 3.95f, 0.0f},
// (vec3s){0.0f, -1.0f, 0.0f},
// (vec3s){0.0f, 0.0f, 1.0f},
// 1.0f, gray_light_material.id, &scene.triangles // 1.0f, gray_light_material.id, &scene.triangles
// ); // );
// quad_create(
quad_create( // (vec3s){0.0f, 0.0f, 0.0f},
(vec3s){0.0f, -2.0f, 0.0f}, // (vec3s){0.0f, 1.0f, 0.0f},
(vec3s){0.0f, 1.0f, 0.0f}, // (vec3s){0.0f, 0.0f, 1.0f},
(vec3s){0.0f, 0.0f, 1.0f}, // 4.0f, floor_material.id, &scene.triangles
4.0f, floor_material.id, &scene.triangles // );
); // quad_create(
quad_create( // (vec3s){0.0f, 4.0f, 0.0f},
(vec3s){0.0f, 2.0f, 0.0f}, // (vec3s){0.0f, -1.0f, 0.0f},
(vec3s){0.0f, -1.0f, 0.0f}, // (vec3s){0.0f, 0.0f, 1.0f},
(vec3s){0.0f, 0.0f, 1.0f}, // 4.0f, gray_material.id, &scene.triangles
4.0f, gray_material.id, &scene.triangles // );
); // quad_create(
quad_create( // (vec3s){0.0f, 2.0f, -2.0f},
(vec3s){0.0f, 0.0f, -2.0f}, // (vec3s){0.0f, 0.0f, 1.0f},
(vec3s){0.0f, 0.0f, 1.0f}, // (vec3s){0.0f, 1.0f, 0.0f},
(vec3s){0.0f, 1.0f, 0.0f}, // 4.0f, green_material.id, &scene.triangles
4.0f, green_material.id, &scene.triangles // );
); // quad_create(
quad_create( // (vec3s){-2.0f, 2.0f, 0.0f},
(vec3s){-2.0f, 0.0f, 0.0f}, // (vec3s){1.0f, 0.0f, 0.0f},
(vec3s){1.0f, 0.0f, 0.0f}, // (vec3s){0.0f, 1.0f, 0.0f},
(vec3s){0.0f, 1.0f, 0.0f}, // 4.0f, blue_material.id, &scene.triangles
4.0f, blue_material.id, &scene.triangles // );
); // quad_create(
quad_create( // (vec3s){2.0f, 2.0f, 0.0f},
(vec3s){2.0f, 0.0f, 0.0f}, // (vec3s){-1.0f, 0.0f, 0.0f},
(vec3s){-1.0f, 0.0f, 0.0f}, // (vec3s){0.0f, 1.0f, 0.0f},
(vec3s){0.0f, 1.0f, 0.0f}, // 4.0f, red_material.id, &scene.triangles
4.0f, red_material.id, &scene.triangles // );
);
#pragma endregion #pragma endregion
render_target_t img; render_target_t img;
render_target_init(config.width, config.height, &img); render_target_init(config.width, config.height, &img);
// render_target_t img = scene_render(&scene, config);
// save_img(&img, config.width, config.height, "output.png"); if (!scene_build_bvh(&scene))
{
fprintf(stderr, "Failed to build BVH\n");
goto exit;
}
// BVH Debug
for (uint32_t i = 0; i < scene.bvh_tree.node_count; i++)
{
bvh_node_t* node = &scene.bvh_tree.nodes[i];
aabb_t bounds = node->bounds;
vec3s min = bounds.min;
vec3s max = bounds.max;
printf("Node %u: Min: (%f, %f, %f), Max: (%f, %f, %f)\n", i, min.x, min.y, min.z, max.x, max.y, max.z);
}
MSG msg; MSG msg;
uint16_t tile_index = 0; uint16_t tile_index = 0;
@@ -169,14 +190,15 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
DispatchMessage(&msg); DispatchMessage(&msg);
} }
if (scene_render_tile(&scene, &ctx, &img, config, tile_index, &current_tile)) if (scene_render_tile(&scene, &ctx, config, tile_index, DEBUG_BVH, &img, &current_tile))
{ {
for (uint32_t y = current_tile.y; y < current_tile.y + current_tile.height; y++) for (uint32_t y = current_tile.y; y < current_tile.y + current_tile.height; y++)
{ {
for (uint32_t x = current_tile.x; x < current_tile.x + current_tile.width; x++) for (uint32_t x = current_tile.x; x < current_tile.x + current_tile.width; x++)
{ {
vec4s pixel = render_target_get_pixel(&img, x, y); vec4s pixel = render_target_get_pixel(&img, x, y);
// pixel = aces_tone_map(pixel); pixel = gamma_correct(pixel, 2.2f);
pixel = aces_tone_map(pixel);
window_update_pixels(pixel, x, y); window_update_pixels(pixel, x, y);
} }
} }