blender/intern/cycles/kernel/closure/volume.h
Brecht Van Lommel 54f543e4c4 Fix #161234: Cycles caustics blurred out after ray differential widening
The ray differential widening for the texture cache blurred out caustics
both from procedural textures and image textures. The widening helps
reduce texture cache memory, but is also fundamentally biased.

The solution now is to tie this to the Filter Glossy setting that must
already be lowered from the default to make caustics work. When left at
the default the texture cache will be most efficient, when set to small
value or zero there will be little or no widening.

So this setting now becomes a more general bias control for shading
after a rough glossy or diffuse bounce, and the tooltip was updated.

Pull Request: https://projects.blender.org/blender/blender/pulls/162153
2026-08-05 21:34:23 +02:00

193 lines
6.5 KiB
C

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/types.h"
#include "kernel/closure/volume_draine.h"
#include "kernel/closure/volume_fournier_forand.h"
#include "kernel/closure/volume_henyey_greenstein.h"
#include "kernel/closure/volume_rayleigh.h"
CCL_NAMESPACE_BEGIN
/* VOLUME EXTINCTION */
ccl_device void volume_extinction_setup(ccl_private ShaderData *sd, Spectrum weight)
{
if (sd->runtime_flag & SR_EXTINCTION) {
sd->closure_transparent_extinction += weight;
}
else {
sd->runtime_flag |= SR_EXTINCTION;
sd->closure_transparent_extinction = weight;
}
}
/* VOLUME SCATTERING */
ccl_device Spectrum volume_phase_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private float *pdf)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
return volume_fournier_forand_eval(sd, svc, wo, pdf);
case CLOSURE_VOLUME_RAYLEIGH_ID:
return volume_rayleigh_eval(sd, wo, pdf);
case CLOSURE_VOLUME_DRAINE_ID:
return volume_draine_eval(sd, svc, wo, pdf);
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return volume_henyey_greenstein_eval(sd, svc, wo, pdf);
default:
kernel_assert(false);
*pdf = 0.0f;
return zero_spectrum();
}
}
ccl_device int volume_phase_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand,
ccl_private Spectrum *eval,
ccl_private float3 *wo,
ccl_private float *pdf)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
return volume_fournier_forand_sample(sd, svc, rand, eval, wo, pdf);
case CLOSURE_VOLUME_RAYLEIGH_ID:
return volume_rayleigh_sample(sd, rand, eval, wo, pdf);
case CLOSURE_VOLUME_DRAINE_ID:
return volume_draine_sample(sd, svc, rand, eval, wo, pdf);
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return volume_henyey_greenstein_sample(sd, svc, rand, eval, wo, pdf);
default:
kernel_assert(false);
*pdf = 0.0f;
return 0;
}
}
/* Widen the compact ray differential dD after a phase function scatter to
* match the lobe's angular spread. See bsdf_widen_dD for details. */
ccl_device_forceinline float volume_phase_widen_dD(const KernelGlobals kg,
const float prev_dD,
const float sampled_roughness)
{
return max(prev_dD, kernel_data.integrator.differential_widen_scale * sampled_roughness);
}
ccl_device bool volume_phase_equal(const ccl_private ShaderClosure *c1,
const ccl_private ShaderClosure *c2)
{
if (c1->type != c2->type) {
return false;
}
switch (c1->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID: {
ccl_private FournierForandVolume *v1 = (ccl_private FournierForandVolume *)c1;
ccl_private FournierForandVolume *v2 = (ccl_private FournierForandVolume *)c2;
return v1->c1 == v2->c1 && v1->c2 == v2->c2 && v1->c3 == v2->c3;
}
case CLOSURE_VOLUME_RAYLEIGH_ID:
return true;
case CLOSURE_VOLUME_DRAINE_ID: {
ccl_private DraineVolume *v1 = (ccl_private DraineVolume *)c1;
ccl_private DraineVolume *v2 = (ccl_private DraineVolume *)c2;
return v1->g == v2->g && v1->alpha == v2->alpha;
}
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: {
ccl_private HenyeyGreensteinVolume *v1 = (ccl_private HenyeyGreensteinVolume *)c1;
ccl_private HenyeyGreensteinVolume *v2 = (ccl_private HenyeyGreensteinVolume *)c2;
return v1->g == v2->g;
}
default:
return false;
}
return false;
}
/* Approximate phase functions as Henyey-Greenstein for volume guiding.
* TODO: This is not ideal, we should use RIS guiding for non-HG phase functions. */
ccl_device float volume_phase_get_g(const ccl_private ShaderVolumeClosure *svc)
{
switch (svc->type) {
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
/* TODO */
return 1.0f;
case CLOSURE_VOLUME_RAYLEIGH_ID:
/* Approximate as isotropic */
return 0.0f;
case CLOSURE_VOLUME_DRAINE_ID:
/* Approximate as HG, TODO */
return ((ccl_private DraineVolume *)svc)->g;
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
return ((ccl_private HenyeyGreensteinVolume *)svc)->g;
default:
return 0.0f;
}
}
/* Volume sampling utilities. */
/* Ignore paths that have volume throughput below this value, to avoid unnecessary work
* and precision issues.
* TODO: this value could be tweaked or turned into a probability to avoid unnecessary work in
* volumes and subsurface scattering. */
#define VOLUME_THROUGHPUT_EPSILON 1e-6f
ccl_device Spectrum volume_color_transmittance(Spectrum sigma, const float t)
{
return exp(-sigma * t);
}
ccl_device float volume_channel_get(Spectrum value, const int channel)
{
return GET_SPECTRUM_CHANNEL(value, channel);
}
/* Sample color channel proportional to throughput and single scattering albedo, to significantly
* reduce noise with many bounce, following:
*
* "Practical and Controllable Subsurface Scattering for Production Path Tracing".
* Matt Jen-Yuan Chiang, Peter Kutz, Brent Burley. SIGGRAPH 2016. */
ccl_device_inline Spectrum volume_sample_channel_pdf(Spectrum albedo, Spectrum throughput)
{
const Spectrum weights = fabs(throughput * albedo);
const float sum_weights = reduce_add(weights);
if ((1.0f - sum_weights) < 1.0f) {
/* The same as `sum_weights > 0.0f`, but avoids the case where `sum_weight` is denormal, which
* could produce `nan` after division. */
return weights / sum_weights;
}
return make_spectrum(1.0f / SPECTRUM_CHANNELS);
}
ccl_device int volume_sample_channel(Spectrum albedo,
Spectrum throughput,
ccl_private float *rand,
ccl_private Spectrum *pdf)
{
*pdf = volume_sample_channel_pdf(albedo, throughput);
float pdf_sum = 0.0f;
FOREACH_SPECTRUM_CHANNEL (i) {
const float channel_pdf = GET_SPECTRUM_CHANNEL(*pdf, i);
if (*rand < pdf_sum + channel_pdf) {
/* Rescale to reuse. */
*rand = (*rand - pdf_sum) / channel_pdf;
return i;
}
pdf_sum += channel_pdf;
}
return SPECTRUM_CHANNELS - 1;
}
CCL_NAMESPACE_END