Files
test/intern/cycles/kernel/light/point.h
Weizhen Huang 9fe87646d5 Cycles: replace spot light disk sampling with sphere sampling
The spotlight is now treated as a sphere instead of a view-aligned disk.
The implementation remains almost identical to that of a point light,
except for the spotlight attenuation and spot blend. There is no
attenuation inside the sphere. Ref #108505

Other changes include:
## Sampling
Instead of sampling the disk area, the new implementation samples either
the cone of the visible portion on the sphere or the spread cone, based
on which cone has a smaller solid angle. This reduces noise when the
spotlight has a large radius and a small spread angle.
| Before | After  |
|   --   |   --   |
|![spot_size_before.png](/attachments/04ea864a-6bf9-40fe-b11b-61c838ae70cf)|![spot_size_after.png](/attachments/7077eaf9-b7a8-41b1-a8b6-aabf1eadb4f4)
## Texture
Spot light can now project texture using UV coordinates.
<video src="/attachments/6db989d2-7a3c-4b41-9340-f5690d48c4fb"
title="spot_light_texture.mp4" controls></video>
## Normalization
Previously, the normalization factor for the spotlight was \(\pi r^2\),
the area of a disk. This factor has been adjusted to \(4\pi r^2\) to
account for the surface area of a sphere. This change also affects point
light since they share the same kernel type.
## Versioning
Some pipeline uses the `Normal` socket of the Texture Coordinate node for
projection, because `ls->Ng` was set to the incoming direction at the
current shading point. Now that `ls->Ng` corresponds to the normal
direction of a point on the sphere (except when the radius is zero),
we replace these nodes with a combination of the Geometry shader node
and the Vector Transform node, which gives the same result as before.
![versioning.png](/attachments/5bbfcacc-26c5-4f7f-8360-c42bcd851f68)
Example file see https://archive.blender.org/developer/T93676

Pull Request: https://projects.blender.org/blender/blender/pulls/109329
2023-07-07 17:15:18 +02:00

194 lines
6.3 KiB
C++

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight,
const float2 rand,
const float3 P,
const float3 N,
const int shader_flags,
ccl_private LightSample *ls)
{
float3 lightN = P - klight->co;
const float d_sq = len_squared(lightN);
const float d = sqrtf(d_sq);
lightN /= d;
const float r_sq = sqr(klight->spot.radius);
float cos_theta;
if (d_sq > r_sq) {
const float one_minus_cos = sin_sqr_to_one_minus_cos(r_sq / d_sq);
sample_uniform_cone_concentric(-lightN, one_minus_cos, rand, &cos_theta, &ls->D, &ls->pdf);
}
else {
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
if (has_transmission) {
ls->D = sample_uniform_sphere(rand);
ls->pdf = M_1_2PI_F * 0.5f;
}
else {
sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf);
}
cos_theta = -dot(ls->D, lightN);
}
/* Law of cosines. */
ls->t = d * cos_theta - copysignf(safe_sqrtf(r_sq - d_sq + d_sq * sqr(cos_theta)), d_sq - r_sq);
ls->P = P + ls->D * ls->t;
ls->eval_fac = klight->spot.eval_fac;
if (r_sq == 0) {
/* Use intensity instead of radiance for point light. */
ls->eval_fac /= sqr(ls->t);
/* `ls->Ng` is not well-defined for point light, so use the incoming direction instead. */
ls->Ng = -ls->D;
}
else {
ls->Ng = normalize(ls->P - klight->co);
/* Remap sampled point onto the sphere to prevent precision issues with small radius. */
ls->P = ls->Ng * klight->spot.radius + klight->co;
}
const Transform itfm = klight->itfm;
const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = uv.y;
ls->v = 1.0f - uv.x - uv.y;
return true;
}
ccl_device_forceinline float point_light_pdf(
const float d_sq, const float r_sq, const float3 N, const float3 D, const uint32_t path_flag)
{
if (d_sq > r_sq) {
return M_1_2PI_F / sin_sqr_to_one_minus_cos(r_sq / d_sq);
}
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
return has_transmission ? M_1_2PI_F * 0.5f : pdf_cos_hemisphere(N, D);
}
ccl_device_forceinline void point_light_mnee_sample_update(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P,
const float3 N,
const uint32_t path_flag)
{
ls->D = normalize_len(ls->P - P, &ls->t);
const float radius = klight->spot.radius;
if (radius > 0) {
const float d_sq = len_squared(P - klight->co);
const float r_sq = sqr(radius);
const float t_sq = sqr(ls->t);
ls->pdf = point_light_pdf(d_sq, r_sq, N, ls->D, path_flag);
/* NOTE : preserve pdf in area measure. */
ls->pdf *= 0.5f * fabsf(d_sq - r_sq - t_sq) / (radius * ls->t * t_sq);
ls->Ng = normalize(ls->P - klight->co);
}
else {
ls->eval_fac = klight->spot.eval_fac;
ls->Ng = -ls->D;
/* PDF does not change. */
}
const Transform itfm = klight->itfm;
const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = uv.y;
ls->v = 1.0f - uv.x - uv.y;
}
ccl_device_inline bool point_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t)
{
const float radius = klight->spot.radius;
if (radius == 0.0f) {
return false;
}
float3 P;
return ray_sphere_intersect(ray->P, ray->D, ray->tmin, ray->tmax, klight->co, radius, &P, t);
}
ccl_device_inline bool point_light_sample_from_intersection(
const ccl_global KernelLight *klight,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
const float3 N,
const uint32_t path_flag,
ccl_private LightSample *ccl_restrict ls)
{
ls->eval_fac = klight->spot.eval_fac;
const float radius = klight->spot.radius;
ls->Ng = radius > 0 ? normalize(ls->P - klight->co) : -ray_D;
const Transform itfm = klight->itfm;
const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = uv.y;
ls->v = 1.0f - uv.x - uv.y;
if (ls->t == FLT_MAX) {
ls->pdf = 0.0f;
}
else {
ls->pdf = point_light_pdf(len_squared(ray_P - klight->co), sqr(radius), N, ray_D, path_flag);
}
return true;
}
template<bool in_volume_segment>
ccl_device_forceinline bool point_light_tree_parameters(const ccl_global KernelLight *klight,
const float3 centroid,
const float3 P,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
if (in_volume_segment) {
cos_theta_u = 1.0f; /* Any value in [-1, 1], irrelevant since theta = 0 */
return true;
}
float dist_point_to_centroid;
point_to_centroid = safe_normalize_len(centroid - P, &dist_point_to_centroid);
const float radius = klight->spot.radius;
if (dist_point_to_centroid > radius) {
/* Equivalent to a disk light with the same angular span. */
cos_theta_u = cos_from_sin(radius / dist_point_to_centroid);
distance = dist_point_to_centroid * make_float2(1.0f / cos_theta_u, 1.0f);
}
else {
/* Similar to background light. */
cos_theta_u = -1.0f;
/* HACK: pack radiance scaling in the distance. */
distance = one_float2() * radius / M_SQRT2_F;
}
return true;
}
CCL_NAMESPACE_END