Note: Currently version still have lots of fireflies after applying normal map. And those fireflies are mostly coming from the nee sky. Need to double check the sky cdf and ray intersection. Changed the `hit_result_t` structure to rename a parameter in `RayIntersection.h`. Removed the `normal` field from the `path_output` structure in `Common.h`. Added new fields `screen_size`, `camera_position`, and `camera_direction` in `Material.h`. Changed the `mipmap_t` structure in `Texture.h` to include a `max_mip` field and modify the `data` field. Changed the `texture_load` function in `Texture.c` to include a `mipmap` parameter and improve texture data handling. Changed the `path_trace` function in `PathTracing.c` to update the `shading_context_t` structure and ray creation. Changed the `evaluate_bsdf_directional` function in `LightEvaluation.c` to modify angular radius calculation. Changed the `sky_create_hdr_sky` and `evaluate_bsdf_hdr_sky` functions in `SkyLight.c` to enhance texture sampling. Changed the `get_surface_data` function in `SimpleLit.c` to incorporate camera distance and view direction in calculations. Changed the `texture_get_pixel` function in `Texture.c` to improve pixel data retrieval. Changed the `warp_uv` function to use a `vec2s` structure for UV coordinates. Changed the `texture_sample` function to include additional parameters for improved sampling accuracy. Changed the `scene_setup` function in `main.c` to adjust sun light intensity and HDRI texture loading.
355 lines
12 KiB
C
355 lines
12 KiB
C
#include "Lighting/SkyLight.h"
|
|
#include "Common.h"
|
|
|
|
// Constant Sky
|
|
|
|
sky_light_t sky_create_constant_sky(const constant_sky_data_t* data)
|
|
{
|
|
sky_light_t light = {
|
|
.data_size = sizeof(constant_sky_data_t),
|
|
.evaluate_bsdf_sky = evaluate_bsdf_const_sky,
|
|
};
|
|
|
|
light.data = malloc(sizeof(constant_sky_data_t));
|
|
if (light.data == NULL)
|
|
{
|
|
return light;
|
|
}
|
|
|
|
memcpy(light.data, data, sizeof(constant_sky_data_t));
|
|
return light;
|
|
}
|
|
|
|
path_output evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
|
|
{
|
|
const constant_sky_data_t* sky_data = (const constant_sky_data_t*)data;
|
|
|
|
path_output output = {0.0f};
|
|
output.state = PS_TERMINATE;
|
|
|
|
if (sky_data->intensity <= 0.0f)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
vec3s sky_light = glms_vec3_scale(sky_data->color, sky_data->intensity);
|
|
float pdf = 0.25f * INV_PI;
|
|
output.pdf = pdf;
|
|
|
|
if (context->bvh_tree == NULL)
|
|
{
|
|
output.direct_lighting = glms_vec3_mul(sky_light, throughput);
|
|
return output;
|
|
}
|
|
|
|
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);
|
|
|
|
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi);
|
|
|
|
hit_result_t shadow_hit = {0};
|
|
ray_intersect_bvh_any(&shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &shadow_hit);
|
|
if (shadow_hit.hit)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
float cos_theta = fmaxf(glms_vec3_dot(wi, context->normal), 0.0f);
|
|
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), cos_theta / pdf);
|
|
|
|
output.wi = wi;
|
|
output.state = PS_NORMAL;
|
|
return output;
|
|
}
|
|
|
|
|
|
static uint32_t lower_bound_float(const float* arr, uint32_t n, float target)
|
|
{
|
|
uint32_t low = 0;
|
|
uint32_t high = n; // Search range [low, high)
|
|
while (low < high)
|
|
{
|
|
uint32_t mid = low + (high - low) / 2;
|
|
if (arr[mid] < target)
|
|
{
|
|
low = mid + 1;
|
|
}
|
|
else
|
|
{
|
|
high = mid;
|
|
}
|
|
}
|
|
|
|
return low; // This is the index where target would be inserted to maintain order
|
|
}
|
|
|
|
// HDR Sky
|
|
|
|
sky_light_t sky_create_hdr_sky(const texture_collection_t* textures, texture_entity_t hdri_entity, float intensity)
|
|
{
|
|
sky_light_t light = {
|
|
.data_size = sizeof(hdr_sky_data_t),
|
|
.evaluate_bsdf_sky = evaluate_bsdf_hdr_sky,
|
|
.free_sky_data = (sky_free_f)hdr_sky_free,
|
|
};
|
|
|
|
const texture_t* hdri = get_texture(textures, hdri_entity);
|
|
if (hdri == NULL)
|
|
{
|
|
return light;
|
|
}
|
|
|
|
hdr_sky_data_t data = {
|
|
.width = hdri->width,
|
|
.height = hdri->height,
|
|
.texture = hdri_entity,
|
|
.intensity = intensity,
|
|
};
|
|
|
|
size_t size_hw = (size_t)data.height * data.width; // height * width elements
|
|
size_t size_w = (size_t)data.width; // width elements
|
|
size_t size_wh = (size_t)data.width * data.height; // width * height elements (same as hw)
|
|
|
|
// Total size for intermediate buffers: p(x,y)/cond_cdf + p(x) + cdf_x + cond_transposed_cdf
|
|
size_t intermediate_buffer_size = size_hw + size_w + size_w + size_hw + size_wh;
|
|
|
|
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;
|
|
}
|
|
|
|
// These pointers partition the single allocated intermediate_buffer
|
|
float* p_xy_original_pdf = intermediate_buffer; // size hw (Stores the normalized p(x,y) values)
|
|
float* p_x_buffer = p_xy_original_pdf + size_hw; // size w (Stores p(x))
|
|
float* cdf_x_buffer = p_x_buffer + size_w; // size w (Stores cdf_x_margin)
|
|
float* cdf_y_conditional = cdf_x_buffer + size_w; // size hw (Stores cdf(y|x))
|
|
float* cdf_y_transposed_buffer = cdf_y_conditional + size_hw; // size wh (Stores transposed cdf(y|x))
|
|
|
|
// --- Calculate Initial Luminance (PDF) ---
|
|
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);
|
|
|
|
for (uint32_t j = 0; j < data.width; ++j)
|
|
{
|
|
vec2s uv = {(float)j / (float)data.width, (float)i / (float)data.height};
|
|
vec4s pixel = texture_get_pixel(hdri, uv, 0);
|
|
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)
|
|
}
|
|
}
|
|
|
|
float inv_lumSum = (lumSum > FLT_EPSILON) ? (1.0f / lumSum) : 0.0f;
|
|
for (size_t i = 0; i < size_hw; ++i)
|
|
{
|
|
p_xy_original_pdf[i] *= inv_lumSum;
|
|
}
|
|
|
|
// --- Calculate Marginal PDF p(x) ---
|
|
for (uint32_t j = 0; j < data.width; ++j)
|
|
{
|
|
p_x_buffer[j] = 0.0f;
|
|
for (uint32_t i = 0; i < data.height; ++i)
|
|
{
|
|
p_x_buffer[j] += p_xy_original_pdf[i * data.width + j];
|
|
}
|
|
}
|
|
|
|
// --- Calculate Marginal CDF (prefix sum on marginal PDF) ---
|
|
memcpy(cdf_x_buffer, p_x_buffer, sizeof(float) * size_w);
|
|
if (data.width > 0)
|
|
{
|
|
for (uint32_t i = 1; i < data.width; ++i)
|
|
{
|
|
cdf_x_buffer[i] += cdf_x_buffer[i - 1]; // Prefix sum on cdf_x_buffer
|
|
}
|
|
// The last element cdf_x_buffer[width-1] should be close to 1.0 (total probability)
|
|
}
|
|
|
|
// --- Calculate Conditional PDF and CDF ---
|
|
// p_xy_buffer currently holds p(x,y)
|
|
// It will be modified in place to hold cdf(y|x)
|
|
memcpy(cdf_y_conditional, p_xy_original_pdf, sizeof(float) * size_hw);
|
|
for (uint32_t j = 0; j < data.width; ++j)
|
|
{
|
|
float marginal_x_val = p_x_buffer[j]; // Get p(x=j) from the stored p_x_buffer
|
|
float inv_marginal_x_val = (marginal_x_val > 1e-6f) ? (1.0f / marginal_x_val) : 0.0f;
|
|
|
|
// Normalize p(x,y) column to get conditional PDF p(y|x) and calculate conditional CDF cdf(y|x)
|
|
if (data.height > 0)
|
|
{
|
|
// First element is p(y=0|x) = p(x,y=0) / p(x), then becomes cdf(y=0|x)
|
|
cdf_y_conditional[0 * data.width + j] *= inv_marginal_x_val;
|
|
// Calculate prefix sum (conditional CDF) in place
|
|
for (uint32_t i = 1; i < data.height; ++i)
|
|
{
|
|
// p(y=i|x) = p(x,y=i) / p(x)
|
|
cdf_y_conditional[i * data.width + j] *= inv_marginal_x_val;
|
|
// cdf(y=i|x) = cdf(y=i-1|x) + p(y=i|x)
|
|
cdf_y_conditional[i * data.width + j] += cdf_y_conditional[(i - 1) * data.width + j];
|
|
}
|
|
// The last element of this column (at row height-1) should be close to 1.0
|
|
}
|
|
}
|
|
|
|
// --- Transpose Conditional CDF ---
|
|
// Transpose cdf_y_conditional (size hw) into cdf_y_transposed_buffer (size wh)
|
|
for (uint32_t i = 0; i < data.height; ++i)
|
|
{
|
|
for (uint32_t j = 0; j < data.width; ++j)
|
|
{
|
|
cdf_y_transposed_buffer[j * data.height + i] = cdf_y_conditional[i * data.width + j];
|
|
}
|
|
}
|
|
|
|
// --- 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)
|
|
}
|
|
}
|
|
|
|
free(intermediate_buffer);
|
|
|
|
light.data = malloc(sizeof(hdr_sky_data_t));
|
|
if (light.data == NULL)
|
|
{
|
|
free(cache);
|
|
return light;
|
|
}
|
|
|
|
data.total_weight = lumSum;
|
|
data.cdf_cache = cache;
|
|
|
|
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)
|
|
{
|
|
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);
|
|
|
|
float convert = (float)(height * width) / (2.0f * PI_TWO * sin_theta);
|
|
|
|
return pdf * convert;
|
|
}
|
|
|
|
path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
|
|
{
|
|
const hdr_sky_data_t* sky_data = (const hdr_sky_data_t*)data;
|
|
|
|
path_output output = {0.0f};
|
|
output.state = PS_TERMINATE;
|
|
|
|
if (sky_data->intensity <= 0.0f)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
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 output;
|
|
}
|
|
|
|
uint32_t i,j;
|
|
// Should we also use sobol numbers for the sky sampling?
|
|
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);
|
|
|
|
float n_dot_l = glms_vec3_dot(wi, context->normal);
|
|
if (n_dot_l <= 0.0f)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
ray_t shadow_ray = ray_create(offset_ray_origin(context->position, context->normal, context->wo), wi);
|
|
hit_result_t shadow_hit = {0};
|
|
ray_intersect_bvh_any(&shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &shadow_hit);
|
|
if (shadow_hit.hit)
|
|
{
|
|
return output;
|
|
}
|
|
|
|
vec2s uv = direction_to_equirectangular(wi);
|
|
|
|
const texture_t* hdri = get_texture(context->textures, sky_data->texture);
|
|
vec4s pixel = texture_sample_lod(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);
|
|
|
|
output.wi = wi;
|
|
output.pdf = pdf;
|
|
output.state = PS_NORMAL;
|
|
return output;
|
|
}
|
|
|
|
void hdr_sky_free(hdr_sky_data_t* data)
|
|
{
|
|
free(data->cdf_cache);
|
|
|
|
data->cdf_cache = NULL;
|
|
}
|