Files
test/intern/cycles/kernel/light/background.h
Alaska 58b7543ede Fix #132322: Artifacts in Cycles volume rendering with Light tree on some devices
In a previous commit (1), adjustments to light tree traversal were made
to try and skip distant lights when deciding which light to sample
while within a world volume.

The skip wasn't implemented properly, and as a result distant lights
were still included in the light tree traversal in that sitaution.
And due to the way the skip was implemented, there were some
unintialized variables used in the processing of the distant lights
importance which caused artifacts on some platforms.

This commit fixes this issue by reverting the skip of distant lights
in that situation.

(1) blender/blender@6fbc958e89

Pull Request: https://projects.blender.org/blender/blender/pulls/132344
2025-01-22 05:50:32 +01:00

501 lines
16 KiB
C++

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/camera/projection.h"
#include "kernel/light/area.h"
#include "kernel/light/common.h"
#include "util/math_intersect.h"
CCL_NAMESPACE_BEGIN
/* Background Light */
ccl_device float3 background_map_sample(KernelGlobals kg,
const float2 rand,
ccl_private float *pdf)
{
/* for the following, the CDF values are actually a pair of floats, with the
* function value as X and the actual CDF as Y. The last entry's function
* value is the CDF total. */
const int res_x = kernel_data.background.map_res_x;
const int res_y = kernel_data.background.map_res_y;
const int cdf_width = res_x + 1;
/* This is basically std::lower_bound as used by PBRT. */
int first = 0;
int count = res_y;
while (count > 0) {
const int step = count >> 1;
const int middle = first + step;
if (kernel_data_fetch(light_background_marginal_cdf, middle).y < rand.y) {
first = middle + 1;
count -= step + 1;
}
else {
count = step;
}
}
const int index_v = max(0, first - 1);
kernel_assert(index_v >= 0 && index_v < res_y);
const float2 cdf_v = kernel_data_fetch(light_background_marginal_cdf, index_v);
const float2 cdf_next_v = kernel_data_fetch(light_background_marginal_cdf, index_v + 1);
const float2 cdf_last_v = kernel_data_fetch(light_background_marginal_cdf, res_y);
/* importance-sampled V direction */
const float dv = inverse_lerp(cdf_v.y, cdf_next_v.y, rand.y);
const float v = (index_v + dv) / res_y;
/* This is basically std::lower_bound as used by PBRT. */
first = 0;
count = res_x;
while (count > 0) {
const int step = count >> 1;
const int middle = first + step;
if (kernel_data_fetch(light_background_conditional_cdf, index_v * cdf_width + middle).y <
rand.x)
{
first = middle + 1;
count -= step + 1;
}
else {
count = step;
}
}
const int index_u = max(0, first - 1);
kernel_assert(index_u >= 0 && index_u < res_x);
const float2 cdf_u = kernel_data_fetch(light_background_conditional_cdf,
index_v * cdf_width + index_u);
const float2 cdf_next_u = kernel_data_fetch(light_background_conditional_cdf,
index_v * cdf_width + index_u + 1);
const float2 cdf_last_u = kernel_data_fetch(light_background_conditional_cdf,
index_v * cdf_width + res_x);
/* importance-sampled U direction */
const float du = inverse_lerp(cdf_u.y, cdf_next_u.y, rand.x);
const float u = (index_u + du) / res_x;
/* compute pdf */
const float sin_theta = sinf(M_PI_F * v);
const float denom = (M_2PI_F * M_PI_F * sin_theta) * cdf_last_u.x * cdf_last_v.x;
if (sin_theta == 0.0f || denom == 0.0f) {
*pdf = 0.0f;
}
else {
*pdf = (cdf_u.x * cdf_v.x) / denom;
}
/* compute direction */
return equirectangular_to_direction(u, v);
}
/* TODO(sergey): Same as above, after the release we should consider using
* 'noinline' for all devices.
*/
ccl_device float background_map_pdf(KernelGlobals kg, const float3 direction)
{
const float2 uv = direction_to_equirectangular(direction);
const int res_x = kernel_data.background.map_res_x;
const int res_y = kernel_data.background.map_res_y;
const int cdf_width = res_x + 1;
const float sin_theta = sinf(uv.y * M_PI_F);
if (sin_theta == 0.0f) {
return 0.0f;
}
const int index_u = clamp(float_to_int(uv.x * res_x), 0, res_x - 1);
const int index_v = clamp(float_to_int(uv.y * res_y), 0, res_y - 1);
/* pdfs in V direction */
const float2 cdf_last_u = kernel_data_fetch(light_background_conditional_cdf,
index_v * cdf_width + res_x);
const float2 cdf_last_v = kernel_data_fetch(light_background_marginal_cdf, res_y);
const float denom = (M_2PI_F * M_PI_F * sin_theta) * cdf_last_u.x * cdf_last_v.x;
if (denom == 0.0f) {
return 0.0f;
}
/* pdfs in U direction */
const float2 cdf_u = kernel_data_fetch(light_background_conditional_cdf,
index_v * cdf_width + index_u);
const float2 cdf_v = kernel_data_fetch(light_background_marginal_cdf, index_v);
return (cdf_u.x * cdf_v.x) / denom;
}
ccl_device_inline bool background_portal_data_fetch_and_check_side(KernelGlobals kg,
const float3 P,
const int index,
ccl_private float3 *lightpos,
ccl_private float3 *dir)
{
const int portal = kernel_data.integrator.portal_offset + index;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
*lightpos = klight->co;
*dir = klight->area.dir;
/* Check whether portal is on the right side. */
if (dot(*dir, P - *lightpos) > 1e-4f) {
return true;
}
return false;
}
ccl_device_inline float background_portal_pdf(KernelGlobals kg,
const float3 P,
float3 direction,
const int ignore_portal,
ccl_private bool *is_possible)
{
float portal_pdf = 0.0f;
int num_possible = 0;
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
if (p == ignore_portal) {
continue;
}
float3 lightpos;
float3 dir;
if (!background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir)) {
continue;
}
/* There's a portal that could be sampled from this position. */
if (is_possible) {
*is_possible = true;
}
num_possible++;
const int portal = kernel_data.integrator.portal_offset + p;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
const float3 axis_u = klight->area.axis_u;
const float len_u = klight->area.len_u;
const float3 axis_v = klight->area.axis_v;
const float len_v = klight->area.len_v;
const float3 inv_extent_u = axis_u / len_u;
const float3 inv_extent_v = axis_v / len_v;
const bool is_round = (klight->area.invarea < 0.0f);
if (!ray_quad_intersect(P,
direction,
1e-4f,
FLT_MAX,
lightpos,
inv_extent_u,
inv_extent_v,
dir,
nullptr,
nullptr,
nullptr,
nullptr,
is_round))
{
continue;
}
if (is_round) {
float t;
const float3 D = normalize_len(lightpos - P, &t);
portal_pdf += fabsf(klight->area.invarea) * light_pdf_area_to_solid_angle(dir, -D, t);
}
else {
portal_pdf += area_light_rect_sample(
P, &lightpos, axis_u, len_u, axis_v, len_v, zero_float2(), false);
}
}
if (ignore_portal >= 0) {
/* We have skipped a portal that could be sampled as well. */
num_possible++;
}
return (num_possible > 0) ? portal_pdf / num_possible : 0.0f;
}
ccl_device int background_num_possible_portals(KernelGlobals kg, const float3 P)
{
int num_possible_portals = 0;
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
float3 lightpos;
float3 dir;
if (background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir)) {
num_possible_portals++;
}
}
return num_possible_portals;
}
ccl_device float3 background_portal_sample(KernelGlobals kg,
const float3 P,
float2 rand,
const int num_possible,
ccl_private int *sampled_portal,
ccl_private float *pdf)
{
/* Pick a portal, then re-normalize rand.y. */
rand.y *= num_possible;
int portal = (int)rand.y;
rand.y -= portal;
/* TODO(sergey): Some smarter way of finding portal to sample
* is welcome.
*/
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
/* Search for the sampled portal. */
float3 lightpos;
float3 dir;
if (!background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir)) {
continue;
}
if (portal == 0) {
/* p is the portal to be sampled. */
const int portal = kernel_data.integrator.portal_offset + p;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
const float3 axis_u = klight->area.axis_u;
const float3 axis_v = klight->area.axis_v;
const float len_u = klight->area.len_u;
const float len_v = klight->area.len_v;
const bool is_round = (klight->area.invarea < 0.0f);
float3 D;
if (is_round) {
lightpos += ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, rand);
float t;
D = normalize_len(lightpos - P, &t);
*pdf = fabsf(klight->area.invarea) * light_pdf_area_to_solid_angle(dir, -D, t);
}
else {
*pdf = area_light_rect_sample(P, &lightpos, axis_u, len_u, axis_v, len_v, rand, true);
D = normalize(lightpos - P);
}
*pdf /= num_possible;
*sampled_portal = p;
return D;
}
portal--;
}
return zero_float3();
}
ccl_device_inline float3 background_sun_sample(KernelGlobals kg,
const float2 rand,
ccl_private float *pdf)
{
const float3 N = make_float3(kernel_data.background.sun);
const float angle = kernel_data.background.sun.w;
float unused;
return sample_uniform_cone(N, one_minus_cos(angle), rand, &unused, pdf);
}
ccl_device_inline float background_sun_pdf(KernelGlobals kg, const float3 D)
{
const float3 N = make_float3(kernel_data.background.sun);
const float angle = kernel_data.background.sun.w;
return pdf_uniform_cone(N, D, angle);
}
ccl_device_inline float3 background_light_sample(KernelGlobals kg,
const float3 P,
float2 rand,
ccl_private float *pdf)
{
float portal_method_pdf = kernel_data.background.portal_weight;
float sun_method_pdf = kernel_data.background.sun_weight;
float map_method_pdf = kernel_data.background.map_weight;
int num_portals = 0;
if (portal_method_pdf > 0.0f) {
/* Check if there are portals in the scene which we can sample. */
num_portals = background_num_possible_portals(kg, P);
if (num_portals == 0) {
portal_method_pdf = 0.0f;
}
}
float pdf_fac = (portal_method_pdf + sun_method_pdf + map_method_pdf);
if (pdf_fac == 0.0f) {
/* Use uniform as a fallback if we can't use any strategy. */
*pdf = 1.0f / M_4PI_F;
return sample_uniform_sphere(rand);
}
pdf_fac = 1.0f / pdf_fac;
portal_method_pdf *= pdf_fac;
sun_method_pdf *= pdf_fac;
map_method_pdf *= pdf_fac;
/* We have 100% in total and split it between the three categories.
* Therefore, we pick portals if rand.x is between 0 and portal_method_pdf,
* sun if rand.x is between portal_method_pdf and (portal_method_pdf + sun_method_pdf)
* and map if rand.x is between (portal_method_pdf + sun_method_pdf) and 1. */
const float sun_method_cdf = portal_method_pdf + sun_method_pdf;
int method = 0;
float3 D;
if (rand.x < portal_method_pdf) {
method = 0;
/* Rescale rand.x. */
if (portal_method_pdf != 1.0f) {
rand.x /= portal_method_pdf;
}
/* Sample a portal. */
int portal;
D = background_portal_sample(kg, P, rand, num_portals, &portal, pdf);
if (num_portals > 1) {
/* Ignore the chosen portal, its pdf is already included. */
*pdf += background_portal_pdf(kg, P, D, portal, nullptr);
}
/* Skip MIS if this is the only method. */
if (portal_method_pdf == 1.0f) {
return D;
}
*pdf *= portal_method_pdf;
}
else if (rand.x < sun_method_cdf) {
method = 1;
/* Rescale rand.x. */
if (sun_method_pdf != 1.0f) {
rand.x = (rand.x - portal_method_pdf) / sun_method_pdf;
}
D = background_sun_sample(kg, rand, pdf);
/* Skip MIS if this is the only method. */
if (sun_method_pdf == 1.0f) {
return D;
}
*pdf *= sun_method_pdf;
}
else {
method = 2;
/* Rescale rand.x. */
if (map_method_pdf != 1.0f) {
rand.x = (rand.x - sun_method_cdf) / map_method_pdf;
}
D = background_map_sample(kg, rand, pdf);
/* Skip MIS if this is the only method. */
if (map_method_pdf == 1.0f) {
return D;
}
*pdf *= map_method_pdf;
}
/* MIS weighting. */
if (method != 0 && portal_method_pdf != 0.0f) {
*pdf += portal_method_pdf * background_portal_pdf(kg, P, D, -1, nullptr);
}
if (method != 1 && sun_method_pdf != 0.0f) {
*pdf += sun_method_pdf * background_sun_pdf(kg, D);
}
if (method != 2 && map_method_pdf != 0.0f) {
*pdf += map_method_pdf * background_map_pdf(kg, D);
}
return D;
}
ccl_device float background_light_pdf(KernelGlobals kg, const float3 P, float3 direction)
{
float portal_method_pdf = kernel_data.background.portal_weight;
float sun_method_pdf = kernel_data.background.sun_weight;
float map_method_pdf = kernel_data.background.map_weight;
float portal_pdf = 0.0f;
/* Portals are a special case here since we need to compute their pdf in order
* to find out if we can sample them. */
if (portal_method_pdf > 0.0f) {
/* Evaluate PDF of sampling this direction by portal sampling. */
bool is_possible = false;
portal_pdf = background_portal_pdf(kg, P, direction, -1, &is_possible);
if (!is_possible) {
/* Portal sampling is not possible here because all portals point to the wrong side.
* If other methods can be used instead, do so, otherwise uniform sampling is used as a
* fallback. */
portal_method_pdf = 0.0f;
}
}
float pdf_fac = (portal_method_pdf + sun_method_pdf + map_method_pdf);
if (pdf_fac == 0.0f) {
/* Use uniform as a fallback if we can't use any strategy. */
return 1.0f / M_4PI_F;
}
pdf_fac = 1.0f / pdf_fac;
portal_method_pdf *= pdf_fac;
sun_method_pdf *= pdf_fac;
map_method_pdf *= pdf_fac;
float pdf = portal_pdf * portal_method_pdf;
if (sun_method_pdf != 0.0f) {
pdf += background_sun_pdf(kg, direction) * sun_method_pdf;
}
if (map_method_pdf != 0.0f) {
pdf += background_map_pdf(kg, direction) * map_method_pdf;
}
return pdf;
}
template<bool in_volume_segment>
ccl_device_forceinline bool background_light_tree_parameters(const float3 centroid,
const float t,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid,
ccl_private float &theta_d)
{
if (in_volume_segment) {
if (t == FLT_MAX) {
/* In world volumes, distant lights can contribute to the lighting of the volume with
* specific configurations of procedurally generated volumes. Use a ray length of 1.0 in this
* case to give the distant light some weight, but one that isn't too high for a typical
* world volume use case. */
theta_d = 1.0f;
}
else {
theta_d = t;
}
}
/* Cover the whole sphere */
cos_theta_u = -1.0f;
distance = make_float2(1.0f, 1.0f);
point_to_centroid = -centroid;
return true;
}
CCL_NAMESPACE_END