Changed README.md to update rendering settings and build instructions. Changed BSDF.h to add functions for normal unpacking and tangent transformation. Changed RayIntersection.h to include tangent vector in hit_result_t. Changed Common.h to include vec2.h for 2D vector handling. Changed String.h to add string_copy function and improve is_absolute_path. Changed GeometryUtilities.h to enhance quad creation with tangent calculations. Changed Mesh.h to include tangents in the vertex structure. Changed Triangle.h to add tangents in the vertex structure for better normal mapping. Changed Light.h to include tangents in the light shading context. Changed SkyLight.h to introduce a new structure for sky lights. Changed Material.h to include tangents in the shading context. Changed SimpleLit.h to add normal and tangent textures for detailed shading. Changed Texture.h to introduce a new structure for texture assets. Changed BSDF.c to add functions for unpacking normals and transforming tangents. Changed PathTracing.c to include tangents in the shading context. Changed RayIntersection.c to calculate normals and tangents in ray-triangle intersections. Changed Mesh.c to improve material texture loading and handle tangents. Changed Material.c to enhance material collection initialization and resizing. Changed SimpleLit.c to incorporate normal mapping with normal textures. Changed Texture.c to improve management of texture assets and resources.
203 lines
8.9 KiB
C
203 lines
8.9 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)));
|
|
}
|
|
|
|
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->normal = normal_ts_to_ws(normal_sample, context->tangent);
|
|
}
|
|
else
|
|
{
|
|
data->normal = context->normal;
|
|
}
|
|
|
|
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);
|
|
}
|