diff options
author | Brecht Van Lommel <brecht@blender.org> | 2021-10-24 15:19:19 +0300 |
---|---|---|
committer | Brecht Van Lommel <brecht@blender.org> | 2021-10-26 16:36:39 +0300 |
commit | d7d40745fa09061a3117bd3669c5a46bbf611eae (patch) | |
tree | 8dbaca086ecbb09aad62c25e9ece66332fe79af3 /intern/cycles/kernel/light/light.h | |
parent | b698fe1e047e56e8ed67ba47464c0017d9c50eea (diff) |
Cycles: changes to source code folders structure
* Split render/ into scene/ and session/. The scene/ folder now contains the
scene and its nodes. The session/ folder contains the render session and
associated data structures like drivers and render buffers.
* Move top level kernel headers into new folders kernel/camera/, kernel/film/,
kernel/light/, kernel/sample/, kernel/util/
* Move integrator related kernel headers into kernel/integrator/
* Move OSL shaders from kernel/shaders/ to kernel/osl/shaders/
For patches and branches, git merge and rebase should be able to detect the
renames and move over code to the right file.
Diffstat (limited to 'intern/cycles/kernel/light/light.h')
-rw-r--r-- | intern/cycles/kernel/light/light.h | 872 |
1 files changed, 872 insertions, 0 deletions
diff --git a/intern/cycles/kernel/light/light.h b/intern/cycles/kernel/light/light.h new file mode 100644 index 00000000000..facbbe23d0f --- /dev/null +++ b/intern/cycles/kernel/light/light.h @@ -0,0 +1,872 @@ +/* + * Copyright 2011-2013 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "kernel/geom/geom.h" +#include "kernel/light/light_background.h" +#include "kernel/sample/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 */ + LightType type; /* type of light */ +} LightSample; + +/* Regular Light */ + +template<bool in_volume_segment> +ccl_device_inline bool light_sample(KernelGlobals kg, + const int lamp, + const float randu, + const float randv, + const float3 P, + const uint32_t path_flag, + ccl_private LightSample *ls) +{ + const ccl_global KernelLight *klight = &kernel_tex_fetch(__lights, lamp); + if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) { + if (klight->shader_id & SHADER_EXCLUDE_SHADOW_CATCHER) { + return false; + } + } + + LightType type = (LightType)klight->type; + ls->type = type; + ls->shader = klight->shader_id; + ls->object = PRIM_NONE; + ls->prim = PRIM_NONE; + ls->lamp = lamp; + ls->u = randu; + ls->v = randv; + + if (in_volume_segment && (type == LIGHT_DISTANT || type == LIGHT_BACKGROUND)) { + /* Distant lights in a volume get a dummy sample, position will not actually + * be used in that case. Only when sampling from a specific scatter position + * do we actually need to evaluate these. */ + ls->P = zero_float3(); + ls->Ng = zero_float3(); + ls->D = zero_float3(); + ls->pdf = true; + 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; + } +#ifdef __BACKGROUND_MIS__ + else if (type == LIGHT_BACKGROUND) { + /* infinite area light (e.g. light dome or env light) */ + float3 D = -background_light_sample(kg, P, randu, randv, &ls->pdf); + + ls->P = D; + ls->Ng = D; + ls->D = -D; + ls->t = FLT_MAX; + ls->eval_fac = 1.0f; + } +#endif + else { + ls->P = make_float3(klight->co[0], klight->co[1], klight->co[2]); + + if (type == LIGHT_POINT || type == LIGHT_SPOT) { + float radius = klight->spot.radius; + + if (radius > 0.0f) + /* sphere light */ + ls->P += sphere_light_sample(P, ls->P, radius, randu, randv); + + ls->D = normalize_len(ls->P - P, &ls->t); + ls->Ng = -ls->D; + + float invarea = klight->spot.invarea; + ls->eval_fac = (0.25f * M_1_PI_F) * invarea; + ls->pdf = invarea; + + if (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); + if (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, ls->Ng, -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 (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; + } + + ls->u = dot(inplane, axisu) * (1.0f / dot(axisu, axisu)) + 0.5f; + ls->v = dot(inplane, axisv) * (1.0f / dot(axisv, axisv)) + 0.5f; + + 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); + } + } + } + + ls->pdf *= kernel_data.integrator.pdf_lights; + + return (ls->pdf > 0.0f); +} + +ccl_device bool lights_intersect(KernelGlobals kg, + ccl_private const Ray *ccl_restrict ray, + ccl_private Intersection *ccl_restrict isect, + const int last_prim, + const int last_object, + const int last_type, + const uint32_t path_flag) +{ + for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) { + const ccl_global KernelLight *klight = &kernel_tex_fetch(__lights, lamp); + + if (path_flag & PATH_RAY_CAMERA) { + if (klight->shader_id & SHADER_EXCLUDE_CAMERA) { + continue; + } + } + else { + if (!(klight->shader_id & SHADER_USE_MIS)) { + continue; + } + } + + if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) { + if (klight->shader_id & SHADER_EXCLUDE_SHADOW_CATCHER) { + continue; + } + } + + LightType type = (LightType)klight->type; + float t = 0.0f, u = 0.0f, v = 0.0f; + + if (type == LIGHT_POINT || type == LIGHT_SPOT) { + /* Sphere 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; + } + + float3 P; + if (!ray_aligned_disk_intersect(ray->P, ray->D, ray->t, lightP, radius, &P, &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, 0.0f, ray->t, light_P, axisu, axisv, Ng, &P, &t, &u, &v, is_round)) { + continue; + } + } + else { + continue; + } + + if (t < isect->t && + !(last_prim == lamp && last_object == OBJECT_NONE && last_type == PRIMITIVE_LAMP)) { + isect->t = t; + isect->u = u; + isect->v = v; + isect->type = PRIMITIVE_LAMP; + isect->prim = lamp; + isect->object = OBJECT_NONE; + } + } + + 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_tex_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; + + if (costheta < cosangle) + return false; + + ls->type = type; + ls->shader = klight->shader_id; + 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; + + /* compute pdf */ + float invarea = klight->distant.invarea; + ls->pdf = invarea / (costheta * costheta * costheta); + ls->pdf *= kernel_data.integrator.pdf_lights; + ls->eval_fac = ls->pdf; + + return true; +} + +ccl_device bool light_sample_from_intersection(KernelGlobals kg, + ccl_private const Intersection *ccl_restrict isect, + const float3 ray_P, + const float3 ray_D, + ccl_private LightSample *ccl_restrict ls) +{ + const int lamp = isect->prim; + ccl_global const KernelLight *klight = &kernel_tex_fetch(__lights, lamp); + LightType type = (LightType)klight->type; + ls->type = type; + ls->shader = klight->shader_id; + ls->object = PRIM_NONE; + ls->prim = PRIM_NONE; + ls->lamp = lamp; + /* todo: missing texture coordinates */ + ls->t = isect->t; + ls->P = ray_P + ray_D * ls->t; + ls->D = ray_D; + + if (type == LIGHT_POINT || type == LIGHT_SPOT) { + ls->Ng = -ray_D; + + float invarea = klight->spot.invarea; + ls->eval_fac = (0.25f * M_1_PI_F) * invarea; + ls->pdf = invarea; + + if (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); + + 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(kg, ls->Ng, -ls->D, ls->t); + } + 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; + } + } + } + else { + kernel_assert(!"Invalid lamp type in light_sample_from_intersection"); + return false; + } + + ls->pdf *= kernel_data.integrator.pdf_lights; + + return true; +} + +/* Triangle Light */ + +/* 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_tex_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_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); + float area = 0.5f * Nl; + + /* flip normal if necessary */ + const int object_flag = kernel_tex_fetch(__object_flag, object); + if (object_flag & SD_OBJECT_NEGATIVE_SCALE_APPLIED) { + ls->Ng = -ls->Ng; + } + ls->eval_fac = 1.0f; + ls->shader = kernel_tex_fetch(__tri_shader, prim); + ls->object = object; + ls->prim = prim; + ls->lamp = LAMP_NONE; + ls->shader |= SHADER_USE_MIS; + ls->type = LIGHT_TRIANGLE; + + 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, + FLT_MAX, +#if defined(__KERNEL_SSE2__) && defined(__KERNEL_SSE__) + (ssef *)V, +#else + V[0], + V[1], + V[2], +#endif + &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 { + /* 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; + } +} + +/* 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_tex_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_tex_fetch(__light_distribution, index).totarea; + float distr_max = kernel_tex_fetch(__light_distribution, index + 1).totarea; + *randu = (r - distr_min) / (distr_max - distr_min); + + return index; +} + +/* Generic Light */ + +ccl_device_inline bool light_select_reached_max_bounces(KernelGlobals kg, int index, int bounce) +{ + return (bounce > kernel_tex_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_tex_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_tex_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 |