This is both a cleanup and a preparation for the Principled v2 changes. Notable changes: - Clearcoat weight is now folded into the closure weight, there's no reason to track this separately. - There's a general-purpose helper for computing a Closure's albedo, which is currently used by the denoising albedo and diffuse/gloss/transmission color passes. - The d/g/t color passes didn't account for closure albedo before, this means that e.g. metallic shaders with Principled v2 now have their color texture included in the glossy color pass. Also fixes T104041 (sheen albedo). - Instead of precomputing and storing the albedo during shader setup, compute it when needed. This is technically redundant since we still need to compute it on shader setup to adjust the sample weight, but the operation is cheap enough that freeing up the storage seems worth it. - Future changes (Principled v2) are easier to integrate since the Fresnel handling isn't all over the place anymore. - Fresnel handling in the Multiscattering GGX code is still ugly, but since removing that entirely is the next step, putting effort into cleaning it up doesn't seem worth it. - Apart from the d/g/t color passes, no changes to render results are expected. Differential Revision: https://developer.blender.org/D17101
250 lines
8.8 KiB
C++
250 lines
8.8 KiB
C++
/* SPDX-License-Identifier: Apache-2.0
|
|
* Copyright 2011-2022 Blender Foundation */
|
|
|
|
/* Evaluate the BSDF from wi to wo.
|
|
* Evaluation is split into the analytical single-scattering BSDF and the multi-scattering BSDF,
|
|
* which is evaluated stochastically through a random walk. At each bounce (except for the first
|
|
* one), the amount of reflection from here towards wo is evaluated before bouncing again.
|
|
*
|
|
* Because of the random walk, the evaluation is not deterministic, but its expected value is equal
|
|
* to the correct BSDF, which is enough for Monte-Carlo rendering. The PDF also can't be determined
|
|
* analytically, so the single-scattering PDF plus a diffuse term to account for the
|
|
* multi-scattered energy is used. In combination with MIS, that is enough to produce an unbiased
|
|
* result, although the balance heuristic isn't necessarily optimal anymore.
|
|
*/
|
|
ccl_device_forceinline Spectrum MF_FUNCTION_FULL_NAME(mf_eval)(float3 wi,
|
|
float3 wo,
|
|
const bool wo_outside,
|
|
const Spectrum color,
|
|
const float alpha_x,
|
|
const float alpha_y,
|
|
ccl_private uint *lcg_state,
|
|
const float eta,
|
|
bool use_fresnel,
|
|
const Spectrum cspec0)
|
|
{
|
|
/* Evaluating for a shallower incoming direction produces less noise, and the properties of the
|
|
* BSDF guarantee reciprocity. */
|
|
bool swapped = false;
|
|
#ifdef MF_MULTI_GLASS
|
|
if (wi.z * wo.z < 0.0f) {
|
|
/* Glass transmission is a special case and requires the directions to change hemisphere. */
|
|
if (-wo.z < wi.z) {
|
|
swapped = true;
|
|
float3 tmp = -wo;
|
|
wo = -wi;
|
|
wi = tmp;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
if (wo.z < wi.z) {
|
|
swapped = true;
|
|
float3 tmp = wo;
|
|
wo = wi;
|
|
wi = tmp;
|
|
}
|
|
|
|
if (wi.z < 1e-5f || (wo.z < 1e-5f && wo_outside) || (wo.z > -1e-5f && !wo_outside))
|
|
return zero_spectrum();
|
|
|
|
const float2 alpha = make_float2(alpha_x, alpha_y);
|
|
|
|
float lambda_r = mf_lambda(-wi, alpha);
|
|
float shadowing_lambda = mf_lambda(wo_outside ? wo : -wo, alpha);
|
|
|
|
/* Analytically compute single scattering for lower noise. */
|
|
Spectrum eval;
|
|
Spectrum throughput = one_spectrum();
|
|
const float3 wh = normalize(wi + wo);
|
|
#ifdef MF_MULTI_GLASS
|
|
eval = mf_eval_phase_glass(-wi, lambda_r, wo, wo_outside, alpha, eta);
|
|
if (wo_outside)
|
|
eval *= -lambda_r / (shadowing_lambda - lambda_r);
|
|
else
|
|
eval *= -lambda_r * beta(-lambda_r, shadowing_lambda + 1.0f);
|
|
#else /* MF_MULTI_GLOSSY */
|
|
const float G2 = 1.0f / (1.0f - (lambda_r + 1.0f) + shadowing_lambda);
|
|
float val = G2 * 0.25f / wi.z;
|
|
if (alpha.x == alpha.y)
|
|
val *= D_ggx(wh, alpha.x);
|
|
else
|
|
val *= D_ggx_aniso(wh, alpha);
|
|
eval = make_spectrum(val);
|
|
#endif
|
|
|
|
if (use_fresnel) {
|
|
throughput = interpolate_fresnel_color(wi, wh, eta, cspec0);
|
|
|
|
eval *= throughput;
|
|
}
|
|
|
|
float3 wr = -wi;
|
|
float hr = 1.0f;
|
|
float C1_r = 1.0f;
|
|
float G1_r = 0.0f;
|
|
bool outside = true;
|
|
|
|
for (int order = 0; order < 10; order++) {
|
|
/* Sample microfacet height. */
|
|
float height_rand = lcg_step_float(lcg_state);
|
|
if (!mf_sample_height(wr, &hr, &C1_r, &G1_r, &lambda_r, height_rand))
|
|
break;
|
|
/* Sample microfacet normal. */
|
|
float vndf_rand_y = lcg_step_float(lcg_state);
|
|
float vndf_rand_x = lcg_step_float(lcg_state);
|
|
float3 wm = mf_sample_vndf(-wr, alpha, vndf_rand_x, vndf_rand_y);
|
|
|
|
#ifdef MF_MULTI_GLASS
|
|
if (order == 0 && use_fresnel) {
|
|
/* Evaluate amount of scattering towards wo on this microfacet. */
|
|
Spectrum phase;
|
|
if (outside)
|
|
phase = mf_eval_phase_glass(wr, lambda_r, wo, wo_outside, alpha, eta);
|
|
else
|
|
phase = mf_eval_phase_glass(wr, lambda_r, -wo, !wo_outside, alpha, 1.0f / eta);
|
|
|
|
eval = throughput * phase *
|
|
mf_G1(wo_outside ? wo : -wo,
|
|
mf_C1((outside == wo_outside) ? hr : -hr),
|
|
shadowing_lambda);
|
|
}
|
|
#endif
|
|
if (order > 0) {
|
|
/* Evaluate amount of scattering towards wo on this microfacet. */
|
|
Spectrum phase;
|
|
#ifdef MF_MULTI_GLASS
|
|
if (outside)
|
|
phase = mf_eval_phase_glass(wr, lambda_r, wo, wo_outside, alpha, eta);
|
|
else
|
|
phase = mf_eval_phase_glass(wr, lambda_r, -wo, !wo_outside, alpha, 1.0f / eta);
|
|
#else /* MF_MULTI_GLOSSY */
|
|
phase = mf_eval_phase_glossy(wr, lambda_r, wo, alpha) * throughput;
|
|
#endif
|
|
eval += throughput * phase *
|
|
mf_G1(wo_outside ? wo : -wo,
|
|
mf_C1((outside == wo_outside) ? hr : -hr),
|
|
shadowing_lambda);
|
|
}
|
|
if (order + 1 < 10) {
|
|
/* Bounce from the microfacet. */
|
|
#ifdef MF_MULTI_GLASS
|
|
bool next_outside;
|
|
float3 wi_prev = -wr;
|
|
float phase_rand = lcg_step_float(lcg_state);
|
|
wr = mf_sample_phase_glass(-wr, outside ? eta : 1.0f / eta, wm, phase_rand, &next_outside);
|
|
if (!next_outside) {
|
|
outside = !outside;
|
|
wr = -wr;
|
|
hr = -hr;
|
|
}
|
|
|
|
if (use_fresnel && !next_outside) {
|
|
throughput *= color;
|
|
}
|
|
else if (use_fresnel && order > 0) {
|
|
throughput *= interpolate_fresnel_color(wi_prev, wm, eta, cspec0);
|
|
}
|
|
#else /* MF_MULTI_GLOSSY */
|
|
if (use_fresnel && order > 0) {
|
|
throughput *= interpolate_fresnel_color(-wr, wm, eta, cspec0);
|
|
}
|
|
wr = mf_sample_phase_glossy(-wr, &throughput, wm);
|
|
#endif
|
|
|
|
lambda_r = mf_lambda(wr, alpha);
|
|
|
|
if (!use_fresnel)
|
|
throughput *= color;
|
|
|
|
C1_r = mf_C1(hr);
|
|
G1_r = mf_G1(wr, C1_r, lambda_r);
|
|
}
|
|
}
|
|
|
|
if (swapped)
|
|
eval *= fabsf(wi.z / wo.z);
|
|
return eval;
|
|
}
|
|
|
|
/* Perform a random walk on the microsurface starting from wi, returning the direction in which the
|
|
* walk escaped the surface in wo. The function returns the throughput between wi and wo. Without
|
|
* reflection losses due to coloring or fresnel absorption in conductors, the sampling is optimal.
|
|
*/
|
|
ccl_device_forceinline Spectrum MF_FUNCTION_FULL_NAME(mf_sample)(float3 wi,
|
|
ccl_private float3 *wo,
|
|
const Spectrum color,
|
|
const float alpha_x,
|
|
const float alpha_y,
|
|
ccl_private uint *lcg_state,
|
|
const float eta,
|
|
bool use_fresnel,
|
|
const Spectrum cspec0)
|
|
{
|
|
const float2 alpha = make_float2(alpha_x, alpha_y);
|
|
|
|
Spectrum throughput = one_spectrum();
|
|
float3 wr = -wi;
|
|
float lambda_r = mf_lambda(wr, alpha);
|
|
float hr = 1.0f;
|
|
float C1_r = 1.0f;
|
|
float G1_r = 0.0f;
|
|
bool outside = true;
|
|
|
|
int order;
|
|
for (order = 0; order < 10; order++) {
|
|
/* Sample microfacet height. */
|
|
float height_rand = lcg_step_float(lcg_state);
|
|
if (!mf_sample_height(wr, &hr, &C1_r, &G1_r, &lambda_r, height_rand)) {
|
|
/* The random walk has left the surface. */
|
|
*wo = outside ? wr : -wr;
|
|
return throughput;
|
|
}
|
|
/* Sample microfacet normal. */
|
|
float vndf_rand_y = lcg_step_float(lcg_state);
|
|
float vndf_rand_x = lcg_step_float(lcg_state);
|
|
float3 wm = mf_sample_vndf(-wr, alpha, vndf_rand_x, vndf_rand_y);
|
|
|
|
/* First-bounce color is already accounted for in mix weight. */
|
|
if (!use_fresnel && order > 0)
|
|
throughput *= color;
|
|
|
|
/* Bounce from the microfacet. */
|
|
#ifdef MF_MULTI_GLASS
|
|
bool next_outside;
|
|
float3 wi_prev = -wr;
|
|
float phase_rand = lcg_step_float(lcg_state);
|
|
wr = mf_sample_phase_glass(-wr, outside ? eta : 1.0f / eta, wm, phase_rand, &next_outside);
|
|
if (!next_outside) {
|
|
hr = -hr;
|
|
wr = -wr;
|
|
outside = !outside;
|
|
}
|
|
|
|
if (use_fresnel) {
|
|
if (!next_outside) {
|
|
throughput *= color;
|
|
}
|
|
else {
|
|
throughput *= interpolate_fresnel_color(wi_prev, wm, eta, cspec0);
|
|
}
|
|
}
|
|
#else /* MF_MULTI_GLOSSY */
|
|
if (use_fresnel) {
|
|
throughput *= interpolate_fresnel_color(-wr, wm, eta, cspec0);
|
|
}
|
|
wr = mf_sample_phase_glossy(-wr, &throughput, wm);
|
|
#endif
|
|
|
|
/* Update random walk parameters. */
|
|
lambda_r = mf_lambda(wr, alpha);
|
|
G1_r = mf_G1(wr, C1_r, lambda_r);
|
|
}
|
|
*wo = make_float3(0.0f, 0.0f, 1.0f);
|
|
return zero_spectrum();
|
|
}
|
|
|
|
#undef MF_MULTI_GLASS
|
|
#undef MF_MULTI_GLOSSY
|
|
#undef MF_PHASE_FUNCTION
|