Fixed genergy conservation problem in specular lobe
This commit is contained in:
@@ -27,12 +27,12 @@ static float oren_nayar_eval(vec3s l, vec3s v, vec3s n, float roughness, float n
|
||||
if (n_dot_v > n_dot_l)
|
||||
{
|
||||
sin_alpha = sin_theta_l;
|
||||
tan_beta = sin_theta_v / fmaxf(n_dot_v, 0.0001f);
|
||||
tan_beta = sin_theta_v / fmaxf(n_dot_v, FLT_EPSILON);
|
||||
}
|
||||
else
|
||||
{
|
||||
sin_alpha = sin_theta_v;
|
||||
tan_beta = sin_theta_l / fmaxf(n_dot_l, 0.0001f);
|
||||
tan_beta = sin_theta_l / fmaxf(n_dot_l, FLT_EPSILON);
|
||||
}
|
||||
|
||||
return (A + B * cos_phi_diff * sin_alpha * tan_beta) * INV_PI;
|
||||
@@ -117,19 +117,20 @@ static vec3s evaluate_bsdf_standard_lit(const shading_context_t* context, standa
|
||||
// Specular (GGX)
|
||||
float D = ggx_distribution(n_dot_h, surface_data->roughness);
|
||||
float G = ggx_g_smith(n_dot_v, n_dot_l, surface_data->roughness);
|
||||
vec3s spec = glms_vec3_scale(glms_vec3_mul(F, (vec3s){D * G, D * G, D * G}), 1.0f / fmaxf(4.0f * n_dot_v * n_dot_l, 0.0001f));
|
||||
vec3s spec = glms_vec3_scale(glms_vec3_mul(F, (vec3s){D * G, D * G, D * G}),
|
||||
1.0f / fmaxf(4.0f * n_dot_v * n_dot_l, FLT_EPSILON));
|
||||
|
||||
// Multi-scatter GGX (broad lobe)
|
||||
vec3s ms = ggx_multi_scatter_lambert(f0, n_dot_v, n_dot_l, surface_data->roughness);
|
||||
|
||||
// Diffuse (Oren-Nayar)
|
||||
// Using (1 - F) here can make rough dielectrics look too dark because our specular is single-scatter GGX
|
||||
// (missing multi-scattering energy compensation). A stable approximation is (1 - F0_diel).
|
||||
float kd_scale = (1.0f - surface_data->metallic) * (1.0f - DIELECTRIC_REFLECTIVE_F0);
|
||||
vec3s kD = glms_vec3_scale(glms_vec3_one(), kd_scale);
|
||||
vec3s kD = glms_vec3_sub(glms_vec3_one(), F);
|
||||
kD = glms_vec3_scale(kD, (1.0f - surface_data->metallic));
|
||||
|
||||
float on_val = oren_nayar_eval(l, v, n, surface_data->diffuse_roughness, n_dot_l, n_dot_v);
|
||||
vec3s diff = glms_vec3_scale(glms_vec3_mul(surface_data->albedo, kD), on_val);
|
||||
|
||||
// return (vec3s){n_dot_h, n_dot_h, n_dot_h};
|
||||
return glms_vec3_add(glms_vec3_add(diff, spec), ms);
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ static float sample_bsdf_pdf(const standard_lit_surface_data_t* surface_data, ve
|
||||
// Lobe probabilities (single-scatter spec vs cosine)
|
||||
// We allocate some cosine probability for multi-scatter spec, especially for rough metals.
|
||||
vec3s f0 = glms_vec3_lerp(DIELECTRIC_F0, surface_data->albedo, surface_data->metallic);
|
||||
float n_dot_v = fmaxf(glms_vec3_dot(surface_data->normal, V), 0.0001f);
|
||||
float n_dot_v = fmaxf(glms_vec3_dot(surface_data->normal, V), 0.0f);
|
||||
vec3s F_est = fresnel_schlick_vec3(f0, n_dot_v);
|
||||
|
||||
float spec_strength = luminance(F_est);
|
||||
@@ -159,6 +160,7 @@ static float sample_bsdf_pdf(const standard_lit_surface_data_t* surface_data, ve
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
w_ss /= sum_w;
|
||||
w_cos /= sum_w;
|
||||
|
||||
@@ -168,16 +170,17 @@ static float sample_bsdf_pdf(const standard_lit_surface_data_t* surface_data, ve
|
||||
// p_w(wi) = p_h(h) / (4 * (V·H))
|
||||
vec3s H = glms_vec3_normalize(glms_vec3_add(L, V));
|
||||
float v_dot_h = glms_vec3_dot(V, H);
|
||||
if (v_dot_h <= 1e-6f)
|
||||
if (v_dot_h <= FLT_EPSILON)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float n_dot_h = fmaxf(glms_vec3_dot(surface_data->normal, H), 0.0f);
|
||||
float D = ggx_distribution(n_dot_h, surface_data->roughness);
|
||||
float G1v = ggx_g1(n_dot_v, surface_data->roughness);
|
||||
|
||||
float pdf_h = (D * G1v * n_dot_h) / fmaxf(n_dot_v, 1e-6f);
|
||||
float pdf_spec = pdf_h / (4.0f * fmaxf(v_dot_h, 1e-6f));
|
||||
float pdf_h = (D * G1v * v_dot_h) / fmaxf(n_dot_v, FLT_EPSILON);
|
||||
float pdf_spec = pdf_h / (4.0f * fmaxf(v_dot_h, FLT_EPSILON));
|
||||
|
||||
// Cosine PDF (used for diffuse + multi-scatter)
|
||||
float pdf_cos = n_dot_l * INV_PI;
|
||||
@@ -210,7 +213,7 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
|
||||
surface_data.normal = context->normal;
|
||||
}
|
||||
|
||||
float n_dot_v = fmaxf(glms_vec3_dot(surface_data.normal, V), 0.0001f);
|
||||
float n_dot_v = fmaxf(glms_vec3_dot(surface_data.normal, V), 0.0f);
|
||||
|
||||
// Ensure LUT is ready (thread-safe, one-time).
|
||||
ggx_ms_init_lut_once();
|
||||
@@ -284,7 +287,7 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
|
||||
|
||||
vec3s f_eval = glms_vec3_zero();
|
||||
float n_dot_l = 0.0f;
|
||||
float r_lobe = sobol_sample_scrambled(context->sample_index, sobol_get_dimension(context->bounce_depth, PRNG_BSDF), scramble);
|
||||
float r_lobe = sobol_sample(context->sample_index, sobol_get_dimension(context->bounce_depth, PRNG_BSDF));
|
||||
bool is_specular = (r_lobe < w_ss);
|
||||
|
||||
if (is_specular)
|
||||
@@ -297,7 +300,7 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
|
||||
float u2 = sobol_sample_scrambled(context->sample_index, d2, scramble);
|
||||
|
||||
vec3s H = ggx_sample_vndf(surface_data.normal, V, surface_data.roughness, u1, u2);
|
||||
output.wi = glms_vec3_reflect(context->wo, H); // reflect(-V, H) -> V is wo inverted
|
||||
output.wi = glms_vec3_reflect(context->wo, H);
|
||||
|
||||
if (glms_vec3_dot(output.wi, surface_data.normal) <= 0.0f || glms_vec3_dot(output.wi, context->normal) <= 0.0f)
|
||||
{
|
||||
@@ -305,62 +308,35 @@ path_output standard_lit_render_loop(const standard_lit_properties_t* properties
|
||||
return output;
|
||||
}
|
||||
|
||||
// Recalculate dots
|
||||
n_dot_l = fmaxf(glms_vec3_dot(surface_data.normal, output.wi), 0.0001f);
|
||||
vec3s H_new = glms_vec3_normalize(glms_vec3_add(output.wi, V));
|
||||
float n_dot_h = fmaxf(glms_vec3_dot(surface_data.normal, H_new), 0.0001f);
|
||||
float v_dot_h = fmaxf(glms_vec3_dot(V, H_new), 0.0001f);
|
||||
n_dot_l = fmaxf(glms_vec3_dot(surface_data.normal, output.wi), FLT_EPSILON);
|
||||
|
||||
// Evaluate BSDF
|
||||
float D = ggx_distribution(n_dot_h, surface_data.roughness);
|
||||
float G1v = ggx_g1(n_dot_v, surface_data.roughness);
|
||||
float G = ggx_g_smith(n_dot_v, n_dot_l, surface_data.roughness);
|
||||
vec3s f0 = glms_vec3_lerp(DIELECTRIC_F0, surface_data.albedo, surface_data.metallic);
|
||||
vec3s F = fresnel_schlick_vec3(f0, v_dot_h);
|
||||
|
||||
vec3s spec_f = glms_vec3_scale(glms_vec3_mul(F, (vec3s){D * G, D * G, D * G}),
|
||||
1.0f / fmaxf(4.0f * n_dot_v * n_dot_l, 1e-6f));
|
||||
|
||||
f_eval = spec_f;
|
||||
|
||||
// Propagate spread angle for ray cones
|
||||
// Heuristic: spread increases with roughness
|
||||
// Spread angle heuristic
|
||||
output.spread_angle = context->spread_angle + surface_data.roughness * QUARTER_PI;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sample Cosine hemisphere (Diffuse + Multi-scatter spec)
|
||||
// Note: We use cosine sampling for Oren-Nayar and the broad MS term.
|
||||
uint32_t d1 = sobol_get_dimension(context->bounce_depth, PRNG_BSDF_U);
|
||||
uint32_t d2 = sobol_get_dimension(context->bounce_depth, PRNG_BSDF_V);
|
||||
output.wi = random_cosine_direction(surface_data.normal, context->sample_index, d1, d2, scramble);
|
||||
|
||||
n_dot_l = fmaxf(glms_vec3_dot(surface_data.normal, output.wi), 0.0001f);
|
||||
if (glms_vec3_dot(output.wi, context->normal) <= 0.0f)
|
||||
{
|
||||
output.state = PS_TERMINATE;
|
||||
return output;
|
||||
}
|
||||
|
||||
float kd_scale = (1.0f - surface_data.metallic) * (1.0f - DIELECTRIC_REFLECTIVE_F0);
|
||||
vec3s kD = glms_vec3_scale(glms_vec3_one(), kd_scale);
|
||||
float on = oren_nayar_eval(output.wi, V, surface_data.normal, surface_data.diffuse_roughness, n_dot_l, n_dot_v);
|
||||
n_dot_l = fmaxf(glms_vec3_dot(surface_data.normal, output.wi), FLT_EPSILON);
|
||||
|
||||
|
||||
// Diffuse bounce significantly increases spread (effectively resets or becomes very wide)
|
||||
// Diffuse bounce spread
|
||||
output.spread_angle = context->spread_angle + 0.5f;
|
||||
vec3s diff_f = glms_vec3_scale(glms_vec3_mul(surface_data.albedo, kD), on);
|
||||
|
||||
// Multi-scatter GGX term (broad): sampled here with cosine.
|
||||
vec3s ms_f = ggx_multi_scatter_lambert(f0, n_dot_v, n_dot_l, surface_data.roughness);
|
||||
|
||||
// Throughput multiplier: (f * NoL) / pdf
|
||||
vec3s f_sum = glms_vec3_add(diff_f, ms_f);
|
||||
f_eval = f_sum;
|
||||
}
|
||||
|
||||
// IMPORTANT: evaluate FULL BSDF for the sampled direction (unbiased for mixture sampling)
|
||||
f_eval = evaluate_bsdf_standard_lit(context, &surface_data, output.wi);
|
||||
|
||||
output.pdf = sample_bsdf_pdf(&surface_data, V, output.wi);
|
||||
if (output.pdf < 1e-12f)
|
||||
if (output.pdf < FLT_EPSILON)
|
||||
{
|
||||
output.state = PS_TERMINATE;
|
||||
return output;
|
||||
|
||||
Reference in New Issue
Block a user