Add HDR files and improve light handling

Added three binary files: `golden_gate_hills_1k.hdr`, `rogland_sunset_1k.hdr`, and `studio_small_03_1k.hdr`.
Added a new inline function `weight_nee_light` in `BSDF.h` to compute the weighted contribution of light based on the next event estimation (NEE).
Added a new function pointer type `sky_free_f` in `Light.h` for freeing sky light data.
Added a new structure `hdr_sky_data_t` in `SkyLight.h` to hold HDR sky data, including texture and intensity.
Changed the `RAY_EPSILON` definition in `Common.h` to a new value.
Changed the `light_collection_free` function in `Light.h` to include freeing sky light data if it exists.
Changed the `sky_create_hdr_sky` function in `SkyLight.h` to initialize HDR sky data and compute marginal and conditional distributions.
Changed the `texture_load` function in `Texture.h` to accept a `stride` parameter for different texture formats.
Changed the `evaluate_bsdf_directional` function in `LightEvaluation.c` to handle light intensity checks.
Changed the `evaluate_bsdf_const_sky` function in `SkyLight.c` to use a pointer for sky data and added checks for intensity.
Removed TODO comments related to handling triangle and material removal in `Triangle.h` and `Light.h`.
Removed the old `weight_sky_light` function in `SkyLight.h` and replaced it with the new `weight_nee_light` function.
Updated the `scene_setup` function in `main.c` to change camera position and light direction, and to load HDR textures.
Increased the sample count in the rendering configuration in `main.c` for better quality rendering.
This commit is contained in:
2025-05-02 01:25:56 +09:00
parent 0061609267
commit 9a1069db90
18 changed files with 378 additions and 82 deletions

View File

@@ -20,16 +20,20 @@ vec4s path_trace(const scene_t* scene, ray_t ray, uint32_t sample_index, uint16_
if (!closest_hit.hit)
{
vec3s sky_light = evaluate_bsdf_sky(&scene->lights, NULL, throughput, sample_index).direct_lighting;
// Set bvh to null indicate that the ray is not hit anything
light_shading_context_t light_context =
{
.wo = active_ray.direction,
.textures = &scene->textures,
};
path_output sky_output = evaluate_bsdf_sky(&scene->lights, &light_context, throughput, sample_index);
if (depth > 0)
{
// Have to multiply the weight since we evaluate the sky at each bounce
float pdf_nee = pdf_cosine_weighted_hemisphere(prev_normal, active_ray.direction);
float weight = power_heuristic(pdf_bsdf, pdf_nee);
sky_light = glms_vec3_scale(sky_light, weight);
float weight = power_heuristic(pdf_bsdf, sky_output.pdf);
sky_output.direct_lighting = glms_vec3_scale(sky_output.direct_lighting, weight);
}
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(sky_light, 0.0f));
accumulated_color = glms_vec4_add(accumulated_color, glms_vec4(sky_output.direct_lighting, 0.0f));
break;
}

View File

@@ -19,7 +19,7 @@ static texture_entity_t load_material_texture(const struct aiMaterial* material,
string_join(directory, image_path, path.data, 1024);
}
return texture_load(path.data, true, &scene->textures);
return texture_load(path.data, true, UINT_8, &scene->textures);
}
return invalid_texture_entity();
}

View File

@@ -6,6 +6,12 @@ path_output evaluate_bsdf_directional(directional_light_t light, const light_sha
{
path_output output = {0.0f};
output.state = TERMINATE;
output.pdf = 1.0f;
if (light.intensity <= 0.0f)
{
return output;
}
float angular_radius = glm_rad(light.angular_diameter / 2.0f);

View File

@@ -1,17 +1,24 @@
#include "Lighting/SkyLight.h"
#include "Algorithm/RayIntersection.h"
#include "Common.h"
path_output evaluate_bsdf_const_sky(const void* data, const light_shading_context_t* context, vec3s throughput, uint32_t sample_index)
{
constant_sky_data_t sky_data = *(const constant_sky_data_t*)data;
vec3s sky_color = glms_vec3_scale(sky_data.color, sky_data.intensity);
const constant_sky_data_t* sky_data = (const constant_sky_data_t*)data;
path_output output = {0.0f};
output.state = TERMINATE;
if (context == NULL)
if (sky_data->intensity <= 0.0f)
{
output.direct_lighting = glms_vec3_mul(sky_color, throughput);
return output;
}
vec3s sky_light = glms_vec3_scale(sky_data->color, sky_data->intensity);
if (context->bvh_tree == NULL)
{
output.direct_lighting = glms_vec3_mul(sky_light, throughput);
return output;
}
@@ -19,7 +26,6 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
uint16_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_LIGHT_V);
vec3s wi = random_uniform_cdf_direction(context->normal, sample_index, d1, d2);
float pdf = 1.0f / (4.0f * (float)M_PI);
ray_t shadow_ray = ray_create(BIAS_RAY_ORIGION(context->hit_point, context->normal), wi);
@@ -32,7 +38,8 @@ 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);
output.direct_lighting = glms_vec3_scale(throughput, cos_theta / pdf);
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;
@@ -40,3 +47,94 @@ path_output evaluate_bsdf_const_sky(const void* data, const light_shading_contex
return output;
}
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 = TERMINATE;
if (sky_data->intensity <= 0.0f)
{
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;
return output;
}
float u0 = random_float();
uint32_t i = 0;
while (i < sky_data->height && u0 > sky_data->marginal[i])
{
i++;
}
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 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)
{
cos_theta * cos_phi,
sin_theta,
cos_theta * sin_phi,
};
ray_t shadow_ray = ray_create(BIAS_RAY_ORIGION(context->hit_point, context->normal), wi);
float closest = FLT_MAX;
hit_result_t shadow_hit = {1};
ray_intersect_bvh(&shadow_ray, context->bvh_tree->nodes, context->bvh_tree->primitive_indices, context->bvh_tree->triangles, 0, &closest, &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(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);
output.wi = wi;
output.pdf = pdf;
output.state = NORMAL;
return output;
}
void hdr_sky_free(hdr_sky_data_t* data)
{
free(data->marginal);
free(data->conditional);
data->marginal = NULL;
data->conditional = NULL;
}

View File

@@ -2,12 +2,11 @@
#include "Algorithm/BSDF.h"
#include "Algorithm/Sobol.h"
#include "Lighting/LightEvaluation.h"
#include "Lighting/SkyLight.h"
#include <float.h>
static float DIELECTRIC_REFLECTIVE_F0 = 0.04f; // Standard dielectric reflectivity coef at incident angle (= 4%)
static vec3s DIELECTRIC_REFLECTIVE = {0.04f, 0.04f, 0.04f}; // Standard dielectric reflectivity coef at incident angle (= 4%)
static float DIELECTRIC_REFLECTIVE_F0 = 0.04f; // Standard dielectric reflectivity coef at incident angle (= 4%)
static vec3s DIELECTRIC_REFLECTIVE = {0.04f, 0.04f, 0.04f};
static void get_surface_data(const shading_context_t* context, const void* properties, surface_data_t* data_out)
{
@@ -20,16 +19,13 @@ static void get_surface_data(const shading_context_t* context, const void* prope
data_out->albedo = glms_vec3_mul(data_out->albedo, glms_vec3(texture_sample(albedo_texture, context->uv.x, context->uv.y)));
}
data_out->normal = context->normal;
const texture_t* normal_texture = get_texture(context->textures, prop->normal_texture);
if (normal_texture != NULL && normal_texture->data != NULL)
{
vec3s normal_sample = glms_vec3(texture_sample(normal_texture, context->uv.x, context->uv.y));
normal_sample = normal_unpack(normal_sample);
data_out->normal = normal_ts_to_ws(normal_sample, context->tangent);
}
else
{
data_out->normal = context->normal;
data_out->normal = glms_vec3_add(data_out->normal, normal_ts_to_ws(normal_sample, context->tangent));
}
data_out->roughness = prop->roughness;
@@ -160,7 +156,7 @@ static float sample_bsdf_pdf_simple_lit(const shading_context_t* context, const
float pdf_diff = pdf_cosine_weighted_hemisphere(context->normal, wi);
float diffuse_pdf_component = prob_diffuse * pdf_diff;
// HACK: Check this.
// When computing specular PDF, L is the wi and wi is the view direction
float pdf_spec = pdf_blinn_phong_lobe(context->normal, L, wi, surface_data->roughness);
float specular_pdf_component = prob_specular * pdf_spec;
@@ -199,6 +195,7 @@ static vec3s evaluate_bsdf_simple_lit(const shading_context_t* context, const su
return glms_vec3_add(diffuse_term, specular_term);
}
// NOTE: Need to double check is this physically correct, especially the nee and mis.
path_output simple_lit_render_loop(const void* properties, const shading_context_t* context)
{
surface_data_t surface_data = {0};
@@ -233,7 +230,7 @@ path_output simple_lit_render_loop(const void* properties, const shading_context
path_output light_output = evaluate_bsdf_directional(context->lights->directional_lights[i], &light_context, context->throughput, context->sample_index);
if (light_output.state == NORMAL)
{
vec3s bsdf_dir_light = evaluate_bsdf_simple_lit(context, &surface_data, light_output.wi);
vec3s bsdf_dir_light = evaluate_bsdf_simple_lit(context, &surface_data, light_output.wi);
vec3s light_contribute = glms_vec3_mul(light_output.direct_lighting, bsdf_dir_light);
output.direct_lighting = glms_vec3_add(output.direct_lighting, light_contribute);
}
@@ -245,7 +242,7 @@ path_output simple_lit_render_loop(const void* properties, const shading_context
{
vec3s bsdf_sky_light = evaluate_bsdf_simple_lit(context, &surface_data, sky_output.wi);
float pdf_bsdf = sample_bsdf_pdf_simple_lit(context, &surface_data, sky_output.wi);
vec3s sky_light = weight_sky_light(bsdf_sky_light, sky_output.direct_lighting, pdf_bsdf, sky_output.pdf);
vec3s sky_light = weight_nee_light(bsdf_sky_light, sky_output.direct_lighting, sky_output.pdf, pdf_bsdf);
output.direct_lighting = glms_vec3_add(output.direct_lighting, sky_light);
}

View File

@@ -4,7 +4,7 @@
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define GET_CHANNEL_DATA(pixel, channel, channel_count, default) (channel < channel_count ? pixel[channel] : default) / 255.0f
#define GET_CHANNEL_DATA(pixel, channel, channel_count, default, max) (channel < channel_count ? pixel[channel] : default) / max
bool texture_collection_init(uint16_t size, texture_collection_t* textures)
{
@@ -64,7 +64,7 @@ void texture_collection_free(texture_collection_t* textures)
textures->buffer = NULL;
}
texture_entity_t texture_load(const char* filename, bool srgb, texture_collection_t* textures)
texture_entity_t texture_load(const char* filename, bool srgb, stride_t stride, texture_collection_t* textures)
{
// TODO: This hurts performance, consider using a hash map or similar structure for faster lookups
@@ -77,26 +77,43 @@ texture_entity_t texture_load(const char* filename, bool srgb, texture_collectio
// }
int width, height, channels;
uint8_t* data = stbi_load(filename, &width, &height, &channels, 0);
char* data = NULL;
switch (stride)
{
case UINT_8:
uint8_t* img_data = stbi_load(filename, &width, &height, &channels, 0);
if (srgb)
{
// Convert to linear space if the texture is in sRGB format
for (int i = 0; i < width * height * channels; i++)
{
img_data[i] = (uint8_t)(powf(img_data[i] / 255.0f, 2.2f) * 255.0f);
}
}
data = (char*)img_data;
break;
case UINT_16:
data = (char*)stbi_load_16(filename, &width, &height, &channels, 0);
break;
case FLOAT_32:
data = (char*)stbi_loadf(filename, &width, &height, &channels, 0);
break;
}
if (data == NULL)
{
return invalid_texture_entity();
}
if (srgb)
{
// Convert to linear space if the texture is in sRGB format
for (int i = 0; i < width * height * channels; i++)
{
data[i] = (uint8_t)(powf(data[i] / 255.0f, 2.2f) * 255.0f);
}
}
texture_t texture = {0};
texture.width = (uint32_t)width;
texture.height = (uint32_t)height;
texture.channel_count = (uint8_t)channels;
texture.stride = stride;
texture.data = data;
texture.wrap_mode = REPEAT;
texture.filter_mode = LINEAR;
@@ -130,20 +147,43 @@ static inline void warp_uv(wrap_mode_t mode, float* u, float* v)
static vec4s get_pixel_color(const texture_t* texture, uint32_t x, uint32_t y)
{
uint32_t pixel_index = y * texture->width + x;
if (pixel_index >= texture->width * texture->height)
if (x >= texture->width || y >= texture->height)
{
return (vec4s){0.0f, 0.0f, 0.0f, 1.0f};
}
uint8_t* pixel = &texture->data[pixel_index * texture->channel_count];
return (vec4s)
uint32_t pixel_index = y * texture->width + x;
size_t pixel_offset = (size_t)pixel_index * texture->channel_count * texture->stride;
uint8_t* base8 = (uint8_t*)texture->data + pixel_offset;
uint16_t* base16 = (uint16_t*)(texture->data) + ((y * texture->width + x) * texture->channel_count);
float* base32 = (float*)(texture->data) + ((y * texture->width + x) * texture->channel_count);
vec4s out = {0, 0, 0, 1}; // default alpha = 1
for (int c = 0; c < texture->channel_count && c < 4; ++c)
{
GET_CHANNEL_DATA(pixel, 0, texture->channel_count, 0),
GET_CHANNEL_DATA(pixel, 1, texture->channel_count, 0),
GET_CHANNEL_DATA(pixel, 2, texture->channel_count, 0),
GET_CHANNEL_DATA(pixel, 3, texture->channel_count, 1)
};
float value = 0.0f;
switch (texture->stride)
{
case UINT_8:
value = base8[c] / 255.0f;
break;
case UINT_16:
value = base16[c] / 65535.0f;
break;
case FLOAT_32:
value = base32[c];
break;
default:
value = (c == 3) ? 1.0f : 0.0f;
break;
}
out.raw[c] = value;
}
return out;
}
static vec4s nearest_filter(const texture_t* texture, float u, float v)

View File

@@ -3,10 +3,10 @@
#include <svpng.inc>
#include "Algorithm/Sobol.h"
// #include "Geometry/GeometryUtilities.h"
#include "Geometry/GeometryUtilities.h"
#include "Geometry/Mesh.h"
#include "Lighting/SkyLight.h"
// #include "Material/SimpleLit.h"
#include "Material/SimpleLit.h"
#include "Rendering/PostProcessing.h"
#include "Rendering/Scene.h"
#include "Window.h"
@@ -21,22 +21,25 @@ static bool scene_setup(scene_t* scene)
return false;
}
scene->camera.position = (vec3s){-7.5f, 2.0f, 0.0f};
scene->camera.rotation = euler_to_quat(10.0f, -90.0f, 0.0f);
scene->camera.position = (vec3s){7.5f, 2.0f, 0.0f};
scene->camera.rotation = euler_to_quat(10.0f, 90.0f, 0.0f);
// TODO: Standardize light unit
light_entity_t sun = light_create_directional_light(&scene->lights);
directional_light_t* sun_light = &scene->lights.directional_lights[sun.id];
sun_light->direction = glms_vec3_normalize((vec3s){-0.5f, 1.0f, 0.25f});
sun_light->direction = glms_vec3_normalize((vec3s){0.5f, 1.0f, -0.25f});
sun_light->color = (vec3s){1.0f, 0.93f, 0.87f};
sun_light->intensity = 2.0f;
sun_light->intensity = 1.0f;
sun_light->angular_diameter = 0.53f;
scene->lights.sky_light = sky_create_constant_sky(&(constant_sky_data_t)
{
.color = (vec3s){0.73f, 0.82f, 1.0f},
.intensity = 1.0f,
});
//scene->lights.sky_light = sky_create_constant_sky(&(constant_sky_data_t)
//{
// .color = (vec3s){0.73f, 0.82f, 1.0f},
// .intensity = 1.0f,
//});
// NOTE: Not sure it's my problem or stb_image's, but the peek value of HDRI is way much lower than actual. Need to double cheeck the cdf.
texture_entity_t hdri = texture_load("./assets/hdri/rogland_sunset_1k.hdr", false, FLOAT_32, &scene->textures);
scene->lights.sky_light = sky_create_hdr_sky(&scene->textures, hdri, 1.0f);
return true;
}
@@ -44,7 +47,17 @@ static bool scene_setup(scene_t* scene)
static bool load_assets(scene_t* scene)
{
mesh_load(SPONZA_PATH, scene);
// quad_create((vec3s){0.0f, 0.0f, 0.0f}, (vec3s){0.0f, 1.0f, 0.0f}, (vec3s){1.0f, 0.0f, 0.0f}, 10.0f, floor_material.id, &scene->triangles);
//material_entity_t floor_material = material_create_simple_lit_default(&(simple_lit_properties_t)
//{
// .albedo = (vec3s){0.8f, 0.8f, 0.8f},
// .roughness = 0.95f,
// .metallic = 0.0f,
// .albedo_texture = invalid_texture_entity(),
// .metallic_texture = invalid_texture_entity(),
// .roughness_texture = invalid_texture_entity(),
//}, &scene->materials);
//quad_create((vec3s){0.0f, 0.0f, 0.0f}, (vec3s){0.0f, 1.0f, 0.0f}, (vec3s){1.0f, 0.0f, 0.0f}, 10.0f, floor_material.id, &scene->triangles);
//quad_create((vec3s){0.0f, 0.5f, 0.0f}, (vec3s){1.0f, 0.0f, 0.0f}, (vec3s){0.0f, 1.0f, 0.0f}, 1.0f, floor_material.id, &scene->triangles);
return scene_build_bvh(scene);
}
@@ -53,7 +66,6 @@ static bool initialize_renderer(const rendering_config_t* config, render_job_t**
{
if (!scene_setup(outScene)
|| !load_assets(outScene)
|| !scene_build_bvh(outScene)
|| !render_target_init(config->width, config->height, outImg))
{
return false;
@@ -155,9 +167,9 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
render_job_t* job = NULL;
rendering_config_t config = {
.width = 1920 / 2,
.height = 1080 / 2,
.sample_count = 16 * 1,
.width = 1920 / 1,
.height = 1080 / 1,
.sample_count = 16 * 8,
.max_depth = 4,
.bucket_size = 64,
};