Fix T102250: Cycles path guiding issue with equiangular sampling

The wrong guiding distribution was used when direct and indirect light
scattering happened at different locations. Now use a different distribution
for each location.

Recording is not quite correct since OpenPGL does not support spliting the
path like this, instead recording at the start of the volume ray. In practice
this seems to make little difference.

Differential Revision: https://developer.blender.org/D16448
This commit is contained in:
Sebastian Herholz 2022-11-24 17:03:45 +01:00 committed by Brecht Van Lommel
parent 38c4f40159
commit 008cc625aa
Notes: blender-bot 2023-02-14 06:00:49 +01:00
Referenced by issue #102250, Path guiding in volumes do not fully support equiangular sampling
3 changed files with 85 additions and 39 deletions

View File

@ -34,6 +34,9 @@ typedef struct VolumeIntegrateResult {
Spectrum direct_throughput;
float direct_t;
ShaderVolumePhases direct_phases;
# ifdef __PATH_GUIDING__
VolumeSampleMethod direct_sample_method;
# endif
/* Throughput and offset for indirect light scattering. */
bool indirect_scatter;
@ -580,6 +583,9 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
result.direct_t = volume_equiangular_sample(
ray, equiangular_light_P, vstate.rscatter, &vstate.equiangular_pdf);
}
# ifdef __PATH_GUIDING__
result.direct_sample_method = vstate.direct_sample_method;
# endif
# ifdef __DENOISING_FEATURES__
const bool write_denoising_features = (INTEGRATOR_STATE(state, path, flag) &
@ -719,6 +725,9 @@ ccl_device_forceinline void integrate_volume_direct_light(
ccl_private const RNGState *ccl_restrict rng_state,
const float3 P,
ccl_private const ShaderVolumePhases *ccl_restrict phases,
# ifdef __PATH_GUIDING__
ccl_private const Spectrum unlit_throughput,
# endif
ccl_private const Spectrum throughput,
ccl_private LightSample *ccl_restrict ls)
{
@ -851,7 +860,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
kernel_data.background.lightgroup + 1;
# ifdef __PATH_GUIDING__
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = throughput;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE(
state, guiding, path_segment);
# endif
@ -990,7 +999,13 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
const float step_size = volume_stack_step_size(kg, volume_read_lambda_pass);
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
/* The current path throughput which is used later to calculate per-segment throughput.*/
const float3 initial_throughput = INTEGRATOR_STATE(state, path, throughput);
/* The path throughput used to calculate the throughput for direct light. */
float3 unlit_throughput = initial_throughput;
/* If a new path segment is generated at the direct scatter position.*/
bool guiding_generated_new_segment = false;
float rand_phase_guiding = 0.5f;
# endif
/* TODO: expensive to zero closures? */
@ -1018,41 +1033,48 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
return VOLUME_PATH_MISSED;
}
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
bool guiding_generated_new_segment = false;
if (kernel_data.integrator.use_guiding) {
/* Record transmittance using change in throughput. */
float3 transmittance_weight = spectrum_to_rgb(
safe_divide_color(result.indirect_throughput, initial_throughput));
guiding_record_volume_transmission(kg, state, transmittance_weight);
if (result.indirect_scatter) {
const float3 P = ray->P + result.indirect_t * ray->D;
/* Record volume segment up to direct scatter position.
* TODO: volume segment is wrong when direct_t and indirect_t. */
if (result.direct_scatter && (result.direct_t == result.indirect_t)) {
guiding_record_volume_segment(kg, state, P, sd.I);
guiding_generated_new_segment = true;
}
# if PATH_GUIDING_LEVEL >= 4
/* TODO: this position will be wrong for direct light pdf computation,
* since the direct light position may be different? */
volume_shader_prepare_guiding(
kg, state, &sd, &rng_state, P, ray->D, &result.direct_phases, direct_sample_method);
# endif
}
else {
/* No guiding if we don't scatter. */
state->guiding.use_volume_guiding = false;
}
}
# endif
/* Direct light. */
if (result.direct_scatter) {
const float3 direct_P = ray->P + result.direct_t * ray->D;
# ifdef __PATH_GUIDING__
if (kernel_data.integrator.use_guiding) {
# if PATH_GUIDING_LEVEL >= 1
if (result.direct_sample_method == VOLUME_SAMPLE_DISTANCE) {
/* If the direct scatter event is generated using VOLUME_SAMPLE_DISTANCE the direct event
* will happen at the same position as the indirect event and the direct light contribution
* will contribute to the position of the next path segment.*/
float3 transmittance_weight = spectrum_to_rgb(
safe_divide_color(result.indirect_throughput, initial_throughput));
guiding_record_volume_transmission(kg, state, transmittance_weight);
guiding_record_volume_segment(kg, state, direct_P, sd.I);
guiding_generated_new_segment = true;
unlit_throughput = result.indirect_throughput / continuation_probability;
rand_phase_guiding = path_state_rng_1D(kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_DISTANCE);
}
else {
/* If the direct scatter event is generated using VOLUME_SAMPLE_EQUIANGULAR the direct
* event will happen at a separate position as the indirect event and the direct light
* contribution will contribute to the position of the current/previous path segment. The
* unlit_throughput has to be adjusted to include the scattering at the previous segment.*/
float3 scatterEval = one_float3();
if (state->guiding.path_segment) {
pgl_vec3f scatteringWeight = state->guiding.path_segment->scatteringWeight;
scatterEval = make_float3(scatteringWeight.x, scatteringWeight.y, scatteringWeight.z);
}
unlit_throughput /= scatterEval;
unlit_throughput *= continuation_probability;
rand_phase_guiding = path_state_rng_1D(
kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_EQUIANGULAR);
}
# endif
# if PATH_GUIDING_LEVEL >= 4
volume_shader_prepare_guiding(
kg, state, &sd, rand_phase_guiding, direct_P, ray->D, &result.direct_phases);
# endif
}
# endif
result.direct_throughput /= continuation_probability;
integrate_volume_direct_light(kg,
state,
@ -1060,6 +1082,9 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
&rng_state,
direct_P,
&result.direct_phases,
# ifdef __PATH_GUIDING__
unlit_throughput,
# endif
result.direct_throughput,
&ls);
}
@ -1069,6 +1094,13 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
* Only divide throughput by continuation_probability if we scatter. For the attenuation
* case the next surface will already do this division. */
if (result.indirect_scatter) {
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!guiding_generated_new_segment) {
float3 transmittance_weight = spectrum_to_rgb(
safe_divide_color(result.indirect_throughput, initial_throughput));
guiding_record_volume_transmission(kg, state, transmittance_weight);
}
# endif
result.indirect_throughput /= continuation_probability;
}
INTEGRATOR_STATE_WRITE(state, path, throughput) = result.indirect_throughput;
@ -1076,10 +1108,21 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
if (result.indirect_scatter) {
sd.P = ray->P + result.indirect_t * ray->D;
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
# if defined(__PATH_GUIDING__)
# if PATH_GUIDING_LEVEL >= 1
if (!guiding_generated_new_segment) {
guiding_record_volume_segment(kg, state, sd.P, sd.I);
}
# endif
# if PATH_GUIDING_LEVEL >= 4
/* If the direct scatter event was generated using VOLUME_SAMPLE_EQUIANGULAR we need to
* initialize the guiding distribution at the indirect scatter position. */
if (result.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR) {
rand_phase_guiding = path_state_rng_1D(kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_DISTANCE);
volume_shader_prepare_guiding(
kg, state, &sd, rand_phase_guiding, sd.P, ray->D, &result.indirect_phases);
}
# endif
# endif
if (integrate_volume_phase_scatter(kg, state, &sd, &rng_state, &result.indirect_phases)) {
@ -1090,6 +1133,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
}
}
else {
# if defined(__PATH_GUIDING__)
/* No guiding if we don't scatter. */
state->guiding.use_volume_guiding = false;
# endif
return VOLUME_PATH_ATTENUATED;
}
}

View File

@ -95,11 +95,10 @@ ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases
ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
ccl_private const RNGState *rng_state,
float rand_phase_guiding,
const float3 P,
const float3 D,
ccl_private ShaderVolumePhases *phases,
const VolumeSampleMethod direct_sample_method)
ccl_private ShaderVolumePhases *phases)
{
/* Have any phase functions to guide? */
const int num_phases = phases->num_closure;
@ -109,7 +108,6 @@ ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
}
const float volume_guiding_probability = kernel_data.integrator.volume_guiding_probability;
float rand_phase_guiding = path_state_rng_1D(kg, rng_state, PRNG_VOLUME_PHASE_GUIDING);
/* If we have more than one phase function we select one random based on its
* sample weight to calculate the product distribution for guiding. */

View File

@ -156,7 +156,8 @@ enum PathTraceDimension {
PRNG_VOLUME_SCATTER_DISTANCE = 5,
PRNG_VOLUME_OFFSET = 6,
PRNG_VOLUME_SHADE_OFFSET = 7,
PRNG_VOLUME_PHASE_GUIDING = 8,
PRNG_VOLUME_PHASE_GUIDING_DISTANCE = 8,
PRNG_VOLUME_PHASE_GUIDING_EQUIANGULAR = 9,
/* Subsurface random walk bounces */
PRNG_SUBSURFACE_BSDF = 0,