Added: - Updated `.gitignore` to ignore the `[Bb]uild/` directory. - Additional tasks added to the roadmap in `README.md` for light unit standardization and GPU backend support. Changed: - Removed line in `settings.json` that disabled error squiggles for C/C++ code. - Modified `Triangle.h` to include `material_id` in `triangle_t` and reorganized properties. - Reordered parameters in `triangle_collection_init` for clarity. - Updated `shading_context_t` in `Material.h` and added size parameter to `material_create`. - Streamlined initialization in `scene_init` and updated `scene_free` for proper resource management. - Updated `window_create` in `Window.h` to accept a `render_job_t` parameter. - Introduced `renderer_start` in `Renderer.c` to handle rendering jobs and optimized pixel rendering logic.
162 lines
7.1 KiB
C
162 lines
7.1 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%)
|
|
|
|
// Simple lit, but keep it unbiased as much as possible
|
|
vec3s sample_bsdf_simple_lit(const void* data, vec3s normal, vec3s wo, uint32_t sample_index, uint32_t bounce, float* pdf_out)
|
|
{
|
|
simple_lit_data_t shading_data = *(const simple_lit_data_t*)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(normal, 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(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(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(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(wo), h_ws);
|
|
}
|
|
|
|
// Final check to ensure wi is in the correct hemisphere
|
|
if (glms_vec3_dot(wi, normal) < 0.0f)
|
|
{
|
|
*pdf_out = 0.0f;
|
|
return glms_vec3_zero();
|
|
}
|
|
|
|
float pdf_diffuse = pdf_cosine_weighted_hemisphere(normal, wi);
|
|
float pdf_specular = pdf_blinn_phong_lobe(normal, wi, wo, shading_data.roughness);
|
|
*pdf_out = prob_diffuse * pdf_diffuse + prob_specular * pdf_specular;
|
|
|
|
return wi;
|
|
}
|
|
|
|
//TODO: Most of the calculation here is same as in sample_bsdf_simple_lit, we can optimize this by using a bsdf data struct to avoid recomputing the same thing in both sample and evaluate
|
|
float sample_bsdf_pdf_simple_lit(const void* data, vec3s normal, vec3s wo, vec3s wi)
|
|
{
|
|
// If wi is below the horizon relative to the normal, PDF must be 0
|
|
if (glms_vec3_dot(normal, wi) <= 0.0f) // Use <= to be safe
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
simple_lit_data_t shading_data = *(const simple_lit_data_t*)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(normal, 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(normal, wi);
|
|
float diffuse_pdf_component = prob_diffuse * pdf_diff;
|
|
|
|
float pdf_spec = pdf_blinn_phong_lobe(normal, wo, 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* data)
|
|
{
|
|
simple_lit_data_t shading_data = *(const simple_lit_data_t*)data;
|
|
shading_context_t shading_context = *context;
|
|
|
|
vec3s h = glms_vec3_normalize(glms_vec3_add(shading_context.wi, shading_context.wo));
|
|
float n_dot_l = fmaxf(FLT_EPSILON, glms_vec3_dot(shading_context.normal, shading_context.wi));
|
|
float n_dot_v = fmaxf(FLT_EPSILON, glms_vec3_dot(shading_context.normal, shading_context.wo));
|
|
float n_dot_h = glms_vec3_dot(shading_context.normal, h);
|
|
float v_dot_h = glms_vec3_dot(shading_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);
|
|
}
|