Fixed cdf and added Standard Lit

This commit is contained in:
2025-12-29 22:01:44 +09:00
parent e1693764f7
commit adee5acd10
24 changed files with 830 additions and 570 deletions

View File

@@ -1,6 +1,6 @@
#include "ALgorithm/BSDF.h"
#include "cglm/util.h"
#include "cglm/struct/mat3.h"
#include "cglm/util.h"
float power_heuristic(float pdf_a, float pdf_b)
{
@@ -39,23 +39,39 @@ vec3s normal_unpack(vec3s normal)
vec3s normal_ts_to_ws(vec3s normal, vec3s geo_normal, vec3s tangent)
{
// 1. Sanitize inputs
tangent = glms_vec3_normalize(tangent);
vec3s bitangent = glms_vec3_cross(geo_normal, tangent);
float w = (glms_vec3_dot(glms_vec3_cross(geo_normal, tangent), bitangent) < 0.0f) ? -1.0f : +1.0f;
geo_normal = glms_vec3_normalize(geo_normal);
// 2. Gram-Schmidt with safety check
float proj = glms_vec3_dot(geo_normal, tangent);
vec3s t_prime = glms_vec3_normalize(glms_vec3_sub(tangent, glms_vec3_scale(geo_normal, proj)));
vec3s b_prime = glms_vec3_scale(glms_vec3_cross(geo_normal, t_prime), w);
vec3s t_prime_unorm = glms_vec3_sub(tangent, glms_vec3_scale(geo_normal, proj));
// Matrix in cglm is column-major, not row-major
mat3s tbn =
// SAFETY: If tangent is parallel to normal, t_prime is zero.
// Fallback to original tangent or arbitrary axis to avoid NaN.
if (glms_vec3_norm2(t_prime_unorm) < FLT_EPSILON)
{
t_prime.x, t_prime.y, t_prime.z,
b_prime.x, b_prime.y, b_prime.z,
geo_normal.x, geo_normal.y, geo_normal.z
};
t_prime_unorm = tangent;
// If tangent was also bad, pick an arbitrary axis
if (glms_vec3_norm2(t_prime_unorm) < FLT_EPSILON)
{
create_orthonormal_basis(geo_normal, &t_prime_unorm, &tangent); // Recycle variable
}
}
vec3s t_prime = glms_vec3_normalize(t_prime_unorm);
// 3. Calculate Bitangent
vec3s b_prime = glms_vec3_cross(geo_normal, t_prime);
// 4. Apply Tangent Handedness (w)
// NOTE: Check if tangent W component is stored/passed correctly.
// If not sure, assuming 1.0 is safer than calculating it from cross products of unnormalized vectors.
float w = (glms_vec3_dot(glms_vec3_cross(geo_normal, tangent), b_prime) < 0.0f) ? -1.0f : 1.0f;
b_prime = glms_vec3_scale(b_prime, w);
mat3s tbn = {t_prime.x, t_prime.y, t_prime.z, b_prime.x, b_prime.y, b_prime.z, geo_normal.x, geo_normal.y, geo_normal.z};
// 5. Transform and Re-normalize
return glms_vec3_normalize(glms_mat3_mulv(tbn, normal));
}
@@ -104,10 +120,10 @@ float pdf_blinn_phong_lobe(vec3s normal, vec3s wi, vec3s wo, float roughness)
return pdf_spec;
}
vec3s sample_cosine_weighted_hemisphere_z_angular(float angular, uint32_t index, uint32_t d1, uint32_t d2)
vec3s sample_cosine_weighted_hemisphere_z_angular(float angular, uint32_t index, uint16_t d1, uint16_t d2, uint32_t scramble)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float r1 = sobol_sample_scrambled(index, d1, scramble);
float r2 = sobol_sample_scrambled(index, d2, scramble);
float phi = 2.0f * PI * r1;
@@ -128,10 +144,10 @@ vec3s sample_cosine_weighted_hemisphere_z_angular(float angular, uint32_t index,
// 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)
vec3s sample_cosine_weighted_hemisphere_z(uint32_t index, uint16_t d1, uint16_t d2, uint32_t scramble)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float r1 = sobol_sample_scrambled(index, d1, scramble);
float r2 = sobol_sample_scrambled(index, d2, scramble);
float r = sqrtf(r1);
float phi = 2.0f * PI * r2;
@@ -155,20 +171,20 @@ 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
a = (vec3s){0.0f, 1.0f, 0.0f}; // Use y-axis
}
else
{
a = (vec3s){{1.0f, 0.0f, 0.0f}}; // Use x-axis
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 random_cosine_direction_angular(vec3s direction, float angular, uint32_t index, uint32_t d1, uint32_t d2, uint32_t scramble)
{
vec3s local_dir = sample_cosine_weighted_hemisphere_z_angular(angular, index, d1, d2);
vec3s local_dir = sample_cosine_weighted_hemisphere_z_angular(angular, index, d1, d2, scramble);
vec3s u, v;
create_orthonormal_basis(direction, &u, &v);
@@ -184,9 +200,9 @@ vec3s random_cosine_direction_angular(vec3s direction, float angular, uint32_t i
// 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 random_cosine_direction(vec3s direction, uint32_t index, uint32_t d1, uint32_t d2, uint32_t scramble)
{
vec3s local_dir = sample_cosine_weighted_hemisphere_z(index, d1, d2);
vec3s local_dir = sample_cosine_weighted_hemisphere_z(index, d1, d2, scramble);
vec3s u, v;
create_orthonormal_basis(direction, &u, &v);
@@ -199,10 +215,10 @@ vec3s random_cosine_direction(vec3s direction, uint32_t index, uint32_t d1, uint
return world_dir;
}
vec3s random_uniform_cdf_direction(vec3s direction, uint32_t index, uint32_t d1, uint32_t d2)
vec3s random_uniform_cdf_direction(vec3s direction, uint32_t index, uint16_t d1, uint16_t d2, uint32_t scramble)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float r1 = sobol_sample_scrambled(index, d1, scramble);
float r2 = sobol_sample_scrambled(index, d2, scramble);
float phi = 2.0f * PI * r1;
float cos_theta = 1.0f - r2 * 2.0f;
@@ -223,10 +239,10 @@ vec3s random_uniform_cdf_direction(vec3s direction, uint32_t index, uint32_t d1,
return world_dir;
}
vec3s random_uniform_cdf_direction_angular(vec3s direction, uint32_t index, float angular, uint32_t d1, uint32_t d2)
vec3s random_uniform_cdf_direction_angular(vec3s direction, uint32_t index, float angular, uint16_t d1, uint16_t d2, uint32_t scramble)
{
float r1 = sobol_sample(index, d1);
float r2 = sobol_sample(index, d2);
float r1 = sobol_sample_scrambled(index, d1, scramble);
float r2 = sobol_sample_scrambled(index, d2, scramble);
float cos_alpha = cosf(angular);
float cos_theta = 1.0f - r1 * (1.0f - cos_alpha);
@@ -249,11 +265,10 @@ vec3s random_uniform_cdf_direction_angular(vec3s direction, uint32_t index, floa
return world_dir;
}
// Must use this function to weight any nee light contribution before accumulate.
vec3s weight_nee_light(vec3s bsdf, vec3s light, float pdf_bsdf, float pdf_sky)
{
light = glms_vec3_mul(bsdf, light);
float weight = power_heuristic(pdf_sky, pdf_bsdf);
return glms_vec3_scale(light, weight);
}
}

View File

@@ -1,6 +1,7 @@
#include "Algorithm/PathTracing.h"
#include "Algorithm/RayIntersection.h"
#include "Lighting/LightEvaluation.h"
#include "Algorithm/BSDF.h"
// TODO: Split the diffuse and specular into different Monte Carlo, so we can decide the sample count for each one
vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_t max_depth)
@@ -9,7 +10,9 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
vec3s throughput = glms_vec3_one();
ray_t active_ray = ray;
float pdf_bsdf = 1.0f;
// PDF of the direction that generated the current ray segment (used for MIS on env hits).
float last_bsdf_pdf = 0.0f;
uint16_t depth = 0;
while (depth < max_depth)
@@ -25,11 +28,26 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
.textures = &scene->textures,
};
path_output sky_output = evaluate_bsdf_sky(&scene->lights, &light_context, throughput, sample_index);
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(sky_output.direct_lighting, 0.0f));
// MIS for BSDF-sampled environment hit (for depth==0 camera ray, use weight 1).
float w = 1.0f;
if (depth > 0)
{
float pdf_env = sky_output.pdf;
float pdf_bsdf = last_bsdf_pdf;
if (pdf_env > 0.0f && pdf_bsdf > 0.0f)
{
w = power_heuristic(pdf_bsdf, pdf_env);
}
}
vec3s env_contrib = glms_vec3_scale(sky_output.direct_lighting, w);
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(env_contrib, 0.0f));
break;
}
const material_t* hit_material = &scene->materials.buffer[scene->triangles.buffer[closest_hit.triangle_id].material_id];
uint8_t material_id = scene->triangles.buffer[closest_hit.triangle_id].material_id;
const material_t* hit_material = &scene->materials.buffer[material_id];
shading_context_t shading_context =
{
.camera_position = scene->camera.position,
@@ -52,10 +70,25 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
};
path_output material_output = render_material(hit_material, &shading_context);
if (glms_vec3_isinf(material_output.direct_lighting) || glms_vec3_isnan(material_output.direct_lighting))
{
goto end_path_trace;
}
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(material_output.direct_lighting, 0.0f));
pdf_bsdf = material_output.pdf;
if (material_output.pdf < FLT_EPSILON)
{
goto end_path_trace;
}
last_bsdf_pdf = material_output.pdf;
throughput = glms_vec3_mul(throughput, material_output.bsdf);
if (glms_vec3_isinf(throughput) || glms_vec3_isnan(throughput))
{
goto end_path_trace;
}
// We do Russian roulette to decide whether to continue tracing or terminate the path
if (depth > 1)

View File

@@ -52,7 +52,7 @@ static inline float sobol_uint_to_float(uint32_t x)
return x * 2.3283064365386963e-10f; // 1/2^32
}
float sobol_sample(uint32_t index, uint32_t dimension)
float sobol_sample(uint32_t index, uint16_t dimension)
{
// return random_float();
if (dimension >= SOBOL_DIMENSIONS)
@@ -71,3 +71,25 @@ float sobol_sample(uint32_t index, uint32_t dimension)
return sobol_uint_to_float(result);
}
float sobol_sample_scrambled(uint32_t index, uint16_t dimension, uint32_t scramble)
{
if (dimension >= SOBOL_DIMENSIONS)
{
return 0.0f;
}
uint32_t result = 0;
for (int i = 0; i < SOBOL_BITS; i++)
{
if (sobol_get_bit(index, i))
{
result ^= sobol_direction_vectors[dimension][i];
}
}
// Apply XOR scrambling to decorrelate pixels
result ^= scramble;
return sobol_uint_to_float(result);
}