Add HDR sky lighting support and improve rendering
Added support for HDR sky lighting, including sampling environment maps with a hierarchical CDF. Added new utility functions for handling HDR sky data, including memory management and sampling functions. Added functions to improve texture handling, including pixel data retrieval and texture coordinate management. Changed the path tracing algorithm to enhance light evaluation from HDR skies and adjust light contribution calculations. Changed BSDF sampling functions to utilize constants from Common.h for better readability. Changed the main application logic to load HDR textures and configure the scene with improved lighting settings. Refactored ray intersection logic to enhance accuracy and performance in triangle intersections. Adjusted the sample count in the rendering configuration to optimize performance. Updated the README.md to document new features and example renders.
This commit is contained in:
@@ -1,6 +1,23 @@
|
||||
#include "Lighting/SkyLight.h"
|
||||
#include "Common.h"
|
||||
|
||||
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;
|
||||
@@ -14,7 +31,8 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -38,15 +56,228 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
|
||||
}
|
||||
|
||||
float cos_theta = fmaxf(glms_vec3_dot(wi, context->normal), 0.0f);
|
||||
float pdf = 1.0f / (4.0f * (float)M_PI);
|
||||
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), cos_theta / pdf);
|
||||
|
||||
output.wi = wi;
|
||||
output.pdf = pdf;
|
||||
output.state = 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
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
light.data = malloc(sizeof(hdr_sky_data_t));
|
||||
if (light.data == 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)
|
||||
{
|
||||
vec4s pixel = texture_get_pixel(hdri, j, i);
|
||||
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);
|
||||
|
||||
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;
|
||||
@@ -59,50 +290,40 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
if (context->bvh_tree == NULL)
|
||||
// if (true)
|
||||
{
|
||||
vec2s uv = direction_to_equirectangular(context->wo);
|
||||
vec4s sky_light = texture_sample(get_texture(context->textures, sky_data->texture), uv.x, uv.y);
|
||||
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(glms_vec3(sky_light), throughput), sky_data->intensity);
|
||||
output.pdf = 0.0f;
|
||||
output.pdf = 1.0f / (4.0f * PI);
|
||||
return output;
|
||||
}
|
||||
|
||||
float u0 = random_float();
|
||||
uint32_t i = 0;
|
||||
while (i < sky_data->height && u0 > sky_data->marginal[i])
|
||||
{
|
||||
i++;
|
||||
}
|
||||
uint32_t i,j;
|
||||
// TODO: Should we also use sobol numbers for the sky sampling?
|
||||
uv_to_index((vec2s){random_float(), random_float()}, sky_data->width, sky_data->height, &j, &i);
|
||||
vec3s cdf = sky_data->cdf_cache[i * sky_data->width + j];
|
||||
|
||||
float u1 = random_float();
|
||||
uint32_t j = 0;
|
||||
while (j < sky_data->width && u1 > sky_data->conditional[i * sky_data->width + j])
|
||||
{
|
||||
j++;
|
||||
}
|
||||
|
||||
float theta = (i - 0.5f) / sky_data->height * (float)M_PI;
|
||||
float phi = (j - 0.5f) / sky_data->width * (2.0f * (float)M_PI);
|
||||
float theta = -cdf.y * PI;
|
||||
float phi = cdf.x * TWO_PI;
|
||||
|
||||
float sin_theta = sinf(theta);
|
||||
float cos_theta = cosf(theta);
|
||||
float sin_phi = sinf(phi);
|
||||
float cos_phi = cosf(phi);
|
||||
|
||||
float domega = (2.0f * (float)M_PI / sky_data->width) * ((float)M_PI / sky_data->height) * sin_theta;
|
||||
float w_ij = (sky_data->marginal[i] - (i > 0 ? sky_data->marginal[i - 1] : 0.0f)) *
|
||||
(sky_data->conditional[i * sky_data->width + j] - (j > 0 ? sky_data->conditional[i * sky_data->width + j - 1] : 0.0f));
|
||||
float pdf = w_ij / domega;
|
||||
|
||||
vec3s wi = (vec3s)
|
||||
|
||||
vec3s wi = glms_vec3_normalize((vec3s)
|
||||
{
|
||||
cos_theta * cos_phi,
|
||||
sin_theta,
|
||||
cos_theta * sin_phi,
|
||||
};
|
||||
sin_theta * cos_phi,
|
||||
cos_theta,
|
||||
sin_theta * sin_phi,
|
||||
});
|
||||
|
||||
float n_dot_l = glms_vec3_dot(wi, context->normal);
|
||||
if (n_dot_l <= 0.0f)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
ray_t shadow_ray = ray_create(BIAS_RAY_ORIGION(context->hit_point, context->normal), wi);
|
||||
|
||||
@@ -120,9 +341,8 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
vec4s pixel = texture_sample(hdri, uv.x, uv.y);
|
||||
vec3s sky_light = glms_vec3_scale(glms_vec3(pixel), sky_data->intensity);
|
||||
|
||||
float angle = fmaxf(glms_vec3_dot(wi, context->normal), 0.0f);
|
||||
output.direct_lighting = glms_vec3_scale(glms_vec3_mul(sky_light, throughput), angle / pdf);
|
||||
output.direct_lighting = glms_vec3_clamp(output.direct_lighting, 0.0f, 1000.0f);
|
||||
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;
|
||||
@@ -132,9 +352,7 @@ path_output evaluate_bsdf_hdr_sky(const void* data, const light_shading_context_
|
||||
|
||||
void hdr_sky_free(hdr_sky_data_t* data)
|
||||
{
|
||||
free(data->marginal);
|
||||
free(data->conditional);
|
||||
free(data->cdf_cache);
|
||||
|
||||
data->marginal = NULL;
|
||||
data->conditional = NULL;
|
||||
data->cdf_cache = NULL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user