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

@@ -15,10 +15,11 @@ path_output evaluate_bsdf_directional(directional_light_t light, const light_sha
float angular_radius = glm_rad(light.angular_diameter * 0.5f);
uint32_t scramble = hash_position(context->position);
uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
vec3s wi = random_uniform_cdf_direction_angular(light.direction, sample_index, angular_radius, d1, d2);
vec3s wi = random_uniform_cdf_direction_angular(light.direction, sample_index, angular_radius, d1, d2, scramble);
float n_dot_l = glms_vec3_dot(context->normal, wi);
if (n_dot_l <= 0.0f)
@@ -41,6 +42,6 @@ path_output evaluate_bsdf_directional(directional_light_t light, const light_sha
output.direct_lighting = glms_vec3_mul(light_radiance, light_contribute);
output.wi = wi;
output.state = PS_NORMAL;
output.state = PS_SUCCESS;
return output;
}

View File

@@ -1,5 +1,6 @@
#include "Lighting/SkyLight.h"
#include "Common.h"
#include <stdio.h>
// Constant Sky
@@ -42,10 +43,11 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
return output;
}
uint32_t scramble = hash_position(context->position);
uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
vec3s wi = random_uniform_cdf_direction(context->normal, sample_index, d1, d2);
vec3s wi = random_uniform_cdf_direction(context->normal, sample_index, d1, d2, scramble);
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi);
@@ -60,11 +62,10 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), cos_theta / pdf);
output.wi = wi;
output.state = PS_NORMAL;
output.state = PS_SUCCESS;
return output;
}
static uint32_t lower_bound_float(const float* arr, uint32_t n, float target)
{
uint32_t low = 0;
@@ -87,7 +88,7 @@ static uint32_t lower_bound_float(const float* arr, uint32_t n, float target)
// HDR Sky
sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_entity_t hdri_entity, float intensity)
sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_handle_t hdri_entity, float intensity)
{
sky_light_t light = {
.data_size = sizeof(hdr_sky_data_t),
@@ -106,6 +107,9 @@ sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_ent
.height = hdri->height,
.texture = hdri_entity,
.intensity = intensity,
.pdf_uv_mass = NULL,
.cdf_x = NULL,
.cdf_y_transposed = NULL,
};
size_t size_hw = (size_t)data.height * data.width; // height * width elements
@@ -118,15 +122,6 @@ sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_ent
float* intermediate_buffer = (float*)malloc(sizeof(float) * intermediate_buffer_size);
if (intermediate_buffer == NULL)
{
free(light.data);
return light;
}
vec3s* cache = (vec3s*)malloc(sizeof(vec3s) * size_hw);
if (cache == NULL)
{
free(light.data);
free(intermediate_buffer);
return light;
}
@@ -141,16 +136,17 @@ sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_ent
float lumSum = 0.0f;
for (uint32_t i = 0; i < data.height; ++i)
{
// Note: If equiareal mapping requires an area element factor like sin(theta),
// calculate it here and multiply the luminance before summing and normalizing.
// float v = (i + 0.5f) / (float)data.height;
// float sin_theta = sinf(v * PI);
// Calculate the sine of the polar angle theta
// Map row i to V [0,1], then to theta [0, PI]
float v = (i + 0.5f) / (float)data.height;
float theta = v * PI;
float sin_theta = sinf(theta);
for (uint32_t j = 0; j < data.width; ++j)
{
vec2s uv = {(float)j / (float)data.width, (float)i / (float)data.height};
vec2s uv = {((float)j + 0.5f) / (float)data.width, ((float)i + 0.5f) / (float)data.height};
vec4s pixel = texture_get_pixel(hdri, uv, 0);
float lum = luminance(glms_vec3(pixel)); // * sin_theta;
float lum = luminance(glms_vec3(pixel)) * sin_theta;
p_xy_original_pdf[i * data.width + j] = lum;
lumSum += lum; // Sum of luminances (or lum * area_element)
@@ -163,6 +159,15 @@ sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_ent
p_xy_original_pdf[i] *= inv_lumSum;
}
// Keep a copy of the normalized discrete probability mass per texel for MIS queries by direction.
float* pdf_uv_mass = (float*)malloc(sizeof(float) * size_hw);
if (pdf_uv_mass == NULL)
{
free(intermediate_buffer);
return light;
}
memcpy(pdf_uv_mass, p_xy_original_pdf, sizeof(float) * size_hw);
// --- Calculate Marginal PDF p(x) ---
for (uint32_t j = 0; j < data.width; ++j)
{
@@ -220,67 +225,69 @@ sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_ent
}
}
// --- Precompute Samples using Inverse Transform Sampling ---
// Populate the 'cache' array directly, which is now vec3s*
// cache[i * width + j] represents the output pixel for input random numbers (i/height, j/width)
for (uint32_t i = 0; i < data.height; ++i)
{ // Corresponds to xi_1 (vertical random number)
for (uint32_t j = 0; j < data.width; ++j)
{
// Input random numbers [0, 1)
float xi_1 = i / (float)data.height;
float xi_2 = j / (float)data.width;
// Sample x index using xi_1 and marginal CDF (cdf_x_buffer)
uint32_t x_sample_idx = lower_bound_float(cdf_x_buffer, data.width, xi_1);
x_sample_idx = (x_sample_idx == data.width) ? data.width - 1 : x_sample_idx;
// Sample y index using xi_2 and conditional CDF for x=x_sample_idx (cdf_y_transposed_buffer)
// The conditional CDF for x_sample_idx is a 1D array starting at cdf_y_transposed_buffer[x_sample_idx * height]
uint32_t y_sample_idx = lower_bound_float(cdf_y_transposed_buffer + x_sample_idx * data.height, data.height, xi_2);
y_sample_idx = (y_sample_idx == data.height) ? data.height - 1 : y_sample_idx;
// Get the normalized PDF value at the sampled texture coordinates (x_sample_idx, y_sample_idx)
// This is stored in p_xy_original_pdf (which holds the normalized p(x,y))
float pdf_at_sample = p_xy_original_pdf[y_sample_idx * data.width + x_sample_idx];
// Store the sampled texture coordinates (normalized [0, 1)) and PDF in the cache (vec3s)
int cache_idx = i * data.width + j;
cache[cache_idx].x = (float)x_sample_idx / (float)data.width; // Sampled U
cache[cache_idx].y = (float)y_sample_idx / (float)data.height; // Sampled V
cache[cache_idx].z = pdf_at_sample; // Normalized PDF at (U, V)
}
// Persist CDF tables for correct runtime sampling (keeps wi/pdf consistent)
float* cdf_x_persist = (float*)malloc(sizeof(float) * size_w);
float* cdf_y_transposed_persist = (float*)malloc(sizeof(float) * size_wh);
if (cdf_x_persist == NULL || cdf_y_transposed_persist == NULL)
{
free(cdf_x_persist);
free(cdf_y_transposed_persist);
free(pdf_uv_mass);
free(intermediate_buffer);
return light;
}
memcpy(cdf_x_persist, cdf_x_buffer, sizeof(float) * size_w);
memcpy(cdf_y_transposed_persist, cdf_y_transposed_buffer, sizeof(float) * size_wh);
free(intermediate_buffer);
light.data = malloc(sizeof(hdr_sky_data_t));
if (light.data == NULL)
{
free(cache);
free(pdf_uv_mass);
free(cdf_x_persist);
free(cdf_y_transposed_persist);
return light;
}
data.total_weight = lumSum;
data.cdf_cache = cache;
data.pdf_uv_mass = pdf_uv_mass;
data.cdf_x = cdf_x_persist;
data.cdf_y_transposed = cdf_y_transposed_persist;
memcpy(light.data, &data, sizeof(hdr_sky_data_t));
return light;
}
static inline float hdr_pdf(const vec3s* cdf, vec3s v, uint32_t height, uint32_t width)
static inline float hdr_pdf_from_uv_mass(float uv_mass, float v, uint32_t height, uint32_t width)
{
vec2s uv = direction_to_equirectangular(v);
uint32_t x, y;
uv_to_index(uv, width, height, &x, &y);
float pdf = cdf[y * width + x].z;
float theta = uv.y * PI;
float sin_theta = fmaxf(sinf(theta), FLT_EPSILON);
// v in [0,1] maps to theta in [0, PI]
float theta = v * PI;
float sin_theta = fmaxf(sinf(theta), 1e-4f);
// Δω per texel for equirectangular map:
// Δω = (2π/width) * (π/height) * sinθ = (2π^2 sinθ)/(W H)
// pdf(ω) = uv_mass / Δω = uv_mass * (W H)/(2π^2 sinθ)
float convert = (float)(height * width) / (2.0f * PI_TWO * sin_theta);
float pdf = uv_mass * convert;
return pdf * convert;
return fminf(pdf, 1e6f);
}
static inline float hdr_sky_pdf_direction(const hdr_sky_data_t* sky_data, vec3s wi)
{
vec2s uv = direction_to_equirectangular(wi);
uint32_t x, y;
uv_to_index(uv, sky_data->width, sky_data->height, &x, &y);
float mass = 0.0f;
if (sky_data->pdf_uv_mass != NULL)
{
mass = sky_data->pdf_uv_mass[y * sky_data->width + x];
}
return hdr_pdf_from_uv_mass(mass, uv.y, sky_data->height, sky_data->width);
}
path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
@@ -295,27 +302,37 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
return output;
}
// Last hit was no geometry, directly sample the sky
if (context->bvh_tree == NULL)
{
vec2s uv = direction_to_equirectangular(context->wo);
vec4s sky_light = texture_sample_lod(get_texture(context->textures, sky_data->texture), uv, 0);
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(glms_vec3(sky_light), throughput), sky_data->intensity);
output.pdf = 1.0f / (4.0f * PI);
// Return the correct environment PDF for MIS when the BSDF-sampled ray escapes to the sky.
output.pdf = hdr_sky_pdf_direction(sky_data, context->wo);
return output;
}
uint32_t i,j;
// Should we also use sobol numbers for the sky sampling?
uint32_t x_idx, y_idx;
uint16_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_U);
uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
float r1 = sobol_sample(sample_index, d1);
float r2 = sobol_sample(sample_index, d2);
//float r1 = random_float();
//float r2 = random_float();
uv_to_index((vec2s){r1, r2}, sky_data->width, sky_data->height, &j, &i);
vec3s cdf = sky_data->cdf_cache[i * sky_data->width + j];
vec3s wi = equirectangular_to_direction(cdf.x,-cdf.y);
if (sky_data->cdf_x == NULL || sky_data->cdf_y_transposed == NULL || sky_data->pdf_uv_mass == NULL)
{
return output;
}
x_idx = lower_bound_float(sky_data->cdf_x, sky_data->width, r1);
x_idx = (x_idx == sky_data->width) ? (sky_data->width - 1) : x_idx;
y_idx = lower_bound_float(sky_data->cdf_y_transposed + x_idx * sky_data->height, sky_data->height, r2);
y_idx = (y_idx == sky_data->height) ? (sky_data->height - 1) : y_idx;
float u = ((float)x_idx + 0.5f) / (float)sky_data->width;
float v = ((float)y_idx + 0.5f) / (float)sky_data->height;
vec3s wi = equirectangular_to_direction(u, v);
float n_dot_l = glms_vec3_dot(wi, context->normal);
if (n_dot_l <= 0.0f)
@@ -331,24 +348,32 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
return output;
}
vec2s uv = direction_to_equirectangular(wi);
vec2s uv = (vec2s){u, v};
const texture_t* hdri = get_texture(context->textures, sky_data->texture);
vec4s pixel = texture_sample_lod(hdri, uv, 0);
vec4s pixel = texture_get_pixel(hdri, uv, 0);
vec3s sky_light = glms_vec3_scale(glms_vec3(pixel), sky_data->intensity);
float pdf = hdr_pdf(sky_data->cdf_cache, wi, sky_data->height, sky_data->width);
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), n_dot_l / pdf);
float mass = sky_data->pdf_uv_mass[y_idx * sky_data->width + x_idx];
float pdf = hdr_pdf_from_uv_mass(mass, v, sky_data->height, sky_data->width);
if (pdf < 1e-12f)
{
return output;
}
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), n_dot_l / pdf);
output.wi = wi;
output.pdf = pdf;
output.state = PS_NORMAL;
output.state = PS_SUCCESS;
return output;
}
void hdr_sky_free(hdr_sky_data_t* data)
{
free(data->cdf_cache);
free(data->pdf_uv_mass);
free(data->cdf_x);
free(data->cdf_y_transposed);
data->cdf_cache = NULL;
data->pdf_uv_mass = NULL;
data->cdf_x = NULL;
data->cdf_y_transposed = NULL;
}