Cycles/EEVEE: change point light to double-sided sphere light #108506
@ -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. */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
* 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;
|
||||
|
Loading…
Reference in New Issue
Block a user