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
This commit is contained in:
Brecht Van Lommel 2026-08-05 21:34:23 +02:00 • committed by Brecht Van Lommel
parent e157ff6519
commit 54f543e4c4
13 changed files with 39 additions and 19 deletions

View file

@ -658,8 +658,9 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
blur_glossy: FloatProperty(
name="Filter Glossy",
description="Adaptively blur glossy shaders after blurry bounces, "
"to reduce noise at the cost of accuracy",
description="Adaptively blur glossy shaders and image textures after blurry bounces, "
"to reduce noise and improve texture cache efficiency at the cost of accuracy. Lower "
"this value to render caustics",
min=0.0, max=10.0,
default=1.0,
)

View file

@ -68,14 +68,22 @@ ccl_device_inline float bsdf_get_roughness_pass_squared(const ccl_private Shader
*
* To achieve that, the sampled roughness is computed as a MIS weighted
* average. This makes it so directions with high contribution from sharp
* BSDFs have a lower roughness, as they will have a high MIS weight. */
ccl_device_forceinline float bsdf_widen_dD(const float prev_dD, const float avg_roughness_squared)
* BSDFs have a lower roughness, as they will have a high MIS weight.
*
* The amount of widening is scaled by Filter Glossy, which is the setting
* to control how much caustics are blurred out. For accurate caustics
* image texture lookups and bump ray differentials must be small enough
* to capture detail at secondary bounces. */
ccl_device_forceinline float bsdf_widen_dD(const KernelGlobals kg,
const float prev_dD,
const float avg_roughness_squared)
{
if (!(avg_roughness_squared > 0.0f)) {
return prev_dD;
}
return max(prev_dD, sqrtf(avg_roughness_squared));
const float scale = kernel_data.integrator.differential_widen_scale;
return max(prev_dD, scale * sqrtf(avg_roughness_squared));
}
/* An additional term to smooth illumination on grazing angles when using bump mapping

View file

@ -4,6 +4,7 @@
#pragma once
#include "kernel/globals.h"
#include "kernel/types.h"
#include "kernel/closure/volume_draine.h"
@ -74,10 +75,11 @@ ccl_device int volume_phase_sample(const ccl_private ShaderData *sd,
/* 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 float prev_dD,
ccl_device_forceinline float volume_phase_widen_dD(const KernelGlobals kg,
const float prev_dD,
const float sampled_roughness)
{
return max(prev_dD, 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,

View file

@ -195,6 +195,7 @@ KERNEL_STRUCT_MEMBER(integrator, int, transparent_shadows)
KERNEL_STRUCT_MEMBER(integrator, int, caustics_reflective)
KERNEL_STRUCT_MEMBER(integrator, int, caustics_refractive)
KERNEL_STRUCT_MEMBER(integrator, float, filter_glossy)
KERNEL_STRUCT_MEMBER(integrator, float, differential_widen_scale)
/* Seed. */
KERNEL_STRUCT_MEMBER_DONT_SPECIALIZE
KERNEL_STRUCT_MEMBER(integrator, int, seed)

View file

@ -433,7 +433,7 @@ ccl_device
#ifdef __RAY_DIFFERENTIALS__
/* Widen ray differences, with same logic as forward sampling to ensure
* both MIS strategies converge to the same result. */
ray.dD = bsdf_widen_dD(INTEGRATOR_STATE(state, ray, dD), avg_roughness_squared);
ray.dD = bsdf_widen_dD(kg, INTEGRATOR_STATE(state, ray, dD), avg_roughness_squared);
#endif
}
@ -586,7 +586,8 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
/* Widen ray differences, with same logic as NEE sampling to ensure
* both MIS strategies converge to the same result. */
const float dD = bsdf_widen_dD(INTEGRATOR_STATE(state, ray, dD), bsdf_avg_roughness_squared);
const float dD = bsdf_widen_dD(
kg, INTEGRATOR_STATE(state, ray, dD), bsdf_avg_roughness_squared);
INTEGRATOR_STATE_WRITE(state, ray, dD) = dD;
#endif
}

View file

@ -2652,8 +2652,8 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX;
# ifdef __RAY_DIFFERENTIALS__
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
INTEGRATOR_STATE_WRITE(state, ray, dD) = volume_phase_widen_dD(INTEGRATOR_STATE(state, ray, dD),
sampled_roughness);
INTEGRATOR_STATE_WRITE(state, ray, dD) = volume_phase_widen_dD(
kg, INTEGRATOR_STATE(state, ray, dD), sampled_roughness);
# endif
// Save memory by storing last hit prim and object in isect
INTEGRATOR_STATE_WRITE(state, isect, prim) = sd->prim;

View file

@ -267,6 +267,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
kintegrator->caustics_reflective = caustics_reflective;
kintegrator->caustics_refractive = caustics_refractive;
kintegrator->filter_glossy = (filter_glossy == 0.0f) ? FLT_MAX : 1.0f / filter_glossy;
kintegrator->differential_widen_scale = min(1.0f, filter_glossy);
kintegrator->filter_closures = 0;
if (!use_direct_light) {

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c21764286b307709d8fa377ce838b78c783b8533b1723149dac0196ca46e8d8e
size 19943
oid sha256:641d7d513fcc4cf14920a3ff0f15b218b2bb1d6c9518ab925aa65e2e34653055
size 19911

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6b0fa33342e16eaf00bccec22751c93e8e8e445032126ba7c07bee92910219a
size 21345
oid sha256:f556b560b4484aaf9ffb7c58771161a2302f5d47c0937a2dba73cafc5b322dbd
size 21473

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ddc755a1855ffeb1a3331b124f77bfdbe8eef288dd1f00059857e3c8cea66ced
size 19280
oid sha256:b6584cddd82dc2df6f48dcfaa320928563c7e332c52abbe4228af58ef58415ba
size 19326

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d7c7ca021cd3f3d35324aa7f2eeb77190ecd2be2780a14136d6ec9c8c176bcc5
size 44582
oid sha256:e52195975bc852249169e0b39724136d1f72c3a56671e15d8ec4df5aa87b3625
size 44573

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bb0c4ea6ffb9ab361dbf4736e7b48f8844840bc03f948acb9a58cc63a113dd01
size 38382

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d0ddde5d7b7051be2f70c968a55f0a282439ffb7f9d03d12c4a02eaed3bd049b
size 123809