Fix #114634: correlated samples in volume when using equiangular sampling and light tree
The same random number was used when sampling from the volume segment and from the direct scattering position, causing correlation issues with light tree. To solve this problem, we ensure the same light is picked for volume segment/direct scattering, equiangular/distance sampling by sampling the light tree only once in volume segment. From the direct scattering position in volume, we sample a position on the picked light as usual. If sampling from the light tree fails, we continue with indirect scattering. For unbiased MIS weight for forward sampling, we retrieve the `P`, `D` and `t` used in volume segment for traversing the light tree. The main changes are: 1. `light_tree_sample()` and `light_distribution_sample()` now only pick lights. Sampling a position on light is done separately via `light_sample()`. 2. `light_tree_sample()` is now only called only once from volume segment. For direct lighting we call `light_sample()`. 3. `light_tree_pdf()` now has a template `<in_volume_segment>`. 4. A new field `emitter_id` is added to struct `LightSample`, which just stores the picked emitter index. 5. Additional field `previous_dt = ray->tmax - ray->tmin` is added to `state->ray`, because we need this quantity for computing the pdf. 6. Distant/Background lights are also picked by light tree in volume segment now, because we have no way to pick them afterwards. The direct sample event for these lights will be handled by `VOLUME_SAMPLE_DISTANCE`. 7. Original paper suggests to use the maximal importance, this results in very poor sampling probability for distant and point lights therefore excessive noise. We have a minimal importance for surface to balance, we could do the same for volume but I do not want to spend much time on this now. Just doing `min_importance = 0.0f` seems to do the job okayish. This way we still won't sample the light with zero `max_importance`. The current solution might perform worse with distance sampling, because the light tree measure is biased towards equiangular sampling. However, it is difficult to perform MIS between equiangular and distance sampling if different lights are picked for each method. This is something we can look into in the future if proved to be a serious regression. Pull Request: https://projects.blender.org/blender/blender/pulls/119389
This commit is contained in:
committed by
Weizhen Huang
parent
565dc1f22b
commit
fdc2962beb
@@ -324,13 +324,6 @@ ccl_device_inline bool volume_equiangular_valid_ray_segment(KernelGlobals kg,
|
||||
ccl_private float2 *t_range,
|
||||
const ccl_private LightSample *ls)
|
||||
{
|
||||
# ifdef __LIGHT_TREE__
|
||||
/* Do not compute ray segment until #119389 is landed. */
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
return true;
|
||||
}
|
||||
# endif
|
||||
|
||||
if (ls->type == LIGHT_SPOT) {
|
||||
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
|
||||
return spot_light_valid_ray_segment(klight, ray_P, ray_D, t_range);
|
||||
@@ -708,7 +701,8 @@ ccl_device_forceinline bool integrate_volume_equiangular_sample_light(
|
||||
ccl_private const Ray *ccl_restrict ray,
|
||||
ccl_private const ShaderData *ccl_restrict sd,
|
||||
ccl_private const RNGState *ccl_restrict rng_state,
|
||||
ccl_private EquiangularCoefficients *ccl_restrict equiangular_coeffs)
|
||||
ccl_private EquiangularCoefficients *ccl_restrict equiangular_coeffs,
|
||||
ccl_private LightSample &ccl_restrict ls)
|
||||
{
|
||||
/* Test if there is a light or BSDF that needs direct light. */
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
@@ -720,7 +714,6 @@ ccl_device_forceinline bool integrate_volume_equiangular_sample_light(
|
||||
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
|
||||
const float3 rand_light = path_state_rng_3D(kg, rng_state, PRNG_LIGHT);
|
||||
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
if (!light_sample_from_volume_segment(kg,
|
||||
rand_light,
|
||||
sd->time,
|
||||
@@ -761,41 +754,26 @@ ccl_device_forceinline void integrate_volume_direct_light(
|
||||
# ifdef __PATH_GUIDING__
|
||||
ccl_private const Spectrum unlit_throughput,
|
||||
# endif
|
||||
ccl_private const Spectrum throughput)
|
||||
ccl_private const Spectrum throughput,
|
||||
ccl_private LightSample &ccl_restrict ls)
|
||||
{
|
||||
PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_DIRECT_LIGHT);
|
||||
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
if (!kernel_data.integrator.use_direct_light || ls.emitter_id == EMITTER_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sample position on the same light again, now from the shading point where we scattered.
|
||||
*
|
||||
* Note that this means we sample the light tree twice when equiangular sampling is used.
|
||||
* We could consider sampling the light tree just once and use the same light position again.
|
||||
*
|
||||
* This would make the PDFs for MIS weights more complicated due to having to account for
|
||||
* both distance/equiangular and direct/indirect light sampling, but could be more accurate.
|
||||
* Additionally we could end up behind the light or outside a spot light cone, which might
|
||||
* waste a sample. Though on the other hand it would be possible to prevent that with
|
||||
* equiangular sampling restricted to a smaller sub-segment where the light has influence. */
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
/* Sample position on the same light again, now from the shading point where we scattered. */
|
||||
{
|
||||
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
|
||||
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
|
||||
const float3 rand_light = path_state_rng_3D(kg, rng_state, PRNG_LIGHT);
|
||||
const float3 N = zero_float3();
|
||||
const int object_receiver = light_link_receiver_nee(kg, sd);
|
||||
const int shader_flags = SD_BSDF_HAS_TRANSMISSION;
|
||||
|
||||
if (!light_sample_from_position(kg,
|
||||
rng_state,
|
||||
rand_light,
|
||||
sd->time,
|
||||
P,
|
||||
zero_float3(),
|
||||
light_link_receiver_nee(kg, sd),
|
||||
SD_BSDF_HAS_TRANSMISSION,
|
||||
bounce,
|
||||
path_flag,
|
||||
&ls))
|
||||
if (!light_sample<false>(
|
||||
kg, rand_light, sd->time, P, N, object_receiver, shader_flags, bounce, path_flag, &ls))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -913,6 +891,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
||||
KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
ccl_private ShaderData *sd,
|
||||
ccl_private const Ray *ray,
|
||||
ccl_private const RNGState *rng_state,
|
||||
ccl_private const ShaderVolumePhases *phases)
|
||||
{
|
||||
@@ -965,6 +944,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
||||
INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P;
|
||||
INTEGRATOR_STATE_WRITE(state, ray, D) = normalize(phase_wo);
|
||||
INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f;
|
||||
INTEGRATOR_STATE_WRITE(state, ray, previous_dt) = ray->tmax - ray->tmin;
|
||||
INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX;
|
||||
# ifdef __RAY_DIFFERENTIALS__
|
||||
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
|
||||
@@ -993,7 +973,8 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
|
||||
|
||||
/* Update path state */
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf;
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = zero_float3();
|
||||
const float3 previous_P = ray->P + ray->D * ray->tmin;
|
||||
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->P - previous_P;
|
||||
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
|
||||
unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
|
||||
|
||||
@@ -1025,13 +1006,15 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
|
||||
/* Sample light ahead of volume stepping, for equiangular sampling. */
|
||||
/* TODO: distant lights are ignored now, but could instead use even distribution. */
|
||||
LightSample ls ccl_optional_struct_init;
|
||||
ls.emitter_id = EMITTER_NONE;
|
||||
const bool need_light_sample = !(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_TERMINATE);
|
||||
|
||||
EquiangularCoefficients equiangular_coeffs = {zero_float3(), make_float2(ray->tmin, ray->tmax)};
|
||||
|
||||
const bool have_equiangular_sample = need_light_sample &&
|
||||
integrate_volume_equiangular_sample_light(
|
||||
kg, state, ray, &sd, &rng_state, &equiangular_coeffs);
|
||||
const bool have_equiangular_sample =
|
||||
need_light_sample && integrate_volume_equiangular_sample_light(
|
||||
kg, state, ray, &sd, &rng_state, &equiangular_coeffs, ls);
|
||||
|
||||
VolumeSampleMethod direct_sample_method = (have_equiangular_sample) ?
|
||||
volume_stack_sample_method(kg, state) :
|
||||
@@ -1129,7 +1112,8 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
# ifdef __PATH_GUIDING__
|
||||
unlit_throughput,
|
||||
# endif
|
||||
result.direct_throughput);
|
||||
result.direct_throughput,
|
||||
ls);
|
||||
}
|
||||
|
||||
/* Indirect light.
|
||||
@@ -1168,7 +1152,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
|
||||
# endif
|
||||
# endif
|
||||
|
||||
if (integrate_volume_phase_scatter(kg, state, &sd, &rng_state, &result.indirect_phases)) {
|
||||
if (integrate_volume_phase_scatter(kg, state, &sd, ray, &rng_state, &result.indirect_phases)) {
|
||||
return VOLUME_PATH_SCATTERED;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -75,6 +75,9 @@ KERNEL_STRUCT_MEMBER(ray, float, tmax, KERNEL_FEATURE_PATH_TRACING)
|
||||
KERNEL_STRUCT_MEMBER(ray, float, time, KERNEL_FEATURE_PATH_TRACING)
|
||||
KERNEL_STRUCT_MEMBER(ray, float, dP, KERNEL_FEATURE_PATH_TRACING)
|
||||
KERNEL_STRUCT_MEMBER(ray, float, dD, KERNEL_FEATURE_PATH_TRACING)
|
||||
#ifdef __LIGHT_TREE__
|
||||
KERNEL_STRUCT_MEMBER(ray, float, previous_dt, KERNEL_FEATURE_PATH_TRACING)
|
||||
#endif
|
||||
KERNEL_STRUCT_END(ray)
|
||||
|
||||
/*************************** Intersection result ******************************/
|
||||
|
||||
@@ -518,9 +518,8 @@ ccl_device_forceinline bool area_light_tree_parameters(const ccl_global KernelLi
|
||||
const bool shape_above_surface = dot(N, centroid - P) + fabsf(dot(N, extentu)) +
|
||||
fabsf(dot(N, extentv)) >
|
||||
0;
|
||||
const bool in_volume = is_zero(N);
|
||||
|
||||
return (front_facing && shape_above_surface) || in_volume;
|
||||
return front_facing && shape_above_surface;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
@@ -12,9 +12,9 @@ CCL_NAMESPACE_BEGIN
|
||||
|
||||
typedef struct LightSample {
|
||||
float3 P; /* position on light, or direction for distant light */
|
||||
float3 Ng; /* normal on light */
|
||||
float3 D; /* direction from shading point to light */
|
||||
packed_float3 Ng; /* normal on light */
|
||||
float t; /* distance to light (FLT_MAX for distant light) */
|
||||
float3 D; /* direction from shading point to light */
|
||||
float u, v; /* parametric coordinate on primitive */
|
||||
float pdf; /* pdf for selecting light and point on light */
|
||||
float pdf_selection; /* pdf for selecting light */
|
||||
@@ -25,6 +25,7 @@ typedef struct LightSample {
|
||||
int lamp; /* lamp id */
|
||||
int group; /* lightgroup */
|
||||
LightType type; /* type of light */
|
||||
int emitter_id; /* index in the emitter array */
|
||||
} LightSample;
|
||||
|
||||
/* Utilities */
|
||||
|
||||
@@ -41,36 +41,14 @@ ccl_device int light_distribution_sample(KernelGlobals kg, const float rand)
|
||||
return index;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
|
||||
const float3 rand,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const int object_receiver,
|
||||
const int shader_flags,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
const float rand,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Sample light index from distribution. */
|
||||
/* The first two dimensions of the Sobol sequence have better stratification. */
|
||||
const int index = light_distribution_sample(kg, rand.z);
|
||||
const float pdf_selection = kernel_data.integrator.distribution_pdf_lights;
|
||||
const float2 rand_uv = float3_to_float2(rand);
|
||||
return light_sample<in_volume_segment>(kg,
|
||||
rand_uv,
|
||||
time,
|
||||
P,
|
||||
N,
|
||||
object_receiver,
|
||||
shader_flags,
|
||||
bounce,
|
||||
path_flag,
|
||||
index,
|
||||
0,
|
||||
pdf_selection,
|
||||
ls);
|
||||
ls->emitter_id = light_distribution_sample(kg, rand);
|
||||
ls->pdf_selection = kernel_data.integrator.distribution_pdf_lights;
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_inline float light_distribution_pdf_lamp(KernelGlobals kg)
|
||||
|
||||
@@ -177,7 +177,7 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_noinline bool light_sample(KernelGlobals kg,
|
||||
const float2 rand,
|
||||
const float3 rand_light,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
@@ -185,33 +185,31 @@ ccl_device_noinline bool light_sample(KernelGlobals kg,
|
||||
const int shader_flags,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
const int emitter_index,
|
||||
const int object_id,
|
||||
const float pdf_selection,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* The first two dimensions of the Sobol sequence have better stratification, use them to sample
|
||||
* position on the light. */
|
||||
const float2 rand = float3_to_float2(rand_light);
|
||||
|
||||
int prim;
|
||||
MeshLight mesh_light;
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
emitter_index);
|
||||
ls->emitter_id);
|
||||
prim = kemitter->light.id;
|
||||
mesh_light.shader_flag = kemitter->mesh_light.shader_flag;
|
||||
mesh_light.object_id = object_id;
|
||||
mesh_light.object_id = ls->object;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(
|
||||
light_distribution, emitter_index);
|
||||
light_distribution, ls->emitter_id);
|
||||
prim = kdistribution->prim;
|
||||
mesh_light = kdistribution->mesh_light;
|
||||
}
|
||||
|
||||
/* A different value would be assigned in `triangle_light_sample()` if `!use_light_tree`. */
|
||||
ls->pdf_selection = pdf_selection;
|
||||
|
||||
if (prim >= 0) {
|
||||
/* Mesh light. */
|
||||
const int object = mesh_light.object_id;
|
||||
|
||||
@@ -329,17 +329,25 @@ ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
const int shader_flags = SD_BSDF_HAS_TRANSMISSION;
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
return light_tree_sample<true>(
|
||||
kg, rand, time, P, D, t, object_receiver, SD_BSDF_HAS_TRANSMISSION, bounce, path_flag, ls);
|
||||
if (!light_tree_sample<true>(kg, rand.z, P, D, t, object_receiver, shader_flags, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
return light_distribution_sample<true>(
|
||||
kg, rand, time, P, D, object_receiver, SD_BSDF_HAS_TRANSMISSION, bounce, path_flag, ls);
|
||||
if (!light_distribution_sample(kg, rand.z, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sample position on the selected light. */
|
||||
return light_sample<true>(
|
||||
kg, rand, time, P, D, object_receiver, shader_flags, bounce, path_flag, ls);
|
||||
}
|
||||
|
||||
ccl_device bool light_sample_from_position(KernelGlobals kg,
|
||||
@@ -354,17 +362,24 @@ ccl_device bool light_sample_from_position(KernelGlobals kg,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Randomly select a light. */
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
return light_tree_sample<false>(
|
||||
kg, rand, time, P, N, 0.0f, object_receiver, shader_flags, bounce, path_flag, ls);
|
||||
if (!light_tree_sample<false>(kg, rand.z, P, N, 0.0f, object_receiver, shader_flags, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
return light_distribution_sample<false>(
|
||||
kg, rand, time, P, N, object_receiver, shader_flags, bounce, path_flag, ls);
|
||||
if (!light_distribution_sample(kg, rand.z, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sample position on the selected light. */
|
||||
return light_sample<false>(
|
||||
kg, rand, time, P, N, object_receiver, shader_flags, bounce, path_flag, ls);
|
||||
}
|
||||
|
||||
/* Update light sample with new shading point position for MNEE. The position on the light is fixed
|
||||
@@ -415,13 +430,15 @@ ccl_device_inline float light_sample_mis_weight_forward_surface(KernelGlobals kg
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
const float dt = INTEGRATOR_STATE(state, ray, previous_dt);
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
|
||||
uint lookup_offset = kernel_data_fetch(object_lookup_offset, sd->object);
|
||||
uint prim_offset = kernel_data_fetch(object_prim_offset, sd->object);
|
||||
uint triangle = kernel_data_fetch(triangle_to_tree, sd->prim - prim_offset + lookup_offset);
|
||||
|
||||
pdf *= light_tree_pdf(
|
||||
kg, ray_P, N, path_flag, sd->object, triangle, light_link_receiver_forward(kg, state));
|
||||
kg, ray_P, N, dt, path_flag, sd->object, triangle, light_link_receiver_forward(kg, state));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
@@ -445,9 +462,11 @@ ccl_device_inline float light_sample_mis_weight_forward_lamp(KernelGlobals kg,
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
const float dt = INTEGRATOR_STATE(state, ray, previous_dt);
|
||||
pdf *= light_tree_pdf(kg,
|
||||
P,
|
||||
N,
|
||||
dt,
|
||||
path_flag,
|
||||
0,
|
||||
kernel_data_fetch(light_to_tree, ls->lamp),
|
||||
@@ -485,9 +504,10 @@ ccl_device_inline float light_sample_mis_weight_forward_background(KernelGlobals
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
|
||||
const float dt = INTEGRATOR_STATE(state, ray, previous_dt);
|
||||
uint light = kernel_data_fetch(light_to_tree, kernel_data.background.light_index);
|
||||
pdf *= light_tree_pdf(
|
||||
kg, ray_P, N, path_flag, 0, light, light_link_receiver_forward(kg, state));
|
||||
kg, ray_P, N, dt, path_flag, 0, light, light_link_receiver_forward(kg, state));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
@@ -148,10 +148,7 @@ ccl_device void light_tree_importance(const float3 N_or_D,
|
||||
float cos_min_incidence_angle = 1.0f;
|
||||
float cos_max_incidence_angle = 1.0f;
|
||||
|
||||
/* When sampling the light tree for the second time in `shade_volume.h` and when query the pdf in
|
||||
* `sample.h`. */
|
||||
const bool in_volume = is_zero(N_or_D);
|
||||
if (!in_volume_segment && !in_volume) {
|
||||
if (!in_volume_segment) {
|
||||
const float3 N = N_or_D;
|
||||
const float cos_theta_i = has_transmission ? fabsf(dot(point_to_centroid, N)) :
|
||||
dot(point_to_centroid, N);
|
||||
@@ -221,9 +218,9 @@ ccl_device void light_tree_importance(const float3 N_or_D,
|
||||
max_importance = fabsf(f_a * cos_min_incidence_angle * energy * cos_min_outgoing_angle /
|
||||
(in_volume_segment ? min_distance : sqr(min_distance)));
|
||||
|
||||
/* TODO: also min importance for volume? */
|
||||
/* TODO: compute proper min importance for volume. */
|
||||
if (in_volume_segment) {
|
||||
min_importance = max_importance;
|
||||
min_importance = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,10 +267,10 @@ ccl_device bool compute_emitter_centroid_and_dir(KernelGlobals kg,
|
||||
/* Arbitrary centroid and direction. */
|
||||
centroid = make_float3(0.0f, 0.0f, 1.0f);
|
||||
dir = make_float3(0.0f, 0.0f, -1.0f);
|
||||
return !in_volume_segment;
|
||||
break;
|
||||
case LIGHT_DISTANT:
|
||||
dir = centroid;
|
||||
return !in_volume_segment;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -323,12 +320,13 @@ ccl_device void light_tree_node_importance(KernelGlobals kg,
|
||||
float cos_theta_u;
|
||||
float distance;
|
||||
if (knode->type == LIGHT_TREE_DISTANT) {
|
||||
if (in_volume_segment) {
|
||||
return;
|
||||
}
|
||||
point_to_centroid = -bcone.axis;
|
||||
cos_theta_u = fast_cosf(bcone.theta_o + bcone.theta_e);
|
||||
distance = 1.0f;
|
||||
if (t == FLT_MAX) {
|
||||
/* In world volume, distant light has no contribution. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const float3 centroid = 0.5f * (bbox.min + bbox.max);
|
||||
@@ -339,6 +337,9 @@ ccl_device void light_tree_node_importance(KernelGlobals kg,
|
||||
/* Minimal distance of the ray to the cluster. */
|
||||
distance = len(centroid - closest_point);
|
||||
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
|
||||
/* FIXME(weizhen): it is not clear from which point the `cos_theta_u` should be computed in
|
||||
* volume segment. We could use `closest_point` as a conservative measure, but then
|
||||
* `point_to_centroid` should also use `closest_point`. */
|
||||
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, closest_point, point_to_centroid);
|
||||
}
|
||||
else {
|
||||
@@ -697,17 +698,16 @@ ccl_device int light_tree_root_node_index(KernelGlobals kg, const int object_rec
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pick a random light from the light tree from a given shading point P, write to the picked light
|
||||
* index and the probability of picking the light. */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
|
||||
const float3 rand,
|
||||
const float time,
|
||||
const float rand,
|
||||
const float3 P,
|
||||
float3 N_or_D,
|
||||
float t,
|
||||
const int object_receiver,
|
||||
const int shader_flags,
|
||||
const int bounce,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
@@ -718,10 +718,8 @@ ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
|
||||
float pdf_leaf = 1.0f;
|
||||
float pdf_selection = 1.0f;
|
||||
int selected_emitter = -1;
|
||||
int object_emitter = 0;
|
||||
int node_index = light_tree_root_node_index(kg, object_receiver);
|
||||
/* The first two dimensions of the Sobol sequence have better stratification. */
|
||||
float rand_selection = rand.z;
|
||||
float rand_selection = rand;
|
||||
|
||||
float3 local_P = P;
|
||||
|
||||
@@ -743,7 +741,7 @@ ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
|
||||
}
|
||||
|
||||
/* Continue with the picked mesh light. */
|
||||
object_emitter = kernel_data_fetch(light_tree_emitters, selected_emitter).mesh.object_id;
|
||||
ls->object = kernel_data_fetch(light_tree_emitters, selected_emitter).mesh.object_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -766,27 +764,18 @@ ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
|
||||
pdf_leaf *= (node_index == left_index) ? left_prob : (1.0f - left_prob);
|
||||
}
|
||||
|
||||
pdf_selection *= pdf_leaf;
|
||||
ls->emitter_id = selected_emitter;
|
||||
ls->pdf_selection = pdf_selection * pdf_leaf;
|
||||
|
||||
return light_sample<in_volume_segment>(kg,
|
||||
float3_to_float2(rand),
|
||||
time,
|
||||
P,
|
||||
N_or_D,
|
||||
object_receiver,
|
||||
shader_flags,
|
||||
bounce,
|
||||
path_flag,
|
||||
selected_emitter,
|
||||
object_emitter,
|
||||
pdf_selection,
|
||||
ls);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We need to be able to find the probability of selecting a given light for MIS. */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device float light_tree_pdf(KernelGlobals kg,
|
||||
float3 P,
|
||||
float3 N,
|
||||
const float dt,
|
||||
const int path_flag,
|
||||
const int object_emitter,
|
||||
const uint index_emitter,
|
||||
@@ -837,8 +826,8 @@ ccl_device float light_tree_pdf(KernelGlobals kg,
|
||||
for (int i = 0; i < knode->num_emitters; i++) {
|
||||
const int emitter = knode->leaf.first_emitter + i;
|
||||
float max_importance, min_importance;
|
||||
light_tree_emitter_importance<false>(
|
||||
kg, P, N, 0, has_transmission, emitter, max_importance, min_importance);
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N, dt, has_transmission, emitter, max_importance, min_importance);
|
||||
num_has_importance += (max_importance > 0);
|
||||
if (emitter == target_emitter) {
|
||||
target_max_importance = max_importance;
|
||||
@@ -860,7 +849,7 @@ ccl_device float light_tree_pdf(KernelGlobals kg,
|
||||
if (subtree_root_index != -1) {
|
||||
/* Arrived at the mesh light. Continue with the subtree. */
|
||||
float unused;
|
||||
light_tree_to_local_space<false>(kg, object_emitter, P, N, unused);
|
||||
light_tree_to_local_space<in_volume_segment>(kg, object_emitter, P, N, unused);
|
||||
|
||||
node_index = subtree_root_index;
|
||||
subtree_root_index = -1;
|
||||
@@ -878,8 +867,8 @@ ccl_device float light_tree_pdf(KernelGlobals kg,
|
||||
const int right_index = knode->inner.right_child;
|
||||
|
||||
float left_prob;
|
||||
if (!get_left_probability<false>(
|
||||
kg, P, N, 0, has_transmission, left_index, right_index, left_prob))
|
||||
if (!get_left_probability<in_volume_segment>(
|
||||
kg, P, N, dt, has_transmission, left_index, right_index, left_prob))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
@@ -897,4 +886,27 @@ ccl_device float light_tree_pdf(KernelGlobals kg,
|
||||
}
|
||||
}
|
||||
|
||||
/* If the function is called in volume, retrieve the previous point in volume segment, and compute
|
||||
* pdf from there. Otherwise compute from the current shading point. */
|
||||
ccl_device_inline float light_tree_pdf(KernelGlobals kg,
|
||||
float3 P,
|
||||
float3 N,
|
||||
const float dt,
|
||||
const int path_flag,
|
||||
const int emitter_object,
|
||||
const uint emitter_id,
|
||||
const int object_receiver)
|
||||
{
|
||||
if (path_flag & PATH_RAY_VOLUME_SCATTER) {
|
||||
const float3 D_times_t = N;
|
||||
const float3 D = normalize(D_times_t);
|
||||
P = P - D_times_t;
|
||||
return light_tree_pdf<true>(
|
||||
kg, P, D, dt, path_flag, emitter_object, emitter_id, object_receiver);
|
||||
}
|
||||
|
||||
return light_tree_pdf<false>(
|
||||
kg, P, N, 0.0f, path_flag, emitter_object, emitter_id, object_receiver);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
@@ -285,7 +285,8 @@ ccl_device_inline bool triangle_light_valid_ray_segment(KernelGlobals kg,
|
||||
|
||||
/* Only one side is sampled, intersect the ray and the triangle light plane to find the visible
|
||||
* ray segment. Flip normal if Emission Sampling is set to back. */
|
||||
return ray_plane_intersect((shader_flag & SD_MIS_BACK) ? -ls->Ng : ls->Ng, P, D, t_range);
|
||||
const float3 N = ls->Ng;
|
||||
return ray_plane_intersect((shader_flag & SD_MIS_BACK) ? -N : N, P, D, t_range);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
@@ -326,9 +327,8 @@ ccl_device_forceinline bool triangle_light_tree_parameters(
|
||||
}
|
||||
|
||||
const bool front_facing = bcone.theta_o != 0.0f || dot(bcone.axis, point_to_centroid) < 0;
|
||||
const bool in_volume = is_zero(N);
|
||||
|
||||
return (front_facing && shape_above_surface) || in_volume;
|
||||
return front_facing && shape_above_surface;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
||||
@@ -45,6 +45,7 @@ CCL_NAMESPACE_BEGIN
|
||||
#define OBJECT_NONE (~0)
|
||||
#define PRIM_NONE (~0)
|
||||
#define LAMP_NONE (~0)
|
||||
#define EMITTER_NONE (~0)
|
||||
#define ID_NONE (0.0f)
|
||||
#define PASS_UNUSED (~0)
|
||||
#define LIGHTGROUP_NONE (~0)
|
||||
|
||||
Submodule tests/data updated: 00af9c6571...9ce3adf54d
Reference in New Issue
Block a user