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:
210
source/Algorithm/BSDF.c
Normal file
210
source/Algorithm/BSDF.c
Normal 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
367
source/Algorithm/BVH.c
Normal 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*)¢roid)[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;
|
||||
}
|
||||
@@ -1,33 +1,32 @@
|
||||
#include "Algorithm/PathTracing.h"
|
||||
#include "Algorithm/RayIntersection.h"
|
||||
#include "Algorithm/BSDF.h"
|
||||
#include "Algorithm/Sobol.h"
|
||||
#include "Lighting/LightEvaluation.h"
|
||||
|
||||
// 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
|
||||
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 bvh_tree_t* bvh_tree = &scene->bvh_tree;
|
||||
const material_collection_t* materials = &scene->materials;
|
||||
const light_collection_t* lights = &scene->lights;
|
||||
|
||||
vec3s accumulated_color = glms_vec3_zero();
|
||||
vec4s accumulated_color = glms_vec4_zero();
|
||||
vec3s throughput = glms_vec3_one();
|
||||
|
||||
ray_t active_ray = ray;
|
||||
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.
|
||||
sobol_state_t sobol_state = {sample_index, 0};
|
||||
float pdf_bsdf = 1.0f;
|
||||
|
||||
int depth = 0;
|
||||
uint16_t depth = 0;
|
||||
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)
|
||||
{
|
||||
vec3s sky_light = evaluate_bsdf_sky(scene, NULL, &sobol_state);
|
||||
vec3s sky_light = evaluate_bsdf_sky(scene, NULL, throughput, sample_index);
|
||||
if (depth > 0)
|
||||
{
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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];
|
||||
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 = {
|
||||
.normal = closest_hit.normal,
|
||||
.hit_point = closest_hit.point,
|
||||
.wo = active_ray.direction,
|
||||
.throughput = throughput,
|
||||
.triangles = triangles,
|
||||
|
||||
.bounce_depth = depth,
|
||||
|
||||
.bvh_tree = bvh_tree,
|
||||
.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.
|
||||
for (uint32_t i = 0; i < lights->directional_light_count; i++)
|
||||
{
|
||||
vec3s l = evaluate_bsdf_directional(lights->directional_lights[i], &light_context, &sobol_state);
|
||||
accumulated_color = glms_vec3_add(accumulated_color, l);
|
||||
vec3s l = evaluate_bsdf_directional(lights->directional_lights[i], &light_context, throughput, sample_index);
|
||||
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(l, 1.0f));
|
||||
}
|
||||
|
||||
vec3s sky_light = evaluate_bsdf_sky(scene, &light_context, &sobol_state);
|
||||
accumulated_color = glms_vec3_add(accumulated_color, sky_light);
|
||||
vec3s sky_light = evaluate_bsdf_sky(scene, &light_context, throughput, sample_index);
|
||||
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(sky_light, 1.0f));
|
||||
|
||||
// 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 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)
|
||||
{
|
||||
break;
|
||||
@@ -86,7 +87,9 @@ vec3s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, int
|
||||
if (depth > 2)
|
||||
{
|
||||
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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
prev_normal = closest_hit.normal;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#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};
|
||||
|
||||
@@ -80,35 +81,201 @@ hit_result_t ray_intersect( triangle_t triangle, ray_t ray)
|
||||
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};
|
||||
closest_hit.distance = FLT_MAX;
|
||||
hit_result_t result = {0};
|
||||
|
||||
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);
|
||||
if (hit_result.hit && hit_result.distance < closest_hit.distance)
|
||||
{
|
||||
closest_hit = hit_result;
|
||||
closest_hit.triangle_id = i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
for (uint64_t i = 0; i < triangles->count; i++)
|
||||
if (u < 0.0f || u > 1.0f)
|
||||
{
|
||||
hit_result_t hit_result = ray_intersect(triangles->buffer[i], ray);
|
||||
if (hit_result.hit)
|
||||
{
|
||||
hit_result.triangle_id = i;
|
||||
return hit_result;
|
||||
}
|
||||
return result; // Hit outside the triangle's u range.
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#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?
|
||||
void sobol_init()
|
||||
{
|
||||
@@ -9,17 +14,15 @@ void sobol_init()
|
||||
sobol_direction_vectors[0][i] = 1U << (31 - i);
|
||||
}
|
||||
|
||||
// Dimensions 2 to 6
|
||||
for (int d = 1; d < SOBOL_DIMENSIONS; d++)
|
||||
{
|
||||
int s = s_vals[d - 1];
|
||||
int a = a_vals[d - 1];
|
||||
const int* m = m_vals[d - 1];
|
||||
|
||||
// Set initial direction numbers
|
||||
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
|
||||
@@ -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)
|
||||
@@ -51,6 +54,7 @@ static inline float sobol_uint_to_float(uint32_t x)
|
||||
|
||||
float sobol_sample(uint32_t index, uint32_t dimension)
|
||||
{
|
||||
// return random_float();
|
||||
if (dimension >= SOBOL_DIMENSIONS)
|
||||
{
|
||||
return 0.0f; // Invalid dimension
|
||||
@@ -67,23 +71,3 @@ float sobol_sample(uint32_t index, uint32_t dimension)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user