Fix #103977: Cycles handling ray visibility inconsistent for forward and nee #107747

Open
Sebastian Herholz wants to merge 6 commits from sherholz/blender:light-visibility-fix into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
9 changed files with 126 additions and 23 deletions

View File

@ -306,6 +306,7 @@ set(SRC_KERNEL_LIGHT_HEADERS
light/spot.h
light/tree.h
light/triangle.h
light/visibility.h
)
set(SRC_KERNEL_SAMPLE_HEADERS

View File

@ -11,6 +11,7 @@
#include "kernel/light/light.h"
#include "kernel/light/sample.h"
#include "kernel/light/visibility.h"
CCL_NAMESPACE_BEGIN
@ -22,20 +23,18 @@ ccl_device Spectrum integrator_eval_background_shader(KernelGlobals kg,
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
/* Use visibility flag to skip lights. */
Spectrum light_visibility = one_spectrum();
if (shader & SHADER_EXCLUDE_ANY) {
if (((shader & SHADER_EXCLUDE_DIFFUSE) && (path_flag & PATH_RAY_DIFFUSE)) ||
((shader & SHADER_EXCLUDE_GLOSSY) && ((path_flag & (PATH_RAY_GLOSSY | PATH_RAY_REFLECT)) ==
(PATH_RAY_GLOSSY | PATH_RAY_REFLECT))) ||
((shader & SHADER_EXCLUDE_TRANSMIT) && (path_flag & PATH_RAY_TRANSMIT)) ||
((shader & SHADER_EXCLUDE_CAMERA) && (path_flag & PATH_RAY_CAMERA)) ||
if (((shader & SHADER_EXCLUDE_CAMERA) && (path_flag & PATH_RAY_CAMERA)) ||
((shader & SHADER_EXCLUDE_SCATTER) && (path_flag & PATH_RAY_VOLUME_SCATTER)))
return zero_spectrum();
light_visibility_correction(kg, state, shader, &light_visibility);
}
/* Use fast constant background color if available. */
Spectrum L = zero_spectrum();
if (surface_shader_constant_emission(kg, shader, &L)) {
return L;
return light_visibility * L;
}
/* Evaluate background shader. */
@ -57,7 +56,7 @@ ccl_device Spectrum integrator_eval_background_shader(KernelGlobals kg,
surface_shader_eval<KERNEL_FEATURE_NODE_MASK_SURFACE_BACKGROUND>(
kg, state, emission_sd, render_buffer, path_flag | PATH_RAY_EMISSION);
return surface_shader_background(emission_sd);
return light_visibility * surface_shader_background(emission_sd);
}
ccl_device_inline void integrate_background(KernelGlobals kg,
@ -138,18 +137,14 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
if (distant_light_sample_from_intersection(kg, ray_D, lamp, &ls)) {
/* Use visibility flag to skip lights. */
Spectrum light_visibility = one_spectrum();
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (ls.shader & SHADER_EXCLUDE_ANY) {
if (((ls.shader & SHADER_EXCLUDE_DIFFUSE) && (path_flag & PATH_RAY_DIFFUSE)) ||
((ls.shader & SHADER_EXCLUDE_GLOSSY) &&
((path_flag & (PATH_RAY_GLOSSY | PATH_RAY_REFLECT)) ==
(PATH_RAY_GLOSSY | PATH_RAY_REFLECT))) ||
((ls.shader & SHADER_EXCLUDE_TRANSMIT) && (path_flag & PATH_RAY_TRANSMIT)) ||
((ls.shader & SHADER_EXCLUDE_CAMERA) && (path_flag & PATH_RAY_CAMERA)) ||
if (((ls.shader & SHADER_EXCLUDE_CAMERA) && (path_flag & PATH_RAY_CAMERA)) ||
((ls.shader & SHADER_EXCLUDE_SCATTER) && (path_flag & PATH_RAY_VOLUME_SCATTER)))
continue;
light_visibility_correction(kg, state, ls.shader, &light_visibility);
}
#endif
@ -168,6 +163,7 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
ShaderDataTinyStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, ray_time);
light_eval *= light_visibility;
if (is_zero(light_eval)) {
continue;
}

View File

@ -35,17 +35,13 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
}
/* Use visibility flag to skip lights. */
Spectrum light_visibility = one_spectrum();
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (ls.shader & SHADER_EXCLUDE_ANY) {
if (((ls.shader & SHADER_EXCLUDE_DIFFUSE) && (path_flag & PATH_RAY_DIFFUSE)) ||
((ls.shader & SHADER_EXCLUDE_GLOSSY) &&
((path_flag & (PATH_RAY_GLOSSY | PATH_RAY_REFLECT)) ==
(PATH_RAY_GLOSSY | PATH_RAY_REFLECT))) ||
((ls.shader & SHADER_EXCLUDE_TRANSMIT) && (path_flag & PATH_RAY_TRANSMIT)) ||
((ls.shader & SHADER_EXCLUDE_SCATTER) && (path_flag & PATH_RAY_VOLUME_SCATTER)))
if (((ls.shader & SHADER_EXCLUDE_SCATTER) && (path_flag & PATH_RAY_VOLUME_SCATTER)))
return;
light_visibility_correction(kg, state, ls.shader, &light_visibility);
}
#endif
@ -54,6 +50,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
ShaderDataTinyStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, ray_time);
light_eval *= light_visibility;
if (is_zero(light_eval)) {
return;
}

View File

@ -16,6 +16,7 @@
#include "kernel/integrator/volume_stack.h"
#include "kernel/light/sample.h"
#include "kernel/light/visibility.h"
CCL_NAMESPACE_BEGIN
@ -429,6 +430,8 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
/* Update throughput. */
const Spectrum bsdf_weight = bsdf_eval_sum(&bsdf_eval) / bsdf_pdf;
INTEGRATOR_STATE_WRITE(state, path, throughput) *= bsdf_weight;
const BsdfEvalRGBE bsdf_eval_RGBE = BsdfEvalToBsdfEvalRGBE(&bsdf_eval);
INTEGRATOR_STATE_WRITE(state, path, scatter_eval) = bsdf_eval_RGBE;
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
if (INTEGRATOR_STATE(state, path, bounce) == 0) {

View File

@ -48,6 +48,7 @@ KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER(path, float, continuation_probability, KERNEL_FEATURE_PATH_TRACING)
/* Throughput. */
KERNEL_STRUCT_MEMBER(path, PackedSpectrum, throughput, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER(path, BsdfEvalRGBE, scatter_eval, KERNEL_FEATURE_PATH_TRACING)
/* Factor to multiple with throughput to get remove any guiding PDFS.
* Such throughput without guiding PDFS is used for Russian roulette termination. */
KERNEL_STRUCT_MEMBER(path, float, unguided_throughput, KERNEL_FEATURE_PATH_GUIDING)

View File

@ -240,12 +240,14 @@ ccl_device_inline float _surface_shader_bsdf_eval_mis(KernelGlobals kg,
}
if (CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) {
if (CLOSURE_IS_BSDF(sc->type) && !_surface_shader_exclude(sc->type, light_shader_flags)) {
if (CLOSURE_IS_BSDF(sc->type)) {
float bsdf_pdf = 0.0f;
Spectrum eval = bsdf_eval(kg, sd, sc, wo, &bsdf_pdf);
if (bsdf_pdf != 0.0f) {
bsdf_eval_accum(result_eval, sc->type, eval * sc->weight);
if (!_surface_shader_exclude(sc->type, light_shader_flags)) {
Review

I wonder, shouldn't the function surface_shader_bsdf_eval_pdfs() just below it follow the same logic and be corrected too?

I wonder, shouldn't the function `surface_shader_bsdf_eval_pdfs()` just below it follow the same logic and be corrected too?
bsdf_eval_accum(result_eval, sc->type, eval * sc->weight);
}
sum_pdf += bsdf_pdf * sc->sample_weight;
}
}

View File

@ -0,0 +1,93 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2023 Blender Foundation */
#pragma once
CCL_NAMESPACE_BEGIN
// float EGB to RGBE conversion as described by Greg Ward
// http://www.graphics.cornell.edu/online/formats/rgbe/
ccl_device_inline SpectrumRGBE SpectrumToSpectrumRGBE(ccl_private const Spectrum *spec)
{
SpectrumRGBE specRGBE;
Review

Can we define SpectrumRGBE as

struct SpectrumRGBE {
  uint8_t r, g, b, e;
};

and avoid such memory casts, and casts between signed/unsigned char?

This also seems to a bit arbitrary place to have such utility functions. Within the current file structure it seems types_spectrum.h is the most fitted place for it.

Can we define `SpectrumRGBE ` as ``` struct SpectrumRGBE { uint8_t r, g, b, e; }; ``` and avoid such memory casts, and casts between signed/unsigned `char`? This also seems to a bit arbitrary place to have such utility functions. Within the current file structure it seems `types_spectrum.h` is the most fitted place for it.

sure no problem.

sure no problem.

@Sergey done.
I am not sure if you can compress the data even more.
The positive side on this approach is that we can realize handling scalar ray types light interactions straight forward instead of only binary ones.

@Sergey done. I am not sure if you can compress the data even more. The positive side on this approach is that we can realize handling scalar ray types light interactions straight forward instead of only binary ones.

Another thing worth considering is moving the conversion methods to a different place (not into visibility.h).
Do you have a good suggestion for it?

Another thing worth considering is moving the conversion methods to a different place (not into `visibility.h`). Do you have a good suggestion for it?
Review

The immediate thought is kernel/util/color.h. There is already spectrum_to_rgb so intuitively feels like spectrum_to_spectrum_rgbe could fit there as well.

The immediate thought is `kernel/util/color.h`. There is already `spectrum_to_rgb` so intuitively feels like `spectrum_to_spectrum_rgbe` could fit there as well.
float v;
int e;
v = max(spec->x, max(spec->y, spec->z));
if (v < 1e-32f) {
specRGBE.r = specRGBE.g = specRGBE.b = specRGBE.e = 0;
}
else {
#if defined(__KERNEL_METAL__)
v = frexp(v, e) * 256.0f / v;
#else
v = frexp(v, &e) * 256.0f / v;
#endif
specRGBE.r = (uint8_t)(spec->x * v);
specRGBE.g = (uint8_t)(spec->y * v);
specRGBE.b = (uint8_t)(spec->z * v);
specRGBE.e = (uint8_t)(e + 128);
}
return specRGBE;
}
ccl_device_inline Spectrum SpectrumRGBEToSpectrum(ccl_private const SpectrumRGBE *specRGBE)
{
Spectrum spec;
if (specRGBE->e) {
const float f = ldexp(1.0f, specRGBE->e - (int)(128 + 8));
spec.x = specRGBE->r * f;
spec.y = specRGBE->g * f;
spec.z = specRGBE->b * f;
}
else {
spec.x = spec.y = spec.z = 0.0f;
}
return spec;
}
ccl_device_inline BsdfEvalRGBE BsdfEvalToBsdfEvalRGBE(ccl_private const BsdfEval *bsdfEval)
{
BsdfEvalRGBE bsdfEvalRGBE;
bsdfEvalRGBE.diffuse = SpectrumToSpectrumRGBE(&bsdfEval->diffuse);
bsdfEvalRGBE.glossy = SpectrumToSpectrumRGBE(&bsdfEval->glossy);
bsdfEvalRGBE.sum = SpectrumToSpectrumRGBE(&bsdfEval->sum);
return bsdfEvalRGBE;
}
ccl_device_inline BsdfEval BsdfEvalRGBEToBsdfEval(ccl_private const BsdfEvalRGBE *bsdfEvalRGBE)
{
BsdfEval bsdfEval;
bsdfEval.diffuse = SpectrumRGBEToSpectrum(&bsdfEvalRGBE->diffuse);
bsdfEval.glossy = SpectrumRGBEToSpectrum(&bsdfEvalRGBE->glossy);
bsdfEval.sum = SpectrumRGBEToSpectrum(&bsdfEvalRGBE->sum);
return bsdfEval;
}
ccl_device_inline void light_visibility_correction(KernelGlobals kg,
IntegratorState state,
ccl_private const int shader,
ccl_private Spectrum *light_visibility)
{
Spectrum visible_components = zero_spectrum();
const BsdfEvalRGBE scatter_eval_rgbe = INTEGRATOR_STATE(state, path, scatter_eval);
const BsdfEval scatter_eval = BsdfEvalRGBEToBsdfEval(&scatter_eval_rgbe);
Spectrum transmission = scatter_eval.sum - (scatter_eval.glossy + scatter_eval.diffuse);
if (!(shader & SHADER_EXCLUDE_DIFFUSE)) {
visible_components += scatter_eval.diffuse;
}
if (!(shader & SHADER_EXCLUDE_GLOSSY)) {
visible_components += scatter_eval.glossy;
}
if (!(shader & SHADER_EXCLUDE_TRANSMIT)) {
visible_components += transmission;
}
*light_visibility = safe_divide(visible_components, scatter_eval.sum);
}
CCL_NAMESPACE_END

View File

@ -443,6 +443,12 @@ typedef struct BsdfEval {
Spectrum sum;
} BsdfEval;
typedef struct BsdfEvalRGBE {
SpectrumRGBE diffuse;
SpectrumRGBE glossy;
SpectrumRGBE sum;
} BsdfEvalRGBE;
/* Closure Filter */
typedef enum FilterClosures {

View File

@ -17,6 +17,10 @@ CCL_NAMESPACE_BEGIN
using Spectrum = SPECTRUM_DATA_TYPE;
using PackedSpectrum = PACKED_SPECTRUM_DATA_TYPE;
struct SpectrumRGBE {
uint8_t r, g, b, e;
};
#define make_spectrum(f) CONCAT(make_, SPECTRUM_DATA_TYPE(f))
#define load_spectrum(f) CONCAT(load_, SPECTRUM_DATA_TYPE(f))
#define store_spectrum(s, f) CONCAT(store_, SPECTRUM_DATA_TYPE((s), (f)))