Add Chromium-only Blender WebEngine parity work
This commit is contained in:
551
blender-5.2.0/intern/cycles/kernel/light/area.h
Normal file
551
blender-5.2.0/intern/cycles/kernel/light/area.h
Normal file
@@ -0,0 +1,551 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
#include "util/math_intersect.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Importance sampling.
|
||||
*
|
||||
* An Area-Preserving Parametrization for Spherical Rectangles.
|
||||
* Carlos Urena et al.
|
||||
*
|
||||
* NOTE: light_p is modified when sample_coord is true. */
|
||||
ccl_device_inline float area_light_rect_sample(const float3 P,
|
||||
ccl_private float3 *light_p,
|
||||
const float3 axis_u,
|
||||
const float len_u,
|
||||
const float3 axis_v,
|
||||
const float len_v,
|
||||
const float2 rand,
|
||||
bool sample_coord)
|
||||
{
|
||||
/* Compute local reference system R. */
|
||||
const float3 x = axis_u;
|
||||
const float3 y = axis_v;
|
||||
float3 z = cross(x, y);
|
||||
/* Compute rectangle coords in local reference system. */
|
||||
const float3 dir = *light_p - P;
|
||||
float z0 = dot(dir, z);
|
||||
/* Flip 'z' to make it point against Q. */
|
||||
if (z0 > 0.0f) {
|
||||
z *= -1.0f;
|
||||
z0 *= -1.0f;
|
||||
}
|
||||
const float xc = dot(dir, x);
|
||||
const float yc = dot(dir, y);
|
||||
const float x0 = xc - 0.5f * len_u;
|
||||
const float x1 = xc + 0.5f * len_u;
|
||||
const float y0 = yc - 0.5f * len_v;
|
||||
const float y1 = yc + 0.5f * len_v;
|
||||
/* Compute predefined constants. */
|
||||
float4 nz = make_float4(-y0, x1, y1, -x0);
|
||||
nz /= sqrt(nz * nz + z0 * z0);
|
||||
/* The original paper uses `acos()` to compute the internal angles here, and then computes the
|
||||
* solid angle as their sum minus 2*pi. However, for very small rectangles, this results in
|
||||
* excessive cancellation error since the sum will be almost 2*pi as well.
|
||||
* This can be avoided by using that `asin(x) = pi/2 - acos(x)`. */
|
||||
const float g0 = safe_asinf(-nz.x * nz.y);
|
||||
const float g1 = safe_asinf(-nz.y * nz.z);
|
||||
const float g2 = safe_asinf(-nz.z * nz.w);
|
||||
const float g3 = safe_asinf(-nz.w * nz.x);
|
||||
const float S = -(g0 + g1 + g2 + g3);
|
||||
|
||||
if (sample_coord) {
|
||||
/* Compute predefined constants. */
|
||||
const float b0 = nz.x;
|
||||
const float b1 = nz.z;
|
||||
const float b0sq = b0 * b0;
|
||||
/* Compute cu.
|
||||
* In the original paper, an additional constant k is involved here. However, just like above,
|
||||
* it causes cancellation issues. The same `asin()` terms from above can be used instead, and
|
||||
* the extra +pi that would remain in the expression for au can be removed by flipping the sign
|
||||
* of cos(au) and sin(au), which also cancels if we flip the sign of b1 in the fu term. */
|
||||
const float au = rand.x * S + g2 + g3;
|
||||
const float fu = safe_divide(cosf(au) * b0 + b1, sinf(au));
|
||||
float cu = copysignf(1.0f / sqrtf(fu * fu + b0sq), fu);
|
||||
cu = clamp(cu, -1.0f, 1.0f);
|
||||
/* Compute xu. */
|
||||
float xu = -(cu * z0) / max(sqrtf(1.0f - cu * cu), 1e-7f);
|
||||
xu = clamp(xu, x0, x1);
|
||||
/* Compute yv. */
|
||||
const float d2 = sqr(xu) + sqr(z0);
|
||||
const float h0 = y0 / sqrtf(d2 + sqr(y0));
|
||||
const float h1 = y1 / sqrtf(d2 + sqr(y1));
|
||||
const float hv = h0 + rand.y * (h1 - h0);
|
||||
const float hv2 = hv * hv;
|
||||
const float yv = (hv2 < 1.0f - 1e-6f) ? hv * sqrtf(d2 / (1.0f - hv2)) : y1;
|
||||
|
||||
/* Transform (xu, yv, z0) to world coords. */
|
||||
*light_p = P + xu * x + yv * y + z0 * z;
|
||||
}
|
||||
|
||||
/* return pdf */
|
||||
if (S < 1e-5f || reduce_min(sqr(nz)) > 0.99999f) {
|
||||
/* The solid angle is too small to be computed accurately in single precision.
|
||||
* As a fallback, approximate it using the planar sampling PDF,
|
||||
* for such tiny lights the difference is irrelevant.
|
||||
*
|
||||
* A threshold of 1e-5 was found to be the smallest option that avoids structured
|
||||
* artifacts at all tested parameter combinations. The additional check of nz is
|
||||
* needed for the case where the light is viewed from grazing angles, see e.g. #98930.
|
||||
*/
|
||||
const float t = len(dir);
|
||||
return safe_divide(-t * t * t, (z0 * len_u * len_v));
|
||||
}
|
||||
return 1.0f / S;
|
||||
}
|
||||
|
||||
/* Light spread. */
|
||||
|
||||
ccl_device float area_light_spread_attenuation(const float3 D,
|
||||
const float3 lightNg,
|
||||
const float tan_half_spread,
|
||||
const float normalize_spread)
|
||||
{
|
||||
/* Model a soft-box grid, computing the ratio of light not hidden by the
|
||||
* slats of the grid at a given angle. (see D10594). */
|
||||
const float tan_a = tan_angle(-D, lightNg);
|
||||
|
||||
if (tan_half_spread == 0.0f) {
|
||||
/* The factor M_PI_F comes from integrating the radiance over the hemisphere */
|
||||
return (tan_a > 1e-5f) ? 0.0f : M_PI_F;
|
||||
}
|
||||
|
||||
return max((tan_half_spread - tan_a) * normalize_spread, 0.0f);
|
||||
}
|
||||
|
||||
/* Compute the minimal rectangle, circle or ellipse that covers the valid sample region, to reduce
|
||||
* noise with low spread. */
|
||||
ccl_device bool area_light_spread_clamp_light(const float3 P,
|
||||
const float3 lightNg,
|
||||
ccl_private float3 *lightP,
|
||||
ccl_private float3 *axis_u,
|
||||
ccl_private float *len_u,
|
||||
ccl_private float3 *axis_v,
|
||||
ccl_private float *len_v,
|
||||
const float tan_half_spread,
|
||||
ccl_private bool *sample_rectangle)
|
||||
{
|
||||
/* Distance from shading point to area light plane and the closest point on that plane. */
|
||||
const float t = dot(lightNg, P - *lightP);
|
||||
const float3 closest_P = P - t * lightNg;
|
||||
|
||||
/* Radius of circle on area light that actually affects the shading point. */
|
||||
const float r_spread = t * tan_half_spread;
|
||||
|
||||
/* Local uv coordinates of closest point. */
|
||||
const float spread_u = dot(*axis_u, closest_P - *lightP);
|
||||
const float spread_v = dot(*axis_v, closest_P - *lightP);
|
||||
|
||||
const bool is_round = !(*sample_rectangle) && (*len_u == *len_v);
|
||||
|
||||
/* Whether we should sample the spread circle. */
|
||||
bool sample_spread = (r_spread == 0.0f);
|
||||
if (is_round && !sample_spread) {
|
||||
/* Distance between the centers of the disk light and the valid region circle. */
|
||||
const float dist = len(make_float2(spread_u, spread_v));
|
||||
|
||||
/* Radius of the disk light. */
|
||||
const float r = *len_u * 0.5f;
|
||||
|
||||
if (dist >= r + r_spread) {
|
||||
/* Two circles are outside each other or touch externally. */
|
||||
return false;
|
||||
}
|
||||
|
||||
sample_spread = (dist <= fabsf(r - r_spread)) && (r_spread < r);
|
||||
if (dist > fabsf(r - r_spread)) {
|
||||
/* Two circles intersect. Find the smallest rectangle that covers the intersection */
|
||||
const float len_u_ = r + r_spread - dist;
|
||||
const float len_v_ = (fabsf(sqr(r) - sqr(r_spread)) >= sqr(dist)) ?
|
||||
2.0f * fminf(r, r_spread) :
|
||||
sqrtf(sqr(2.0f * r_spread) -
|
||||
sqr(dist + (sqr(r_spread) - sqr(r)) / dist));
|
||||
|
||||
const float rect_area = len_u_ * len_v_;
|
||||
const float circle_area = M_PI_F * sqr(r);
|
||||
const float spread_area = M_PI_F * sqr(r_spread);
|
||||
|
||||
/* Sample the shape with minimal area. */
|
||||
if (rect_area < fminf(circle_area, spread_area)) {
|
||||
*sample_rectangle = true;
|
||||
*axis_u = normalize(*lightP - closest_P);
|
||||
*axis_v = rotate_around_axis(*axis_u, lightNg, M_PI_2_F);
|
||||
*len_u = len_u_;
|
||||
*len_v = len_v_;
|
||||
*lightP = 0.5f * (*lightP + closest_P + *axis_u * (r_spread - r));
|
||||
return true;
|
||||
}
|
||||
|
||||
sample_spread = (spread_area < circle_area);
|
||||
}
|
||||
}
|
||||
else if (!is_round && !sample_spread) {
|
||||
/* Compute rectangle encompassing the circle that affects the shading point,
|
||||
* clamped to the bounds of the area light. */
|
||||
const float min_u = max(spread_u - r_spread, -*len_u * 0.5f);
|
||||
const float max_u = min(spread_u + r_spread, *len_u * 0.5f);
|
||||
const float min_v = max(spread_v - r_spread, -*len_v * 0.5f);
|
||||
const float max_v = min(spread_v + r_spread, *len_v * 0.5f);
|
||||
|
||||
/* Skip if rectangle is empty. */
|
||||
if (min_u >= max_u || min_v >= max_v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float rect_len_u = max_u - min_u;
|
||||
const float rect_len_v = max_v - min_v;
|
||||
|
||||
const float rect_area = rect_len_u * rect_len_v;
|
||||
const float ellipse_area = (*sample_rectangle) ? FLT_MAX : M_PI_4_F * (*len_u) * (*len_v);
|
||||
const float spread_area = M_PI_F * sqr(r_spread);
|
||||
|
||||
/* Sample the shape with minimal area. */
|
||||
/* NOTE: we don't switch to spread circle sampling for rectangle light because rectangle light
|
||||
* supports solid angle sampling, which has less variance than sampling the area. If ellipse
|
||||
* area light also supports solid angle sampling, `*sample_rectangle ||` could be deleted. */
|
||||
if (*sample_rectangle || rect_area < fminf(ellipse_area, spread_area)) {
|
||||
*sample_rectangle = true;
|
||||
|
||||
/* Compute new area light center position and axes from rectangle in local
|
||||
* uv coordinates. */
|
||||
const float new_center_u = 0.5f * (min_u + max_u);
|
||||
const float new_center_v = 0.5f * (min_v + max_v);
|
||||
|
||||
*len_u = rect_len_u;
|
||||
*len_v = rect_len_v;
|
||||
*lightP = *lightP + *axis_u * new_center_u + *axis_v * new_center_v;
|
||||
return true;
|
||||
}
|
||||
*sample_rectangle = false;
|
||||
sample_spread = (spread_area < ellipse_area);
|
||||
}
|
||||
|
||||
if (sample_spread) {
|
||||
*sample_rectangle = false;
|
||||
*lightP = *lightP + *axis_u * spread_u + *axis_v * spread_v;
|
||||
*len_u = r_spread * 2.0f;
|
||||
*len_v = r_spread * 2.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Don't clamp. */
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline bool area_light_is_ellipse(const ccl_global KernelAreaLight *light)
|
||||
{
|
||||
return light->invarea < 0.0f;
|
||||
}
|
||||
|
||||
/* Common API. */
|
||||
/* Compute `eval_fac` and `pdf`. Also sample a new position on the light if `sample_coord`. */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool area_light_eval(const ccl_global KernelLight *klight,
|
||||
const float3 ray_P,
|
||||
ccl_private float3 *light_P,
|
||||
ccl_private LightSample *ccl_restrict ls,
|
||||
const float2 rand,
|
||||
bool sample_coord)
|
||||
{
|
||||
float3 axis_u = klight->area.axis_u;
|
||||
float3 axis_v = klight->area.axis_v;
|
||||
float len_u = klight->area.len_u;
|
||||
float len_v = klight->area.len_v;
|
||||
|
||||
const float3 Ng = klight->area.dir;
|
||||
const float invarea = fabsf(klight->area.invarea);
|
||||
bool sample_rectangle = (klight->area.invarea > 0.0f);
|
||||
|
||||
float3 light_P_new = *light_P;
|
||||
|
||||
if (in_volume_segment) {
|
||||
light_P_new += sample_rectangle ?
|
||||
rectangle_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, rand) :
|
||||
ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, rand);
|
||||
ls->pdf = invarea;
|
||||
}
|
||||
else {
|
||||
if (klight->area.normalize_spread > 0) {
|
||||
if (!area_light_spread_clamp_light(ray_P,
|
||||
Ng,
|
||||
&light_P_new,
|
||||
&axis_u,
|
||||
&len_u,
|
||||
&axis_v,
|
||||
&len_v,
|
||||
klight->area.tan_half_spread,
|
||||
&sample_rectangle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_rectangle) {
|
||||
ls->pdf = area_light_rect_sample(
|
||||
ray_P, &light_P_new, axis_u, len_u, axis_v, len_v, rand, sample_coord);
|
||||
}
|
||||
else {
|
||||
if (klight->area.tan_half_spread == 0.0f) {
|
||||
ls->pdf = 1.0f;
|
||||
}
|
||||
else {
|
||||
if (sample_coord) {
|
||||
light_P_new += ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, rand);
|
||||
}
|
||||
ls->pdf = 4.0f * M_1_PI_F / (len_u * len_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_coord) {
|
||||
*light_P = light_P_new;
|
||||
ls->D = safe_normalize_len(*light_P - ray_P, &ls->t);
|
||||
}
|
||||
|
||||
/* Convert radiant flux to radiance. */
|
||||
ls->eval_fac = M_1_PI_F * invarea;
|
||||
|
||||
if (klight->area.normalize_spread > 0) {
|
||||
/* Area Light spread angle attenuation */
|
||||
ls->eval_fac *= area_light_spread_attenuation(
|
||||
ls->D, Ng, klight->area.tan_half_spread, klight->area.normalize_spread);
|
||||
}
|
||||
|
||||
if (in_volume_segment || (!sample_rectangle && klight->area.tan_half_spread > 0)) {
|
||||
ls->pdf *= light_pdf_area_to_solid_angle(Ng, -ls->D, ls->t);
|
||||
}
|
||||
|
||||
return in_volume_segment || ls->eval_fac > 0;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
|
||||
const float2 rand,
|
||||
const float3 P,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
ls->P = klight->co;
|
||||
ls->Ng = klight->area.dir;
|
||||
|
||||
if (!in_volume_segment) {
|
||||
if (dot(ls->P - P, ls->Ng) > 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!area_light_eval<in_volume_segment>(klight, P, &ls->P, ls, rand, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float3 inplane = ls->P - klight->co;
|
||||
float light_u = dot(inplane, klight->area.axis_u);
|
||||
float light_v = dot(inplane, klight->area.axis_v);
|
||||
|
||||
if (!in_volume_segment && klight->area.normalize_spread > 0) {
|
||||
const bool is_ellipse = area_light_is_ellipse(&klight->area);
|
||||
|
||||
/* Check whether the sampled point lies outside of the area light.
|
||||
* For very small area lights, numerical issues can cause this to be
|
||||
* slightly off since the sampling logic clamps the result right at the border,
|
||||
* so allow for a small margin of error. */
|
||||
const float len_u_epsilon = ((0.5f + 1e-7f) * klight->area.len_u + 1e-6f);
|
||||
const float len_v_epsilon = ((0.5f + 1e-7f) * klight->area.len_v + 1e-6f);
|
||||
if (is_ellipse && (sqr(light_u / len_u_epsilon) + sqr(light_v / len_v_epsilon) > 1.0f)) {
|
||||
return false;
|
||||
}
|
||||
if (!is_ellipse && (fabsf(light_u) > len_u_epsilon || fabsf(light_v) > len_v_epsilon)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
light_u /= klight->area.len_u;
|
||||
light_v /= klight->area.len_v;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline void area_light_mnee_sample_update(const ccl_global KernelLight *klight,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
if (klight->area.tan_half_spread == 0) {
|
||||
/* Update position on the light to keep the direction fixed. */
|
||||
area_light_eval<false>(klight, P, &ls->P, ls, zero_float2(), true);
|
||||
}
|
||||
else {
|
||||
ls->D = safe_normalize_len(ls->P - P, &ls->t);
|
||||
area_light_eval<false>(klight, P, &ls->P, ls, zero_float2(), false);
|
||||
/* Convert pdf to be in area measure. */
|
||||
ls->pdf /= light_pdf_area_to_solid_angle(ls->Ng, -ls->D, ls->t);
|
||||
}
|
||||
}
|
||||
|
||||
ccl_device_inline bool area_light_intersect(const ccl_global KernelLight *klight,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private float *t)
|
||||
{
|
||||
/* Area light. */
|
||||
const float invarea = fabsf(klight->area.invarea);
|
||||
const bool is_ellipse = area_light_is_ellipse(&klight->area);
|
||||
if (invarea == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float3 inv_extent_u = klight->area.axis_u / klight->area.len_u;
|
||||
const float3 inv_extent_v = klight->area.axis_v / klight->area.len_v;
|
||||
const float3 Ng = klight->area.dir;
|
||||
|
||||
/* One sided. */
|
||||
if (dot(ray->D, Ng) >= 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float3 light_P = klight->co;
|
||||
|
||||
float3 P;
|
||||
float u, v;
|
||||
return ray_quad_intersect(ray->P,
|
||||
ray->D,
|
||||
ray->tmin,
|
||||
ray->tmax,
|
||||
light_P,
|
||||
inv_extent_u,
|
||||
inv_extent_v,
|
||||
Ng,
|
||||
&P,
|
||||
t,
|
||||
&u,
|
||||
&v,
|
||||
is_ellipse);
|
||||
}
|
||||
|
||||
ccl_device_inline float2 area_light_uv(const ccl_global KernelLight *klight, const float3 P)
|
||||
{
|
||||
/* Compute uv when we already know there is an intersection, to avoid the need
|
||||
* of storing this in the integrate state. */
|
||||
const float3 inv_extent_u = klight->area.axis_u / klight->area.len_u;
|
||||
const float3 inv_extent_v = klight->area.axis_v / klight->area.len_v;
|
||||
const float3 light_P = klight->co;
|
||||
|
||||
const float3 inplane = P - light_P;
|
||||
const float u = clamp(dot(inplane, inv_extent_u), -0.5f, 0.5f);
|
||||
const float v = clamp(dot(inplane, inv_extent_v), -0.5f, 0.5f);
|
||||
|
||||
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
|
||||
return make_float2(v + 0.5f, -u - v);
|
||||
}
|
||||
|
||||
ccl_device_inline LightEval area_light_eval_from_intersection(const ccl_global KernelLight *klight,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
const float t)
|
||||
{
|
||||
LightSample ls{};
|
||||
ls.t = t;
|
||||
ls.P = ray_P + ray_D * t;
|
||||
ls.D = ray_D;
|
||||
ls.Ng = klight->area.dir;
|
||||
|
||||
float3 light_P = klight->co;
|
||||
if (!area_light_eval<false>(klight, ray_P, &light_P, &ls, zero_float2(), false)) {
|
||||
return LightEval{};
|
||||
}
|
||||
|
||||
return LightEval{ls.eval_fac, ls.pdf};
|
||||
}
|
||||
|
||||
/* Returns the maximal distance between the light center and the boundary. */
|
||||
ccl_device_forceinline float area_light_max_extent(const ccl_global KernelAreaLight *light)
|
||||
{
|
||||
return 0.5f * (area_light_is_ellipse(light) ? fmaxf(light->len_u, light->len_v) :
|
||||
len(make_float2(light->len_u, light->len_v)));
|
||||
}
|
||||
|
||||
/* Find the ray segment lit by the area light. */
|
||||
ccl_device_inline bool area_light_valid_ray_segment(const ccl_global KernelAreaLight *light,
|
||||
float3 P,
|
||||
float3 D,
|
||||
ccl_private Interval<float> *t_range)
|
||||
{
|
||||
bool valid;
|
||||
const float tan_half_spread = light->tan_half_spread;
|
||||
float3 axis = light->dir;
|
||||
|
||||
const bool angle_almost_zero = (tan_half_spread < 1e-5f);
|
||||
if (angle_almost_zero) {
|
||||
/* Map to local coordinate of the light. Do not use `itfm` in `KernelLight` as there might be
|
||||
* additional scaling in the light size. */
|
||||
const Transform tfm = make_transform(light->axis_u, light->axis_v, axis);
|
||||
P = transform_point(&tfm, P);
|
||||
D = transform_direction(&tfm, D);
|
||||
axis = make_float3(0.0f, 0.0f, 1.0f);
|
||||
|
||||
const float half_len_u = 0.5f * light->len_u;
|
||||
const float half_len_v = 0.5f * light->len_v;
|
||||
if (area_light_is_ellipse(light)) {
|
||||
valid = ray_infinite_cylinder_intersect(P, D, half_len_u, half_len_v, t_range);
|
||||
}
|
||||
else {
|
||||
const float3 bbox_min = make_float3(-half_len_u, -half_len_v, 0.0f);
|
||||
const float3 bbox_max = make_float3(half_len_u, half_len_v, FLT_MAX);
|
||||
valid = ray_aabb_intersect(bbox_min, bbox_max, P, D, t_range);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Conservative estimation with the smallest possible cone covering the whole spread. */
|
||||
const float3 apex_to_point = P + area_light_max_extent(light) / tan_half_spread * axis;
|
||||
const float cos_angle_sq = 1.0f / (1.0f + sqr(tan_half_spread));
|
||||
|
||||
valid = ray_cone_intersect(axis, apex_to_point, D, cos_angle_sq, t_range);
|
||||
}
|
||||
|
||||
/* Limit the range to the positive side of the area light. */
|
||||
return valid && ray_plane_intersect(axis, P, D, t_range);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool area_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const float3 bcone_axis,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it worth the
|
||||
* overhead to compute the accurate minimal distance? */
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = make_float2(min_distance, min_distance);
|
||||
|
||||
cos_theta_u = FLT_MAX;
|
||||
|
||||
const float3 extentu = klight->area.axis_u * klight->area.len_u;
|
||||
const float3 extentv = klight->area.axis_v * klight->area.len_v;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
const float3 corner = ((i & 1) - 0.5f) * extentu + 0.5f * ((i & 2) - 1) * extentv + centroid;
|
||||
float distance_point_to_corner;
|
||||
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
if (!in_volume_segment) {
|
||||
distance.x = fmaxf(distance.x, distance_point_to_corner);
|
||||
}
|
||||
}
|
||||
|
||||
const bool front_facing = dot(bcone_axis, point_to_centroid) < 0;
|
||||
const bool shape_above_surface = dot(N, centroid - P) + fabsf(dot(N, extentu)) +
|
||||
fabsf(dot(N, extentv)) >
|
||||
0;
|
||||
|
||||
return front_facing && shape_above_surface;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
509
blender-5.2.0/intern/cycles/kernel/light/background.h
Normal file
509
blender-5.2.0/intern/cycles/kernel/light/background.h
Normal file
@@ -0,0 +1,509 @@
|
||||
/* 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
|
||||
96
blender-5.2.0/intern/cycles/kernel/light/common.h
Normal file
96
blender-5.2.0/intern/cycles/kernel/light/common.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/types.h"
|
||||
|
||||
#include "kernel/sample/mapping.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Result from light sampling with next event estimation.
|
||||
*
|
||||
* TODO: It may be possible to reduce the size of this struct now that shader evaluation
|
||||
* no longer uses this. For example D, Ng or P, though it's not trivial. */
|
||||
struct LightSample {
|
||||
float3 P; /* position on light, or direction for distant 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 pdf; /* pdf for selecting light and point on light */
|
||||
float pdf_selection; /* pdf for selecting light */
|
||||
float eval_fac; /* intensity multiplier (normalization, spot falloff) */
|
||||
int object; /* object id for triangle/curve lights */
|
||||
int prim; /* lamp id for lights, primitive id for triangle/curve lights */
|
||||
int shader; /* shader id */
|
||||
int group; /* lightgroup */
|
||||
LightType type; /* type of light */
|
||||
int emitter_id; /* index in the emitter array */
|
||||
};
|
||||
|
||||
/* Result of evaluating a light from an intersection. */
|
||||
struct LightEval {
|
||||
float eval_fac = 0.0f; /* Intensity multiplier (normalization, spot falloff) */
|
||||
float pdf = 0.0f; /* PDF for light sampling with next event estimation sampling. */
|
||||
};
|
||||
|
||||
/* Utilities */
|
||||
|
||||
ccl_device_inline float3 ellipse_sample(const float3 ru, const float3 rv, const float2 rand)
|
||||
{
|
||||
const float2 uv = sample_uniform_disk(rand);
|
||||
return ru * uv.x + rv * uv.y;
|
||||
}
|
||||
|
||||
ccl_device_inline float3 rectangle_sample(const float3 ru, const float3 rv, const float2 rand)
|
||||
{
|
||||
return ru * (2.0f * rand.x - 1.0f) + rv * (2.0f * rand.y - 1.0f);
|
||||
}
|
||||
|
||||
ccl_device float3 disk_light_sample(const float3 n, const float2 rand)
|
||||
{
|
||||
float3 ru;
|
||||
float3 rv;
|
||||
|
||||
make_orthonormals(n, &ru, &rv);
|
||||
|
||||
return ellipse_sample(ru, rv, rand);
|
||||
}
|
||||
|
||||
ccl_device float light_pdf_area_to_solid_angle(const float3 Ng, const float3 I, const float t)
|
||||
{
|
||||
const float cos_pi = dot(Ng, I);
|
||||
|
||||
if (cos_pi <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return t * t / cos_pi;
|
||||
}
|
||||
|
||||
/* Visibility flag om the light shader. */
|
||||
ccl_device_inline bool is_light_shader_visible_to_path(const int shader,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
if ((shader & SHADER_EXCLUDE_ANY) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (((shader & SHADER_EXCLUDE_DIFFUSE) && (path_visibility & PATH_RAY_VISIBILITY_DIFFUSE)) ||
|
||||
((shader & SHADER_EXCLUDE_GLOSSY) &&
|
||||
((path_visibility & PATH_RAY_VISIBILITY_GLOSSY) && (path_flag & PATH_RAY_REFLECT))) ||
|
||||
((shader & SHADER_EXCLUDE_TRANSMIT) && (path_visibility & PATH_RAY_VISIBILITY_TRANSMIT)) ||
|
||||
((shader & SHADER_EXCLUDE_CAMERA) && (path_visibility & PATH_RAY_VISIBILITY_CAMERA)) ||
|
||||
((shader & SHADER_EXCLUDE_SCATTER) &&
|
||||
(path_visibility & PATH_RAY_VISIBILITY_VOLUME_SCATTER)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
60
blender-5.2.0/intern/cycles/kernel/light/distribution.h
Normal file
60
blender-5.2.0/intern/cycles/kernel/light/distribution.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/globals.h"
|
||||
|
||||
#include "kernel/light/common.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 {
|
||||
const int half_len = len >> 1;
|
||||
const 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. */
|
||||
const int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
ccl_device bool light_distribution_sample(KernelGlobals kg,
|
||||
const float rand,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
/* Sample light index from distribution. */
|
||||
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)
|
||||
{
|
||||
return kernel_data.integrator.distribution_pdf_lights;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
493
blender-5.2.0/intern/cycles/kernel/light/light.h
Normal file
493
blender-5.2.0/intern/cycles/kernel/light/light.h
Normal file
@@ -0,0 +1,493 @@
|
||||
/* SPDX-FileCopyrightText: 2010-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/geom/object.h"
|
||||
#include "kernel/globals.h"
|
||||
|
||||
#include "kernel/integrator/state.h"
|
||||
|
||||
#include "kernel/light/area.h"
|
||||
#include "kernel/light/background.h"
|
||||
#include "kernel/light/point.h"
|
||||
#include "kernel/light/spot.h"
|
||||
#include "kernel/light/sun.h"
|
||||
#include "kernel/light/triangle.h"
|
||||
#include "kernel/sample/lcg.h"
|
||||
#include "kernel/types.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Light info. */
|
||||
|
||||
ccl_device_inline bool light_select_reached_max_bounces(KernelGlobals kg,
|
||||
const int index,
|
||||
const int bounce)
|
||||
{
|
||||
return (bounce > kernel_data_fetch(lights, index).max_bounces);
|
||||
}
|
||||
|
||||
/* Light linking. */
|
||||
|
||||
ccl_device_inline int light_link_receiver_nee(KernelGlobals kg, const ccl_private ShaderData *sd)
|
||||
{
|
||||
#ifdef __LIGHT_LINKING__
|
||||
if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
|
||||
return OBJECT_NONE;
|
||||
}
|
||||
|
||||
return sd->object;
|
||||
#else
|
||||
return OBJECT_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
ccl_device_inline int light_link_receiver_forward(KernelGlobals kg, IntegratorState state)
|
||||
{
|
||||
#ifdef __LIGHT_LINKING__
|
||||
if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
|
||||
return OBJECT_NONE;
|
||||
}
|
||||
|
||||
return INTEGRATOR_STATE(state, path, mis_ray_object);
|
||||
#else
|
||||
return OBJECT_NONE;
|
||||
#endif
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_link_object_match(KernelGlobals kg,
|
||||
const int receiver,
|
||||
const int emitter)
|
||||
{
|
||||
#ifdef __LIGHT_LINKING__
|
||||
if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
kernel_assert(emitter != OBJECT_NONE);
|
||||
kernel_assert(receiver != OBJECT_NONE);
|
||||
|
||||
const uint64_t set_membership = kernel_data_fetch(objects, emitter).light_set_membership;
|
||||
const uint receiver_set = kernel_data_fetch(objects, receiver).receiver_light_set;
|
||||
return ((uint64_t(1) << uint64_t(receiver_set)) & set_membership) != 0;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Sample point on an individual light. */
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_inline bool light_sample(KernelGlobals kg,
|
||||
const int lamp,
|
||||
const float2 rand,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const int shader_flags,
|
||||
const uint32_t path_flag,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
|
||||
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
|
||||
if (klight->shader_id & SHADER_EXCLUDE_SHADOW_CATCHER) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const LightType type = (LightType)klight->type;
|
||||
ls->type = type;
|
||||
ls->shader = klight->shader_id;
|
||||
ls->object = klight->object_id;
|
||||
ls->prim = lamp;
|
||||
ls->group = object_lightgroup(kg, ls->object);
|
||||
|
||||
if (in_volume_segment && (type == LIGHT_SUN || type == LIGHT_BACKGROUND)) {
|
||||
/* Distant lights in a volume get a dummy sample, position will not actually
|
||||
* be used in that case. Only when sampling from a specific scatter position
|
||||
* do we actually need to evaluate these. */
|
||||
ls->P = zero_float3();
|
||||
ls->Ng = zero_float3();
|
||||
ls->D = zero_float3();
|
||||
ls->pdf = 1.0f;
|
||||
ls->eval_fac = 0.0f;
|
||||
ls->t = FLT_MAX;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == LIGHT_SUN) {
|
||||
if (!sun_light_sample(klight, rand, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_BACKGROUND) {
|
||||
/* infinite area light (e.g. light dome or env light) */
|
||||
const float3 D = -background_light_sample(kg, P, rand, &ls->pdf);
|
||||
|
||||
ls->P = D;
|
||||
ls->Ng = D;
|
||||
ls->D = -D;
|
||||
ls->t = FLT_MAX;
|
||||
ls->eval_fac = 1.0f;
|
||||
}
|
||||
else if (type == LIGHT_SPOT) {
|
||||
if (!spot_light_sample<in_volume_segment>(kg, klight, rand, P, N, shader_flags, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
if (!point_light_sample(klight, rand, P, N, shader_flags, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* area light */
|
||||
if (!area_light_sample<in_volume_segment>(klight, rand, P, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return in_volume_segment || (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
/* Sample a point on the chosen emitter. */
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device bool light_sample(KernelGlobals kg,
|
||||
const float3 rand_light,
|
||||
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)
|
||||
{
|
||||
/* The first two dimensions of the Sobol sequence have better stratification, use them to sample
|
||||
* position on the light. */
|
||||
const float2 rand = make_float2(rand_light);
|
||||
|
||||
int prim;
|
||||
int visibility_flag;
|
||||
int object_id;
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
ls->emitter_id);
|
||||
prim = kemitter->light.id;
|
||||
visibility_flag = kemitter->visibility_flag;
|
||||
object_id = (prim >= 0) ? ls->object : kemitter->object_id;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
const ccl_global KernelLightDistribution *kdistribution = &kernel_data_fetch(
|
||||
light_distribution, ls->emitter_id);
|
||||
prim = kdistribution->prim;
|
||||
object_id = kdistribution->object_id;
|
||||
visibility_flag = kdistribution->visibility_flag;
|
||||
}
|
||||
|
||||
if (!light_link_object_match(kg, object_receiver, object_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prim >= 0) {
|
||||
/* Mesh light. */
|
||||
|
||||
/* Exclude synthetic meshes from shadow catcher pass. */
|
||||
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
|
||||
!(kernel_data_fetch(object_flag, object_id) & SD_OBJECT_SHADOW_CATCHER))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!triangle_light_sample<in_volume_segment>(kg, prim, object_id, rand, time, ls, P)) {
|
||||
return false;
|
||||
}
|
||||
ls->shader |= visibility_flag;
|
||||
}
|
||||
else {
|
||||
const int light = ~prim;
|
||||
|
||||
if (UNLIKELY(light_select_reached_max_bounces(kg, light, bounce))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!light_sample<in_volume_segment>(kg, light, rand, P, N, shader_flags, path_flag, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
return in_volume_segment || (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
/* Intersect ray with individual light. */
|
||||
|
||||
/* Returns the total number of hits (the input num_hits plus the number of the new intersections).
|
||||
*/
|
||||
template<bool is_main_path>
|
||||
ccl_device_forceinline int lights_intersect_impl(KernelGlobals kg,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private Intersection *ccl_restrict isect,
|
||||
const int last_prim,
|
||||
const int last_object,
|
||||
const int last_type,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag,
|
||||
const uint8_t path_mnee,
|
||||
const int receiver_forward,
|
||||
ccl_private uint *lcg_state,
|
||||
int num_hits)
|
||||
{
|
||||
#ifdef __SHADOW_LINKING__
|
||||
const bool is_indirect_ray = !(path_visibility & PATH_RAY_VISIBILITY_CAMERA);
|
||||
#endif
|
||||
|
||||
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
|
||||
const int object = klight->object_id;
|
||||
|
||||
if (path_visibility & PATH_RAY_VISIBILITY_CAMERA) {
|
||||
if (klight->shader_id & SHADER_EXCLUDE_CAMERA) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!(klight->shader_id & SHADER_USE_MIS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef __MNEE__
|
||||
/* This path should have been resolved with mnee, it will
|
||||
* generate a firefly for small lights since it is improbable. */
|
||||
if ((path_mnee & PATH_MNEE_CULL_LIGHT_CONNECTION) && klight->use_caustics) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
|
||||
if (klight->shader_id & SHADER_EXCLUDE_SHADOW_CATCHER) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __SHADOW_LINKING__
|
||||
/* For the main path exclude shadow-linked lights if intersecting with an indirect light ray.
|
||||
* Those lights are handled via dedicated light intersect and shade kernels.
|
||||
* For the shadow path used for the dedicated light shading ignore all non-shadow-linked
|
||||
* lights. */
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING) {
|
||||
if (is_main_path) {
|
||||
if (is_indirect_ray &&
|
||||
kernel_data_fetch(objects, object).shadow_set_membership != LIGHT_LINK_MASK_ALL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (kernel_data_fetch(objects, object).shadow_set_membership == LIGHT_LINK_MASK_ALL) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __LIGHT_LINKING__
|
||||
/* Light linking. */
|
||||
if (!(path_visibility & PATH_RAY_VISIBILITY_CAMERA) &&
|
||||
!light_link_object_match(kg, receiver_forward, object))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
const LightType type = (LightType)klight->type;
|
||||
float t = 0.0f;
|
||||
|
||||
if (type == LIGHT_SPOT) {
|
||||
if (!spot_light_intersect(klight, ray, &t)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
if (!point_light_intersect(klight, ray, &t)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_AREA) {
|
||||
if (!area_light_intersect(klight, ray, &t)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (type == LIGHT_SUN) {
|
||||
if (is_main_path || ray->tmax != FLT_MAX) {
|
||||
continue;
|
||||
}
|
||||
if (!sun_light_intersect(klight, ray, &t)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Avoid self-intersections. */
|
||||
if (last_prim == lamp && last_object == object && last_type == PRIMITIVE_LAMP) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++num_hits;
|
||||
|
||||
#ifdef __SHADOW_LINKING__
|
||||
if (!is_main_path) {
|
||||
/* The non-main rays are only raced by the dedicated light kernel, after the shadow linking
|
||||
* feature check. */
|
||||
kernel_assert(kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING);
|
||||
|
||||
if ((isect->prim != PRIM_NONE) && (lcg_step_float(lcg_state) > 1.0f / num_hits)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (t >= isect->t)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
isect->t = t;
|
||||
isect->u = 0.0f;
|
||||
isect->v = 0.0f;
|
||||
isect->type = PRIMITIVE_LAMP;
|
||||
isect->prim = lamp;
|
||||
isect->object = object;
|
||||
}
|
||||
|
||||
return num_hits;
|
||||
}
|
||||
|
||||
/* Lights intersection for the main path.
|
||||
* Intersects spot, point, and area lights. */
|
||||
ccl_device bool lights_intersect(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private Intersection *ccl_restrict isect,
|
||||
const int last_prim,
|
||||
const int last_object,
|
||||
const int last_type,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
const uint8_t path_mnee = INTEGRATOR_STATE(state, path, mnee);
|
||||
const int receiver_forward = light_link_receiver_forward(kg, state);
|
||||
|
||||
lights_intersect_impl<true>(kg,
|
||||
ray,
|
||||
isect,
|
||||
last_prim,
|
||||
last_object,
|
||||
last_type,
|
||||
path_visibility,
|
||||
path_flag,
|
||||
path_mnee,
|
||||
receiver_forward,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
return isect->prim != PRIM_NONE;
|
||||
}
|
||||
|
||||
/* Lights intersection for the shadow linking.
|
||||
* Intersects spot, point, area, and sun lights.
|
||||
*
|
||||
* Returns the total number of hits (the input num_hits plus the number of the new intersections).
|
||||
*/
|
||||
ccl_device int lights_intersect_shadow_linked(KernelGlobals kg,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private Intersection *ccl_restrict isect,
|
||||
const int last_prim,
|
||||
const int last_object,
|
||||
const int last_type,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag,
|
||||
const int receiver_forward,
|
||||
ccl_private uint *lcg_state,
|
||||
const int num_hits)
|
||||
{
|
||||
return lights_intersect_impl<false>(kg,
|
||||
ray,
|
||||
isect,
|
||||
last_prim,
|
||||
last_object,
|
||||
last_type,
|
||||
path_visibility,
|
||||
path_flag,
|
||||
PATH_MNEE_NONE,
|
||||
receiver_forward,
|
||||
lcg_state,
|
||||
num_hits);
|
||||
}
|
||||
|
||||
/* Setup light sample from intersection. */
|
||||
|
||||
ccl_device LightEval
|
||||
light_eval_from_intersection(KernelGlobals kg,
|
||||
const ccl_private Intersection *ccl_restrict isect,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
const float3 N,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, isect->prim);
|
||||
const LightType type = (LightType)klight->type;
|
||||
|
||||
if (type == LIGHT_SPOT) {
|
||||
return spot_light_eval_from_intersection(kg, klight, ray_P, ray_D, isect->t, N, path_flag);
|
||||
}
|
||||
if (type == LIGHT_POINT) {
|
||||
return point_light_eval_from_intersection(klight, ray_P, ray_D, isect->t, N, path_flag);
|
||||
}
|
||||
if (type == LIGHT_AREA) {
|
||||
return area_light_eval_from_intersection(klight, ray_P, ray_D, isect->t);
|
||||
}
|
||||
|
||||
kernel_assert(!"Invalid lamp type in light_eval_from_intersection");
|
||||
return LightEval{};
|
||||
}
|
||||
|
||||
/* Get light coordinates from position on light. */
|
||||
ccl_device void light_normal_uv_from_position(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
ccl_private float3 &Ng,
|
||||
ccl_private float2 &uv)
|
||||
{
|
||||
const LightType type = (LightType)klight->type;
|
||||
|
||||
if (type == LIGHT_SPOT) {
|
||||
Ng = (klight->spot.is_sphere) ? normalize(P - klight->co) : -D;
|
||||
const float3 local_ray = spot_light_to_local(kg, klight, -D);
|
||||
uv = spot_light_uv(local_ray, klight->spot.half_cot_half_spot_angle);
|
||||
}
|
||||
else if (type == LIGHT_POINT) {
|
||||
Ng = (klight->spot.is_sphere) ? normalize(P - klight->co) : -D;
|
||||
uv = point_light_uv(kg, klight, Ng);
|
||||
}
|
||||
else if (type == LIGHT_AREA) {
|
||||
Ng = klight->area.dir;
|
||||
uv = area_light_uv(klight, P);
|
||||
}
|
||||
else if (type == LIGHT_SUN) {
|
||||
Ng = -D;
|
||||
uv = sun_light_uv(kg, klight, D);
|
||||
}
|
||||
else {
|
||||
kernel_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
223
blender-5.2.0/intern/cycles/kernel/light/point.h
Normal file
223
blender-5.2.0/intern/cycles/kernel/light/point.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/globals.h"
|
||||
|
||||
#include "kernel/geom/object.h"
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
#include "util/defines.h"
|
||||
#include "util/math_intersect.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)
|
||||
{
|
||||
const float r_sq = sqr(klight->spot.radius);
|
||||
|
||||
float3 lightN = P - klight->co;
|
||||
const float d_sq = len_squared(lightN);
|
||||
const float d = sqrtf(d_sq);
|
||||
lightN /= d;
|
||||
|
||||
ls->eval_fac = klight->spot.eval_fac;
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
/* Spherical light geometry. */
|
||||
float cos_theta;
|
||||
if (d_sq > r_sq) {
|
||||
/* Outside sphere. */
|
||||
const float one_minus_cos = sin_sqr_to_one_minus_cos(r_sq / d_sq);
|
||||
ls->D = sample_uniform_cone(-lightN, one_minus_cos, rand, &cos_theta, &ls->pdf);
|
||||
}
|
||||
else {
|
||||
/* Inside sphere. */
|
||||
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);
|
||||
|
||||
/* Remap sampled point onto the sphere to prevent precision issues with small radius. */
|
||||
ls->P = P + ls->D * ls->t;
|
||||
ls->Ng = normalize(ls->P - klight->co);
|
||||
ls->P = ls->Ng * klight->spot.radius + klight->co;
|
||||
}
|
||||
else {
|
||||
/* Point light with ad-hoc radius based on oriented disk. */
|
||||
ls->P = klight->co;
|
||||
if (r_sq > 0.0f) {
|
||||
ls->P += disk_light_sample(lightN, rand) * klight->spot.radius;
|
||||
}
|
||||
|
||||
ls->D = safe_normalize_len(ls->P - P, &ls->t);
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
/* PDF. */
|
||||
const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f;
|
||||
ls->pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ls->D, ls->t);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline float sphere_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 float2 point_light_uv(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
const float3 Ng)
|
||||
{
|
||||
/* Texture coordinates. */
|
||||
const Transform itfm = lamp_get_inverse_transform(kg, klight);
|
||||
const float2 uv = map_to_sphere(transform_direction(&itfm, Ng));
|
||||
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
|
||||
return make_float2(uv.y, 1.0f - uv.x - uv.y);
|
||||
}
|
||||
|
||||
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 = safe_normalize_len(ls->P - P, &ls->t);
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
const float d_sq = len_squared(P - klight->co);
|
||||
const float r_sq = sqr(radius);
|
||||
const float t_sq = sqr(ls->t);
|
||||
|
||||
/* NOTE : preserve pdf in area measure. */
|
||||
const float jacobian_solid_angle_to_area = 0.5f * fabsf(d_sq - r_sq - t_sq) /
|
||||
(radius * ls->t * t_sq);
|
||||
ls->pdf = sphere_light_pdf(d_sq, r_sq, N, ls->D, path_flag) * jacobian_solid_angle_to_area;
|
||||
|
||||
ls->Ng = normalize(ls->P - klight->co);
|
||||
}
|
||||
else {
|
||||
/* NOTE : preserve pdf in area measure. */
|
||||
ls->pdf = ls->eval_fac * 4.0f * M_PI_F;
|
||||
|
||||
ls->Ng = -ls->D;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
float3 P;
|
||||
return ray_sphere_intersect(ray->P, ray->D, ray->tmin, ray->tmax, klight->co, radius, &P, t);
|
||||
}
|
||||
|
||||
float3 P;
|
||||
const float3 diskN = normalize(ray->P - klight->co);
|
||||
return ray_disk_intersect(
|
||||
ray->P, ray->D, ray->tmin, ray->tmax, klight->co, diskN, radius, &P, t);
|
||||
}
|
||||
|
||||
ccl_device_inline LightEval
|
||||
point_light_eval_from_intersection(const ccl_global KernelLight *klight,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
const float t,
|
||||
const float3 N,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
const float r_sq = sqr(klight->spot.radius);
|
||||
|
||||
LightEval light_eval = {klight->spot.eval_fac, 0.0f};
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
const float d_sq = len_squared(ray_P - klight->co);
|
||||
light_eval.pdf = sphere_light_pdf(d_sq, r_sq, N, ray_D, path_flag);
|
||||
}
|
||||
else {
|
||||
if (t != FLT_MAX) {
|
||||
const float3 lightN = normalize(ray_P - klight->co);
|
||||
const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f;
|
||||
light_eval.pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ray_D, t);
|
||||
}
|
||||
}
|
||||
|
||||
return light_eval;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = min_distance * one_float2();
|
||||
|
||||
if (in_volume_segment) {
|
||||
cos_theta_u = 1.0f; /* Any value in [-1, 1], irrelevant since theta = 0 */
|
||||
return true;
|
||||
}
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
if (min_distance > radius) {
|
||||
/* Equivalent to a disk light with the same angular span. */
|
||||
cos_theta_u = cos_from_sin(radius / min_distance);
|
||||
distance.x = min_distance / cos_theta_u;
|
||||
}
|
||||
else {
|
||||
/* Similar to background light. */
|
||||
cos_theta_u = -1.0f;
|
||||
/* HACK: pack radiance scaling in the distance. */
|
||||
distance = one_float2() * radius / M_SQRT2_F;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
|
||||
cos_theta_u = min_distance / hypotenus;
|
||||
|
||||
distance.x = hypotenus;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
617
blender-5.2.0/intern/cycles/kernel/light/sample.h
Normal file
617
blender-5.2.0/intern/cycles/kernel/light/sample.h
Normal file
@@ -0,0 +1,617 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/integrator/surface_shader.h"
|
||||
|
||||
#include "kernel/light/distribution.h"
|
||||
#include "kernel/light/light.h"
|
||||
#include "kernel/types.h"
|
||||
|
||||
#ifdef __LIGHT_TREE__
|
||||
# include "kernel/light/tree.h"
|
||||
#endif
|
||||
|
||||
#include "kernel/geom/shader_data.h"
|
||||
|
||||
#include "kernel/sample/mis.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Evaluate constant factors for a direct light sample. */
|
||||
ccl_device bool light_sample_shader_eval_nee_constant(KernelGlobals kg,
|
||||
const int shader_id,
|
||||
const int prim,
|
||||
const bool is_light,
|
||||
ccl_private Spectrum &eval)
|
||||
{
|
||||
eval = one_spectrum();
|
||||
const bool is_constant = surface_shader_constant_emission(kg, shader_id, &eval);
|
||||
|
||||
if (is_light) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, prim);
|
||||
eval *= rgb_to_spectrum(
|
||||
make_float3(klight->strength[0], klight->strength[1], klight->strength[2]));
|
||||
}
|
||||
|
||||
return is_constant;
|
||||
}
|
||||
|
||||
/* Evaluate shader on light. Not supported for background and triangle lights, that happens
|
||||
* in shade_surface and shader_background. */
|
||||
ccl_device_noinline_cpu ShaderEvalResult
|
||||
light_sample_shader_eval_forward(KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const int light_id,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
const float t,
|
||||
const float time,
|
||||
ccl_private Spectrum &r_eval)
|
||||
{
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, light_id);
|
||||
|
||||
/* setup shading at emitter */
|
||||
Spectrum eval = zero_spectrum();
|
||||
|
||||
if (!surface_shader_constant_emission(kg, klight->shader_id, &eval)) {
|
||||
/* Setup shader data and call surface_shader_eval once, better
|
||||
* for GPU coherence and compile times. */
|
||||
PROFILING_INIT_FOR_SHADER(kg, PROFILING_SHADE_LIGHT_SETUP);
|
||||
|
||||
ShaderDataTinyStorage emission_sd_storage;
|
||||
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
|
||||
|
||||
const float3 P = (t == FLT_MAX) ? -ray_D : ray_P + ray_D * t;
|
||||
float3 Ng = zero_float3();
|
||||
float2 uv = zero_float2();
|
||||
light_normal_uv_from_position(kg, klight, P, ray_D, Ng, uv);
|
||||
|
||||
shader_setup_from_sample(kg,
|
||||
emission_sd,
|
||||
P,
|
||||
Ng,
|
||||
-ray_D,
|
||||
klight->shader_id,
|
||||
klight->object_id,
|
||||
light_id,
|
||||
uv.x,
|
||||
uv.y,
|
||||
t,
|
||||
time,
|
||||
false,
|
||||
true);
|
||||
|
||||
PROFILING_SHADER(emission_sd->object, emission_sd->shader);
|
||||
PROFILING_EVENT(PROFILING_SHADE_LIGHT_EVAL);
|
||||
|
||||
/* No proper path flag, we're evaluating this for all closures. that's
|
||||
* weak but we'd have to do multiple evaluations otherwise. */
|
||||
surface_shader_eval<KERNEL_FEATURE_NODE_MASK_SURFACE_LIGHT>(
|
||||
kg, state, emission_sd, nullptr, PATH_RAY_VISIBILITY_NONE, PATH_RAY_EMISSION);
|
||||
if (emission_sd->flag & SD_CACHE_MISS) {
|
||||
return SHADER_EVAL_CACHE_MISS;
|
||||
}
|
||||
|
||||
/* Evaluate closures. */
|
||||
eval = surface_shader_emission(emission_sd);
|
||||
}
|
||||
|
||||
{
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, light_id);
|
||||
eval *= rgb_to_spectrum(
|
||||
make_float3(klight->strength[0], klight->strength[1], klight->strength[2]));
|
||||
}
|
||||
|
||||
r_eval = eval;
|
||||
|
||||
return SHADER_EVAL_OK;
|
||||
}
|
||||
|
||||
/* Early path termination of shadow rays. */
|
||||
ccl_device_inline float light_sample_terminate_probability(KernelGlobals kg,
|
||||
ccl_private Spectrum eval)
|
||||
{
|
||||
return (kernel_data.integrator.light_inv_rr_threshold > 0.0f) ?
|
||||
reduce_max(fabs(eval)) * kernel_data.integrator.light_inv_rr_threshold :
|
||||
1.0f;
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_sample_terminate(KernelGlobals kg,
|
||||
ccl_private BsdfEval *ccl_restrict eval,
|
||||
const float rand_terminate)
|
||||
{
|
||||
if (bsdf_eval_is_zero(eval)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const float probability = light_sample_terminate_probability(kg, bsdf_eval_sum(eval));
|
||||
if (probability < 1.0f) {
|
||||
if (rand_terminate >= probability) {
|
||||
return true;
|
||||
}
|
||||
bsdf_eval_mul(eval, 1.0f / probability);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ccl_device_inline bool light_sample_terminate(KernelGlobals kg,
|
||||
ccl_private Spectrum &light_eval,
|
||||
const float bsdf_eval,
|
||||
const float rand_terminate)
|
||||
{
|
||||
/* Same logic as above, but where bsdf_eval is already part of the throughput so
|
||||
* we only need to modify the light eval while still taking into account bsdf eval
|
||||
* for the termination probability. */
|
||||
if (is_zero(light_eval)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const float probability = light_sample_terminate_probability(kg, light_eval * bsdf_eval);
|
||||
if (probability < 1.0f) {
|
||||
if (rand_terminate >= probability) {
|
||||
return true;
|
||||
}
|
||||
light_eval /= probability;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* This function should be used to compute a modified ray start position for
|
||||
* rays leaving from a surface. The algorithm slightly distorts flat surface
|
||||
* of a triangle. Surface is lifted by amount h along normal n in the incident
|
||||
* point. */
|
||||
|
||||
ccl_device_inline float3 shadow_ray_smooth_surface_offset(
|
||||
KernelGlobals kg, const ccl_private ShaderData *ccl_restrict sd, const float3 Ng)
|
||||
{
|
||||
float3 V[3];
|
||||
float3 N[3];
|
||||
|
||||
if (sd->type == PRIMITIVE_MOTION_TRIANGLE) {
|
||||
motion_triangle_vertices_and_normals(kg, sd, V, N);
|
||||
}
|
||||
else {
|
||||
kernel_assert(sd->type == PRIMITIVE_TRIANGLE);
|
||||
triangle_vertices_and_normals(kg, sd, V, N);
|
||||
}
|
||||
|
||||
const float u = 1.0f - sd->u - sd->v;
|
||||
const float v = sd->u;
|
||||
const float w = sd->v;
|
||||
const float3 P = V[0] * u + V[1] * v + V[2] * w; /* Local space */
|
||||
float3 n = N[0] * u + N[1] * v + N[2] * w; /* We get away without normalization */
|
||||
|
||||
if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
object_dir_transform(kg, sd, &n); /* Normal x scale, to world space */
|
||||
}
|
||||
|
||||
/* Parabolic approximation */
|
||||
const float a = dot(N[2] - N[0], V[0] - V[2]);
|
||||
const float b = dot(N[2] - N[1], V[1] - V[2]);
|
||||
const float c = dot(N[1] - N[0], V[1] - V[0]);
|
||||
float h = a * u * (u - 1) + (a + b + c) * u * v + b * v * (v - 1);
|
||||
|
||||
/* Check flipped normals */
|
||||
if (dot(n, Ng) > 0) {
|
||||
/* Local linear envelope */
|
||||
float h0 = max(max(dot(V[1] - V[0], N[0]), dot(V[2] - V[0], N[0])), 0.0f);
|
||||
float h1 = max(max(dot(V[0] - V[1], N[1]), dot(V[2] - V[1], N[1])), 0.0f);
|
||||
float h2 = max(max(dot(V[0] - V[2], N[2]), dot(V[1] - V[2], N[2])), 0.0f);
|
||||
h0 = max(dot(V[0] - P, N[0]) + h0, 0.0f);
|
||||
h1 = max(dot(V[1] - P, N[1]) + h1, 0.0f);
|
||||
h2 = max(dot(V[2] - P, N[2]) + h2, 0.0f);
|
||||
h = max(min(min(h0, h1), h2), h * 0.5f);
|
||||
}
|
||||
else {
|
||||
float h0 = max(max(dot(V[0] - V[1], N[0]), dot(V[0] - V[2], N[0])), 0.0f);
|
||||
float h1 = max(max(dot(V[1] - V[0], N[1]), dot(V[1] - V[2], N[1])), 0.0f);
|
||||
float h2 = max(max(dot(V[2] - V[0], N[2]), dot(V[2] - V[1], N[2])), 0.0f);
|
||||
h0 = max(dot(P - V[0], N[0]) + h0, 0.0f);
|
||||
h1 = max(dot(P - V[1], N[1]) + h1, 0.0f);
|
||||
h2 = max(dot(P - V[2], N[2]) + h2, 0.0f);
|
||||
h = min(-min(min(h0, h1), h2), h * 0.5f);
|
||||
}
|
||||
|
||||
return n * h;
|
||||
}
|
||||
|
||||
/* Ray offset to avoid shadow terminator artifact. */
|
||||
|
||||
ccl_device_inline float3 shadow_ray_offset(KernelGlobals kg,
|
||||
const ccl_private ShaderData *ccl_restrict sd,
|
||||
const float3 L,
|
||||
ccl_private bool *r_skip_self)
|
||||
{
|
||||
float3 P = sd->P;
|
||||
|
||||
if ((sd->type & PRIMITIVE_TRIANGLE) && (sd->shader & SHADER_SMOOTH_NORMAL)) {
|
||||
const float offset_cutoff =
|
||||
kernel_data_fetch(objects, sd->object).shadow_terminator_geometry_offset;
|
||||
/* Do ray offset (heavy stuff) only for close to be terminated triangles:
|
||||
* offset_cutoff = 0.1f means that 10-20% of rays will be affected. Also
|
||||
* make a smooth transition near the threshold. */
|
||||
if (offset_cutoff > 0.0f) {
|
||||
float NL = dot(sd->N, L);
|
||||
const bool transmit = (NL < 0.0f);
|
||||
if (NL < 0) {
|
||||
NL = -NL;
|
||||
}
|
||||
|
||||
const float3 Ng = (transmit ? -sd->Ng : sd->Ng);
|
||||
const float NgL = dot(Ng, L);
|
||||
|
||||
const float offset_amount = (NL < offset_cutoff) ?
|
||||
clamp(2.0f - (NgL + NL) / offset_cutoff, 0.0f, 1.0f) :
|
||||
clamp(1.0f - NgL / offset_cutoff, 0.0f, 1.0f);
|
||||
|
||||
if (offset_amount > 0.0f) {
|
||||
P += shadow_ray_smooth_surface_offset(kg, sd, Ng) * offset_amount;
|
||||
|
||||
/* Only skip self intersections if light direction and geometric normal point in the same
|
||||
* direction, otherwise we're meant to hit this surface. */
|
||||
*r_skip_self = (NgL > 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return P;
|
||||
}
|
||||
|
||||
ccl_device_inline void shadow_ray_setup(const ccl_private ShaderData *ccl_restrict sd,
|
||||
const ccl_private LightSample *ccl_restrict ls,
|
||||
const float3 P,
|
||||
ccl_private Ray *ray,
|
||||
const bool skip_self)
|
||||
{
|
||||
/* Setup ray. */
|
||||
ray->P = P;
|
||||
ray->tmin = 0.0f;
|
||||
|
||||
if (ls->t == FLT_MAX) {
|
||||
/* Distant light. */
|
||||
ray->D = ls->D;
|
||||
ray->tmax = ls->t;
|
||||
}
|
||||
else {
|
||||
/* Other lights, avoid self-intersection. */
|
||||
ray->D = ls->P - P;
|
||||
ray->D = safe_normalize_len(ray->D, &ray->tmax);
|
||||
}
|
||||
|
||||
if ((ls->shader & SHADER_CAST_SHADOW) == 0) {
|
||||
/* Signal to not cast shadow ray.
|
||||
* Relies on the intersection_ray_valid() rejecting the ray early on. */
|
||||
ray->tmin = FLT_MAX;
|
||||
}
|
||||
|
||||
ray->dP = differential_make_compact(sd->dP);
|
||||
ray->dD = differential_zero_compact();
|
||||
ray->time = sd->time;
|
||||
|
||||
/* Fill in intersection surface and light details. */
|
||||
ray->self.object = (skip_self) ? sd->object : OBJECT_NONE;
|
||||
ray->self.prim = (skip_self) ? sd->prim : PRIM_NONE;
|
||||
ray->self.light_object = ls->object;
|
||||
ray->self.light_prim = ls->prim;
|
||||
}
|
||||
|
||||
/* Create shadow ray towards light sample. */
|
||||
ccl_device_inline void light_sample_to_surface_shadow_ray(
|
||||
KernelGlobals kg,
|
||||
const ccl_private ShaderData *ccl_restrict sd,
|
||||
const ccl_private LightSample *ccl_restrict ls,
|
||||
ccl_private Ray *ray)
|
||||
{
|
||||
bool skip_self = true;
|
||||
const float3 P = shadow_ray_offset(kg, sd, ls->D, &skip_self);
|
||||
shadow_ray_setup(sd, ls, P, ray, skip_self);
|
||||
}
|
||||
|
||||
/* Create shadow ray towards light sample. */
|
||||
ccl_device_inline void light_sample_to_volume_shadow_ray(
|
||||
const ccl_private ShaderData *ccl_restrict sd,
|
||||
const ccl_private LightSample *ccl_restrict ls,
|
||||
const float3 P,
|
||||
ccl_private Ray *ray)
|
||||
{
|
||||
shadow_ray_setup(sd, ls, P, ray, false);
|
||||
}
|
||||
|
||||
/* Multiple importance sampling weights. */
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward(KernelGlobals kg,
|
||||
const float forward_pdf,
|
||||
const float nee_pdf)
|
||||
{
|
||||
#ifdef WITH_CYCLES_DEBUG
|
||||
if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_FORWARD) {
|
||||
return 1.0f;
|
||||
}
|
||||
if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_NEE) {
|
||||
return 0.0f;
|
||||
}
|
||||
#else
|
||||
(void)kg;
|
||||
#endif
|
||||
return power_heuristic(forward_pdf, nee_pdf);
|
||||
}
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_nee(KernelGlobals kg,
|
||||
const float nee_pdf,
|
||||
const float forward_pdf)
|
||||
{
|
||||
#ifdef WITH_CYCLES_DEBUG
|
||||
if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_FORWARD) {
|
||||
/* Return 0.0f to only account for the contribution in forward path tracing, unless when the
|
||||
* light can not be forward sampled, in which case return 1.0f so it converges to the same
|
||||
* result. */
|
||||
return (forward_pdf == 0.0f);
|
||||
}
|
||||
if (kernel_data.integrator.direct_light_sampling_type == DIRECT_LIGHT_SAMPLING_NEE) {
|
||||
return 1.0f;
|
||||
}
|
||||
#else
|
||||
(void)kg;
|
||||
#endif
|
||||
return power_heuristic(nee_pdf, forward_pdf);
|
||||
}
|
||||
|
||||
/* Next event estimation sampling.
|
||||
*
|
||||
* Sample a position on a light in the scene, from a position on a surface or
|
||||
* from a volume segment.
|
||||
*
|
||||
* Uses either a flat distribution or light tree. */
|
||||
|
||||
ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
|
||||
const float3 rand,
|
||||
const float time,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
const float t,
|
||||
const int object_receiver,
|
||||
const int bounce,
|
||||
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) {
|
||||
if (!light_tree_sample<true>(kg, rand.z, P, D, t, object_receiver, shader_flags, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
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,
|
||||
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)
|
||||
{
|
||||
/* Randomly select a light. */
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
if (!light_tree_sample<false>(kg, rand.z, P, N, 0.0f, object_receiver, shader_flags, ls)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
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
|
||||
* except for directional light. */
|
||||
ccl_device_forceinline void light_sample_update(KernelGlobals kg,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->prim);
|
||||
|
||||
if (ls->type == LIGHT_POINT) {
|
||||
point_light_mnee_sample_update(klight, ls, P, N, path_flag);
|
||||
}
|
||||
else if (ls->type == LIGHT_SPOT) {
|
||||
spot_light_mnee_sample_update(kg, klight, ls, P, N, path_flag);
|
||||
}
|
||||
else if (ls->type == LIGHT_AREA) {
|
||||
area_light_mnee_sample_update(klight, ls, P);
|
||||
}
|
||||
else {
|
||||
/* Keep previous values. */
|
||||
}
|
||||
|
||||
/* Re-apply already computed selection pdf. */
|
||||
ls->pdf *= ls->pdf_selection;
|
||||
}
|
||||
|
||||
/* Forward sampling.
|
||||
*
|
||||
* Multiple importance sampling weights for hitting surface, light or background
|
||||
* through indirect light ray.
|
||||
*
|
||||
* The BSDF or phase pdf from the previous bounce was stored in mis_ray_pdf and
|
||||
* is used for balancing with the light sampling pdf. */
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_surface(
|
||||
KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag,
|
||||
const ccl_private ShaderData *sd)
|
||||
{
|
||||
bool has_mis = !(path_flag & PATH_RAY_MIS_SKIP) &&
|
||||
(sd->flag & ((sd->flag & SD_BACKFACING) ? SD_MIS_BACK : SD_MIS_FRONT));
|
||||
|
||||
#ifdef __HAIR__
|
||||
has_mis &= (sd->type & PRIMITIVE_TRIANGLE);
|
||||
#endif
|
||||
|
||||
if (!has_mis) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const float bsdf_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
const float t = sd->ray_length;
|
||||
float pdf = triangle_light_pdf(kg, sd, t);
|
||||
|
||||
/* Light selection pdf. */
|
||||
#ifdef __LIGHT_TREE__
|
||||
if (kernel_data.integrator.use_light_tree) {
|
||||
const 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);
|
||||
|
||||
const uint lookup_offset = kernel_data_fetch(object_lookup_offset, sd->object);
|
||||
const uint prim_offset = kernel_data_fetch(object_prim_offset, sd->object);
|
||||
const uint triangle = kernel_data_fetch(triangle_to_tree,
|
||||
sd->prim - prim_offset + lookup_offset);
|
||||
|
||||
pdf *= light_tree_pdf(kg,
|
||||
ray_P,
|
||||
N,
|
||||
dt,
|
||||
path_visibility,
|
||||
path_flag,
|
||||
sd->object,
|
||||
triangle,
|
||||
light_link_receiver_forward(kg, state));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* Handled in triangle_light_pdf for efficiency. */
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
|
||||
}
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_lamp(
|
||||
KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag,
|
||||
const int object_id,
|
||||
const float light_sample_pdf,
|
||||
const float3 P)
|
||||
{
|
||||
if (path_flag & PATH_RAY_MIS_SKIP) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
float pdf = light_sample_pdf;
|
||||
|
||||
/* Light selection pdf. */
|
||||
#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_visibility,
|
||||
path_flag,
|
||||
0,
|
||||
kernel_data_fetch(light_to_tree, object_id),
|
||||
light_link_receiver_forward(kg, state));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
|
||||
}
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_distant(
|
||||
KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag,
|
||||
const int object_id,
|
||||
const float light_sample_pdf)
|
||||
{
|
||||
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
return light_sample_mis_weight_forward_lamp(
|
||||
kg, state, path_visibility, path_flag, object_id, light_sample_pdf, ray_P);
|
||||
}
|
||||
|
||||
ccl_device_inline float light_sample_mis_weight_forward_background(
|
||||
KernelGlobals kg,
|
||||
IntegratorState state,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
/* Check if background light exists or if we should skip PDF. */
|
||||
if (!kernel_data.background.use_mis || (path_flag & PATH_RAY_MIS_SKIP)) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
|
||||
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
|
||||
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
|
||||
|
||||
float pdf = background_light_pdf(kg, ray_P, ray_D);
|
||||
|
||||
/* Light selection pdf. */
|
||||
#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);
|
||||
const uint light = kernel_data_fetch(light_to_tree, kernel_data.background.object_index);
|
||||
pdf *= light_tree_pdf(kg,
|
||||
ray_P,
|
||||
N,
|
||||
dt,
|
||||
path_visibility,
|
||||
path_flag,
|
||||
0,
|
||||
light,
|
||||
light_link_receiver_forward(kg, state));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
pdf *= light_distribution_pdf_lamp(kg);
|
||||
}
|
||||
|
||||
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
322
blender-5.2.0/intern/cycles/kernel/light/spot.h
Normal file
322
blender-5.2.0/intern/cycles/kernel/light/spot.h
Normal file
@@ -0,0 +1,322 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
#include "kernel/light/point.h"
|
||||
|
||||
#include "util/math_fast.h"
|
||||
#include "util/math_intersect.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Transform vector to spot light's local coordinate system. */
|
||||
ccl_device float3 spot_light_to_local(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
const float3 ray)
|
||||
{
|
||||
const Transform itfm = lamp_get_inverse_transform(kg, klight);
|
||||
float3 transformed_ray = safe_normalize(transform_direction(&itfm, ray));
|
||||
transformed_ray.z = -transformed_ray.z;
|
||||
|
||||
return transformed_ray;
|
||||
}
|
||||
|
||||
/* Compute spot light attenuation of a ray given in local coordinate system. */
|
||||
ccl_device float spot_light_attenuation(const ccl_global KernelSpotLight *spot, const float3 ray)
|
||||
{
|
||||
return smoothstepf((ray.z - spot->cos_half_spot_angle) * spot->spot_smooth);
|
||||
}
|
||||
|
||||
ccl_device float2 spot_light_uv(const float3 ray, const float half_cot_half_spot_angle)
|
||||
{
|
||||
/* Ensures that the spot light projects the full image regardless of the spot angle. */
|
||||
const float factor = half_cot_half_spot_angle / ray.z;
|
||||
|
||||
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
|
||||
return make_float2(ray.y * factor + 0.5f, -(ray.x + ray.y) * factor);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_inline bool spot_light_sample(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
const float2 rand,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const int shader_flags,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
const float r_sq = sqr(klight->spot.radius);
|
||||
|
||||
float3 lightN = P - klight->co;
|
||||
const float d_sq = len_squared(lightN);
|
||||
const float d = sqrtf(d_sq);
|
||||
lightN /= d;
|
||||
|
||||
ls->eval_fac = klight->spot.eval_fac;
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
/* Spherical light geometry. */
|
||||
float cos_theta;
|
||||
ls->t = FLT_MAX;
|
||||
if (d_sq > r_sq) {
|
||||
/* Outside sphere. */
|
||||
const float one_minus_cos_half_spot_spread = 1.0f - klight->spot.cos_half_larger_spread;
|
||||
const float one_minus_cos_half_angle = sin_sqr_to_one_minus_cos(r_sq / d_sq);
|
||||
|
||||
if (in_volume_segment || one_minus_cos_half_angle < one_minus_cos_half_spot_spread) {
|
||||
/* Sample visible part of the sphere. */
|
||||
ls->D = sample_uniform_cone(-lightN, one_minus_cos_half_angle, rand, &cos_theta, &ls->pdf);
|
||||
}
|
||||
else {
|
||||
/* Sample spread cone. */
|
||||
ls->D = sample_uniform_cone(
|
||||
-klight->spot.dir, one_minus_cos_half_spot_spread, rand, &cos_theta, &ls->pdf);
|
||||
|
||||
if (!ray_sphere_intersect(
|
||||
P, ls->D, 0.0f, FLT_MAX, klight->co, klight->spot.radius, &ls->P, &ls->t))
|
||||
{
|
||||
/* Sampled direction does not intersect with the light. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Inside sphere. */
|
||||
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);
|
||||
}
|
||||
|
||||
/* Attenuation. */
|
||||
const float3 local_ray = spot_light_to_local(kg, klight, -ls->D);
|
||||
if (d_sq > r_sq) {
|
||||
ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
|
||||
}
|
||||
if (!in_volume_segment && ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ls->t == FLT_MAX) {
|
||||
/* 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;
|
||||
}
|
||||
else {
|
||||
/* Already computed when sampling the spread cone. */
|
||||
}
|
||||
|
||||
/* Remap sampled point onto the sphere to prevent precision issues with small radius. */
|
||||
ls->Ng = normalize(ls->P - klight->co);
|
||||
ls->P = ls->Ng * klight->spot.radius + klight->co;
|
||||
}
|
||||
else {
|
||||
/* Point light with ad-hoc radius based on oriented disk. */
|
||||
ls->P = klight->co;
|
||||
if (r_sq > 0.0f) {
|
||||
ls->P += disk_light_sample(lightN, rand) * klight->spot.radius;
|
||||
}
|
||||
|
||||
ls->D = safe_normalize_len(ls->P - P, &ls->t);
|
||||
ls->Ng = -ls->D;
|
||||
|
||||
/* Attenuation. */
|
||||
const float3 local_ray = spot_light_to_local(kg, klight, -ls->D);
|
||||
ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
|
||||
if (!in_volume_segment && ls->eval_fac == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* PDF. */
|
||||
const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f;
|
||||
ls->pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ls->D, ls->t);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device_forceinline float spot_light_pdf(const ccl_global KernelSpotLight *spot,
|
||||
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 /
|
||||
min(sin_sqr_to_one_minus_cos(r_sq / d_sq), 1.0f - spot->cos_half_larger_spread);
|
||||
}
|
||||
|
||||
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 spot_light_mnee_sample_update(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
ls->D = safe_normalize_len(ls->P - P, &ls->t);
|
||||
|
||||
ls->eval_fac = klight->spot.eval_fac;
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
bool use_attenuation = true;
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
const float d_sq = len_squared(P - klight->co);
|
||||
const float r_sq = sqr(radius);
|
||||
const float t_sq = sqr(ls->t);
|
||||
|
||||
/* NOTE : preserve pdf in area measure. */
|
||||
const float jacobian_solid_angle_to_area = 0.5f * fabsf(d_sq - r_sq - t_sq) /
|
||||
(radius * ls->t * t_sq);
|
||||
ls->pdf = spot_light_pdf(&klight->spot, d_sq, r_sq, N, ls->D, path_flag) *
|
||||
jacobian_solid_angle_to_area;
|
||||
|
||||
ls->Ng = normalize(ls->P - klight->co);
|
||||
|
||||
use_attenuation = (d_sq > r_sq);
|
||||
}
|
||||
else {
|
||||
/* NOTE : preserve pdf in area measure. */
|
||||
ls->pdf = ls->eval_fac * 4.0f * M_PI_F;
|
||||
|
||||
ls->Ng = -ls->D;
|
||||
}
|
||||
|
||||
/* Attenuation. */
|
||||
const float3 local_ray = spot_light_to_local(kg, klight, -ls->D);
|
||||
if (use_attenuation) {
|
||||
ls->eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
|
||||
}
|
||||
}
|
||||
|
||||
ccl_device_inline bool spot_light_intersect(const ccl_global KernelLight *klight,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private float *t)
|
||||
{
|
||||
/* One sided. */
|
||||
if (dot(ray->D, ray->P - klight->co) >= 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return point_light_intersect(klight, ray, t);
|
||||
}
|
||||
|
||||
ccl_device_inline LightEval spot_light_eval_from_intersection(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D,
|
||||
const float t,
|
||||
const float3 N,
|
||||
const uint32_t path_flag)
|
||||
{
|
||||
const float r_sq = sqr(klight->spot.radius);
|
||||
const float d_sq = len_squared(ray_P - klight->co);
|
||||
|
||||
LightEval light_eval = {klight->spot.eval_fac, 0.0f};
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
light_eval.pdf = spot_light_pdf(&klight->spot, d_sq, r_sq, N, ray_D, path_flag);
|
||||
}
|
||||
else {
|
||||
if (t != FLT_MAX) {
|
||||
const float3 lightN = normalize(ray_P - klight->co);
|
||||
const float invarea = (r_sq > 0.0f) ? 1.0f / (r_sq * M_PI_F) : 1.0f;
|
||||
light_eval.pdf = invarea * light_pdf_area_to_solid_angle(lightN, -ray_D, t);
|
||||
}
|
||||
}
|
||||
|
||||
/* Attenuation. */
|
||||
const float3 local_ray = spot_light_to_local(kg, klight, -ray_D);
|
||||
if (!klight->spot.is_sphere || d_sq > r_sq) {
|
||||
light_eval.eval_fac *= spot_light_attenuation(&klight->spot, local_ray);
|
||||
}
|
||||
|
||||
return light_eval;
|
||||
}
|
||||
|
||||
/* Find the ray segment lit by the spot light. */
|
||||
ccl_device_inline bool spot_light_valid_ray_segment(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
ccl_private Interval<float> *t_range)
|
||||
{
|
||||
/* Convert to local space of the spot light. */
|
||||
const Transform itfm = lamp_get_inverse_transform(kg, klight);
|
||||
float3 local_P = P + klight->spot.dir * klight->spot.ray_segment_dp;
|
||||
local_P = transform_point(&itfm, local_P);
|
||||
const float3 local_D = transform_direction(&itfm, D);
|
||||
const float3 axis = make_float3(0.0f, 0.0f, -1.0f);
|
||||
|
||||
/* Intersect the ray with the smallest enclosing cone of the light spread. */
|
||||
return ray_cone_intersect(
|
||||
axis, local_P, local_D, sqr(klight->spot.cos_half_spot_angle), t_range);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLight *klight,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
const ccl_private KernelBoundingCone &bcone,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid,
|
||||
ccl_private float &energy)
|
||||
{
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = min_distance * one_float2();
|
||||
|
||||
const float radius = klight->spot.radius;
|
||||
|
||||
if (klight->spot.is_sphere) {
|
||||
cos_theta_u = (min_distance > radius) ? cos_from_sin(radius / min_distance) : -1.0f;
|
||||
|
||||
if (in_volume_segment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
distance = (min_distance > radius) ? min_distance * make_float2(1.0f / cos_theta_u, 1.0f) :
|
||||
one_float2() * radius / M_SQRT2_F;
|
||||
}
|
||||
else {
|
||||
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
|
||||
cos_theta_u = min_distance / hypotenus;
|
||||
|
||||
if (in_volume_segment) {
|
||||
return true;
|
||||
}
|
||||
|
||||
distance.x = hypotenus;
|
||||
}
|
||||
|
||||
/* Apply a similar scaling as in `spot_light_attenuation()` to account for spot blend. */
|
||||
{
|
||||
/* Minimum angle formed by the emitter axis and the direction to the shading point,
|
||||
* cos(theta') in the paper. */
|
||||
const float cos_min_outgoing_angle = cosf(
|
||||
fmaxf(0.0f, fast_acosf(dot(bcone.axis, -point_to_centroid)) - fast_acosf(cos_theta_u)));
|
||||
/* Use `cos(bcone.theta_e)` instead of `klight->spot.cos_half_spot_angle` to account for
|
||||
* non-uniform scaling. */
|
||||
energy *= smoothstepf((cos_min_outgoing_angle - cosf(bcone.theta_e)) *
|
||||
klight->spot.spot_smooth);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
122
blender-5.2.0/intern/cycles/kernel/light/sun.h
Normal file
122
blender-5.2.0/intern/cycles/kernel/light/sun.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/geom/object.h"
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
#include "util/math_fast.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
ccl_device_inline float2 sun_light_uv(KernelGlobals kg,
|
||||
const ccl_global KernelLight *klight,
|
||||
const float3 D)
|
||||
{
|
||||
/* Map direction (x, y, z) to disk [-0.5, 0.5]^2:
|
||||
* r^2 = (1 - z) / (1 - cos(klight->sun.angle))
|
||||
* u_ = 0.5 * x * r / sin_angle(D, -klight->co)
|
||||
* v_ = 0.5 * y * r / sin_angle(D, -klight->co) */
|
||||
const float fac = klight->sun.half_inv_sin_half_angle / len(D - klight->co);
|
||||
|
||||
/* Get u axis and v axis. */
|
||||
const Transform itfm = lamp_get_inverse_transform(kg, klight);
|
||||
const float u_ = dot(D, make_float3(itfm.x)) * fac;
|
||||
const float v_ = dot(D, make_float3(itfm.y)) * fac;
|
||||
|
||||
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
|
||||
return make_float2(v_ + 0.5f, -u_ - v_);
|
||||
}
|
||||
|
||||
ccl_device_inline bool sun_light_sample(const ccl_global KernelLight *klight,
|
||||
const float2 rand,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
float unused;
|
||||
ls->Ng = sample_uniform_cone(
|
||||
klight->co, klight->sun.one_minus_cosangle, rand, &unused, &ls->pdf);
|
||||
|
||||
ls->P = ls->Ng;
|
||||
ls->D = -ls->Ng;
|
||||
ls->t = FLT_MAX;
|
||||
|
||||
ls->eval_fac = klight->sun.eval_fac;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Special intersection check.
|
||||
* Returns true if the sun_light_eval_from_intersection() for this light would return true.
|
||||
*
|
||||
* The intersection parameters t, u, v are optimized for the shadow ray towards a dedicated light:
|
||||
* u = v = 0, t = FLT_MAX.
|
||||
*/
|
||||
ccl_device bool sun_light_intersect(const ccl_global KernelLight *klight,
|
||||
const ccl_private Ray *ccl_restrict ray,
|
||||
ccl_private float *t)
|
||||
{
|
||||
kernel_assert(klight->type == LIGHT_SUN);
|
||||
|
||||
if (klight->sun.angle == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vector_angle(-klight->co, ray->D) > klight->sun.angle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*t = FLT_MAX;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device LightEval sun_light_eval_from_intersection(const ccl_global KernelLight *klight,
|
||||
const float3 ray_D)
|
||||
{
|
||||
if (klight->sun.angle == 0.0f) {
|
||||
return LightEval{};
|
||||
}
|
||||
|
||||
if (vector_angle(-klight->co, ray_D) > klight->sun.angle) {
|
||||
return LightEval{};
|
||||
}
|
||||
|
||||
return LightEval{klight->sun.eval_fac, klight->sun.pdf};
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool sun_light_tree_parameters(const float3 centroid,
|
||||
const float theta_e,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Treating it as a disk light 1 unit away */
|
||||
cos_theta_u = fast_cosf(theta_e);
|
||||
|
||||
distance = make_float2(1.0f / cos_theta_u, 1.0f);
|
||||
|
||||
point_to_centroid = -centroid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
952
blender-5.2.0/intern/cycles/kernel/light/tree.h
Normal file
952
blender-5.2.0/intern/cycles/kernel/light/tree.h
Normal file
@@ -0,0 +1,952 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
/* This code implements a modified version of the paper [Importance Sampling of Many Lights with
|
||||
* Adaptive Tree Splitting](https://fpsunflower.github.io/ckulla/data/many-lights-hpg2018.pdf)
|
||||
* by Alejandro Conty Estevez and Christopher Kulla.
|
||||
* The original paper traverses both children when the variance of a node is too high (called
|
||||
* splitting). However, Cycles does not support multiple lights per shading point. Therefore, we
|
||||
* adjust the importance computation: instead of using a conservative measure (i.e., the maximal
|
||||
* possible contribution a node could make to a shading point) as in the paper, we additionally
|
||||
* compute the minimal possible contribution and choose uniformly between these two measures. Also,
|
||||
* support for distant lights is added, which is not included in the paper.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/light/area.h"
|
||||
#include "kernel/light/background.h"
|
||||
#include "kernel/light/common.h"
|
||||
#include "kernel/light/point.h"
|
||||
#include "kernel/light/spot.h"
|
||||
#include "kernel/light/sun.h"
|
||||
#include "kernel/light/triangle.h"
|
||||
|
||||
#include "util/math_fast.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Consine of the angle subtended by the smallest enclosing sphere of the node bounding box. */
|
||||
ccl_device float light_tree_cos_bound_subtended_angle(const KernelBoundingBox bbox,
|
||||
const float3 centroid,
|
||||
const float3 P)
|
||||
{
|
||||
const float distance_to_center_sq = len_squared(P - centroid);
|
||||
const float radius_sq = len_squared(bbox.max - centroid);
|
||||
|
||||
/* If P is inside the bounding sphere, `theta_u` covers the whole sphere and return -1.0
|
||||
* Otherwise compute cos(theta_u) by substituting our values into the cos_from_sin() formula on
|
||||
* the basis that `sin(theta_u) = radius / distance_to_center`. */
|
||||
return (distance_to_center_sq <= radius_sq) ?
|
||||
-1.0f :
|
||||
safe_sqrtf(1.0f - (radius_sq / distance_to_center_sq));
|
||||
}
|
||||
|
||||
/* Compute vector v as in Fig .8. P_v is the corresponding point along the ray. */
|
||||
ccl_device float3 compute_v(
|
||||
const float3 centroid, const float3 P, const float3 D, const float3 bcone_axis, const float t)
|
||||
{
|
||||
const float3 unnormalized_v0 = P - centroid;
|
||||
const float3 unnormalized_v1 = unnormalized_v0 + D * fminf(t, 1e12f);
|
||||
const float3 v0 = normalize(unnormalized_v0);
|
||||
const float3 v1 = normalize(unnormalized_v1);
|
||||
|
||||
const float3 o0 = v0;
|
||||
float3 o1;
|
||||
float3 o2;
|
||||
make_orthonormals_tangent(o0, v1, &o1, &o2);
|
||||
|
||||
const float dot_o0_a = dot(o0, bcone_axis);
|
||||
const float dot_o1_a = dot(o1, bcone_axis);
|
||||
const float inv_len = inversesqrtf(sqr(dot_o0_a) + sqr(dot_o1_a));
|
||||
const float cos_phi0 = dot_o0_a * inv_len;
|
||||
|
||||
return (dot_o1_a < 0 || dot(v0, v1) > cos_phi0) ? (dot_o0_a > dot(v1, bcone_axis) ? v0 : v1) :
|
||||
cos_phi0 * o0 + dot_o1_a * inv_len * o1;
|
||||
}
|
||||
|
||||
ccl_device_inline bool is_light(const ccl_global KernelLightTreeEmitter *kemitter)
|
||||
{
|
||||
return kemitter->light.id < 0;
|
||||
}
|
||||
|
||||
ccl_device_inline bool is_mesh(const ccl_global KernelLightTreeEmitter *kemitter)
|
||||
{
|
||||
return !is_light(kemitter) && kemitter->object_id == OBJECT_NONE;
|
||||
}
|
||||
|
||||
ccl_device_inline bool is_triangle(const ccl_global KernelLightTreeEmitter *kemitter)
|
||||
{
|
||||
return !is_light(kemitter) && kemitter->object_id != OBJECT_NONE;
|
||||
}
|
||||
|
||||
ccl_device_inline bool is_leaf(const ccl_global KernelLightTreeNode *knode)
|
||||
{
|
||||
/* The distant node is also considered o leaf node. */
|
||||
return knode->type >= LIGHT_TREE_LEAF;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_to_local_space(KernelGlobals kg,
|
||||
const int object_id,
|
||||
ccl_private float3 &P,
|
||||
ccl_private float3 &N_or_D,
|
||||
ccl_private float &t)
|
||||
{
|
||||
const uint object_flag = kernel_data_fetch(object_flag, object_id);
|
||||
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
#ifdef __OBJECT_MOTION__
|
||||
Transform itfm;
|
||||
object_fetch_transform_motion_test(kg, object_id, 0.5f, &itfm);
|
||||
#else
|
||||
const Transform itfm = object_fetch_transform(kg, object_id, OBJECT_INVERSE_TRANSFORM);
|
||||
#endif
|
||||
P = transform_point(&itfm, P);
|
||||
if (in_volume_segment) {
|
||||
/* Transform direction. */
|
||||
const float3 D_local = transform_direction(&itfm, N_or_D);
|
||||
float scale;
|
||||
N_or_D = normalize_len(D_local, &scale);
|
||||
|
||||
t *= scale;
|
||||
}
|
||||
else if (!is_zero(N_or_D)) {
|
||||
/* Transform normal. */
|
||||
const Transform tfm = object_fetch_transform(kg, object_id, OBJECT_TRANSFORM);
|
||||
N_or_D = normalize(transform_direction_transposed(&tfm, N_or_D));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This is the general function for calculating the importance of either a cluster or an emitter.
|
||||
* Both of the specialized functions obtain the necessary data before calling this function. */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_importance(const float3 N_or_D,
|
||||
const bool has_transmission,
|
||||
const float3 point_to_centroid,
|
||||
const float cos_theta_u,
|
||||
const KernelBoundingCone bcone,
|
||||
const float max_distance,
|
||||
const float min_distance,
|
||||
const float energy,
|
||||
const float theta_d,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
|
||||
const float sin_theta_u = sin_from_cos(cos_theta_u);
|
||||
|
||||
/* cos(theta_i') in the paper, omitted for volume. */
|
||||
float cos_min_incidence_angle = 1.0f;
|
||||
float cos_max_incidence_angle = 1.0f;
|
||||
|
||||
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);
|
||||
const float sin_theta_i = sin_from_cos(cos_theta_i);
|
||||
|
||||
/* cos_min_incidence_angle = cos(max{theta_i - theta_u, 0}) = cos(theta_i') in the paper */
|
||||
cos_min_incidence_angle = cos_theta_i >= cos_theta_u ?
|
||||
1.0f :
|
||||
cos_theta_i * cos_theta_u + sin_theta_i * sin_theta_u;
|
||||
|
||||
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
|
||||
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
|
||||
* surface. This is more accurate than the bbox test if we are calculating the importance of
|
||||
* an emitter with radius. */
|
||||
if (!has_transmission && cos_min_incidence_angle < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* cos_max_incidence_angle = cos(min{theta_i + theta_u, pi}) */
|
||||
cos_max_incidence_angle = fmaxf(cos_theta_i * cos_theta_u - sin_theta_i * sin_theta_u, 0.0f);
|
||||
}
|
||||
|
||||
float cos_theta;
|
||||
float sin_theta;
|
||||
if (isequal(bcone.axis, -point_to_centroid)) {
|
||||
/* When `bcone.axis == -point_to_centroid`, dot(bcone.axis, -point_to_centroid) doesn't always
|
||||
* return 1 due to floating point precision issues. We account for that case here. */
|
||||
cos_theta = 1.0f;
|
||||
sin_theta = 0.0f;
|
||||
}
|
||||
else {
|
||||
cos_theta = dot(bcone.axis, -point_to_centroid);
|
||||
sin_theta = sin_from_cos(cos_theta);
|
||||
}
|
||||
|
||||
/* cos(theta - theta_u) */
|
||||
const float cos_theta_minus_theta_u = cos_theta * cos_theta_u + sin_theta * sin_theta_u;
|
||||
|
||||
float cos_theta_o;
|
||||
float sin_theta_o;
|
||||
fast_sincosf(bcone.theta_o, &sin_theta_o, &cos_theta_o);
|
||||
|
||||
/* Minimum angle an emitter's axis would form with the direction to the shading point,
|
||||
* cos(theta') in the paper. */
|
||||
float cos_min_outgoing_angle;
|
||||
if ((cos_theta >= cos_theta_u) || (cos_theta_minus_theta_u >= cos_theta_o)) {
|
||||
/* theta - theta_o - theta_u <= 0 */
|
||||
kernel_assert((fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u)) < 1e-3f);
|
||||
cos_min_outgoing_angle = 1.0f;
|
||||
}
|
||||
else if ((bcone.theta_o + bcone.theta_e > M_PI_F) ||
|
||||
(cos_theta_minus_theta_u > cosf(bcone.theta_o + bcone.theta_e)))
|
||||
{
|
||||
/* theta' = theta - theta_o - theta_u < theta_e */
|
||||
kernel_assert(
|
||||
(fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u) - bcone.theta_e) < 5e-4f);
|
||||
const float sin_theta_minus_theta_u = sin_from_cos(cos_theta_minus_theta_u);
|
||||
cos_min_outgoing_angle = cos_theta_minus_theta_u * cos_theta_o +
|
||||
sin_theta_minus_theta_u * sin_theta_o;
|
||||
}
|
||||
else {
|
||||
/* Cluster is invisible. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: find a good approximation for f_a. */
|
||||
const float f_a = 1.0f;
|
||||
/* Use `(theta_b - theta_a) / d` for volume, see Eq. (4) in the paper. */
|
||||
max_importance = fabsf(f_a * cos_min_incidence_angle * energy * cos_min_outgoing_angle *
|
||||
(in_volume_segment ? theta_d / min_distance : 1.0f / sqr(min_distance)));
|
||||
|
||||
/* TODO: compute proper min importance for volume. */
|
||||
if (in_volume_segment) {
|
||||
min_importance = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
/* cos(theta + theta_o + theta_u) if theta + theta_o + theta_u < theta_e, 0 otherwise */
|
||||
float cos_max_outgoing_angle;
|
||||
const float cos_theta_plus_theta_u = cos_theta * cos_theta_u - sin_theta * sin_theta_u;
|
||||
if (bcone.theta_e - bcone.theta_o < 0 || cos_theta < 0 || cos_theta_u < 0 ||
|
||||
cos_theta_plus_theta_u < fast_cosf(bcone.theta_e - bcone.theta_o))
|
||||
{
|
||||
min_importance = 0.0f;
|
||||
}
|
||||
else {
|
||||
const float sin_theta_plus_theta_u = sin_from_cos(cos_theta_plus_theta_u);
|
||||
cos_max_outgoing_angle = cos_theta_plus_theta_u * cos_theta_o -
|
||||
sin_theta_plus_theta_u * sin_theta_o;
|
||||
min_importance = fabsf(f_a * cos_max_incidence_angle * energy * cos_max_outgoing_angle /
|
||||
sqr(max_distance));
|
||||
}
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device bool compute_emitter_centroid_and_dir(KernelGlobals kg,
|
||||
const ccl_global KernelLightTreeEmitter *kemitter,
|
||||
const float3 P,
|
||||
ccl_private float3 ¢roid,
|
||||
ccl_private packed_float3 &dir)
|
||||
{
|
||||
if (is_light(kemitter)) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~(kemitter->light.id));
|
||||
centroid = klight->co;
|
||||
|
||||
switch (klight->type) {
|
||||
case LIGHT_SPOT:
|
||||
dir = klight->spot.dir;
|
||||
break;
|
||||
case LIGHT_POINT:
|
||||
/* Disk-oriented normal. */
|
||||
dir = safe_normalize(P - centroid);
|
||||
break;
|
||||
case LIGHT_AREA:
|
||||
dir = klight->area.dir;
|
||||
break;
|
||||
case LIGHT_BACKGROUND:
|
||||
/* Arbitrary centroid and direction. */
|
||||
centroid = make_float3(0.0f, 0.0f, 1.0f);
|
||||
dir = make_float3(0.0f, 0.0f, -1.0f);
|
||||
break;
|
||||
case LIGHT_SUN:
|
||||
dir = centroid;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
kernel_assert(is_triangle(kemitter));
|
||||
const int object = kemitter->object_id;
|
||||
float3 vertices[3];
|
||||
triangle_vertices(kg, object, kemitter->triangle.id, vertices);
|
||||
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
|
||||
|
||||
const bool is_front_only = (kemitter->triangle.emission_sampling == EMISSION_SAMPLING_FRONT);
|
||||
const bool is_back_only = (kemitter->triangle.emission_sampling == EMISSION_SAMPLING_BACK);
|
||||
if (is_front_only || is_back_only) {
|
||||
dir = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
|
||||
if (is_back_only) {
|
||||
dir = -dir;
|
||||
}
|
||||
const uint object_flag = kernel_data_fetch(object_flag, object);
|
||||
if ((object_flag & SD_OBJECT_TRANSFORM_APPLIED) && (object_flag & SD_OBJECT_NEGATIVE_SCALE))
|
||||
{
|
||||
dir = -dir;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Double-sided: any vector in the plane. */
|
||||
dir = safe_normalize(vertices[0] - vertices[1]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_node_importance(const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const ccl_global KernelLightTreeNode *knode,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
const KernelBoundingCone bcone = knode->bcone;
|
||||
const KernelBoundingBox bbox = knode->bbox;
|
||||
|
||||
float3 point_to_centroid;
|
||||
float cos_theta_u;
|
||||
float distance;
|
||||
float theta_d;
|
||||
if (knode->type == LIGHT_TREE_DISTANT) {
|
||||
point_to_centroid = -bcone.axis;
|
||||
cos_theta_u = fast_cosf(bcone.theta_o + bcone.theta_e);
|
||||
distance = 1.0f;
|
||||
/* For distant lights, the integral in Eq. (4) gives the ray length. */
|
||||
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;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const float3 centroid = 0.5f * (bbox.min + bbox.max);
|
||||
|
||||
if (in_volume_segment) {
|
||||
const float3 D = N_or_D;
|
||||
const float closest_t = dot(centroid - P, D);
|
||||
const float3 closest_point = P + D * clamp(closest_t, 0.0f, t);
|
||||
/* Minimal distance of the ray to the cluster. */
|
||||
distance = len(centroid - P - D * closest_t);
|
||||
|
||||
/* Estimate `theta_b - theta_a` using the centroid of the cluster and the complete ray
|
||||
* segment in volume. */
|
||||
if (t == FLT_MAX) {
|
||||
theta_d = fast_atan2f(closest_t, distance) + M_PI_2_F;
|
||||
}
|
||||
else {
|
||||
/* Original equation is `theta_d = atan((t - closest) /d) + atan(closest / d)`, convert to
|
||||
* the below equation using the equality `atan(a) + atan(b) = atan2(a + b, 1 - a*b)` for
|
||||
* better precision at small angles. */
|
||||
theta_d = atan2f(t, distance - closest_t * safe_divide(t - closest_t, distance));
|
||||
}
|
||||
|
||||
/* Vector that forms a minimal angle with the emitter centroid. */
|
||||
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
|
||||
cos_theta_u = light_tree_cos_bound_subtended_angle(bbox, centroid, closest_point);
|
||||
}
|
||||
else {
|
||||
const float3 N = N_or_D;
|
||||
const float3 bbox_extent = bbox.max - centroid;
|
||||
const bool bbox_is_visible = has_transmission |
|
||||
(dot(N, centroid - P) + dot(fabs(N), fabs(bbox_extent)) > 0);
|
||||
|
||||
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
|
||||
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
|
||||
* surface. */
|
||||
if (!bbox_is_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
point_to_centroid = normalize_len(centroid - P, &distance);
|
||||
cos_theta_u = light_tree_cos_bound_subtended_angle(bbox, centroid, P);
|
||||
theta_d = 1.0f;
|
||||
}
|
||||
/* Clamp distance to half the radius of the cluster when splitting is disabled. */
|
||||
distance = fmaxf(0.5f * len(centroid - bbox.max), distance);
|
||||
}
|
||||
/* TODO: currently max_distance = min_distance, max_importance = min_importance for the
|
||||
* nodes. Do we need better weights for complex scenes? */
|
||||
light_tree_importance<in_volume_segment>(N_or_D,
|
||||
has_transmission,
|
||||
point_to_centroid,
|
||||
cos_theta_u,
|
||||
bcone,
|
||||
distance,
|
||||
distance,
|
||||
knode->energy,
|
||||
theta_d,
|
||||
max_importance,
|
||||
min_importance);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_emitter_importance(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const int emitter_index,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
|
||||
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
emitter_index);
|
||||
|
||||
if (is_mesh(kemitter)) {
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes,
|
||||
kemitter->mesh.node_id);
|
||||
|
||||
light_tree_node_importance<in_volume_segment>(
|
||||
P, N_or_D, t, has_transmission, knode, max_importance, min_importance);
|
||||
return;
|
||||
}
|
||||
|
||||
KernelBoundingCone bcone;
|
||||
bcone.theta_o = kemitter->theta_o;
|
||||
bcone.theta_e = kemitter->theta_e;
|
||||
float cos_theta_u;
|
||||
float theta_d = 1.0f;
|
||||
float2 distance; /* distance.x = max_distance, distance.y = min_distance */
|
||||
float3 centroid;
|
||||
float3 point_to_centroid;
|
||||
float3 P_c = P;
|
||||
|
||||
if (!compute_emitter_centroid_and_dir<in_volume_segment>(kg, kemitter, P, centroid, bcone.axis))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_volume_segment) {
|
||||
const float3 D = N_or_D;
|
||||
/* Closest point from ray to the emitter centroid. */
|
||||
const float closest_t = dot(centroid - P, D);
|
||||
P_c += D * clamp(closest_t, 0.0f, t);
|
||||
const float d = len(centroid - P - D * closest_t);
|
||||
if (t == FLT_MAX) {
|
||||
theta_d = fast_atan2f(closest_t, d) + M_PI_2_F;
|
||||
}
|
||||
else {
|
||||
theta_d = atan2f(t, d - closest_t * safe_divide(t - closest_t, d));
|
||||
}
|
||||
}
|
||||
|
||||
/* Early out if the emitter is guaranteed to be invisible. */
|
||||
bool is_visible;
|
||||
float energy = kemitter->energy;
|
||||
if (is_triangle(kemitter)) {
|
||||
is_visible = triangle_light_tree_parameters<in_volume_segment>(
|
||||
kg, kemitter, centroid, P_c, N_or_D, bcone, cos_theta_u, distance, point_to_centroid);
|
||||
}
|
||||
else {
|
||||
kernel_assert(is_light(kemitter));
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~(kemitter->light.id));
|
||||
switch (klight->type) {
|
||||
/* Function templates only modifies cos_theta_u when in_volume_segment = true. */
|
||||
case LIGHT_SPOT:
|
||||
is_visible = spot_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, bcone, cos_theta_u, distance, point_to_centroid, energy);
|
||||
break;
|
||||
case LIGHT_POINT:
|
||||
is_visible = point_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
|
||||
bcone.theta_o = 0.0f;
|
||||
break;
|
||||
case LIGHT_AREA:
|
||||
is_visible = area_light_tree_parameters<in_volume_segment>(
|
||||
klight, centroid, P_c, N_or_D, bcone.axis, cos_theta_u, distance, point_to_centroid);
|
||||
break;
|
||||
case LIGHT_BACKGROUND:
|
||||
is_visible = background_light_tree_parameters<in_volume_segment>(
|
||||
centroid, t, cos_theta_u, distance, point_to_centroid, theta_d);
|
||||
break;
|
||||
case LIGHT_SUN:
|
||||
is_visible = sun_light_tree_parameters<in_volume_segment>(
|
||||
centroid, bcone.theta_e, t, cos_theta_u, distance, point_to_centroid, theta_d);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
is_visible |= has_transmission;
|
||||
if (!is_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_volume_segment) {
|
||||
/* Vector that forms a minimal angle with the emitter centroid. */
|
||||
point_to_centroid = -compute_v(centroid, P, N_or_D, bcone.axis, t);
|
||||
|
||||
if (is_light(kemitter)) {
|
||||
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~(kemitter->light.id));
|
||||
if (klight->type == LIGHT_SUN) {
|
||||
/* For sun light `theta_min` is 0, but due to numerical issues this is not always true.
|
||||
* Therefore explicitly assign `-bcone.axis` to `point_to_centroid` in this case. */
|
||||
point_to_centroid = -bcone.axis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
light_tree_importance<in_volume_segment>(N_or_D,
|
||||
has_transmission,
|
||||
point_to_centroid,
|
||||
cos_theta_u,
|
||||
bcone,
|
||||
distance.x,
|
||||
distance.y,
|
||||
energy,
|
||||
theta_d,
|
||||
max_importance,
|
||||
min_importance);
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device void light_tree_child_importance(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const ccl_global KernelLightTreeNode *knode,
|
||||
ccl_private float &max_importance,
|
||||
ccl_private float &min_importance)
|
||||
{
|
||||
max_importance = 0.0f;
|
||||
min_importance = 0.0f;
|
||||
|
||||
if (knode->num_emitters == 1) {
|
||||
light_tree_emitter_importance<in_volume_segment>(kg,
|
||||
P,
|
||||
N_or_D,
|
||||
t,
|
||||
has_transmission,
|
||||
knode->leaf.first_emitter,
|
||||
max_importance,
|
||||
min_importance);
|
||||
}
|
||||
else if (knode->num_emitters != 0) {
|
||||
light_tree_node_importance<in_volume_segment>(
|
||||
P, N_or_D, t, has_transmission, knode, max_importance, min_importance);
|
||||
}
|
||||
}
|
||||
|
||||
/* Select an element from the reservoir with probability proportional to its weight.
|
||||
* Expect `selected_index` to be initialized to -1, and stays -1 if all the weights are invalid. */
|
||||
ccl_device void sample_reservoir(const int current_index,
|
||||
const float current_weight,
|
||||
ccl_private int &selected_index,
|
||||
ccl_private float &selected_weight,
|
||||
ccl_private float &total_weight,
|
||||
ccl_private float &rand)
|
||||
{
|
||||
if (!(current_weight > 0.0f)) {
|
||||
return;
|
||||
}
|
||||
total_weight += current_weight;
|
||||
|
||||
/* When `-ffast-math` is used it is possible that the threshold is almost 1 but not quite.
|
||||
* For this case we check the first valid element explicitly (instead of relying on the threshold
|
||||
* to be 1, giving it certain probability). */
|
||||
if (selected_index == -1) {
|
||||
selected_index = current_index;
|
||||
selected_weight = current_weight;
|
||||
/* The threshold is expected to be 1 in this case with strict mathematics, so no need to divide
|
||||
* the rand. In fact, division in such case could lead the rand to exceed 1 because of division
|
||||
* by something smaller than 1. */
|
||||
return;
|
||||
}
|
||||
|
||||
const float thresh = current_weight / total_weight;
|
||||
if (rand <= thresh) {
|
||||
selected_index = current_index;
|
||||
selected_weight = current_weight;
|
||||
rand = rand / thresh;
|
||||
}
|
||||
else {
|
||||
rand = (rand - thresh) / (1.0f - thresh);
|
||||
}
|
||||
|
||||
/* Ensure the `rand` is always within 0..1 range, which could be violated above when
|
||||
* `-ffast-math` is used. */
|
||||
rand = saturatef(rand);
|
||||
}
|
||||
|
||||
/* Pick an emitter from a leaf node using reservoir sampling, keep two reservoirs for upper and
|
||||
* lower bounds. */
|
||||
template<bool in_volume_segment>
|
||||
ccl_device int light_tree_cluster_select_emitter(KernelGlobals kg,
|
||||
ccl_private float &rand,
|
||||
ccl_private float3 &P,
|
||||
ccl_private float3 &N_or_D,
|
||||
ccl_private float &t,
|
||||
const bool has_transmission,
|
||||
ccl_private int *node_index,
|
||||
ccl_private float *pdf_factor)
|
||||
{
|
||||
float selected_importance[2] = {0.0f, 0.0f};
|
||||
float total_importance[2] = {0.0f, 0.0f};
|
||||
int selected_index = -1;
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, *node_index);
|
||||
*node_index = -1;
|
||||
|
||||
kernel_assert(knode->num_emitters <= sizeof(uint) * 8);
|
||||
/* Mark emitters with valid importance. Used for reservoir when total minimum importance = 0. */
|
||||
uint has_importance = 0;
|
||||
|
||||
const bool sample_max = (rand > 0.5f); /* Sampling using the maximum importance. */
|
||||
if (knode->num_emitters > 1) {
|
||||
rand = rand * 2.0f - float(sample_max);
|
||||
}
|
||||
|
||||
for (int i = 0; i < knode->num_emitters; i++) {
|
||||
int current_index = knode->leaf.first_emitter + i;
|
||||
/* maximum importance = importance[0], minimum importance = importance[1] */
|
||||
float importance[2];
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, current_index, importance[0], importance[1]);
|
||||
|
||||
sample_reservoir(current_index,
|
||||
importance[!sample_max],
|
||||
selected_index,
|
||||
selected_importance[!sample_max],
|
||||
total_importance[!sample_max],
|
||||
rand);
|
||||
if (selected_index == current_index) {
|
||||
selected_importance[sample_max] = importance[sample_max];
|
||||
}
|
||||
total_importance[sample_max] += importance[sample_max];
|
||||
|
||||
has_importance |= ((importance[0] > 0) << i);
|
||||
}
|
||||
|
||||
if (!has_importance) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (total_importance[1] == 0.0f) {
|
||||
/* Uniformly sample emitters with positive maximum importance. */
|
||||
if (sample_max) {
|
||||
selected_importance[1] = 1.0f;
|
||||
total_importance[1] = float(popcount(has_importance));
|
||||
}
|
||||
else {
|
||||
selected_index = -1;
|
||||
for (int i = 0; i < knode->num_emitters; i++) {
|
||||
const int current_index = knode->leaf.first_emitter + i;
|
||||
sample_reservoir(current_index,
|
||||
float(has_importance & 1),
|
||||
selected_index,
|
||||
selected_importance[1],
|
||||
total_importance[1],
|
||||
rand);
|
||||
has_importance >>= 1;
|
||||
}
|
||||
|
||||
float discard;
|
||||
light_tree_emitter_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, selected_index, selected_importance[0], discard);
|
||||
}
|
||||
}
|
||||
|
||||
*pdf_factor *= 0.5f * (selected_importance[0] / total_importance[0] +
|
||||
selected_importance[1] / total_importance[1]);
|
||||
|
||||
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
selected_index);
|
||||
|
||||
if (is_mesh(kemitter)) {
|
||||
/* Transform ray from world to local space. */
|
||||
light_tree_to_local_space<in_volume_segment>(kg, kemitter->mesh.object_id, P, N_or_D, t);
|
||||
|
||||
*node_index = kemitter->mesh.node_id;
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes,
|
||||
*node_index);
|
||||
if (knode->type == LIGHT_TREE_INSTANCE) {
|
||||
/* Switch to the node with the subtree. */
|
||||
*node_index = knode->instance.reference;
|
||||
}
|
||||
}
|
||||
|
||||
return selected_index;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device bool get_left_probability(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 N_or_D,
|
||||
const float t,
|
||||
const bool has_transmission,
|
||||
const int left_index,
|
||||
const int right_index,
|
||||
ccl_private float &left_probability)
|
||||
{
|
||||
const ccl_global KernelLightTreeNode *left = &kernel_data_fetch(light_tree_nodes, left_index);
|
||||
const ccl_global KernelLightTreeNode *right = &kernel_data_fetch(light_tree_nodes, right_index);
|
||||
|
||||
float min_left_importance;
|
||||
float max_left_importance;
|
||||
float min_right_importance;
|
||||
float max_right_importance;
|
||||
light_tree_child_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, left, max_left_importance, min_left_importance);
|
||||
light_tree_child_importance<in_volume_segment>(
|
||||
kg, P, N_or_D, t, has_transmission, right, max_right_importance, min_right_importance);
|
||||
|
||||
const float total_max_importance = max_left_importance + max_right_importance;
|
||||
if (total_max_importance == 0.0f) {
|
||||
return false;
|
||||
}
|
||||
const float total_min_importance = min_left_importance + min_right_importance;
|
||||
|
||||
/* Average two probabilities of picking the left child node using lower and upper bounds. */
|
||||
const float probability_max = max_left_importance / total_max_importance;
|
||||
const float probability_min = total_min_importance > 0 ?
|
||||
min_left_importance / total_min_importance :
|
||||
0.5f * (float(max_left_importance > 0) +
|
||||
float(max_right_importance == 0.0f));
|
||||
left_probability = 0.5f * (probability_max + probability_min);
|
||||
return true;
|
||||
}
|
||||
|
||||
ccl_device int light_tree_root_node_index(KernelGlobals kg, const int object_receiver)
|
||||
{
|
||||
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING) {
|
||||
const uint receiver_light_set =
|
||||
(object_receiver != OBJECT_NONE) ?
|
||||
kernel_data_fetch(objects, object_receiver).receiver_light_set :
|
||||
0;
|
||||
return kernel_data.light_link_sets[receiver_light_set].light_tree_root;
|
||||
}
|
||||
|
||||
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 bool light_tree_sample(KernelGlobals kg,
|
||||
const float rand,
|
||||
const float3 P,
|
||||
float3 N_or_D,
|
||||
float t,
|
||||
const int object_receiver,
|
||||
const int shader_flags,
|
||||
ccl_private LightSample *ls)
|
||||
{
|
||||
if (!kernel_data.integrator.use_direct_light) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
|
||||
float pdf_leaf = 1.0f;
|
||||
float pdf_selection = 1.0f;
|
||||
int selected_emitter = -1;
|
||||
int node_index = light_tree_root_node_index(kg, object_receiver);
|
||||
float rand_selection = rand;
|
||||
|
||||
float3 local_P = P;
|
||||
|
||||
/* Traverse the light tree until a leaf node is reached. */
|
||||
while (true) {
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
|
||||
|
||||
if (is_leaf(knode)) {
|
||||
/* At a leaf node, we pick an emitter. */
|
||||
selected_emitter = light_tree_cluster_select_emitter<in_volume_segment>(
|
||||
kg, rand_selection, local_P, N_or_D, t, has_transmission, &node_index, &pdf_selection);
|
||||
|
||||
if (selected_emitter < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node_index < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Continue with the picked mesh light. */
|
||||
ls->object = kernel_data_fetch(light_tree_emitters, selected_emitter).mesh.object_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Inner node. */
|
||||
const int left_index = knode->inner.left_child;
|
||||
const int right_index = knode->inner.right_child;
|
||||
|
||||
float left_prob;
|
||||
if (!get_left_probability<in_volume_segment>(
|
||||
kg, local_P, N_or_D, t, has_transmission, left_index, right_index, left_prob))
|
||||
{
|
||||
return false; /* Both child nodes have zero importance. */
|
||||
}
|
||||
|
||||
float discard;
|
||||
float total_prob = left_prob;
|
||||
node_index = left_index;
|
||||
sample_reservoir(
|
||||
right_index, 1.0f - left_prob, node_index, discard, total_prob, rand_selection);
|
||||
pdf_leaf *= (node_index == left_index) ? left_prob : (1.0f - left_prob);
|
||||
}
|
||||
|
||||
ls->emitter_id = selected_emitter;
|
||||
ls->pdf_selection = pdf_selection * pdf_leaf;
|
||||
|
||||
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 uint32_t path_flag,
|
||||
const int object_emitter,
|
||||
const uint index_emitter,
|
||||
const int object_receiver)
|
||||
{
|
||||
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
|
||||
|
||||
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
|
||||
index_emitter);
|
||||
int subtree_root_index;
|
||||
uint bit_trail;
|
||||
uint target_emitter;
|
||||
|
||||
if (is_triangle(kemitter)) {
|
||||
/* If the target is an emissive triangle, first traverse the top level tree to find the mesh
|
||||
* light emitter, then traverse the subtree. */
|
||||
target_emitter = kernel_data_fetch(light_to_tree, object_emitter);
|
||||
const ccl_global KernelLightTreeEmitter *kmesh = &kernel_data_fetch(light_tree_emitters,
|
||||
target_emitter);
|
||||
subtree_root_index = kmesh->mesh.node_id;
|
||||
const ccl_global KernelLightTreeNode *kroot = &kernel_data_fetch(light_tree_nodes,
|
||||
subtree_root_index);
|
||||
bit_trail = kroot->bit_trail;
|
||||
|
||||
if (kroot->type == LIGHT_TREE_INSTANCE) {
|
||||
subtree_root_index = kroot->instance.reference;
|
||||
}
|
||||
}
|
||||
else {
|
||||
subtree_root_index = -1;
|
||||
bit_trail = kemitter->bit_trail;
|
||||
target_emitter = index_emitter;
|
||||
}
|
||||
|
||||
float pdf = 1.0f;
|
||||
int node_index = light_tree_root_node_index(kg, object_receiver);
|
||||
|
||||
/* Traverse the light tree until we reach the target leaf node. */
|
||||
while (true) {
|
||||
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
|
||||
|
||||
if (is_leaf(knode)) {
|
||||
/* Iterate through leaf node to find the probability of sampling the target emitter. */
|
||||
float target_max_importance = 0.0f;
|
||||
float target_min_importance = 0.0f;
|
||||
float total_max_importance = 0.0f;
|
||||
float total_min_importance = 0.0f;
|
||||
int num_has_importance = 0;
|
||||
for (int i = 0; i < knode->num_emitters; i++) {
|
||||
const int emitter = knode->leaf.first_emitter + i;
|
||||
float max_importance;
|
||||
float 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;
|
||||
target_min_importance = min_importance;
|
||||
}
|
||||
total_max_importance += max_importance;
|
||||
total_min_importance += min_importance;
|
||||
}
|
||||
|
||||
if (target_max_importance > 0.0f) {
|
||||
pdf *= 0.5f * (target_max_importance / total_max_importance +
|
||||
(total_min_importance > 0 ? target_min_importance / total_min_importance :
|
||||
1.0f / num_has_importance));
|
||||
}
|
||||
else {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (subtree_root_index != -1) {
|
||||
/* Arrived at the mesh light. Continue with the subtree. */
|
||||
float unused;
|
||||
light_tree_to_local_space<in_volume_segment>(kg, object_emitter, P, N, unused);
|
||||
|
||||
node_index = subtree_root_index;
|
||||
subtree_root_index = -1;
|
||||
target_emitter = index_emitter;
|
||||
bit_trail = kemitter->bit_trail;
|
||||
continue;
|
||||
}
|
||||
return pdf;
|
||||
}
|
||||
|
||||
/* Inner node. */
|
||||
const int left_index = knode->inner.left_child;
|
||||
const int right_index = knode->inner.right_child;
|
||||
|
||||
float left_prob;
|
||||
if (!get_left_probability<in_volume_segment>(
|
||||
kg, P, N, dt, has_transmission, left_index, right_index, left_prob))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bit_trail >>= kernel_data_fetch(light_tree_nodes, node_index).bit_skip;
|
||||
const bool go_left = (bit_trail & 1) == 0;
|
||||
bit_trail >>= 1;
|
||||
|
||||
node_index = go_left ? left_index : right_index;
|
||||
pdf *= go_left ? left_prob : (1.0f - left_prob);
|
||||
|
||||
if (pdf == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 float light_tree_pdf(KernelGlobals kg,
|
||||
float3 P,
|
||||
const float3 N,
|
||||
const float dt,
|
||||
const PathRayVisibility path_visibility,
|
||||
const uint32_t path_flag,
|
||||
const int emitter_object,
|
||||
const uint emitter_id,
|
||||
const int object_receiver)
|
||||
{
|
||||
if (path_visibility & PATH_RAY_VISIBILITY_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
|
||||
351
blender-5.2.0/intern/cycles/kernel/light/triangle.h
Normal file
351
blender-5.2.0/intern/cycles/kernel/light/triangle.h
Normal file
@@ -0,0 +1,351 @@
|
||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/globals.h"
|
||||
|
||||
#include "kernel/light/common.h"
|
||||
|
||||
#include "kernel/geom/motion_triangle.h"
|
||||
#include "kernel/geom/object.h"
|
||||
#include "kernel/geom/triangle.h"
|
||||
|
||||
#include "util/math_fast.h"
|
||||
#include "util/math_intersect.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* returns true if the triangle is has motion blur or an instancing transform applied */
|
||||
ccl_device_inline bool triangle_world_space_vertices(
|
||||
KernelGlobals kg, const int object, const int prim, const float time, float3 V[3])
|
||||
{
|
||||
bool has_motion = false;
|
||||
const uint object_flag = kernel_data_fetch(object_flag, object);
|
||||
|
||||
if (object_flag & SD_OBJECT_HAS_VERTEX_MOTION && time >= 0.0f) {
|
||||
motion_triangle_vertices(kg, object, prim, time, V);
|
||||
has_motion = true;
|
||||
}
|
||||
else {
|
||||
triangle_vertices(kg, object, prim, V);
|
||||
}
|
||||
|
||||
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
|
||||
#ifdef __OBJECT_MOTION__
|
||||
const float object_time = (time >= 0.0f) ? time : 0.5f;
|
||||
const Transform tfm = object_fetch_transform_motion_test(kg, object, object_time, nullptr);
|
||||
#else
|
||||
Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
|
||||
#endif
|
||||
V[0] = transform_point(&tfm, V[0]);
|
||||
V[1] = transform_point(&tfm, V[1]);
|
||||
V[2] = transform_point(&tfm, V[2]);
|
||||
has_motion = true;
|
||||
}
|
||||
return has_motion;
|
||||
}
|
||||
|
||||
ccl_device_inline float triangle_light_pdf_area_sampling(const float3 Ng,
|
||||
const float3 I,
|
||||
const float t)
|
||||
{
|
||||
const float cos_pi = fabsf(dot(Ng, I));
|
||||
|
||||
if (cos_pi == 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return t * t / cos_pi;
|
||||
}
|
||||
|
||||
ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
|
||||
const ccl_private ShaderData *sd,
|
||||
const float t)
|
||||
{
|
||||
/* A naive heuristic to decide between costly solid angle sampling
|
||||
* and simple area sampling, comparing the distance to the triangle plane
|
||||
* to the length of the edges of the triangle. */
|
||||
|
||||
float3 V[3];
|
||||
const bool has_motion = triangle_world_space_vertices(kg, sd->object, sd->prim, sd->time, V);
|
||||
|
||||
const float3 e0 = V[1] - V[0];
|
||||
const float3 e1 = V[2] - V[0];
|
||||
const float3 e2 = V[2] - V[1];
|
||||
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
|
||||
const float3 N = cross(e0, e1);
|
||||
const float distance_to_plane = fabsf(dot(N, sd->wi * t)) / dot(N, N);
|
||||
const float area = 0.5f * len(N);
|
||||
|
||||
float pdf;
|
||||
|
||||
if (longest_edge_squared > distance_to_plane * distance_to_plane) {
|
||||
/* sd contains the point on the light source
|
||||
* calculate Px, the point that we're shading */
|
||||
const float3 Px = sd->P + sd->wi * t;
|
||||
|
||||
const float3 A = safe_normalize(V[0] - Px);
|
||||
const float3 B = safe_normalize(V[1] - Px);
|
||||
const float3 C = safe_normalize(V[2] - Px);
|
||||
|
||||
const float solid_angle = 2.0f * fast_atan2f(fabsf(dot(A, cross(B, C))),
|
||||
(1.0f + dot(B, C) + dot(A, C) + dot(A, B)));
|
||||
|
||||
/* distribution_pdf_triangles is calculated over triangle area, but we're not sampling over
|
||||
* its area */
|
||||
if (UNLIKELY(solid_angle == 0.0f)) {
|
||||
return 0.0f;
|
||||
}
|
||||
pdf = 1.0f / solid_angle;
|
||||
}
|
||||
else {
|
||||
if (UNLIKELY(area == 0.0f)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
pdf = triangle_light_pdf_area_sampling(sd->Ng, sd->wi, t) / area;
|
||||
}
|
||||
|
||||
/* Belongs in distribution.h but can reuse computations here. */
|
||||
if (!kernel_data.integrator.use_light_tree) {
|
||||
float distribution_area = area;
|
||||
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
}
|
||||
|
||||
pdf *= distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
}
|
||||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
template<bool in_volume_segment>
|
||||
ccl_device_forceinline bool triangle_light_sample(KernelGlobals kg,
|
||||
const int prim,
|
||||
const int object,
|
||||
const float2 rand,
|
||||
const float time,
|
||||
ccl_private LightSample *ls,
|
||||
const float3 P)
|
||||
{
|
||||
/* A naive heuristic to decide between costly solid angle sampling
|
||||
* and simple area sampling, comparing the distance to the triangle plane
|
||||
* to the length of the edges of the triangle. */
|
||||
|
||||
float3 V[3];
|
||||
const bool has_motion = triangle_world_space_vertices(kg, object, prim, time, V);
|
||||
|
||||
const float3 e0 = V[1] - V[0];
|
||||
const float3 e1 = V[2] - V[0];
|
||||
const float3 e2 = V[2] - V[1];
|
||||
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
|
||||
float3 N0 = cross(e0, e1);
|
||||
/* Flip normal if necessary. */
|
||||
const uint object_flag = kernel_data_fetch(object_flag, object);
|
||||
if (object_flag & SD_OBJECT_NEGATIVE_SCALE) {
|
||||
N0 = -N0;
|
||||
}
|
||||
|
||||
/* Do not draw samples from the side without MIS. */
|
||||
ls->shader = kernel_data_fetch(tri_shader, prim);
|
||||
const float distance_to_plane = dot(N0, V[0] - P) / dot(N0, N0);
|
||||
const int ls_shader_flag = kernel_data_fetch(shaders, ls->shader & SHADER_MASK).flags;
|
||||
if (!in_volume_segment &&
|
||||
!(ls_shader_flag & (distance_to_plane > 0 ? SD_MIS_BACK : SD_MIS_FRONT)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float Nl = 0.0f;
|
||||
ls->Ng = safe_normalize_len(N0, &Nl);
|
||||
const float area = 0.5f * Nl;
|
||||
|
||||
ls->eval_fac = 1.0f;
|
||||
ls->object = object;
|
||||
ls->prim = prim;
|
||||
ls->shader |= SHADER_USE_MIS;
|
||||
ls->type = LIGHT_TRIANGLE;
|
||||
ls->group = object_lightgroup(kg, object);
|
||||
|
||||
if (!in_volume_segment && (longest_edge_squared > distance_to_plane * distance_to_plane)) {
|
||||
/* A modified version of James Arvo, "Stratified Sampling of Spherical Triangles"
|
||||
* http://www.graphics.cornell.edu/pubs/1995/Arv95c.pdf */
|
||||
|
||||
/* Project the triangle to the unit sphere and calculate the three unit vector that spans the
|
||||
* spherical triangle. */
|
||||
const float3 A = safe_normalize(V[0] - P);
|
||||
const float3 B = safe_normalize(V[1] - P);
|
||||
const float3 C = safe_normalize(V[2] - P);
|
||||
|
||||
const float cos_a = dot(B, C);
|
||||
const float cos_b = dot(A, C);
|
||||
const float cos_c = dot(A, B);
|
||||
|
||||
const float mixed_product = fabsf(dot(A, cross(B, C)));
|
||||
|
||||
/* The area of the spherical triangle is equal to the subtended solid angle. */
|
||||
const float solid_angle = 2.0f * fast_atan2f(mixed_product, (1.0f + cos_a + cos_b + cos_c));
|
||||
|
||||
/* Compute the angle at A. */
|
||||
const float cos_alpha = dot(safe_normalize(cross(A, B)), safe_normalize(cross(A, C)));
|
||||
const float sin_alpha = sin_from_cos(cos_alpha);
|
||||
const float alpha = safe_acosf(cos_alpha);
|
||||
|
||||
/* Select a random sub-area of the spherical triangle and calculate the third vertex C_ of that
|
||||
* new triangle. */
|
||||
const float A_hat = rand.x * solid_angle;
|
||||
float sin_phi;
|
||||
float cos_phi;
|
||||
fast_sincosf(A_hat - alpha, &sin_phi, &cos_phi);
|
||||
const float u = cos_phi - cos_alpha;
|
||||
const float v = sin_phi + sin_alpha * cos_c;
|
||||
const float num = (v * cos_phi - u * sin_phi) * cos_alpha - v;
|
||||
const float den = (v * sin_phi + u * cos_phi) * sin_alpha;
|
||||
const float q = (den == 0.0f) ? 1.0f : num / den;
|
||||
|
||||
const float3 U = safe_normalize(C - cos_b * A);
|
||||
const float3 C_ = safe_normalize(q * A + sin_from_cos(q) * U);
|
||||
|
||||
/* Finally, select a random point along the edge of the new triangle
|
||||
* That point on the spherical triangle is the sampled ray direction */
|
||||
const float z = 1.0f - rand.y * (1.0f - dot(C_, B));
|
||||
ls->D = z * B + sin_from_cos(z) * safe_normalize(C_ - dot(C_, B) * B);
|
||||
|
||||
/* calculate intersection with the planar triangle */
|
||||
float unused_u, unused_v;
|
||||
if (!ray_triangle_intersect(
|
||||
P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &unused_u, &unused_v, &ls->t))
|
||||
{
|
||||
ls->pdf = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
ls->P = P + ls->D * ls->t;
|
||||
|
||||
/* distribution_pdf_triangles is calculated over triangle area, but we're sampling over solid
|
||||
* angle */
|
||||
if (UNLIKELY(solid_angle == 0.0f)) {
|
||||
ls->pdf = 0.0f;
|
||||
return false;
|
||||
}
|
||||
ls->pdf = 1.0f / solid_angle;
|
||||
}
|
||||
else {
|
||||
if (UNLIKELY(area == 0.0f)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* compute random point in triangle. From Eric Heitz's "A Low-Distortion Map Between Triangle
|
||||
* and Square" */
|
||||
float u = rand.x;
|
||||
float v = rand.y;
|
||||
if (v > u) {
|
||||
u *= 0.5f;
|
||||
v -= u;
|
||||
}
|
||||
else {
|
||||
v *= 0.5f;
|
||||
u -= v;
|
||||
}
|
||||
|
||||
const float t = 1.0f - u - v;
|
||||
ls->P = t * V[0] + u * V[1] + v * V[2];
|
||||
/* compute incoming direction, distance and pdf */
|
||||
ls->D = normalize_len(ls->P - P, &ls->t);
|
||||
ls->pdf = triangle_light_pdf_area_sampling(ls->Ng, -ls->D, ls->t) / area;
|
||||
}
|
||||
|
||||
/* Belongs in distribution.h but can reuse computations here. */
|
||||
if (!kernel_data.integrator.use_light_tree) {
|
||||
float distribution_area = area;
|
||||
|
||||
if (has_motion && area != 0.0f) {
|
||||
/* For motion blur need area of triangle at fixed time as used in the CDF. */
|
||||
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
|
||||
distribution_area = triangle_area(V[0], V[1], V[2]);
|
||||
}
|
||||
|
||||
ls->pdf_selection = distribution_area * kernel_data.integrator.distribution_pdf_triangles;
|
||||
}
|
||||
|
||||
return (ls->pdf > 0.0f);
|
||||
}
|
||||
|
||||
/* Find the ray segment lit by the triangle light. */
|
||||
ccl_device_inline bool triangle_light_valid_ray_segment(KernelGlobals kg,
|
||||
const float3 P,
|
||||
const float3 D,
|
||||
ccl_private Interval<float> *t_range,
|
||||
const ccl_private LightSample *ls)
|
||||
{
|
||||
const int shader_flag = kernel_data_fetch(shaders, ls->shader & SHADER_MASK).flags;
|
||||
const int SD_MIS_BOTH = SD_MIS_BACK | SD_MIS_FRONT;
|
||||
if ((shader_flag & SD_MIS_BOTH) == SD_MIS_BOTH) {
|
||||
/* Both sides are sampled, the complete ray segment is visible. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
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>
|
||||
ccl_device_forceinline bool triangle_light_tree_parameters(
|
||||
KernelGlobals kg,
|
||||
const ccl_global KernelLightTreeEmitter *kemitter,
|
||||
const float3 centroid,
|
||||
const float3 P,
|
||||
const float3 N,
|
||||
const KernelBoundingCone bcone,
|
||||
ccl_private float &cos_theta_u,
|
||||
ccl_private float2 &distance,
|
||||
ccl_private float3 &point_to_centroid)
|
||||
{
|
||||
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it worth the
|
||||
* overhead to compute the accurate minimal distance? */
|
||||
float min_distance;
|
||||
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
|
||||
distance = make_float2(min_distance, min_distance);
|
||||
|
||||
cos_theta_u = FLT_MAX;
|
||||
|
||||
float3 vertices[3];
|
||||
triangle_vertices(kg, kemitter->object_id, kemitter->triangle.id, vertices);
|
||||
|
||||
bool shape_above_surface = false;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const float3 corner = vertices[i];
|
||||
float distance_point_to_corner;
|
||||
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
|
||||
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
|
||||
shape_above_surface |= dot(point_to_corner, N) > 0;
|
||||
if (!in_volume_segment) {
|
||||
distance.x = fmaxf(distance.x, distance_point_to_corner);
|
||||
}
|
||||
}
|
||||
|
||||
const bool front_facing = bcone.theta_o != 0.0f || dot(bcone.axis, point_to_centroid) < 0;
|
||||
|
||||
return front_facing && shape_above_surface;
|
||||
}
|
||||
|
||||
ccl_device float2 triangle_light_uv(KernelGlobals kg,
|
||||
const int object,
|
||||
const int prim,
|
||||
const float time,
|
||||
const float3 ray_P,
|
||||
const float3 ray_D)
|
||||
{
|
||||
float3 V[3];
|
||||
triangle_world_space_vertices(kg, object, prim, time, V);
|
||||
return ray_triangle_uv(ray_P, ray_D, V[0], V[1], V[2]);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
Reference in New Issue
Block a user