Files
blender/intern/cycles/kernel/closure/bsdf_microfacet_multi_impl.h
Andrii Symkin d832d993c5 Cycles: add new Spectrum and PackedSpectrum types
These replace float3 and packed_float3 in various places in the kernel where a
spectral color representation will be used in the future. That representation
will require more than 3 channels and conversion to from/RGB. The kernel code
was refactored to remove the assumption that Spectrum and RGB colors are the
same thing.

There are no functional changes, Spectrum is still a float3 and the conversion
functions are no-ops.

Differential Revision: https://developer.blender.org/D15535
2022-08-09 16:49:34 +02:00

263 lines
9.1 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
float F0 = fresnel_dielectric_cos(1.0f, eta);
if (use_fresnel) {
throughput = interpolate_fresnel_color(wi, wh, eta, F0, 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, F0, cspec0);
}
#else /* MF_MULTI_GLOSSY */
if (use_fresnel && order > 0) {
throughput *= interpolate_fresnel_color(-wr, wm, eta, F0, 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;
float F0 = fresnel_dielectric_cos(1.0f, eta);
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 {
Spectrum t_color = interpolate_fresnel_color(wi_prev, wm, eta, F0, cspec0);
if (order == 0)
throughput = t_color;
else
throughput *= t_color;
}
}
#else /* MF_MULTI_GLOSSY */
if (use_fresnel) {
Spectrum t_color = interpolate_fresnel_color(-wr, wm, eta, F0, cspec0);
if (order == 0)
throughput = t_color;
else
throughput *= t_color;
}
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