Cycles/EEVEE: change point light to double-sided sphere light #108506

Merged
Weizhen Huang merged 18 commits from weizhen/blender:point_light_to_sphere_light into main 2023-06-20 12:23:12 +02:00
16 changed files with 320 additions and 124 deletions

View File

@ -775,7 +775,9 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
surface_shader_bsdf_eval(kg, state, sd, wo, throughput, ls->shader);
/* Update light sample with new position / direction and keep pdf in vertex area measure. */
light_sample_update(kg, ls, vertices[vertex_count - 1].p);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
light_sample_update(
kg, ls, vertices[vertex_count - 1].p, vertices[vertex_count - 1].n, path_flag);
/* Save state path bounce info in case a light path node is used in the refractive interface or
* light shader graph. */

View File

@ -20,6 +20,8 @@ ccl_device_inline bool shadow_linking_light_sample_from_intersection(
KernelGlobals kg,
ccl_private const Intersection &ccl_restrict isect,
ccl_private const Ray &ccl_restrict ray,
const float3 N,
const uint32_t path_flag,
ccl_private LightSample *ccl_restrict ls)
{
const int lamp = isect.prim;
@ -31,7 +33,7 @@ ccl_device_inline bool shadow_linking_light_sample_from_intersection(
return distant_light_sample_from_intersection(kg, ray.D, lamp, ls);
}
return light_sample_from_intersection(kg, &isect, ray.P, ray.D, ls);
return light_sample_from_intersection(kg, &isect, ray.P, ray.D, N, path_flag, ls);
}
ccl_device_inline float shadow_linking_light_sample_mis_weight(KernelGlobals kg,
@ -88,8 +90,11 @@ ccl_device bool shadow_linking_shade_light(KernelGlobals kg,
ccl_private float &mis_weight,
ccl_private int &ccl_restrict light_group)
{
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
LightSample ls ccl_optional_struct_init;
const bool use_light_sample = shadow_linking_light_sample_from_intersection(kg, isect, ray, &ls);
const bool use_light_sample = shadow_linking_light_sample_from_intersection(
kg, isect, ray, N, path_flag, &ls);
if (!use_light_sample) {
/* No light to be sampled, so no direct light contribution either. */
return false;
@ -100,7 +105,6 @@ ccl_device bool shadow_linking_shade_light(KernelGlobals kg,
return false;
}
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!is_light_shader_visible_to_path(ls.shader, path_flag)) {
return false;
}

View File

@ -24,12 +24,15 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float ray_time = INTEGRATOR_STATE(state, ray, time);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
/* Advance ray to new start distance. */
INTEGRATOR_STATE_WRITE(state, ray, tmin) = intersection_t_offset(isect.t);
LightSample ls ccl_optional_struct_init;
const bool use_light_sample = light_sample_from_intersection(kg, &isect, ray_P, ray_D, &ls);
const bool use_light_sample = light_sample_from_intersection(
kg, &isect, ray_P, ray_D, N, path_flag, &ls);
if (!use_light_sample) {
return;
@ -37,7 +40,6 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
/* Use visibility flag to skip lights. */
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!is_light_shader_visible_to_path(ls.shader, path_flag)) {
return;
}

View File

@ -46,7 +46,9 @@ ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
const float3 rand,
const float time,
const float3 P,
const float3 N,
const int object_receiver,
const int shader_flags,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
@ -56,8 +58,19 @@ ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
const int index = light_distribution_sample(kg, rand.z);
const float pdf_selection = kernel_data.integrator.distribution_pdf_lights;
const float2 rand_uv = float3_to_float2(rand);
return light_sample<in_volume_segment>(
kg, rand_uv, time, P, object_receiver, bounce, path_flag, index, 0, pdf_selection, ls);
return light_sample<in_volume_segment>(kg,
rand_uv,
time,
P,
N,
object_receiver,
shader_flags,
bounce,
path_flag,
index,
0,
pdf_selection,
ls);
}
ccl_device_inline float light_distribution_pdf_lamp(KernelGlobals kg)

View File

@ -96,6 +96,8 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
const int lamp,
const float2 rand,
const float3 P,
const float3 N,
const int shader_flags,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
@ -150,7 +152,7 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
}
}
else if (type == LIGHT_POINT) {
if (!point_light_sample<in_volume_segment>(klight, rand, P, ls)) {
if (!point_light_sample(klight, rand, P, N, shader_flags, ls)) {
return false;
}
}
@ -171,7 +173,9 @@ ccl_device_noinline bool light_sample(KernelGlobals kg,
const float2 rand,
const float time,
const float3 P,
const float3 N,
const int object_receiver,
const int shader_flags,
const int bounce,
const uint32_t path_flag,
const int emitter_index,
@ -233,7 +237,7 @@ ccl_device_noinline bool light_sample(KernelGlobals kg,
return false;
}
if (!light_sample<in_volume_segment>(kg, light, rand, P, path_flag, ls)) {
if (!light_sample<in_volume_segment>(kg, light, rand, P, N, shader_flags, path_flag, ls)) {
return false;
}
}
@ -446,6 +450,8 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
const float3 N,
const uint32_t path_flag,
ccl_private LightSample *ccl_restrict ls)
{
const int lamp = isect->prim;
@ -468,7 +474,7 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
}
}
else if (type == LIGHT_POINT) {
if (!point_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
if (!point_light_sample_from_intersection(klight, isect, ray_P, ray_D, N, path_flag, ls)) {
return false;
}
}

View File

@ -8,68 +8,123 @@
CCL_NAMESPACE_BEGIN
template<bool in_volume_segment>
ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight,
const float2 rand,
const float3 P,
const float3 N,
const int shader_flags,
ccl_private LightSample *ls)
{
float3 center = klight->co;
float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
float3 lightN = P - klight->co;
const float d_sq = len_squared(lightN);
weizhen marked this conversation as resolved Outdated

The Blender convention is to use _sq suffix.

The Blender convention is to use `_sq` suffix.
const float d = sqrtf(d_sq);
lightN /= d;
if (radius > 0.0f) {
ls->P += disk_light_sample(lightN, rand) * radius;
const float r_sq = sqr(klight->spot.radius);
float cos_theta;
if (d_sq > r_sq) {
const float one_minus_cos = sin_sqr_to_one_minus_cos(r_sq / d_sq);
sample_uniform_cone_concentric(-lightN, one_minus_cos, rand, &cos_theta, &ls->D, &ls->pdf);
}
else {
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
if (has_transmission) {
ls->D = sample_uniform_sphere(rand);
ls->pdf = M_1_2PI_F * 0.5f;
}
else {
sample_cos_hemisphere(N, rand, &ls->D, &ls->pdf);
}
cos_theta = -dot(ls->D, lightN);
}
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;
/* Law of cosines. */
ls->t = d * cos_theta - copysignf(safe_sqrtf(r_sq - d_sq + d_sq * sqr(cos_theta)), d_sq - r_sq);
ls->P = P + ls->D * ls->t;
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
if (r_sq == 0) {
/* Use intensity instead of radiance for point light. */
ls->eval_fac /= sqr(ls->t);
/* `ls->Ng` is not well-defined for point light, so use the incoming direction instead. */
ls->Ng = -ls->D;
}
else {
ls->Ng = normalize(ls->P - klight->co);
/* Remap sampled point onto the sphere to prevent precision issues with small radius. */
ls->P = ls->Ng * klight->spot.radius + klight->co;
}
const Transform itfm = klight->itfm;
const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = uv.y;
ls->v = 1.0f - uv.x - uv.y;
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 float point_light_pdf(
const float d_sq, const float r_sq, const float3 N, const float3 D, const uint32_t path_flag)
{
if (d_sq > r_sq) {
return M_1_2PI_F / sin_sqr_to_one_minus_cos(r_sq / d_sq);
}
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
return has_transmission ? M_1_2PI_F * 0.5f : pdf_cos_hemisphere(N, D);
}
ccl_device_forceinline void point_light_mnee_sample_update(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
const float3 P,
const float3 N,
const uint32_t path_flag)
{
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;
const float radius = klight->spot.radius;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
/* NOTE : preserve pdf in area measure. */
ls->pdf = invarea;
if (radius > 0) {
const float d_sq = len_squared(P - klight->co);
const float r_sq = sqr(radius);
const float t_sq = sqr(ls->t);
ls->pdf = point_light_pdf(d_sq, r_sq, N, ls->D, path_flag);
/* NOTE : preserve pdf in area measure. */
ls->pdf *= 0.5f * fabsf(d_sq - r_sq - t_sq) / (radius * ls->t * t_sq);
ls->Ng = normalize(ls->P - klight->co);
}
else {
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
ls->Ng = -ls->D;
/* PDF does not change. */
}
const Transform itfm = klight->itfm;
const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = uv.y;
ls->v = 1.0f - uv.x - uv.y;
}
ccl_device_inline bool point_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t)
{
/* 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);
return ray_sphere_intersect(ray->P, ray->D, ray->tmin, ray->tmax, klight->co, radius, &P, t);
}
ccl_device_inline bool point_light_sample_from_intersection(
@ -77,27 +132,27 @@ ccl_device_inline bool point_light_sample_from_intersection(
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
const float3 N,
const uint32_t path_flag,
ccl_private LightSample *ccl_restrict ls)
{
const float3 lighN = normalize(ray_P - klight->co);
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
const float radius = klight->spot.radius;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
ls->Ng = radius > 0 ? normalize(ls->P - klight->co) : -ray_D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
const Transform itfm = klight->itfm;
const float2 uv = map_to_sphere(transform_direction(&itfm, ls->Ng));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = uv.y;
ls->v = 1.0f - uv.x - uv.y;
/* compute pdf */
if (ls->t != FLT_MAX) {
ls->pdf *= lamp_light_pdf(lighN, -ls->D, ls->t);
if (ls->t == FLT_MAX) {
ls->pdf = 0.0f;
}
else {
ls->pdf = 0.f;
ls->pdf = point_light_pdf(len_squared(ray_P - klight->co), sqr(radius), N, ray_D, path_flag);
}
return true;
@ -115,14 +170,22 @@ ccl_device_forceinline bool point_light_tree_parameters(const ccl_global KernelL
cos_theta_u = 1.0f; /* Any value in [-1, 1], irrelevant since theta = 0 */
return true;
}
float min_distance;
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
float dist_point_to_centroid;
point_to_centroid = safe_normalize_len(centroid - P, &dist_point_to_centroid);
const float radius = klight->spot.radius;
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
cos_theta_u = min_distance / hypotenus;
distance = make_float2(hypotenus, min_distance);
if (dist_point_to_centroid > radius) {
/* Equivalent to a disk light with the same angular span. */
cos_theta_u = cos_from_sin(radius / dist_point_to_centroid);
distance = dist_point_to_centroid * make_float2(1.0f / cos_theta_u, 1.0f);
}
else {
/* Similar to background light. */
cos_theta_u = -1.0f;
/* HACK: pack radiance scaling in the distance. */
distance = one_float2() * radius / M_SQRT2_F;
}
return true;
}

View File

@ -338,7 +338,7 @@ ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
#endif
{
return light_distribution_sample<true>(
kg, rand, time, P, object_receiver, bounce, path_flag, ls);
kg, rand, time, P, D, object_receiver, SD_BSDF_HAS_TRANSMISSION, bounce, path_flag, ls);
}
}
@ -363,7 +363,7 @@ ccl_device bool light_sample_from_position(KernelGlobals kg,
#endif
{
return light_distribution_sample<false>(
kg, rand, time, P, object_receiver, bounce, path_flag, ls);
kg, rand, time, P, N, object_receiver, shader_flags, bounce, path_flag, ls);
}
}
@ -371,12 +371,14 @@ ccl_device bool light_sample_from_position(KernelGlobals kg,
* except for directional light. */
ccl_device_forceinline void light_sample_update(KernelGlobals kg,
ccl_private LightSample *ls,
const float3 P)
const float3 P,
const float3 N,
const uint32_t path_flag)
{
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
if (ls->type == LIGHT_POINT) {
point_light_mnee_sample_update(klight, ls, P);
point_light_mnee_sample_update(klight, ls, P, N, path_flag);
}
else if (ls->type == LIGHT_SPOT) {
spot_light_mnee_sample_update(klight, ls, P);

View File

@ -761,7 +761,9 @@ ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
float3_to_float2(rand),
time,
P,
N_or_D,
object_receiver,
shader_flags,
bounce,
path_flag,
selected_emitter,

View File

@ -19,6 +19,29 @@ ccl_device void to_unit_disk(ccl_private float2 *rand)
rand->y = r * sinf(phi);
}
/* Distribute 2D uniform random samples on [0, 1] over unit disk [-1, 1], with concentric mapping
* to better preserve stratification for some RNG sequences. */
ccl_device float2 concentric_sample_disk(const float2 rand)
{
float phi, r;
float a = 2.0f * rand.x - 1.0f;
float b = 2.0f * rand.y - 1.0f;
if (a == 0.0f && b == 0.0f) {
return zero_float2();
}
else if (a * a > b * b) {
r = a;
phi = M_PI_4_F * (b / a);
}
else {
r = b;
phi = M_PI_2_F - M_PI_4_F * (a / b);
}
return make_float2(r * cosf(phi), r * sinf(phi));
}
/* return an orthogonal tangent and bitangent given a normal and tangent that
* may not be exactly orthogonal */
ccl_device void make_orthonormals_tangent(const float3 N,
@ -45,6 +68,12 @@ ccl_device_inline void sample_cos_hemisphere(const float3 N,
*pdf = costheta * M_1_PI_F;
}
ccl_device_inline float pdf_cos_hemisphere(const float3 N, const float3 D)
{
const float cos_theta = dot(N, D);
return cos_theta > 0 ? cos_theta * M_1_PI_F : 0.0f;
}
/* sample direction uniformly distributed in hemisphere */
ccl_device_inline void sample_uniform_hemisphere(const float3 N,
const float2 rand,
@ -91,6 +120,42 @@ ccl_device_inline float pdf_uniform_cone(const float3 N, float3 D, float angle)
return 0.0f;
}
/* Uniformly sample a direction in a cone of given angle around `N`. Use concentric mapping to
* better preserve stratification. Return the angle between `N` and the sampled direction as
* `cos_theta`.
* Pass `1 - cos(angle)` as argument instead of `angle` to alleviate precision issues at small
* angles (see sphere light for reference). */
ccl_device_inline void sample_uniform_cone_concentric(const float3 N,
const float one_minus_cos_angle,
const float2 rand,
ccl_private float *cos_theta,
ccl_private float3 *wo,
ccl_private float *pdf)
{
if (one_minus_cos_angle > 0) {
/* Map random number from 2D to 1D. */
float2 xy = concentric_sample_disk(rand);
const float r2 = len_squared(xy);
/* Equivalent to `mix(cos_angle, 1.0f, 1.0f - r2)` */
*cos_theta = 1.0f - r2 * one_minus_cos_angle;
/* Equivalent to `xy *= sin_theta / sqrt(r2); */
xy *= safe_sqrtf(one_minus_cos_angle * (2.0f - one_minus_cos_angle * r2));
float3 T, B;
make_orthonormals(N, &T, &B);
*wo = xy.x * T + xy.y * B + *cos_theta * N;
*pdf = M_1_2PI_F / one_minus_cos_angle;
}
else {
*cos_theta = 1.0f;
*wo = N;
*pdf = 1.0f;
}
}
/* sample uniform point on the surface of a sphere */
ccl_device float3 sample_uniform_sphere(const float2 rand)
{
@ -103,29 +168,6 @@ ccl_device float3 sample_uniform_sphere(const float2 rand)
return make_float3(x, y, z);
}
/* distribute uniform xy on [0,1] over unit disk [-1,1], with concentric mapping
* to better preserve stratification for some RNG sequences */
ccl_device float2 concentric_sample_disk(const float2 rand)
{
float phi, r;
float a = 2.0f * rand.x - 1.0f;
float b = 2.0f * rand.y - 1.0f;
if (a == 0.0f && b == 0.0f) {
return zero_float2();
}
else if (a * a > b * b) {
r = a;
phi = M_PI_4_F * (b / a);
}
else {
r = b;
phi = M_PI_2_F - M_PI_4_F * (a / b);
}
return make_float2(r * cosf(phi), r * sinf(phi));
}
/* sample point in unit polygon with given number of corners and rotation */
ccl_device float2 regular_polygon_sample(float corners, float rotation, const float2 rand)
{

View File

@ -1214,6 +1214,9 @@ void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Sce
shader_id &= ~SHADER_AREA_LIGHT;
float radius = light->size;
/* TODO: `invarea` was used for disk sampling, with the current solid angle sampling this
* becomes unnecessary. We could store `eval_fac` instead, but currently it shares the same
* #KernelSpotLight type with #LIGHT_SPOT, so keep it know until refactor for spot light. */
float invarea = (light->normalize && radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) :
1.0f;

View File

@ -748,6 +748,12 @@ ccl_device_inline float cos_from_sin(const float s)
return safe_sqrtf(1.0f - sqr(s));
}
ccl_device_inline float sin_sqr_to_one_minus_cos(const float s_sq)
{
/* Using second-order Taylor expansion at small angles for better accuracy. */
return s_sq > 0.0004f ? 1.0f - safe_sqrtf(1.0f - s_sq) : 0.5f * s_sq;
}
ccl_device_inline float pow20(float a)
{
return sqr(sqr(sqr(sqr(a)) * a));

View File

@ -18,29 +18,32 @@ ccl_device bool ray_sphere_intersect(float3 ray_P,
ccl_private float3 *isect_P,
ccl_private float *isect_t)
{
const float3 d = sphere_P - ray_P;
const float radiussq = sphere_radius * sphere_radius;
const float tsq = dot(d, d);
const float3 d_vec = sphere_P - ray_P;
const float r_sq = sphere_radius * sphere_radius;
const float d_sq = dot(d_vec, d_vec);
const float d_cos_theta = dot(d_vec, ray_D);
if (tsq > radiussq) {
/* Ray origin outside sphere. */
const float tp = dot(d, ray_D);
if (tp < 0.0f) {
/* Ray points away from sphere. */
return false;
}
const float dsq = tsq - tp * tp; /* Pythagoras. */
if (dsq > radiussq) {
/* Closest point on ray outside sphere. */
return false;
}
const float t = tp - sqrtf(radiussq - dsq); /* pythagoras */
if (t > ray_tmin && t < ray_tmax) {
*isect_t = t;
*isect_P = ray_P + ray_D * t;
return true;
}
if (d_sq > r_sq && d_cos_theta < 0.0f) {
/* Ray origin outside sphere and points away from sphere. */
return false;
}
const float d_sin_theta_sq = d_sq - d_cos_theta * d_cos_theta;
if (d_sin_theta_sq > r_sq) {
/* Closest point on ray outside sphere. */
return false;
}
/* Law of cosines. */
const float t = d_cos_theta - copysignf(sqrtf(r_sq - d_sin_theta_sq), d_sq - r_sq);
if (t > ray_tmin && t < ray_tmax) {
*isect_t = t;
*isect_P = ray_P + ray_D * t;
return true;
}
return false;
}

View File

@ -86,8 +86,7 @@ static float light_shape_radiance_get(const Light *la, const EEVEE_Light *evli)
case LA_LOCAL: {
/* Sphere area. */
float area = 4.0f * (float)M_PI * square_f(evli->radius);
/* NOTE: Presence of a factor of PI here to match Cycles. But it should be missing to be
* consistent with the other cases. */
/* Convert radiant flux to radiance. */
return 1.0f / (area * (float)M_PI);
}
default:
@ -128,10 +127,9 @@ static float light_volume_radiance_factor_get(const Light *la,
}
case LA_SPOT:
case LA_LOCAL: {
/* Sphere solid angle. */
float area = 4.0f * (float)M_PI;
/* NOTE: Missing a factor of PI here to match Cycles. */
power *= 1.0f / area;
/* Convert radiant flux to intensity. */
/* Inverse of sphere solid angle. */
power *= 0.25f * (float)M_1_PI;
break;
}
default:

View File

@ -325,6 +325,20 @@ float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector)
return ltc_evaluate_disk(N, V, mat3(1.0), points);
}
else if (ld.l_type == POINT) {
if (l_vector.w < ld.l_radius) {
/* Inside, treat as hemispherical light. */
return 1.0;
}
else {
/* Outside, treat as disk light spanning the same solid angle. */
/* The result is the same as passing the scaled radius to #ltc_evaluate_disk_simple (see
* #light_specular), using simplified math here. */
float r_sq = sqr(ld.l_radius / l_vector.w);
vec3 L = l_vector.xyz / l_vector.w;
return r_sq * diffuse_sphere_integral(dot(N, L), r_sq);
}
}
else {
float radius = ld.l_radius;
radius /= (ld.l_type == SUN) ? 1.0 : l_vector.w;
@ -347,11 +361,22 @@ float light_specular(LightData ld, vec4 ltc_mat, vec3 N, vec3 V, vec4 l_vector)
return ltc_evaluate_quad(corners, vec3(0.0, 0.0, 1.0));
}
else if (ld.l_type == POINT && l_vector.w < ld.l_radius) {
/* Inside the sphere light, integrate over the hemisphere. */
return 1.0;
}
else {
bool is_ellipse = (ld.l_type == AREA_ELLIPSE);
float radius_x = is_ellipse ? ld.l_sizex : ld.l_radius;
float radius_y = is_ellipse ? ld.l_sizey : ld.l_radius;
if (ld.l_type == POINT) {
/* The sine of the half-angle spanned by a sphere light is equal to the tangent of the
* half-angle spanned by a disk light with the same radius. */
radius_x *= inversesqrt(1.0 - sqr(radius_x / l_vector.w));
radius_y *= inversesqrt(1.0 - sqr(radius_y / l_vector.w));
}
vec3 L = (ld.l_type == SUN) ? -ld.l_forward : l_vector.xyz;
vec3 Px = ld.l_right;
vec3 Py = ld.l_up;

View File

@ -186,8 +186,7 @@ float Light::shape_radiance_get(const ::Light *la)
case LA_LOCAL: {
/* Sphere area. */
float area = 4.0f * float(M_PI) * square_f(_radius);
/* NOTE: Presence of a factor of PI here to match Cycles. But it should be missing to be
* consistent with the other cases. */
/* Convert radiant flux to radiance. */
return 1.0f / (area * float(M_PI));
}
default:
@ -221,10 +220,9 @@ float Light::point_radiance_get(const ::Light *la)
}
case LA_SPOT:
case LA_LOCAL: {
/* Sphere solid angle. */
float area = 4.0f * float(M_PI);
/* NOTE: Missing a factor of PI here to match Cycles. */
return 1.0f / area;
/* Convert radiant flux to intensity. */
/* Inverse of sphere solid angle. */
return 0.25f * float(M_1_PI);
}
default:
case LA_SUN: {

View File

@ -106,7 +106,20 @@ float light_diffuse(sampler2DArray utility_tx,
vec3 L,
float dist)
{
if (is_directional || !is_area_light(ld.type)) {
if (ld.type == LIGHT_POINT) {
if (dist < ld._radius) {
/* Inside, treat as hemispherical light. */
return 1.0;
}
else {
/* Outside, treat as disk light spanning the same solid angle. */
/* The result is the same as passing the scaled radius to #ltc_evaluate_disk_simple (see
* #light_ltc), using simplified math here. */
float r_sq = sqr(ld._radius / dist);
return r_sq * ltc_diffuse_sphere_integral(utility_tx, dot(N, L), r_sq);
}
}
else if (is_directional || ld.type == LIGHT_SPOT) {
float radius = ld._radius / dist;
return ltc_evaluate_disk_simple(utility_tx, radius, dot(N, L));
}
@ -147,7 +160,11 @@ float light_ltc(sampler2DArray utility_tx,
float dist,
vec4 ltc_mat)
{
if (is_directional || ld.type != LIGHT_RECT) {
if (ld.type == LIGHT_POINT && dist < ld._radius) {
/* Inside the sphere light, integrate over the hemisphere. */
return 1.0;
}
else if (is_directional || ld.type != LIGHT_RECT) {
vec3 Px = ld._right;
vec3 Py = ld._up;
@ -156,8 +173,18 @@ float light_ltc(sampler2DArray utility_tx,
}
vec3 points[3];
points[0] = Px * -ld._area_size_x + Py * -ld._area_size_y;
points[1] = Px * ld._area_size_x + Py * -ld._area_size_y;
if (ld.type == LIGHT_POINT) {
/* The sine of the half-angle spanned by a sphere light is equal to the tangent of the
weizhen marked this conversation as resolved Outdated

This sin -> tan is a bit cryptic. It could help if it was a full sentence.

This `sin -> tan` is a bit cryptic. It could help if it was a full sentence.
* half-angle spanned by a disk light with the same radius. */
float radius = ld._radius * inversesqrt(1.0 - sqr(ld._radius / dist));
points[0] = Px * -radius + Py * -radius;
points[1] = Px * radius + Py * -radius;
}
else {
points[0] = Px * -ld._area_size_x + Py * -ld._area_size_y;
points[1] = Px * ld._area_size_x + Py * -ld._area_size_y;
}
points[2] = -points[0];
points[0] += L * dist;