for energy preservation and better compatibility with other renderes. Ref: #108505 Point light now behaves the same as a spherical mesh light with the same overall energy (scaling from emission strength to power is \(4\pi^2R^2\)). # Cycles ## Comparison | Mesh Light | This patch | Previous behavior | | -------- | -------- | -------- | |  |  |  | The behavior stays the same when `radius = 0`. | This patch | Previous behavior | | -------- | -------- | |  |  | No obvious performance change observed. ## Sampling When shading point lies outside the sphere, sample the spanned solid angle uniformly. When shading point lies inside the sphere, sample spherical direction uniformly when inside volume or the surface is transmissive, otherwise sample cosine-weighted upper hemisphere. ## Light Tree When shading point lies outside the sphere, treat as a disk light spanning the same solid angle. When shading point lies inside the sphere, it behaves like a background light, with estimated outgoing radiance \[L_o=\int f_aL_i\cos\theta_i\mathrm{d}\omega_i=\int f_a\frac{E}{\pi r^2}\cos\theta_i\mathrm{d}\omega_i\approx f_a \frac{E}{r^2}\], with \(f_a\) being the BSDF and \(E\) `measure.energy` in `light_tree.cpp`. The importance calculation for `LIGHT_POINT` is \[L_o=f_a E\cos\theta_i\frac{\cos\theta}{d^2}\]. Consider `min_importance = 0` because maximal incidence angle is \(\pi\), we could substitute \(d^2\) with \(\frac{r^2}{2}\) so the averaged outgoing radiance is \(f_a \frac{E}{r^2}\). This only holds for non-transmissive surface, but should be fine to use in volume. # EEVEE When shading point lies outside the sphere, the sphere light is equivalent to a disk light spanning the same solid angle. The sine of the new half-angle is the tangent of the previous half-angle. When shading point lies inside the sphere, integrating over the cosine-weighted hemisphere gives 1.0. ## Comparison with Cycles The plane is diffuse, the blue sphere has specular component. | Before | |After || |---|--|--|--| |Cycles|EEVEE|Cycles|EEVEE| ||||| Pull Request: https://projects.blender.org/blender/blender/pulls/108506
82 lines
3.0 KiB
C++
82 lines
3.0 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#pragma once
|
|
|
|
#include "kernel/light/light.h"
|
|
#include "kernel/light/triangle.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
/* Simple CDF based sampling over all lights in the scene, without taking into
|
|
* account shading position or normal. */
|
|
|
|
ccl_device int light_distribution_sample(KernelGlobals kg, const float rand)
|
|
{
|
|
/* This is basically std::upper_bound as used by PBRT, to find a point light or
|
|
* triangle to emit from, proportional to area. a good improvement would be to
|
|
* also sample proportional to power, though it's not so well defined with
|
|
* arbitrary shaders. */
|
|
int first = 0;
|
|
int len = kernel_data.integrator.num_distribution + 1;
|
|
|
|
do {
|
|
int half_len = len >> 1;
|
|
int middle = first + half_len;
|
|
|
|
if (rand < kernel_data_fetch(light_distribution, middle).totarea) {
|
|
len = half_len;
|
|
}
|
|
else {
|
|
first = middle + 1;
|
|
len = len - half_len - 1;
|
|
}
|
|
} while (len > 0);
|
|
|
|
/* Clamping should not be needed but float rounding errors seem to
|
|
* make this fail on rare occasions. */
|
|
int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
|
|
|
|
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,
|
|
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);
|
|
}
|
|
|
|
ccl_device_inline float light_distribution_pdf_lamp(KernelGlobals kg)
|
|
{
|
|
return kernel_data.integrator.distribution_pdf_lights;
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|