Refactor: Cycles light sampling code reorganization

* Split light types into own files, move light type specific code from
  light tree and MNEE.
* Move flat light distribution code into own kernel file and host side
  building function, in preparation of light tree addition. Add light/sample.h
  as main entry point to kernel light sampling.
* Better separate calculation of pdf for selecting a light, and pdf for
  sampling a point on the light. The selection pdf is now also stored in
  LightSampling for MNEE to correctly recalculate the full pdf when the
  shading position changes but the point on the light remains fixed.
* Improvement to kernel light storage, using packed_float3, better variable
  names, etc.

Includes contributions by Brecht Van Lommel and Weizhen Huang.

Ref T77889
This commit is contained in:
Brecht Van Lommel 2022-11-30 20:17:45 +01:00
parent db1728096a
commit ac51d331df
Notes: blender-bot 2023-02-13 22:07:48 +01:00
Referenced by issue #77889, Cycles: Many Lights Sampling
22 changed files with 1535 additions and 1287 deletions

View File

@ -285,10 +285,16 @@ set(SRC_KERNEL_INTEGRATOR_HEADERS
)
set(SRC_KERNEL_LIGHT_HEADERS
light/light.h
light/area.h
light/background.h
light/common.h
light/distant.h
light/distribution.h
light/light.h
light/point.h
light/sample.h
light/spot.h
light/triangle.h
)
set(SRC_KERNEL_SAMPLE_HEADERS

View File

@ -23,24 +23,19 @@ KERNEL_STRUCT_MEMBER(background, int, volume_shader)
KERNEL_STRUCT_MEMBER(background, float, volume_step_size)
KERNEL_STRUCT_MEMBER(background, int, transparent)
KERNEL_STRUCT_MEMBER(background, float, transparent_roughness_squared_threshold)
/* Portal sampling. */
KERNEL_STRUCT_MEMBER(background, float, portal_weight)
KERNEL_STRUCT_MEMBER(background, int, num_portals)
KERNEL_STRUCT_MEMBER(background, int, portal_offset)
/* Sun sampling. */
KERNEL_STRUCT_MEMBER(background, float, sun_weight)
/* Importance map sampling. */
KERNEL_STRUCT_MEMBER(background, float, map_weight)
KERNEL_STRUCT_MEMBER(background, float, portal_weight)
KERNEL_STRUCT_MEMBER(background, int, map_res_x)
KERNEL_STRUCT_MEMBER(background, int, map_res_y)
/* Multiple importance sampling. */
KERNEL_STRUCT_MEMBER(background, int, use_mis)
/* Lightgroup. */
KERNEL_STRUCT_MEMBER(background, int, lightgroup)
/* Padding. */
KERNEL_STRUCT_MEMBER(background, int, pad1)
KERNEL_STRUCT_MEMBER(background, int, pad2)
KERNEL_STRUCT_MEMBER(background, int, pad3)
/* Light Index. */
KERNEL_STRUCT_MEMBER(background, int, light_index)
KERNEL_STRUCT_END(KernelBackground)
/* BVH: own BVH2 if no native device acceleration struct used. */
@ -147,10 +142,17 @@ KERNEL_STRUCT_END(KernelFilm)
KERNEL_STRUCT_BEGIN(KernelIntegrator, integrator)
/* Emission. */
KERNEL_STRUCT_MEMBER(integrator, int, use_direct_light)
KERNEL_STRUCT_MEMBER(integrator, int, use_light_mis)
KERNEL_STRUCT_MEMBER(integrator, int, num_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_distant_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_background_lights)
/* Portal sampling. */
KERNEL_STRUCT_MEMBER(integrator, int, num_portals)
KERNEL_STRUCT_MEMBER(integrator, int, portal_offset)
/* Flat light distribution. */
KERNEL_STRUCT_MEMBER(integrator, int, num_distribution)
KERNEL_STRUCT_MEMBER(integrator, int, num_all_lights)
KERNEL_STRUCT_MEMBER(integrator, float, pdf_triangles)
KERNEL_STRUCT_MEMBER(integrator, float, pdf_lights)
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_triangles)
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_lights)
KERNEL_STRUCT_MEMBER(integrator, float, light_inv_rr_threshold)
/* Bounces. */
KERNEL_STRUCT_MEMBER(integrator, int, min_bounce)
@ -177,8 +179,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, seed)
/* Clamp. */
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_direct)
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_indirect)
/* MIS. */
KERNEL_STRUCT_MEMBER(integrator, int, use_lamp_mis)
/* Caustics. */
KERNEL_STRUCT_MEMBER(integrator, int, use_caustics)
/* Sampling pattern. */
@ -195,7 +195,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, has_shadow_catcher)
KERNEL_STRUCT_MEMBER(integrator, int, filter_closures)
/* MIS debugging. */
KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type)
/* Path Guiding */
KERNEL_STRUCT_MEMBER(integrator, float, surface_guiding_probability)
KERNEL_STRUCT_MEMBER(integrator, float, volume_guiding_probability)

View File

@ -11,10 +11,10 @@
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/shadow_catcher.h"
#include "kernel/light/light.h"
#include "kernel/geom/geom.h"
#include "kernel/light/light.h"
#include "kernel/bvh/bvh.h"
CCL_NAMESPACE_BEGIN
@ -387,7 +387,7 @@ ccl_device void integrator_intersect_closest(KernelGlobals kg,
#endif /* __MNEE__ */
/* Light intersection for MIS. */
if (kernel_data.integrator.use_lamp_mis) {
if (kernel_data.integrator.use_light_mis) {
/* NOTE: if we make lights visible to camera rays, we'll need to initialize
* these in the path_state_init. */
const int last_type = INTEGRATOR_STATE(state, isect, type);

View File

@ -108,48 +108,6 @@ ccl_device_inline float mat22_inverse(const float4 m, ccl_private float4 &m_inve
return det;
}
/* Update light sample */
ccl_device_forceinline void mnee_update_light_sample(KernelGlobals kg,
const float3 P,
ccl_private LightSample *ls)
{
/* correct light sample position/direction and pdf
* NOTE: preserve pdf in area measure */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
if (ls->type == LIGHT_POINT || ls->type == LIGHT_SPOT) {
ls->D = normalize_len(ls->P - P, &ls->t);
ls->Ng = -ls->D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
if (ls->type == LIGHT_SPOT) {
/* spot light attenuation */
float3 dir = make_float3(klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
ls->eval_fac *= spot_light_attenuation(
dir, klight->spot.spot_angle, klight->spot.spot_smooth, ls->Ng);
}
}
else if (ls->type == LIGHT_AREA) {
float invarea = fabsf(klight->area.invarea);
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = invarea;
if (klight->area.tan_spread > 0.f) {
ls->eval_fac = 0.25f * invarea;
ls->eval_fac *= light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
}
}
ls->pdf *= kernel_data.integrator.pdf_lights;
}
/* Manifold vertex setup from ray and intersection data */
ccl_device_forceinline void mnee_setup_manifold_vertex(KernelGlobals kg,
ccl_private ManifoldVertex *vtx,
@ -819,7 +777,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
/* Update light sample with new position / direct.ion
* and keep pdf in vertex area measure */
mnee_update_light_sample(kg, vertices[vertex_count - 1].p, ls);
light_sample_update_position(kg, ls, vertices[vertex_count - 1].p);
/* Save state path bounce info in case a light path node is used in the refractive interface or
* light shader graph. */

View File

@ -69,9 +69,9 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
bool eval_background = true;
float transparent = 0.0f;
int path_flag = INTEGRATOR_STATE(state, path, flag);
const bool is_transparent_background_ray = kernel_data.background.transparent &&
(INTEGRATOR_STATE(state, path, flag) &
PATH_RAY_TRANSPARENT_BACKGROUND);
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND);
if (is_transparent_background_ray) {
transparent = average(INTEGRATOR_STATE(state, path, throughput));
@ -86,7 +86,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
#ifdef __MNEE__
if (INTEGRATOR_STATE(state, path, mnee) & PATH_MNEE_CULL_LIGHT_CONNECTION) {
if (kernel_data.background.use_mis) {
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
/* This path should have been resolved with mnee, it will
* generate a firefly for small lights since it is improbable. */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
@ -116,14 +116,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
/* Check if background light exists or if we should skip pdf. */
if (!(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_MIS_SKIP) &&
kernel_data.background.use_mis) {
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);
/* multiple importance sampling, get background light pdf for ray
* direction, and compute weight with respect to BSDF pdf */
const float pdf = background_light_pdf(kg, ray_P, ray_D);
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
mis_weight = light_sample_mis_weight_forward_background(kg, state, path_flag);
}
guiding_record_background(kg, state, L, mis_weight);
@ -142,8 +135,8 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float ray_time = INTEGRATOR_STATE(state, ray, time);
LightSample ls ccl_optional_struct_init;
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
if (light_sample_from_distant_ray(kg, ray_D, lamp, &ls)) {
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
if (distant_light_sample_from_intersection(kg, ray_D, lamp, &ls)) {
/* Use visibility flag to skip lights. */
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
@ -182,10 +175,7 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
/* MIS weighting. */
float mis_weight = 1.0f;
if (!(path_flag & PATH_RAY_MIS_SKIP)) {
/* multiple importance sampling, get regular light pdf,
* and compute weight with respect to BSDF pdf */
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
mis_weight = light_sample_mis_weight_forward_distant(kg, state, path_flag, &ls);
}
/* Write to render buffer. */

View File

@ -61,10 +61,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
/* MIS weighting. */
float mis_weight = 1.0f;
if (!(path_flag & PATH_RAY_MIS_SKIP)) {
/* multiple importance sampling, get regular light pdf,
* and compute weight with respect to BSDF pdf */
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
mis_weight = light_sample_mis_weight_forward_lamp(kg, state, path_flag, &ls, ray_P);
}
/* Write to render buffer. */

View File

@ -15,7 +15,6 @@
#include "kernel/integrator/surface_shader.h"
#include "kernel/integrator/volume_stack.h"
#include "kernel/light/light.h"
#include "kernel/light/sample.h"
CCL_NAMESPACE_BEGIN
@ -120,13 +119,7 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS))
#endif
{
const float bsdf_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
const float t = sd->ray_length;
/* Multiple importance sampling, get triangle light pdf,
* and compute weight with respect to BSDF pdf. */
float pdf = triangle_light_pdf(kg, sd, t);
mis_weight = light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
mis_weight = light_sample_mis_weight_forward_surface(kg, state, path_flag, sd);
}
guiding_record_surface_emission(kg, state, L, mis_weight);
@ -154,8 +147,17 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
if (!light_distribution_sample_from_position(
kg, rand_light.x, rand_light.y, sd->time, sd->P, bounce, path_flag, &ls)) {
if (!light_sample_from_position(kg,
rng_state,
rand_light.x,
rand_light.y,
sd->time,
sd->P,
sd->N,
sd->flag,
bounce,
path_flag,
&ls)) {
return;
}
}

View File

@ -690,6 +690,7 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
ccl_device_forceinline bool integrate_volume_sample_light(
KernelGlobals kg,
IntegratorState state,
ccl_private const Ray *ccl_restrict ray,
ccl_private const ShaderData *ccl_restrict sd,
ccl_private const RNGState *ccl_restrict rng_state,
ccl_private LightSample *ccl_restrict ls)
@ -704,8 +705,16 @@ ccl_device_forceinline bool integrate_volume_sample_light(
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
if (!light_distribution_sample_from_volume_segment(
kg, rand_light.x, rand_light.y, sd->time, sd->P, bounce, path_flag, ls)) {
if (!light_sample_from_volume_segment(kg,
rand_light.x,
rand_light.y,
sd->time,
sd->P,
ray->D,
ray->tmax - ray->tmin,
bounce,
path_flag,
ls)) {
return false;
}
@ -737,18 +746,32 @@ ccl_device_forceinline void integrate_volume_direct_light(
return;
}
/* Sample position on the same light again, now from the shading
* point where we scattered.
/* Sample position on the same light again, now from the shading point where we scattered.
*
* TODO: decorrelate random numbers and use light_sample_new_position to
* avoid resampling the CDF. */
* Note that this means we sample the light tree twice when equiangular sampling is used.
* We could consider sampling the light tree just once and use the same light position again.
*
* This would make the PDFs for MIS weights more complicated due to having to account for
* both distance/equiangular and direct/indirect light sampling, but could be more accurate.
* Additionally we could end up behind the light or outside a spot light cone, which might
* waste a sample. Though on the other hand it would be possible to prevent that with
* equiangular sampling restricted to a smaller sub-segment where the light has influence. */
{
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
if (!light_distribution_sample_from_position(
kg, rand_light.x, rand_light.y, sd->time, P, bounce, path_flag, ls)) {
if (!light_sample_from_position(kg,
rng_state,
rand_light.x,
rand_light.y,
sd->time,
P,
zero_float3(),
0,
bounce,
path_flag,
ls)) {
return;
}
}
@ -987,7 +1010,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
const bool need_light_sample = !(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_TERMINATE);
const bool have_equiangular_sample = need_light_sample &&
integrate_volume_sample_light(
kg, state, &sd, &rng_state, &ls) &&
kg, state, ray, &sd, &rng_state, &ls) &&
(ls.t != FLT_MAX);
VolumeSampleMethod direct_sample_method = (have_equiangular_sample) ?

View File

@ -0,0 +1,324 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/common.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(float3 P,
ccl_private float3 *light_p,
float3 extentu,
float3 extentv,
float randu,
float randv,
bool sample_coord)
{
/* In our name system we're using P for the center, which is o in the paper. */
float3 corner = *light_p - extentu * 0.5f - extentv * 0.5f;
float extentu_len, extentv_len;
/* Compute local reference system R. */
float3 x = normalize_len(extentu, &extentu_len);
float3 y = normalize_len(extentv, &extentv_len);
float3 z = cross(x, y);
/* Compute rectangle coords in local reference system. */
float3 dir = corner - P;
float z0 = dot(dir, z);
/* Flip 'z' to make it point against Q. */
if (z0 > 0.0f) {
z *= -1.0f;
z0 *= -1.0f;
}
float x0 = dot(dir, x);
float y0 = dot(dir, y);
float x1 = x0 + extentu_len;
float y1 = y0 + extentv_len;
/* Compute internal angles (gamma_i). */
float4 diff = make_float4(x0, y1, x1, y0) - make_float4(x1, y0, x0, y1);
float4 nz = make_float4(y0, x1, y1, x0) * diff;
nz = nz / sqrt(z0 * z0 * diff * diff + nz * nz);
float g0 = safe_acosf(-nz.x * nz.y);
float g1 = safe_acosf(-nz.y * nz.z);
float g2 = safe_acosf(-nz.z * nz.w);
float g3 = safe_acosf(-nz.w * nz.x);
/* Compute predefined constants. */
float b0 = nz.x;
float b1 = nz.z;
float b0sq = b0 * b0;
float k = M_2PI_F - g2 - g3;
/* Compute solid angle from internal angles. */
float S = g0 + g1 - k;
if (sample_coord) {
/* Compute cu. */
float au = randu * S + k;
float fu = (cosf(au) * b0 - b1) / sinf(au);
float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
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. */
float z0sq = z0 * z0;
float y0sq = y0 * y0;
float y1sq = y1 * y1;
float d = sqrtf(xu * xu + z0sq);
float h0 = y0 / sqrtf(d * d + y0sq);
float h1 = y1 / sqrtf(d * d + y1sq);
float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
/* Transform (xu, yv, z0) to world coords. */
*light_p = P + xu * x + yv * y + z0 * z;
}
/* return pdf */
if (S != 0.0f)
return 1.0f / S;
else
return 0.0f;
}
/* Light spread. */
ccl_device float area_light_spread_attenuation(const float3 D,
const float3 lightNg,
const float tan_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 cos_a = -dot(D, lightNg);
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
const float tan_a = sin_a / cos_a;
return max((1.0f - (tan_spread * tan_a)) * normalize_spread, 0.0f);
}
/* Compute subset of area light that actually has an influence on the shading point, to
* reduce noise with low spread. */
ccl_device bool area_light_spread_clamp_area_light(const float3 P,
const float3 lightNg,
ccl_private float3 *lightP,
ccl_private float3 *extentu,
ccl_private float3 *extentv,
const float tan_spread)
{
/* Closest point in area light plane and distance to that plane. */
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
const float t = len(closest_P - P);
/* Radius of circle on area light that actually affects the shading point. */
const float radius = t / tan_spread;
/* TODO: would be faster to store as normalized vector + length, also in area_light_rect_sample.
*/
float len_u, len_v;
const float3 u = normalize_len(*extentu, &len_u);
const float3 v = normalize_len(*extentv, &len_v);
/* Local uv coordinates of closest point. */
const float closest_u = dot(u, closest_P - *lightP);
const float closest_v = dot(v, closest_P - *lightP);
/* Compute rectangle encompassing the circle that affects the shading point,
* clamped to the bounds of the area light. */
const float min_u = max(closest_u - radius, -len_u * 0.5f);
const float max_u = min(closest_u + radius, len_u * 0.5f);
const float min_v = max(closest_v - radius, -len_v * 0.5f);
const float max_v = min(closest_v + radius, len_v * 0.5f);
/* Skip if rectangle is empty. */
if (min_u >= max_u || min_v >= max_v) {
return false;
}
/* 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);
const float new_len_u = max_u - min_u;
const float new_len_v = max_v - min_v;
*lightP = *lightP + new_center_u * u + new_center_v * v;
*extentu = u * new_len_u;
*extentv = v * new_len_v;
return true;
}
/* Common API. */
template<bool in_volume_segment>
ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
{
ls->P = klight->co;
float3 extentu = klight->area.extentu;
float3 extentv = klight->area.extentv;
float3 Ng = klight->area.dir;
float invarea = fabsf(klight->area.invarea);
bool is_round = (klight->area.invarea < 0.0f);
if (!in_volume_segment) {
if (dot(ls->P - P, Ng) > 0.0f) {
return false;
}
}
float3 inplane;
if (is_round || in_volume_segment) {
inplane = ellipse_sample(extentu * 0.5f, extentv * 0.5f, randu, randv);
ls->P += inplane;
ls->pdf = invarea;
}
else {
inplane = ls->P;
float3 sample_extentu = extentu;
float3 sample_extentv = extentv;
if (!in_volume_segment && klight->area.tan_spread > 0.0f) {
if (!area_light_spread_clamp_area_light(
P, Ng, &ls->P, &sample_extentu, &sample_extentv, klight->area.tan_spread)) {
return false;
}
}
ls->pdf = area_light_rect_sample(
P, &ls->P, sample_extentu, sample_extentv, randu, randv, true);
inplane = ls->P - inplane;
}
const float light_u = dot(inplane, extentu) * (1.0f / dot(extentu, extentu));
const float light_v = dot(inplane, extentv) * (1.0f / dot(extentv, extentv));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = light_v + 0.5f;
ls->v = -light_u - light_v;
ls->Ng = Ng;
ls->D = normalize_len(ls->P - P, &ls->t);
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
}
if (is_round) {
ls->pdf *= lamp_light_pdf(Ng, -ls->D, ls->t);
}
return true;
}
ccl_device_forceinline void area_light_update_position(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
{
const float invarea = fabsf(klight->area.invarea);
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = invarea;
if (klight->area.tan_spread > 0.f) {
ls->eval_fac = 0.25f * invarea;
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
}
}
ccl_device_inline bool area_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t,
ccl_private float *u,
ccl_private float *v)
{
/* Area light. */
const float invarea = fabsf(klight->area.invarea);
const bool is_round = (klight->area.invarea < 0.0f);
if (invarea == 0.0f) {
return false;
}
const float3 extentu = klight->area.extentu;
const float3 extentv = klight->area.extentv;
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;
return ray_quad_intersect(
ray->P, ray->D, ray->tmin, ray->tmax, light_P, extentu, extentv, Ng, &P, t, u, v, is_round);
}
ccl_device_inline bool area_light_sample_from_intersection(
const ccl_global KernelLight *klight,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
ccl_private LightSample *ccl_restrict ls)
{
/* area light */
float invarea = fabsf(klight->area.invarea);
float3 extentu = klight->area.extentu;
float3 extentv = klight->area.extentv;
float3 Ng = klight->area.dir;
float3 light_P = klight->co;
ls->u = isect->u;
ls->v = isect->v;
ls->D = ray_D;
ls->Ng = Ng;
const bool is_round = (klight->area.invarea < 0.0f);
if (is_round) {
ls->pdf = invarea * lamp_light_pdf(Ng, -ray_D, ls->t);
}
else {
float3 sample_extentu = extentu;
float3 sample_extentv = extentv;
if (klight->area.tan_spread > 0.0f) {
if (!area_light_spread_clamp_area_light(
ray_P, Ng, &light_P, &sample_extentu, &sample_extentv, klight->area.tan_spread)) {
return false;
}
}
ls->pdf = area_light_rect_sample(ray_P, &light_P, sample_extentu, sample_extentv, 0, 0, false);
}
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
if (ls->eval_fac == 0.0f) {
return false;
}
}
return true;
}
CCL_NAMESPACE_END

View File

@ -3,6 +3,7 @@
#pragma once
#include "kernel/light/area.h"
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
@ -130,11 +131,11 @@ ccl_device float background_map_pdf(KernelGlobals kg, float3 direction)
ccl_device_inline bool background_portal_data_fetch_and_check_side(
KernelGlobals kg, float3 P, int index, ccl_private float3 *lightpos, ccl_private float3 *dir)
{
int portal = kernel_data.background.portal_offset + index;
int portal = kernel_data.integrator.portal_offset + index;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
*lightpos = make_float3(klight->co[0], klight->co[1], klight->co[2]);
*dir = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
*lightpos = klight->co;
*dir = klight->area.dir;
/* Check whether portal is on the right side. */
if (dot(*dir, P - *lightpos) > 1e-4f)
@ -149,7 +150,7 @@ ccl_device_inline float background_portal_pdf(
float portal_pdf = 0.0f;
int num_possible = 0;
for (int p = 0; p < kernel_data.background.num_portals; p++) {
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
if (p == ignore_portal)
continue;
@ -163,12 +164,10 @@ ccl_device_inline float background_portal_pdf(
}
num_possible++;
int portal = kernel_data.background.portal_offset + p;
int portal = kernel_data.integrator.portal_offset + p;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
float3 extentu = klight->area.extentu;
float3 extentv = klight->area.extentv;
bool is_round = (klight->area.invarea < 0.0f);
if (!ray_quad_intersect(P,
@ -176,8 +175,8 @@ ccl_device_inline float background_portal_pdf(
1e-4f,
FLT_MAX,
lightpos,
axisu,
axisv,
extentu,
extentv,
dir,
NULL,
NULL,
@ -189,10 +188,10 @@ ccl_device_inline float background_portal_pdf(
if (is_round) {
float t;
float3 D = normalize_len(lightpos - P, &t);
portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t);
}
else {
portal_pdf += rect_light_sample(P, &lightpos, axisu, axisv, 0.0f, 0.0f, false);
portal_pdf += area_light_rect_sample(P, &lightpos, extentu, extentv, 0.0f, 0.0f, false);
}
}
@ -207,7 +206,7 @@ ccl_device_inline float background_portal_pdf(
ccl_device int background_num_possible_portals(KernelGlobals kg, float3 P)
{
int num_possible_portals = 0;
for (int p = 0; p < kernel_data.background.num_portals; p++) {
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
float3 lightpos, dir;
if (background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir))
num_possible_portals++;
@ -231,7 +230,7 @@ ccl_device float3 background_portal_sample(KernelGlobals kg,
/* TODO(sergey): Some smarter way of finding portal to sample
* is welcome.
*/
for (int p = 0; p < kernel_data.background.num_portals; p++) {
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
/* Search for the sampled portal. */
float3 lightpos, dir;
if (!background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir))
@ -239,23 +238,21 @@ ccl_device float3 background_portal_sample(KernelGlobals kg,
if (portal == 0) {
/* p is the portal to be sampled. */
int portal = kernel_data.background.portal_offset + p;
int portal = kernel_data.integrator.portal_offset + p;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
float3 extentu = klight->area.extentu;
float3 extentv = klight->area.extentv;
bool is_round = (klight->area.invarea < 0.0f);
float3 D;
if (is_round) {
lightpos += ellipse_sample(axisu * 0.5f, axisv * 0.5f, randu, randv);
lightpos += ellipse_sample(extentu * 0.5f, extentv * 0.5f, randu, randv);
float t;
D = normalize_len(lightpos - P, &t);
*pdf = fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
*pdf = fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t);
}
else {
*pdf = rect_light_sample(P, &lightpos, axisu, axisv, randu, randv, true);
*pdf = area_light_rect_sample(P, &lightpos, extentu, extentv, randu, randv, true);
D = normalize(lightpos - P);
}
@ -414,7 +411,7 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
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 kernel_data.integrator.pdf_lights / M_4PI_F;
return 1.0f / M_4PI_F;
}
pdf_fac = 1.0f / pdf_fac;
@ -430,7 +427,6 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
pdf += background_map_pdf(kg, direction) * map_method_pdf;
}
return pdf * kernel_data.integrator.pdf_lights;
return pdf;
}
CCL_NAMESPACE_END

View File

@ -7,92 +7,26 @@
CCL_NAMESPACE_BEGIN
/* Area light sampling */
/* Light Sample Result */
/* Uses the following paper:
*
* Carlos Urena et al.
* An Area-Preserving Parametrization for Spherical Rectangles.
*
* https://www.solidangle.com/research/egsr2013_spherical_rectangle.pdf
*
* NOTE: light_p is modified when sample_coord is true.
*/
ccl_device_inline float rect_light_sample(float3 P,
ccl_private float3 *light_p,
float3 axisu,
float3 axisv,
float randu,
float randv,
bool sample_coord)
{
/* In our name system we're using P for the center,
* which is o in the paper.
*/
typedef struct LightSample {
float3 P; /* position on light, or direction for distant light */
float3 Ng; /* normal on light */
float3 D; /* direction from shading point to light */
float t; /* distance to light (FLT_MAX for distant light) */
float u, v; /* parametric coordinate on primitive */
float pdf; /* pdf for selecting light and point on light */
float pdf_selection; /* pdf for selecting light */
float eval_fac; /* intensity multiplier */
int object; /* object id for triangle/curve lights */
int prim; /* primitive id for triangle/curve lights */
int shader; /* shader id */
int lamp; /* lamp id */
int group; /* lightgroup */
LightType type; /* type of light */
} LightSample;
float3 corner = *light_p - axisu * 0.5f - axisv * 0.5f;
float axisu_len, axisv_len;
/* Compute local reference system R. */
float3 x = normalize_len(axisu, &axisu_len);
float3 y = normalize_len(axisv, &axisv_len);
float3 z = cross(x, y);
/* Compute rectangle coords in local reference system. */
float3 dir = corner - P;
float z0 = dot(dir, z);
/* Flip 'z' to make it point against Q. */
if (z0 > 0.0f) {
z *= -1.0f;
z0 *= -1.0f;
}
float x0 = dot(dir, x);
float y0 = dot(dir, y);
float x1 = x0 + axisu_len;
float y1 = y0 + axisv_len;
/* Compute internal angles (gamma_i). */
float4 diff = make_float4(x0, y1, x1, y0) - make_float4(x1, y0, x0, y1);
float4 nz = make_float4(y0, x1, y1, x0) * diff;
nz = nz / sqrt(z0 * z0 * diff * diff + nz * nz);
float g0 = safe_acosf(-nz.x * nz.y);
float g1 = safe_acosf(-nz.y * nz.z);
float g2 = safe_acosf(-nz.z * nz.w);
float g3 = safe_acosf(-nz.w * nz.x);
/* Compute predefined constants. */
float b0 = nz.x;
float b1 = nz.z;
float b0sq = b0 * b0;
float k = M_2PI_F - g2 - g3;
/* Compute solid angle from internal angles. */
float S = g0 + g1 - k;
if (sample_coord) {
/* Compute cu. */
float au = randu * S + k;
float fu = (cosf(au) * b0 - b1) / sinf(au);
float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
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. */
float z0sq = z0 * z0;
float y0sq = y0 * y0;
float y1sq = y1 * y1;
float d = sqrtf(xu * xu + z0sq);
float h0 = y0 / sqrtf(d * d + y0sq);
float h1 = y1 / sqrtf(d * d + y1sq);
float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
/* Transform (xu, yv, z0) to world coords. */
*light_p = P + xu * x + yv * y + z0 * z;
}
/* return pdf */
if (S != 0.0f)
return 1.0f / S;
else
return 0.0f;
}
/* Utilities */
ccl_device_inline float3 ellipse_sample(float3 ru, float3 rv, float randu, float randv)
{
@ -109,99 +43,7 @@ ccl_device float3 disk_light_sample(float3 v, float randu, float randv)
return ellipse_sample(ru, rv, randu, randv);
}
ccl_device float3 distant_light_sample(float3 D, float radius, float randu, float randv)
{
return normalize(D + disk_light_sample(D, randu, randv) * radius);
}
ccl_device float3
sphere_light_sample(float3 P, float3 center, float radius, float randu, float randv)
{
return disk_light_sample(normalize(P - center), randu, randv) * radius;
}
ccl_device float spot_light_attenuation(float3 dir, float spot_angle, float spot_smooth, float3 N)
{
float attenuation = dot(dir, N);
if (attenuation <= spot_angle) {
attenuation = 0.0f;
}
else {
float t = attenuation - spot_angle;
if (t < spot_smooth && spot_smooth != 0.0f)
attenuation *= smoothstepf(t / spot_smooth);
}
return attenuation;
}
ccl_device float light_spread_attenuation(const float3 D,
const float3 lightNg,
const float tan_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 cos_a = -dot(D, lightNg);
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
const float tan_a = sin_a / cos_a;
return max((1.0f - (tan_spread * tan_a)) * normalize_spread, 0.0f);
}
/* Compute subset of area light that actually has an influence on the shading point, to
* reduce noise with low spread. */
ccl_device bool light_spread_clamp_area_light(const float3 P,
const float3 lightNg,
ccl_private float3 *lightP,
ccl_private float3 *axisu,
ccl_private float3 *axisv,
const float tan_spread)
{
/* Closest point in area light plane and distance to that plane. */
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
const float t = len(closest_P - P);
/* Radius of circle on area light that actually affects the shading point. */
const float radius = t / tan_spread;
/* TODO: would be faster to store as normalized vector + length, also in rect_light_sample. */
float len_u, len_v;
const float3 u = normalize_len(*axisu, &len_u);
const float3 v = normalize_len(*axisv, &len_v);
/* Local uv coordinates of closest point. */
const float closest_u = dot(u, closest_P - *lightP);
const float closest_v = dot(v, closest_P - *lightP);
/* Compute rectangle encompassing the circle that affects the shading point,
* clamped to the bounds of the area light. */
const float min_u = max(closest_u - radius, -len_u * 0.5f);
const float max_u = min(closest_u + radius, len_u * 0.5f);
const float min_v = max(closest_v - radius, -len_v * 0.5f);
const float max_v = min(closest_v + radius, len_v * 0.5f);
/* Skip if rectangle is empty. */
if (min_u >= max_u || min_v >= max_v) {
return false;
}
/* 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);
const float new_len_u = max_u - min_u;
const float new_len_v = max_v - min_v;
*lightP = *lightP + new_center_u * u + new_center_v * v;
*axisu = u * new_len_u;
*axisv = v * new_len_v;
return true;
}
ccl_device float lamp_light_pdf(KernelGlobals kg, const float3 Ng, const float3 I, float t)
ccl_device float lamp_light_pdf(const float3 Ng, const float3 I, float t)
{
float cos_pi = dot(Ng, I);

View File

@ -0,0 +1,110 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/geom/geom.h"
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline bool distant_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
ccl_private LightSample *ls)
{
/* distant light */
float3 lightD = klight->co;
float3 D = lightD;
float radius = klight->distant.radius;
float invarea = klight->distant.invarea;
if (radius > 0.0f) {
D = normalize(D + disk_light_sample(D, randu, randv) * radius);
}
ls->P = D;
ls->Ng = D;
ls->D = -D;
ls->t = FLT_MAX;
float costheta = dot(lightD, D);
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
return true;
}
ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
const float3 ray_D,
const int lamp,
ccl_private LightSample *ccl_restrict ls)
{
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, lamp);
const int shader = klight->shader_id;
const float radius = klight->distant.radius;
const LightType type = (LightType)klight->type;
if (type != LIGHT_DISTANT) {
return false;
}
if (!(shader & SHADER_USE_MIS)) {
return false;
}
if (radius == 0.0f) {
return false;
}
/* a distant light is infinitely far away, but equivalent to a disk
* shaped light exactly 1 unit away from the current shading point.
*
* radius t^2/cos(theta)
* <----------> t = sqrt(1^2 + tan(theta)^2)
* tan(th) area = radius*radius*pi
* <----->
* \ | (1 + tan(theta)^2)/cos(theta)
* \ | (1 + tan(acos(cos(theta)))^2)/cos(theta)
* t \th| 1 simplifies to
* \-| 1/(cos(theta)^3)
* \| magic!
* P
*/
float3 lightD = klight->co;
float costheta = dot(-lightD, ray_D);
float cosangle = klight->distant.cosangle;
/* Workaround to prevent a hang in the classroom scene with AMD HIP drivers 22.10,
* Remove when a compiler fix is available. */
#ifdef __HIP__
ls->shader = klight->shader_id;
#endif
if (costheta < cosangle)
return false;
ls->type = type;
#ifndef __HIP__
ls->shader = klight->shader_id;
#endif
ls->object = PRIM_NONE;
ls->prim = PRIM_NONE;
ls->lamp = lamp;
/* todo: missing texture coordinates */
ls->u = 0.0f;
ls->v = 0.0f;
ls->t = FLT_MAX;
ls->P = -ray_D;
ls->Ng = -ray_D;
ls->D = ray_D;
ls->group = lamp_lightgroup(kg, lamp);
/* compute pdf */
float invarea = klight->distant.invarea;
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
return true;
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,103 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/light.h"
#include "kernel/light/triangle.h"
CCL_NAMESPACE_BEGIN
/* Simple CDF based sampling over all lights in the scene, without taking into
* account shading position or normal. */
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *randu)
{
/* 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;
float r = *randu;
do {
int half_len = len >> 1;
int middle = first + half_len;
if (r < kernel_data_fetch(light_distribution, middle).totarea) {
len = half_len;
}
else {
first = middle + 1;
len = len - half_len - 1;
}
} while (len > 0);
/* Clamping should not be needed but float rounding errors seem to
* make this fail on rare occasions. */
int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
/* Rescale to reuse random number. this helps the 2D samples within
* each area light be stratified as well. */
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
*randu = (r - distr_min) / (distr_max - distr_min);
return index;
}
template<bool in_volume_segment>
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
/* Sample light index from distribution. */
const int index = light_distribution_sample(kg, &randu);
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
index);
const int prim = kdistribution->prim;
if (prim >= 0) {
/* Mesh light. */
const int object = kdistribution->mesh_light.object_id;
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
const int shader_flag = kdistribution->mesh_light.shader_flag;
triangle_light_sample<in_volume_segment>(kg, prim, object, randu, randv, time, ls, P);
ls->shader |= shader_flag;
return (ls->pdf > 0.0f);
}
const int lamp = -prim - 1;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
if (!light_sample<in_volume_segment>(kg, lamp, randu, randv, P, path_flag, ls)) {
return false;
}
ls->pdf_selection = kernel_data.integrator.distribution_pdf_lights;
ls->pdf *= ls->pdf_selection;
return true;
}
ccl_device_inline float light_distribution_pdf_lamp(KernelGlobals kg)
{
return kernel_data.integrator.distribution_pdf_lights;
}
CCL_NAMESPACE_END

View File

@ -3,31 +3,18 @@
#pragma once
#include "kernel/geom/geom.h"
#include "kernel/light/area.h"
#include "kernel/light/background.h"
#include "kernel/light/distant.h"
#include "kernel/light/point.h"
#include "kernel/light/spot.h"
#include "kernel/light/triangle.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
/* Light Sample result */
typedef struct LightSample {
float3 P; /* position on light, or direction for distant light */
float3 Ng; /* normal on light */
float3 D; /* direction from shading point to light */
float t; /* distance to light (FLT_MAX for distant light) */
float u, v; /* parametric coordinate on primitive */
float pdf; /* light sampling probability density function */
float eval_fac; /* intensity multiplier */
int object; /* object id for triangle/curve lights */
int prim; /* primitive id for triangle/curve lights */
int shader; /* shader id */
int lamp; /* lamp id */
int group; /* lightgroup */
LightType type; /* type of light */
} LightSample;
/* Regular Light */
/* Sample point on an individual light. */
template<bool in_volume_segment>
ccl_device_inline bool light_sample(KernelGlobals kg,
@ -63,28 +50,15 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
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_DISTANT) {
/* distant light */
float3 lightD = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float3 D = lightD;
float radius = klight->distant.radius;
float invarea = klight->distant.invarea;
if (radius > 0.0f)
D = distant_light_sample(D, radius, randu, randv);
ls->P = D;
ls->Ng = D;
ls->D = -D;
ls->t = FLT_MAX;
float costheta = dot(lightD, D);
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
if (!distant_light_sample(klight, randu, randv, ls)) {
return false;
}
}
else if (type == LIGHT_BACKGROUND) {
/* infinite area light (e.g. light dome or env light) */
@ -96,139 +70,28 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
ls->t = FLT_MAX;
ls->eval_fac = 1.0f;
}
else if (type == LIGHT_SPOT) {
if (!spot_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
return false;
}
}
else if (type == LIGHT_POINT) {
if (!point_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
return false;
}
}
else {
ls->P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
if (type == LIGHT_SPOT) {
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float radius = klight->spot.radius;
const float3 dir = make_float3(
klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f)
/* disk light */
ls->P += disk_light_sample(lightN, randu, randv) * radius;
const float invarea = klight->spot.invarea;
ls->pdf = invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
dir, klight->spot.spot_angle, klight->spot.spot_smooth, -ls->D);
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
}
else if (type == LIGHT_POINT) {
float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f) {
ls->P += disk_light_sample(lightN, randu, randv) * radius;
}
ls->pdf = klight->spot.invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
}
else {
/* area light */
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
float invarea = fabsf(klight->area.invarea);
bool is_round = (klight->area.invarea < 0.0f);
if (!in_volume_segment) {
if (dot(ls->P - P, Ng) > 0.0f) {
return false;
}
}
float3 inplane;
if (is_round || in_volume_segment) {
inplane = ellipse_sample(axisu * 0.5f, axisv * 0.5f, randu, randv);
ls->P += inplane;
ls->pdf = invarea;
}
else {
inplane = ls->P;
float3 sample_axisu = axisu;
float3 sample_axisv = axisv;
if (!in_volume_segment && klight->area.tan_spread > 0.0f) {
if (!light_spread_clamp_area_light(
P, Ng, &ls->P, &sample_axisu, &sample_axisv, klight->area.tan_spread)) {
return false;
}
}
ls->pdf = rect_light_sample(P, &ls->P, sample_axisu, sample_axisv, randu, randv, true);
inplane = ls->P - inplane;
}
const float light_u = dot(inplane, axisu) * (1.0f / dot(axisu, axisu));
const float light_v = dot(inplane, axisv) * (1.0f / dot(axisv, axisv));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = light_v + 0.5f;
ls->v = -light_u - light_v;
ls->Ng = Ng;
ls->D = normalize_len(ls->P - P, &ls->t);
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
}
if (is_round) {
ls->pdf *= lamp_light_pdf(kg, Ng, -ls->D, ls->t);
}
/* area light */
if (!area_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
return false;
}
}
ls->pdf *= kernel_data.integrator.pdf_lights;
return in_volume_segment || (ls->pdf > 0.0f);
}
/* Intersect ray with individual light. */
ccl_device bool lights_intersect(KernelGlobals kg,
IntegratorState state,
ccl_private const Ray *ccl_restrict ray,
@ -238,7 +101,7 @@ ccl_device bool lights_intersect(KernelGlobals kg,
const int last_type,
const uint32_t path_flag)
{
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
if (path_flag & PATH_RAY_CAMERA) {
@ -271,76 +134,17 @@ ccl_device bool lights_intersect(KernelGlobals kg,
float t = 0.0f, u = 0.0f, v = 0.0f;
if (type == LIGHT_SPOT) {
/* Spot/Disk light. */
const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float radius = klight->spot.radius;
if (radius == 0.0f) {
continue;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
/* One sided. */
if (dot(ray->D, lightN) >= 0.0f) {
continue;
}
float3 P;
if (!ray_disk_intersect(
ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) {
if (!spot_light_intersect(klight, ray, &t)) {
continue;
}
}
else if (type == LIGHT_POINT) {
/* Sphere light (aka, aligned disk light). */
const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float radius = klight->spot.radius;
if (radius == 0.0f) {
continue;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
float3 P;
if (!ray_disk_intersect(
ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) {
if (!point_light_intersect(klight, ray, &t)) {
continue;
}
}
else if (type == LIGHT_AREA) {
/* Area light. */
const float invarea = fabsf(klight->area.invarea);
const bool is_round = (klight->area.invarea < 0.0f);
if (invarea == 0.0f) {
continue;
}
const float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
const float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
const float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
/* One sided. */
if (dot(ray->D, Ng) >= 0.0f) {
continue;
}
const float3 light_P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float3 P;
if (!ray_quad_intersect(ray->P,
ray->D,
ray->tmin,
ray->tmax,
light_P,
axisu,
axisv,
Ng,
&P,
&t,
&u,
&v,
is_round)) {
if (!area_light_intersect(klight, ray, &t, &u, &v)) {
continue;
}
}
@ -362,78 +166,7 @@ ccl_device bool lights_intersect(KernelGlobals kg,
return isect->prim != PRIM_NONE;
}
ccl_device bool light_sample_from_distant_ray(KernelGlobals kg,
const float3 ray_D,
const int lamp,
ccl_private LightSample *ccl_restrict ls)
{
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, lamp);
const int shader = klight->shader_id;
const float radius = klight->distant.radius;
const LightType type = (LightType)klight->type;
if (type != LIGHT_DISTANT) {
return false;
}
if (!(shader & SHADER_USE_MIS)) {
return false;
}
if (radius == 0.0f) {
return false;
}
/* a distant light is infinitely far away, but equivalent to a disk
* shaped light exactly 1 unit away from the current shading point.
*
* radius t^2/cos(theta)
* <----------> t = sqrt(1^2 + tan(theta)^2)
* tan(th) area = radius*radius*pi
* <----->
* \ | (1 + tan(theta)^2)/cos(theta)
* \ | (1 + tan(acos(cos(theta)))^2)/cos(theta)
* t \th| 1 simplifies to
* \-| 1/(cos(theta)^3)
* \| magic!
* P
*/
float3 lightD = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float costheta = dot(-lightD, ray_D);
float cosangle = klight->distant.cosangle;
/* Workaround to prevent a hang in the classroom scene with AMD HIP drivers 22.10,
* Remove when a compiler fix is available. */
#ifdef __HIP__
ls->shader = klight->shader_id;
#endif
if (costheta < cosangle)
return false;
ls->type = type;
#ifndef __HIP__
ls->shader = klight->shader_id;
#endif
ls->object = PRIM_NONE;
ls->prim = PRIM_NONE;
ls->lamp = lamp;
/* todo: missing texture coordinates */
ls->u = 0.0f;
ls->v = 0.0f;
ls->t = FLT_MAX;
ls->P = -ray_D;
ls->Ng = -ray_D;
ls->D = ray_D;
ls->group = lamp_lightgroup(kg, lamp);
/* compute pdf */
float invarea = klight->distant.invarea;
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
ls->pdf *= kernel_data.integrator.pdf_lights;
return true;
}
/* Setup light sample from intersection. */
ccl_device bool light_sample_from_intersection(KernelGlobals kg,
ccl_private const Intersection *ccl_restrict isect,
@ -456,102 +189,18 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
ls->group = lamp_lightgroup(kg, lamp);
if (type == LIGHT_SPOT) {
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float3 dir = make_float3(klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
/* the normal of the oriented disk */
const float3 lightN = normalize(ray_P - center);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
dir, klight->spot.spot_angle, klight->spot.spot_smooth, -ls->D);
if (ls->eval_fac == 0.0f) {
if (!spot_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX)
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
else
ls->pdf = 0.f;
}
else if (type == LIGHT_POINT) {
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float3 lighN = normalize(ray_P - center);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
if (ls->eval_fac == 0.0f) {
if (!point_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX)
ls->pdf *= lamp_light_pdf(kg, lighN, -ls->D, ls->t);
else
ls->pdf = 0.f;
}
else if (type == LIGHT_AREA) {
/* area light */
float invarea = fabsf(klight->area.invarea);
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
float3 light_P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
ls->u = isect->u;
ls->v = isect->v;
ls->D = ray_D;
ls->Ng = Ng;
const bool is_round = (klight->area.invarea < 0.0f);
if (is_round) {
ls->pdf = invarea * lamp_light_pdf(kg, Ng, -ray_D, ls->t);
}
else {
float3 sample_axisu = axisu;
float3 sample_axisv = axisv;
if (klight->area.tan_spread > 0.0f) {
if (!light_spread_clamp_area_light(
ray_P, Ng, &light_P, &sample_axisu, &sample_axisv, klight->area.tan_spread)) {
return false;
}
}
ls->pdf = rect_light_sample(ray_P, &light_P, sample_axisu, sample_axisv, 0, 0, false);
}
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
if (ls->eval_fac == 0.0f) {
return false;
}
if (!area_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
return false;
}
}
else {
@ -559,411 +208,33 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
return false;
}
ls->pdf *= kernel_data.integrator.pdf_lights;
return true;
}
/* Triangle Light */
/* Update light sample for changed new position, for MNEE. */
/* returns true if the triangle is has motion blur or an instancing transform applied */
ccl_device_inline bool triangle_world_space_vertices(
KernelGlobals kg, int object, int prim, float time, float3 V[3])
{
bool has_motion = false;
const int 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, prim, V);
}
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
#ifdef __OBJECT_MOTION__
float object_time = (time >= 0.0f) ? time : 0.5f;
Transform tfm = object_fetch_transform_motion_test(kg, object, object_time, NULL);
#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(KernelGlobals kg,
const float3 Ng,
const float3 I,
float t)
{
float pdf = kernel_data.integrator.pdf_triangles;
float cos_pi = fabsf(dot(Ng, I));
if (cos_pi == 0.0f)
return 0.0f;
return t * t * pdf / cos_pi;
}
ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
ccl_private const ShaderData *sd,
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];
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->I * t)) / dot(N, N);
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->I * t;
const float3 v0_p = V[0] - Px;
const float3 v1_p = V[1] - Px;
const float3 v2_p = V[2] - Px;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float alpha = fast_acosf(dot(u02, u01));
const float beta = fast_acosf(-dot(u01, u12));
const float gamma = fast_acosf(dot(u02, u12));
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* pdf_triangles is calculated over triangle area, but we're not sampling over its area */
if (UNLIKELY(solid_angle == 0.0f)) {
return 0.0f;
}
else {
float area = 1.0f;
if (has_motion) {
/* get the center frame vertices, this is what the PDF was calculated from */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
area = triangle_area(V[0], V[1], V[2]);
}
else {
area = 0.5f * len(N);
}
const float pdf = area * kernel_data.integrator.pdf_triangles;
return pdf / solid_angle;
}
}
else {
float pdf = triangle_light_pdf_area(kg, sd->Ng, sd->I, t);
if (has_motion) {
const float area = 0.5f * len(N);
if (UNLIKELY(area == 0.0f)) {
return 0.0f;
}
/* scale the PDF.
* area = the area the sample was taken from
* area_pre = the are from which pdf_triangles was calculated from */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
const float area_pre = triangle_area(V[0], V[1], V[2]);
pdf = pdf * area_pre / area;
}
return pdf;
}
}
template<bool in_volume_segment>
ccl_device_forceinline void triangle_light_sample(KernelGlobals kg,
int prim,
int object,
float randu,
float randv,
float time,
ccl_device_forceinline void light_update_position(KernelGlobals kg,
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. */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
float3 V[3];
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)));
const float3 N0 = cross(e0, e1);
float Nl = 0.0f;
ls->Ng = safe_normalize_len(N0, &Nl);
float area = 0.5f * Nl;
/* flip normal if necessary */
const int object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_NEGATIVE_SCALE_APPLIED) {
ls->Ng = -ls->Ng;
if (ls->type == LIGHT_POINT) {
point_light_update_position(klight, ls, P);
}
ls->eval_fac = 1.0f;
ls->shader = kernel_data_fetch(tri_shader, prim);
ls->object = object;
ls->prim = prim;
ls->lamp = LAMP_NONE;
ls->shader |= SHADER_USE_MIS;
ls->type = LIGHT_TRIANGLE;
ls->group = object_lightgroup(kg, object);
float distance_to_plane = fabsf(dot(N0, V[0] - P) / dot(N0, N0));
if (!in_volume_segment && (longest_edge_squared > distance_to_plane * distance_to_plane)) {
/* see 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 its edges and angles */
const float3 v0_p = V[0] - P;
const float3 v1_p = V[1] - P;
const float3 v2_p = V[2] - P;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float3 A = safe_normalize(v0_p);
const float3 B = safe_normalize(v1_p);
const float3 C = safe_normalize(v2_p);
const float cos_alpha = dot(u02, u01);
const float cos_beta = -dot(u01, u12);
const float cos_gamma = dot(u02, u12);
/* calculate dihedral angles */
const float alpha = fast_acosf(cos_alpha);
const float beta = fast_acosf(cos_beta);
const float gamma = fast_acosf(cos_gamma);
/* the area of the unit spherical triangle = solid angle */
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* precompute a few things
* these could be re-used to take several samples
* as they are independent of randu/randv */
const float cos_c = dot(A, B);
const float sin_alpha = fast_sinf(alpha);
const float product = sin_alpha * cos_c;
/* Select a random sub-area of the spherical triangle
* and calculate the third vertex C_ of that new triangle */
const float phi = randu * solid_angle - alpha;
float s, t;
fast_sincosf(phi, &s, &t);
const float u = t - cos_alpha;
const float v = s + product;
const float3 U = safe_normalize(C - dot(C, A) * A);
float q = 1.0f;
const float det = ((v * s + u * t) * sin_alpha);
if (det != 0.0f) {
q = ((v * t - u * s) * cos_alpha - v) / det;
}
const float temp = max(1.0f - q * q, 0.0f);
const float3 C_ = safe_normalize(q * A + sqrtf(temp) * 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 - randv * (1.0f - dot(C_, B));
ls->D = z * B + safe_sqrtf(1.0f - z * z) * safe_normalize(C_ - dot(C_, B) * B);
/* calculate intersection with the planar triangle */
if (!ray_triangle_intersect(
P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->v, &ls->t)) {
ls->pdf = 0.0f;
return;
}
ls->P = P + ls->D * ls->t;
/* 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;
}
else {
if (has_motion) {
/* get the center frame vertices, this is what the PDF was calculated from */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
area = triangle_area(V[0], V[1], V[2]);
}
const float pdf = area * kernel_data.integrator.pdf_triangles;
ls->pdf = pdf / solid_angle;
}
else if (ls->type == LIGHT_SPOT) {
spot_light_update_position(klight, ls, P);
}
else {
/* compute random point in triangle. From Eric Heitz's "A Low-Distortion Map Between Triangle
* and Square" */
float u = randu;
float v = randv;
if (v > u) {
u *= 0.5f;
v -= u;
}
else {
v *= 0.5f;
u -= v;
}
const float t = 1.0f - u - v;
ls->P = u * V[0] + v * V[1] + t * V[2];
/* compute incoming direction, distance and pdf */
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = triangle_light_pdf_area(kg, ls->Ng, -ls->D, ls->t);
if (has_motion && area != 0.0f) {
/* scale the PDF.
* area = the area the sample was taken from
* area_pre = the are from which pdf_triangles was calculated from */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
const float area_pre = triangle_area(V[0], V[1], V[2]);
ls->pdf = ls->pdf * area_pre / area;
}
ls->u = u;
ls->v = v;
else if (ls->type == LIGHT_AREA) {
area_light_update_position(klight, ls, P);
}
}
/* Light Distribution */
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *randu)
{
/* 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;
float r = *randu;
do {
int half_len = len >> 1;
int middle = first + half_len;
if (r < kernel_data_fetch(light_distribution, middle).totarea) {
len = half_len;
}
else {
first = middle + 1;
len = len - half_len - 1;
}
} while (len > 0);
/* Clamping should not be needed but float rounding errors seem to
* make this fail on rare occasions. */
int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
/* Rescale to reuse random number. this helps the 2D samples within
* each area light be stratified as well. */
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
*randu = (r - distr_min) / (distr_max - distr_min);
return index;
}
/* Generic Light */
/* Light info. */
ccl_device_inline bool light_select_reached_max_bounces(KernelGlobals kg, int index, int bounce)
{
return (bounce > kernel_data_fetch(lights, index).max_bounces);
}
template<bool in_volume_segment>
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
/* Sample light index from distribution. */
const int index = light_distribution_sample(kg, &randu);
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
index);
const int prim = kdistribution->prim;
if (prim >= 0) {
/* Mesh light. */
const int object = kdistribution->mesh_light.object_id;
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
const int shader_flag = kdistribution->mesh_light.shader_flag;
triangle_light_sample<in_volume_segment>(kg, prim, object, randu, randv, time, ls, P);
ls->shader |= shader_flag;
return (ls->pdf > 0.0f);
}
const int lamp = -prim - 1;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
return light_sample<in_volume_segment>(kg, lamp, randu, randv, P, path_flag, ls);
}
ccl_device_inline bool light_distribution_sample_from_volume_segment(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<true>(kg, randu, randv, time, P, bounce, path_flag, ls);
}
ccl_device_inline bool light_distribution_sample_from_position(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<false>(kg, randu, randv, time, P, bounce, path_flag, ls);
}
ccl_device_inline bool light_distribution_sample_new_position(KernelGlobals kg,
const float randu,
const float randv,
const float time,
const float3 P,
ccl_private LightSample *ls)
{
/* Sample a new position on the same light, for volume sampling. */
if (ls->type == LIGHT_TRIANGLE) {
triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P);
return (ls->pdf > 0.0f);
}
else {
return light_sample<false>(kg, ls->lamp, randu, randv, P, 0, ls);
}
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,111 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
template<bool in_volume_segment>
ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
{
float3 center = klight->co;
float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f) {
ls->P += disk_light_sample(lightN, randu, randv) * radius;
}
ls->pdf = klight->spot.invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
return true;
}
ccl_device_forceinline void point_light_update_position(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
{
ls->D = normalize_len(ls->P - P, &ls->t);
ls->Ng = -ls->D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
}
ccl_device_inline bool point_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t)
{
/* Sphere light (aka, aligned disk light). */
const float3 lightP = klight->co;
const float radius = klight->spot.radius;
if (radius == 0.0f) {
return false;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
float3 P;
return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t);
}
ccl_device_inline bool point_light_sample_from_intersection(
const ccl_global KernelLight *klight,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
ccl_private LightSample *ccl_restrict ls)
{
const float3 lighN = normalize(ray_P - klight->co);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
if (ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX) {
ls->pdf *= lamp_light_pdf(lighN, -ls->D, ls->t);
}
else {
ls->pdf = 0.f;
}
return true;
}
CCL_NAMESPACE_END

View File

@ -6,6 +6,7 @@
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/surface_shader.h"
#include "kernel/light/distribution.h"
#include "kernel/light/light.h"
#include "kernel/sample/mapping.h"
@ -277,6 +278,8 @@ ccl_device_inline void light_sample_to_volume_shadow_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)
@ -309,4 +312,138 @@ ccl_device_inline float light_sample_mis_weight_nee(KernelGlobals kg,
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. */
ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const float3 D,
const float t,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<true>(kg, randu, randv, time, P, bounce, path_flag, ls);
}
ccl_device bool light_sample_from_position(KernelGlobals kg,
ccl_private const RNGState *rng_state,
const float randu,
const float randv,
const float time,
const float3 P,
const float3 N,
const int shader_flags,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<false>(kg, randu, randv, time, P, bounce, path_flag, ls);
}
ccl_device_inline bool light_sample_new_position(KernelGlobals kg,
const float randu,
const float randv,
const float time,
const float3 P,
ccl_private LightSample *ls)
{
/* Sample a new position on the same light, for volume sampling. */
if (ls->type == LIGHT_TRIANGLE) {
if (!triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P)) {
return false;
}
return true;
}
else {
if (!light_sample<false>(kg, ls->lamp, randu, randv, P, 0, ls)) {
return false;
}
ls->pdf *= ls->pdf_selection;
return true;
}
}
ccl_device_forceinline void light_sample_update_position(KernelGlobals kg,
ccl_private LightSample *ls,
const float3 P)
{
/* Update light sample for new shading point position, while keeping
* position on the light fixed. */
/* NOTE : preserve pdf in area measure. */
light_update_position(kg, ls, P);
/* 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 uint32_t path_flag,
const ccl_private ShaderData *sd)
{
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. */
/* Handled in triangle_light_pdf for effeciency. */
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 uint32_t path_flag,
const ccl_private LightSample *ls,
const float3 P)
{
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
float pdf = ls->pdf;
/* Light selection pdf. */
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 uint32_t path_flag,
const ccl_private LightSample *ls)
{
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
return light_sample_mis_weight_forward_lamp(kg, state, path_flag, ls, ray_P);
}
ccl_device_inline float light_sample_mis_weight_forward_background(KernelGlobals kg,
IntegratorState state,
const uint32_t path_flag)
{
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. */
pdf *= light_distribution_pdf_lamp(kg);
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,153 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
ccl_device float spot_light_attenuation(float3 dir,
float cos_half_spot_angle,
float spot_smooth,
float3 N)
{
float attenuation = dot(dir, N);
if (attenuation <= cos_half_spot_angle) {
attenuation = 0.0f;
}
else {
float t = attenuation - cos_half_spot_angle;
if (t < spot_smooth && spot_smooth != 0.0f)
attenuation *= smoothstepf(t / spot_smooth);
}
return attenuation;
}
template<bool in_volume_segment>
ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
{
ls->P = klight->co;
const float3 center = klight->co;
const float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f) {
/* disk light */
ls->P += disk_light_sample(lightN, randu, randv) * radius;
}
const float invarea = klight->spot.invarea;
ls->pdf = invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, -ls->D);
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
return true;
}
ccl_device_forceinline void spot_light_update_position(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
{
ls->D = normalize_len(ls->P - P, &ls->t);
ls->Ng = -ls->D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, ls->Ng);
}
ccl_device_inline bool spot_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t)
{
/* Spot/Disk light. */
const float3 lightP = klight->co;
const float radius = klight->spot.radius;
if (radius == 0.0f) {
return false;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
/* One sided. */
if (dot(ray->D, lightN) >= 0.0f) {
return false;
}
float3 P;
return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t);
}
ccl_device_inline bool spot_light_sample_from_intersection(
const ccl_global KernelLight *klight,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
ccl_private LightSample *ccl_restrict ls)
{
/* the normal of the oriented disk */
const float3 lightN = normalize(ray_P - klight->co);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, -ls->D);
if (ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX) {
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
}
else {
ls->pdf = 0.f;
}
return true;
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,281 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/geom/geom.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, int object, int prim, float time, float3 V[3])
{
bool has_motion = false;
const int 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, prim, V);
}
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
#ifdef __OBJECT_MOTION__
float object_time = (time >= 0.0f) ? time : 0.5f;
Transform tfm = object_fetch_transform_motion_test(kg, object, object_time, NULL);
#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, float t)
{
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,
ccl_private const ShaderData *sd,
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];
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->I * 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->I * t;
const float3 v0_p = V[0] - Px;
const float3 v1_p = V[1] - Px;
const float3 v2_p = V[2] - Px;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float alpha = fast_acosf(dot(u02, u01));
const float beta = fast_acosf(-dot(u01, u12));
const float gamma = fast_acosf(dot(u02, u12));
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* 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;
}
else {
pdf = 1.0f / solid_angle;
}
}
else {
if (UNLIKELY(area == 0.0f)) {
return 0.0f;
}
pdf = triangle_light_pdf_area_sampling(sd->Ng, sd->I, t) / area;
}
/* Belongs in distribution.h but can reuse computations here. */
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,
int prim,
int object,
float randu,
float randv,
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];
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)));
const float3 N0 = cross(e0, e1);
float Nl = 0.0f;
ls->Ng = safe_normalize_len(N0, &Nl);
const float area = 0.5f * Nl;
/* flip normal if necessary */
const int object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_NEGATIVE_SCALE_APPLIED) {
ls->Ng = -ls->Ng;
}
ls->eval_fac = 1.0f;
ls->shader = kernel_data_fetch(tri_shader, prim);
ls->object = object;
ls->prim = prim;
ls->lamp = LAMP_NONE;
ls->shader |= SHADER_USE_MIS;
ls->type = LIGHT_TRIANGLE;
ls->group = object_lightgroup(kg, object);
float distance_to_plane = fabsf(dot(N0, V[0] - P) / dot(N0, N0));
if (!in_volume_segment && (longest_edge_squared > distance_to_plane * distance_to_plane)) {
/* see 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 its edges and angles */
const float3 v0_p = V[0] - P;
const float3 v1_p = V[1] - P;
const float3 v2_p = V[2] - P;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float3 A = safe_normalize(v0_p);
const float3 B = safe_normalize(v1_p);
const float3 C = safe_normalize(v2_p);
const float cos_alpha = dot(u02, u01);
const float cos_beta = -dot(u01, u12);
const float cos_gamma = dot(u02, u12);
/* calculate dihedral angles */
const float alpha = fast_acosf(cos_alpha);
const float beta = fast_acosf(cos_beta);
const float gamma = fast_acosf(cos_gamma);
/* the area of the unit spherical triangle = solid angle */
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* precompute a few things
* these could be re-used to take several samples
* as they are independent of randu/randv */
const float cos_c = dot(A, B);
const float sin_alpha = fast_sinf(alpha);
const float product = sin_alpha * cos_c;
/* Select a random sub-area of the spherical triangle
* and calculate the third vertex C_ of that new triangle */
const float phi = randu * solid_angle - alpha;
float s, t;
fast_sincosf(phi, &s, &t);
const float u = t - cos_alpha;
const float v = s + product;
const float3 U = safe_normalize(C - dot(C, A) * A);
float q = 1.0f;
const float det = ((v * s + u * t) * sin_alpha);
if (det != 0.0f) {
q = ((v * t - u * s) * cos_alpha - v) / det;
}
const float temp = max(1.0f - q * q, 0.0f);
const float3 C_ = safe_normalize(q * A + sqrtf(temp) * 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 - randv * (1.0f - dot(C_, B));
ls->D = z * B + safe_sqrtf(1.0f - z * z) * safe_normalize(C_ - dot(C_, B) * B);
/* calculate intersection with the planar triangle */
if (!ray_triangle_intersect(
P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->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;
}
else {
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 = randu;
float v = randv;
if (v > u) {
u *= 0.5f;
v -= u;
}
else {
v *= 0.5f;
u -= v;
}
const float t = 1.0f - u - v;
ls->P = u * V[0] + v * V[1] + t * 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;
ls->u = u;
ls->v = v;
}
/* Belongs in distribution.h but can reuse computations here. */
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;
ls->pdf *= ls->pdf_selection;
return (ls->pdf > 0.0f);
}
CCL_NAMESPACE_END

View File

@ -1268,20 +1268,20 @@ static_assert_align(KernelCurveSegment, 8);
typedef struct KernelSpotLight {
float radius;
float invarea;
float spot_angle;
float cos_half_spot_angle;
float spot_smooth;
float dir[3];
packed_float3 dir;
float pad;
} KernelSpotLight;
/* PointLight is SpotLight with only radius and invarea being used. */
typedef struct KernelAreaLight {
float axisu[3];
packed_float3 extentu;
float invarea;
float axisv[3];
packed_float3 extentv;
float tan_spread;
float dir[3];
packed_float3 dir;
float normalize_spread;
} KernelAreaLight;
@ -1294,7 +1294,7 @@ typedef struct KernelDistantLight {
typedef struct KernelLight {
int type;
float co[3];
packed_float3 co;
int shader_id;
float max_bounces;
float random;

View File

@ -4,6 +4,7 @@
#include "scene/background.h"
#include "device/device.h"
#include "scene/integrator.h"
#include "scene/light.h"
#include "scene/scene.h"
#include "scene/shader.h"
#include "scene/shader_graph.h"

View File

@ -263,30 +263,20 @@ bool LightManager::object_usable_as_light(Object *object)
return false;
}
void LightManager::device_update_distribution(Device *,
void LightManager::device_update_distribution(Device *device,
DeviceScene *dscene,
Scene *scene,
Progress &progress)
{
KernelIntegrator *kintegrator = &dscene->data.integrator;
KernelFilm *kfilm = &dscene->data.film;
/* Update CDF over lights. */
progress.set_status("Updating Lights", "Computing distribution");
/* count */
size_t num_lights = 0;
size_t num_portals = 0;
size_t num_background_lights = 0;
/* Counts emissive triangles in the scene. */
size_t num_triangles = 0;
bool background_mis = false;
foreach (Light *light, scene->lights) {
if (light->is_enabled) {
num_lights++;
}
if (light->is_portal) {
num_portals++;
}
}
foreach (Object *object, scene->objects) {
if (progress.get_cancel())
return;
@ -295,9 +285,10 @@ void LightManager::device_update_distribution(Device *,
continue;
}
/* Count triangles. */
/* Count emissive triangles. */
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
size_t mesh_num_triangles = mesh->num_triangles();
for (size_t i = 0; i < mesh_num_triangles; i++) {
int shader_index = mesh->get_shader()[i];
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
@ -310,14 +301,20 @@ void LightManager::device_update_distribution(Device *,
}
}
size_t num_distribution = num_triangles + num_lights;
const size_t num_lights = kintegrator->num_lights;
const size_t num_background_lights = kintegrator->num_background_lights;
const size_t num_distribution = num_triangles + num_lights;
/* Distribution size. */
kintegrator->num_distribution = num_distribution;
VLOG_INFO << "Total " << num_distribution << " of light distribution primitives.";
/* emission area */
/* Emission area. */
KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1);
float totarea = 0.0f;
/* triangles */
/* Triangles. */
size_t offset = 0;
int j = 0;
@ -390,9 +387,9 @@ void LightManager::device_update_distribution(Device *,
j++;
}
float trianglearea = totarea;
/* point lights */
bool use_lamp_mis = false;
const float trianglearea = totarea;
/* Lights. */
int light_index = 0;
if (num_lights > 0) {
@ -407,20 +404,6 @@ void LightManager::device_update_distribution(Device *,
distribution[offset].lamp.size = light->size;
totarea += lightarea;
if (light->light_type == LIGHT_DISTANT) {
use_lamp_mis |= (light->angle > 0.0f && light->use_mis);
}
else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
use_lamp_mis |= (light->size > 0.0f && light->use_mis);
}
else if (light->light_type == LIGHT_AREA) {
use_lamp_mis |= light->use_mis;
}
else if (light->light_type == LIGHT_BACKGROUND) {
num_background_lights++;
background_mis |= light->use_mis;
}
light_index++;
offset++;
}
@ -441,82 +424,42 @@ void LightManager::device_update_distribution(Device *,
if (progress.get_cancel())
return;
/* update device */
KernelIntegrator *kintegrator = &dscene->data.integrator;
KernelBackground *kbackground = &dscene->data.background;
KernelFilm *kfilm = &dscene->data.film;
/* Update integrator state. */
kintegrator->use_direct_light = (totarea > 0.0f);
if (kintegrator->use_direct_light) {
/* number of emissives */
kintegrator->num_distribution = num_distribution;
/* precompute pdfs */
kintegrator->pdf_triangles = 0.0f;
kintegrator->pdf_lights = 0.0f;
/* sample one, with 0.5 probability of light or triangle */
kintegrator->num_all_lights = num_lights;
if (trianglearea > 0.0f) {
kintegrator->pdf_triangles = 1.0f / trianglearea;
if (num_lights)
kintegrator->pdf_triangles *= 0.5f;
}
/* Precompute pdfs for distribution sampling.
* Sample one, with 0.5 probability of light or triangle. */
kintegrator->distribution_pdf_triangles = 0.0f;
kintegrator->distribution_pdf_lights = 0.0f;
if (trianglearea > 0.0f) {
kintegrator->distribution_pdf_triangles = 1.0f / trianglearea;
if (num_lights) {
kintegrator->pdf_lights = 1.0f / num_lights;
if (trianglearea > 0.0f)
kintegrator->pdf_lights *= 0.5f;
kintegrator->distribution_pdf_triangles *= 0.5f;
}
kintegrator->use_lamp_mis = use_lamp_mis;
/* bit of an ugly hack to compensate for emitting triangles influencing
* amount of samples we get for this pass */
kfilm->pass_shadow_scale = 1.0f;
if (kintegrator->pdf_triangles != 0.0f)
kfilm->pass_shadow_scale /= 0.5f;
if (num_background_lights < num_lights)
kfilm->pass_shadow_scale /= (float)(num_lights - num_background_lights) / (float)num_lights;
/* CDF */
dscene->light_distribution.copy_to_device();
/* Portals */
if (num_portals > 0) {
kbackground->portal_offset = light_index;
kbackground->num_portals = num_portals;
kbackground->portal_weight = 1.0f;
}
else {
kbackground->num_portals = 0;
kbackground->portal_offset = 0;
kbackground->portal_weight = 0.0f;
}
/* Map */
kbackground->map_weight = background_mis ? 1.0f : 0.0f;
}
else {
dscene->light_distribution.free();
kintegrator->num_distribution = 0;
kintegrator->num_all_lights = 0;
kintegrator->pdf_triangles = 0.0f;
kintegrator->pdf_lights = 0.0f;
kintegrator->use_lamp_mis = false;
kbackground->num_portals = 0;
kbackground->portal_offset = 0;
kbackground->portal_weight = 0.0f;
kbackground->sun_weight = 0.0f;
kbackground->map_weight = 0.0f;
kfilm->pass_shadow_scale = 1.0f;
if (num_lights) {
kintegrator->distribution_pdf_lights = 1.0f / num_lights;
if (trianglearea > 0.0f) {
kintegrator->distribution_pdf_lights *= 0.5f;
}
}
/* bit of an ugly hack to compensate for emitting triangles influencing
* amount of samples we get for this pass */
kfilm->pass_shadow_scale = 1.0f;
if (kintegrator->distribution_pdf_triangles != 0.0f) {
kfilm->pass_shadow_scale /= 0.5f;
}
if (num_background_lights < num_lights) {
kfilm->pass_shadow_scale /= (float)(num_lights - num_background_lights) / (float)num_lights;
}
/* Copy distribution to device. */
dscene->light_distribution.copy_to_device();
}
static void background_cdf(
@ -564,31 +507,34 @@ void LightManager::device_update_background(Device *device,
Scene *scene,
Progress &progress)
{
KernelIntegrator *kintegrator = &dscene->data.integrator;
KernelBackground *kbackground = &dscene->data.background;
Light *background_light = NULL;
bool background_mis = false;
/* find background light */
foreach (Light *light, scene->lights) {
if (light->light_type == LIGHT_BACKGROUND) {
if (light->light_type == LIGHT_BACKGROUND && light->is_enabled) {
background_light = light;
break;
background_mis |= light->use_mis;
}
}
kbackground->portal_weight = kintegrator->num_portals > 0 ? 1.0f : 0.0f;
kbackground->map_weight = background_mis ? 1.0f : 0.0f;
kbackground->sun_weight = 0.0f;
/* no background light found, signal renderer to skip sampling */
if (!background_light || !background_light->is_enabled) {
kbackground->map_res_x = 0;
kbackground->map_res_y = 0;
kbackground->map_weight = 0.0f;
kbackground->sun_weight = 0.0f;
kbackground->use_mis = (kbackground->portal_weight > 0.0f);
return;
}
progress.set_status("Updating Lights", "Importance map");
assert(dscene->data.integrator.use_direct_light);
int2 environment_res = make_int2(0, 0);
Shader *shader = scene->background->get_shader(scene);
int num_suns = 0;
@ -632,6 +578,7 @@ void LightManager::device_update_background(Device *device,
kbackground->sun = make_float4(
sun_direction.x, sun_direction.y, sun_direction.z, half_angle);
/* empirical value */
kbackground->sun_weight = 4.0f;
environment_res.x = max(environment_res.x, 512);
environment_res.y = max(environment_res.y, 256);
@ -714,27 +661,83 @@ void LightManager::device_update_background(Device *device,
dscene->light_background_conditional_cdf.copy_to_device();
}
void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *scene)
void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *scene)
{
int num_scene_lights = scene->lights.size();
/* Counts lights in the scene. */
size_t num_lights = 0;
size_t num_portals = 0;
size_t num_background_lights = 0;
size_t num_distant_lights = 0;
bool use_light_mis = false;
int num_lights = 0;
foreach (Light *light, scene->lights) {
if (light->is_enabled || light->is_portal) {
if (light->is_enabled) {
num_lights++;
if (light->light_type == LIGHT_DISTANT) {
num_distant_lights++;
}
else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
use_light_mis |= (light->size > 0.0f && light->use_mis);
}
else if (light->light_type == LIGHT_AREA) {
use_light_mis |= light->use_mis;
}
else if (light->light_type == LIGHT_BACKGROUND) {
num_distant_lights++;
num_background_lights++;
}
}
if (light->is_portal) {
num_portals++;
}
}
KernelLight *klights = dscene->lights.alloc(num_lights);
/* Update integrator settings. */
KernelIntegrator *kintegrator = &dscene->data.integrator;
kintegrator->num_lights = num_lights;
kintegrator->num_distant_lights = num_distant_lights;
kintegrator->num_background_lights = num_background_lights;
kintegrator->use_light_mis = use_light_mis;
if (num_lights == 0) {
VLOG_WORK << "No effective light, ignoring points update.";
return;
}
kintegrator->num_portals = num_portals;
kintegrator->portal_offset = num_lights;
/* Create KernelLight for every portal and enabled light in the scene. */
KernelLight *klights = dscene->lights.alloc(num_lights + num_portals);
int light_index = 0;
int portal_index = num_lights;
foreach (Light *light, scene->lights) {
/* Consider moving portals update to their own function
* keeping this one more manageable. */
if (light->is_portal) {
assert(light->light_type == LIGHT_AREA);
float3 extentu = light->axisu * (light->sizeu * light->size);
float3 extentv = light->axisv * (light->sizev * light->size);
float area = len(extentu) * len(extentv);
if (light->round) {
area *= -M_PI_4_F;
}
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
float3 dir = light->dir;
dir = safe_normalize(dir);
klights[portal_index].co = light->co;
klights[portal_index].area.extentu = extentu;
klights[portal_index].area.extentv = extentv;
klights[portal_index].area.invarea = invarea;
klights[portal_index].area.dir = dir;
klights[portal_index].tfm = light->tfm;
klights[portal_index].itfm = transform_inverse(light->tfm);
portal_index++;
continue;
}
if (!light->is_enabled) {
continue;
}
@ -781,10 +784,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
if (light->use_mis && radius > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].co = co;
klights[light_index].spot.radius = radius;
klights[light_index].spot.invarea = invarea;
}
@ -803,10 +803,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
if (light->use_mis && area > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = dir.x;
klights[light_index].co[1] = dir.y;
klights[light_index].co[2] = dir.z;
klights[light_index].co = dir;
klights[light_index].distant.invarea = invarea;
klights[light_index].distant.radius = radius;
klights[light_index].distant.cosangle = cosangle;
@ -814,6 +811,8 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
else if (light->light_type == LIGHT_BACKGROUND) {
uint visibility = scene->background->get_visibility();
dscene->data.background.light_index = light_index;
shader_id &= ~SHADER_AREA_LIGHT;
shader_id |= SHADER_USE_MIS;
@ -831,9 +830,9 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
}
}
else if (light->light_type == LIGHT_AREA) {
float3 axisu = light->axisu * (light->sizeu * light->size);
float3 axisv = light->axisv * (light->sizev * light->size);
float area = len(axisu) * len(axisv);
float3 extentu = light->axisu * (light->sizeu * light->size);
float3 extentv = light->axisv * (light->sizev * light->size);
float area = len(extentu) * len(extentv);
if (light->round) {
area *= -M_PI_4_F;
}
@ -854,20 +853,11 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
if (light->use_mis && area != 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].area.axisu[0] = axisu.x;
klights[light_index].area.axisu[1] = axisu.y;
klights[light_index].area.axisu[2] = axisu.z;
klights[light_index].area.axisv[0] = axisv.x;
klights[light_index].area.axisv[1] = axisv.y;
klights[light_index].area.axisv[2] = axisv.z;
klights[light_index].co = co;
klights[light_index].area.extentu = extentu;
klights[light_index].area.extentv = extentv;
klights[light_index].area.invarea = invarea;
klights[light_index].area.dir[0] = dir.x;
klights[light_index].area.dir[1] = dir.y;
klights[light_index].area.dir[2] = dir.z;
klights[light_index].area.dir = dir;
klights[light_index].area.tan_spread = tan_spread;
klights[light_index].area.normalize_spread = normalize_spread;
}
@ -876,8 +866,8 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
float radius = light->size;
float invarea = (radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) : 1.0f;
float spot_angle = cosf(light->spot_angle * 0.5f);
float spot_smooth = (1.0f - spot_angle) * light->spot_smooth;
float cos_half_spot_angle = cosf(light->spot_angle * 0.5f);
float spot_smooth = (1.0f - cos_half_spot_angle) * light->spot_smooth;
float3 dir = light->dir;
dir = safe_normalize(dir);
@ -885,17 +875,12 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
if (light->use_mis && radius > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].co = co;
klights[light_index].spot.radius = radius;
klights[light_index].spot.invarea = invarea;
klights[light_index].spot.spot_angle = spot_angle;
klights[light_index].spot.cos_half_spot_angle = cos_half_spot_angle;
klights[light_index].spot.spot_smooth = spot_smooth;
klights[light_index].spot.dir[0] = dir.x;
klights[light_index].spot.dir[1] = dir.y;
klights[light_index].spot.dir[2] = dir.z;
klights[light_index].spot.dir = dir;
}
klights[light_index].shader_id = shader_id;
@ -918,49 +903,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
light_index++;
}
/* TODO(sergey): Consider moving portals update to their own function
* keeping this one more manageable.
*/
foreach (Light *light, scene->lights) {
if (!light->is_portal)
continue;
assert(light->light_type == LIGHT_AREA);
float3 co = light->co;
float3 axisu = light->axisu * (light->sizeu * light->size);
float3 axisv = light->axisv * (light->sizev * light->size);
float area = len(axisu) * len(axisv);
if (light->round) {
area *= -M_PI_4_F;
}
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
float3 dir = light->dir;
dir = safe_normalize(dir);
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].area.axisu[0] = axisu.x;
klights[light_index].area.axisu[1] = axisu.y;
klights[light_index].area.axisu[2] = axisu.z;
klights[light_index].area.axisv[0] = axisv.x;
klights[light_index].area.axisv[1] = axisv.y;
klights[light_index].area.axisv[2] = axisv.z;
klights[light_index].area.invarea = invarea;
klights[light_index].area.dir[0] = dir.x;
klights[light_index].area.dir[1] = dir.y;
klights[light_index].area.dir[2] = dir.z;
klights[light_index].tfm = light->tfm;
klights[light_index].itfm = transform_inverse(light->tfm);
light_index++;
}
VLOG_INFO << "Number of lights sent to the device: " << light_index;
VLOG_INFO << "Number of lights without contribution: " << num_scene_lights - light_index;
VLOG_INFO << "Number of lights sent to the device: " << num_lights;
dscene->lights.copy_to_device();
}
@ -986,11 +929,7 @@ void LightManager::device_update(Device *device,
device_free(device, dscene, need_update_background);
device_update_points(device, dscene, scene);
if (progress.get_cancel())
return;
device_update_distribution(device, dscene, scene, progress);
device_update_lights(device, dscene, scene);
if (progress.get_cancel())
return;
@ -1000,6 +939,10 @@ void LightManager::device_update(Device *device,
return;
}
device_update_distribution(device, dscene, scene, progress);
if (progress.get_cancel())
return;
device_update_ies(dscene);
if (progress.get_cancel())
return;

View File

@ -127,11 +127,12 @@ class LightManager {
*/
void test_enabled_lights(Scene *scene);
void device_update_points(Device *device, DeviceScene *dscene, Scene *scene);
void device_update_lights(Device *device, DeviceScene *dscene, Scene *scene);
void device_update_distribution(Device *device,
DeviceScene *dscene,
Scene *scene,
Progress &progress);
void device_update_tree(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress);
void device_update_background(Device *device,
DeviceScene *dscene,
Scene *scene,