Files
SimpleRayTracing/source/Material/SimpleLit.c
Misaki 3c3168af7a Add texture handling and refactor material functions
Added a new function `blinn_phong_specular_exponent_to_roughness` in `BSDF.h` to convert a specular exponent to roughness.
Added a `textures` member to the `shading_context_t` structure in `Material.h` for passing texture information during shading.
Added a new member `textures` in the `light_shading_context_t` structure in `Light.h` to hold texture information.
Added inline functions in `Texture.h` for handling texture entities, including `invalid_texture_entity`, `is_texture_entity_valid`, and `get_texture`.
Changed the texture-related members in `simple_lit_properties_t` in `SimpleLit.h` from pointers to `texture_entity_t` to better manage texture entities.
Changed the `sample_material_bsdf` and `sample_material_bsdf_pdf` functions in `Material.h` to accept a `shading_context_t` instead of individual parameters.
Changed the `mesh_load` function in `Mesh.c` to use the new roughness calculation and texture entity handling.
Changed the `path_trace` function in `PathTracing.c` to use the new structure and functions for handling materials and textures.
Refactored the `sample_material_bsdf` and `sample_material_bsdf_pdf` functions in `Material.c` to utilize the new `shading_context_t` structure.
Updated the `material_collection_init` function in `Material.h` to reflect changes in the material sampling functions.
Updated the `evaluate_bsdf_directional` and `evaluate_bsdf_const_sky` functions to use the new shading context structure.
Adjusted the `simple_lit_data_default` function in `SimpleLit.c` to work with the new texture handling approach.
Added texture entity handling in `Texture.c` for managing invalid texture entities.
2025-04-29 13:29:29 +09:00

193 lines
8.4 KiB
C

#include "Material/SimpleLit.h"
#include "Algorithm/BSDF.h"
#include "Algorithm/Sobol.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%)
void simple_lit_data_default(const shading_context_t* context, const void* properties, void* data_out)
{
const simple_lit_properties_t* prop = (simple_lit_properties_t*)properties;
simple_lit_data_t* data = (simple_lit_data_t*)data_out;
data->albedo = prop->albedo;
const texture_t* albedo_texture = get_texture(context->textures, prop->albedo_texture);
if (albedo_texture != NULL && albedo_texture->data != NULL)
{
data->albedo = glms_vec3_mul(data->albedo, glms_vec3(texture_sample(albedo_texture, context->uv.x, context->uv.y)));
}
data->roughness = prop->roughness;
const texture_t* roughness_texture = get_texture(context->textures, prop->roughness_texture);
if (roughness_texture != NULL && roughness_texture->data != NULL)
{
data->roughness = data->roughness * texture_sample(roughness_texture, context->uv.x, context->uv.y).x;
}
data->metallic = prop->metallic;
const texture_t* metallic_texture = get_texture(context->textures, prop->metallic_texture);
if (metallic_texture != NULL && metallic_texture->data != NULL)
{
data->metallic = data->metallic * texture_sample(metallic_texture, context->uv.x, context->uv.y).x;
}
}
// Simple lit, but keep it unbiased as much as possible
vec3s sample_bsdf_simple_lit(const shading_context_t* context, const void* properties, const compute_surface_data_f compute_surface_data, uint32_t sample_index, uint32_t bounce, float* pdf_out)
{
simple_lit_data_t shading_data;
compute_surface_data(context, properties, &shading_data);
//TODO: having a bsdf data struct to avoid recomputing the same thing in both sample and evaluate
vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, shading_data.albedo, shading_data.metallic);
float cos_theta_0 = fmaxf(glms_vec3_dot(context->normal, context->wo), 0.0f);
vec3s f = fresnel_schlick_vec3(f0, cos_theta_0);
float lum_f = (f.x + f.y + f.z) / 3.0f;
float prob_specular = glm_lerp(lum_f, 1.0f, shading_data.metallic);
float prob_diffuse = (1.0f - shading_data.metallic) * (1.0f - lum_f); // Diffuse only for non-metals, reduced by reflection
float total_prob = prob_diffuse + prob_specular;
if (total_prob < FLT_EPSILON)
{
*pdf_out = 0.0f;
return glms_vec3_zero();
}
// Normalize probabilities
// total_prob should be 1.0f, worth it? Maybe still need to avoid floating point errors
prob_diffuse /= total_prob;
prob_specular /= total_prob;
vec3s wi;
float lob_sample = sobol_sample(sample_index, sobol_get_dimension(bounce, PRNG_BSDF));
uint16_t d1 = sobol_get_dimension(bounce, PRNG_BSDF_U);
uint16_t d2 = sobol_get_dimension(bounce, PRNG_BSDF_V);
if (lob_sample < prob_diffuse) // Diffuse Lobe
{
wi = random_cosine_direction(context->normal, sample_index, d1, d2);
}
else // Specular Lobe
{
// For simplification we use blinn-phong lobe distribution, we will implement GGX for standard lit later
// When talking about simplification, wen even can use a simple interpolation bwtween roughness and wi, but it's too biased.
// A common simplification involves sampling spherical coordinates(theta and phi angles) related to normal such that cose(theta) is distributed according to the Blinn-Phong distribution
// We can use a inversion sampling where cos(theta) = powf(random_float(), 1.0f / (specular_exponent + 1.0f)) and phi = 2 * PI * random_float()
float specular_exponent = roughness_to_blinn_phong_specular_exponent(shading_data.roughness);
float theta = acosf(powf(sobol_sample(sample_index, d1), 1.0f / (specular_exponent + 1.0f)));
float phi = 2.0f * (float)M_PI * sobol_sample(sample_index, d2);
// float theta = acosf(powf(random_float(), 1.0f / (specular_exponent + 1.0f)));
// float phi = 2.0f * (float)M_PI * random_float();
vec3s h_ts = (vec3s)
{
sinf(theta) * cosf(phi),
sinf(theta) * sinf(phi),
cosf(theta)
};
vec3s tangent_u; // World-space tangent (U)
vec3s bitangent_v; // World-space bitangent (V)
create_orthonormal_basis(context->normal, &tangent_u, &bitangent_v);
vec3s scaled_u = glms_vec3_scale(tangent_u, h_ts.x);
vec3s scaled_v = glms_vec3_scale(bitangent_v, h_ts.y);
vec3s scaled_n = glms_vec3_scale(context->normal, h_ts.z);
// Transform h from tangent space to world space
vec3s h_ws;
h_ws = glms_vec3_add(scaled_u, scaled_v);
h_ws = glms_vec3_add(h_ws, scaled_n);
h_ws = glms_vec3_normalize(h_ws);
// wi is simple now, just reflect wo around normal
wi = glms_vec3_reflect(glms_vec3_negate(context->wo), h_ws);
}
// Final check to ensure wi is in the correct hemisphere
if (glms_vec3_dot(wi, context->normal) < 0.0f)
{
*pdf_out = 0.0f;
return glms_vec3_zero();
}
float pdf_diffuse = pdf_cosine_weighted_hemisphere(context->normal, wi);
float pdf_specular = pdf_blinn_phong_lobe(context->normal, wi, context->wo, shading_data.roughness);
*pdf_out = prob_diffuse * pdf_diffuse + prob_specular * pdf_specular;
return wi;
}
float sample_bsdf_pdf_simple_lit(const shading_context_t* context, const void* properties, const compute_surface_data_f compute_surface_data)
{
// If wi is below the horizon relative to the normal, PDF must be 0
if (glms_vec3_dot(context->normal, context->wi) <= 0.0f) // Use <= to be safe
{
return 0.0f;
}
simple_lit_data_t shading_data;
compute_surface_data(context, properties, &shading_data);
// Again, we need bsdf data;
vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, shading_data.albedo, shading_data.metallic);
float cos_theta_o = fmaxf(glms_vec3_dot(context->normal, context->wo), 0.0f); // Use 'o' for outgoing (wo)
float F = glms_vec3_max(fresnel_schlick_vec3(f0, cos_theta_o));
float prob_specular = glm_lerp(F, 1.0f, shading_data.metallic);
float prob_diffuse = (1.0f - shading_data.metallic) * (1.0f - F);
float total_prob = prob_diffuse + prob_specular;
if (total_prob < FLT_EPSILON)
{
return 0.0f; // No probability of scattering
}
prob_diffuse /= total_prob;
prob_specular /= total_prob;
float pdf_diff = pdf_cosine_weighted_hemisphere(context->normal, context->wi);
float diffuse_pdf_component = prob_diffuse * pdf_diff;
float pdf_spec = pdf_blinn_phong_lobe(context->normal, context->wo, context->wi, shading_data.roughness);
float specular_pdf_component = prob_specular * pdf_spec;
return diffuse_pdf_component + specular_pdf_component;
}
vec3s evaluate_bsdf_simple_lit(const shading_context_t* context, const void* properties, const compute_surface_data_f compute_surface_data)
{
simple_lit_data_t shading_data;
compute_surface_data(context, properties, &shading_data);
vec3s h = glms_vec3_normalize(glms_vec3_add(context->wi, context->wo));
float n_dot_l = fmaxf(FLT_EPSILON, glms_vec3_dot(context->normal, context->wi));
float n_dot_v = fmaxf(FLT_EPSILON, glms_vec3_dot(context->normal, context->wo));
float n_dot_h = glms_vec3_dot(context->normal, h);
float v_dot_h = glms_vec3_dot(context->wo, h);
vec3s f0 = glms_vec3_lerp(DIELECTRIC_REFLECTIVE, shading_data.albedo, shading_data.metallic);
vec3s diffuse_color = glms_vec3_scale(shading_data.albedo, 1.0f - shading_data.metallic);
vec3s diffuse_term = glms_vec3_scale(diffuse_color, (float)M_1_PI);
float specular_exponent = roughness_to_blinn_phong_specular_exponent(shading_data.roughness);
// Normalization factor D (Blinn-Phong distribution)
float D_norm = (specular_exponent + 2.0f) / (2.0f * (float)M_PI); // Common normalization
float D = D_norm * powf(n_dot_h, specular_exponent);
vec3s F = fresnel_schlick_vec3(f0, v_dot_h);
float denominator = 4.0f * n_dot_l * n_dot_v;
if (denominator < FLT_EPSILON)
{
return diffuse_term;
}
vec3s specular_term = glms_vec3_scale(F, D / denominator); // Specular term (Blinn-Phong), we assume that G = 1.0f for simplicity
return glms_vec3_add(diffuse_term, specular_term);
}