mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
When the texture cache evaluates the background shader at a low mipmap level for next event estimation, it can hit a bright pixel from e.g. very bright blurred. But in the importance map generated at a higher resolution, the corresponding pixels may not be bright at all and get a low importance sampling probability. This leads to significant noise. To avoid this, clamp the ray differentials so they are at least the ray differentials used for generating the importance map. This both reduces noise and mipmap bias. It comes at some extra memory cost, but the importance map resolution is limited by default and overall it's worth it. Pull Request: https://projects.blender.org/blender/blender/pulls/159824
509 lines
16 KiB
C++
509 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 */
|
|
|
|
/* Clamp the ray differential so the background is not evaluated coarser than the importance map.
|
|
* At lower mip levels a sharp feature like the sun becomes bigger and blurred. This is not
|
|
* accounted for in the higher resolution importance map where the sun remains smaller, leading
|
|
* to noise. The blurring may also make shadows too soft and clamping reduces that bias. */
|
|
ccl_device_forceinline float background_light_clamp_dD(KernelGlobals kg, const float dD)
|
|
{
|
|
return min(dD, kernel_data.background.map_dD);
|
|
}
|
|
|
|
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
|