WIP: EEVEE-Next: Add light spread support #119574

Draft
Clément Foucault wants to merge 12 commits from fclem/blender:eevee-next-lightspread into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
4 changed files with 209 additions and 6 deletions

View File

@ -119,6 +119,7 @@ class DATA_PT_EEVEE_light(DataButtonsPanel, Panel):
col.prop(light, "use_shadow", text="Cast Shadow")
col.prop(light, "shadow_softness_factor", text="Shadow Softness")
col.prop(light, "shadow_filter_radius", text="Filtering Radius")
col.prop(light, "spread")
if light.type == 'SUN':
col.prop(light, "shadow_trace_distance", text="Trace Distance")

View File

@ -170,6 +170,8 @@ void Light::shape_parameters_set(const ::Light *la, const float scale[3])
/* For volume point lighting. */
radius_squared = max_ff(0.001f, hypotf(_area_size_x, _area_size_y) * 0.5f);
radius_squared = square_f(radius_squared);
spread_half_angle = la->area_spread / 2.0f;
spread_mix_fac = la->area_spread / M_PI;
}
else {
if (la->type == LA_SPOT) {

View File

@ -793,6 +793,12 @@ struct LightData {
float radius_squared;
/** Spot angle tangent. */
float spot_tan;
float spread_half_angle;
float spread_mix_fac;
float _pad2;
float _pad3;
/** Reuse for directional LOD bias. */
#define _clipmap_lod_bias spot_tan

View File

@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_ltc_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
@ -102,7 +103,200 @@ float light_spot_attenuation(LightData light, vec3 L)
return spotmask * step(0.0, -dot(L, -light._back));
}
float light_attenuation_common(LightData light, const bool is_directional, vec3 L)
/**
* Approximate the ratio of the area of intersection of two spherical caps divided by the area of
* the smallest cap.
*/
float light_cone_cone_ratio(float cone_angle_1, float cone_angle_2, float angle_between_axes)
{
/* From "Ambient Aperture Lighting" by Chris Oat
* Slide 15.
* Simplified since we divide by the solid angle of the smallest cone. */
return smoothstep(
cone_angle_1 + cone_angle_2, abs(cone_angle_1 - cone_angle_2), abs(angle_between_axes));
}
float linearstep(float edge0, float edge1, float x)
{
return saturate((x - edge0) / (edge1 - edge0));
}
float ratio_cone_cone(float cone_angle_1, float cone_angle_2, float angle_between_axes)
{
return light_cone_cone_ratio(cone_angle_1, cone_angle_2, angle_between_axes);
}
float area_cone(float cone_angle)
{
return M_TAU * (1.0 - cos(cone_angle));
}
float area_lune(float lune_angle)
{
return linearstep(0.0, M_TAU, abs(lune_angle));
}
float area_cone_cone(float cone_angle_1, float cone_angle_2, float angle_between_axes)
{
return ratio_cone_cone(cone_angle_1, cone_angle_2, angle_between_axes) *
area_cone(min(cone_angle_1, cone_angle_2));
}
float area_cone_hemisphere(float cone_angle, float angle_between_axes)
{
float r = abs(cone_angle);
float d = abs(angle_between_axes);
d = clamp(d, M_PI_2 - r + 0.0001, M_PI_2 + r - 0.0001);
return -2.0 * acos(cos(d) / sin(r)) - 2.0 * acos(-cos(d) * cos(r) / (sin(d) * sin(r))) * cos(r) +
M_TAU;
}
float light_cone_lune_area(float spread_half_angle, float edge_angle_1, float edge_angle_2)
{
float angle_hemi1 = edge_angle_1 + M_PI_2;
float angle_hemi2 = edge_angle_2 - M_PI_2;
float ratio_hemi1_cone_isect = area_cone_hemisphere(spread_half_angle, angle_hemi1) /
min(area_cone_hemisphere(M_PI_2, angle_hemi1),
area_cone(spread_half_angle));
float ratio_hemi2_cone_isect = area_cone_hemisphere(spread_half_angle, angle_hemi2) /
min(area_cone_hemisphere(M_PI_2, angle_hemi2),
area_cone(spread_half_angle));
return min(ratio_hemi1_cone_isect, ratio_hemi2_cone_isect);
}
float light_lune_cone_ratio(float spread_half_angle, float edge_angle_1, float edge_angle_2)
{
return light_cone_lune_area(spread_half_angle, edge_angle_1, edge_angle_2);
}
/* https://www.shadertoy.com/view/4dVcR1 */
vec2 msign(vec2 x)
{
return vec2((x.x < 0.0) ? -1.0 : 1.0, (x.y < 0.0) ? -1.0 : 1.0);
}
vec2 sdEllipseNearestPoint(vec2 p, vec2 ab)
{
vec2 p_sign = msign(p);
// symmetry
p = abs(p);
// find root with Newton solver
vec2 q = ab * (p - ab);
float w = (q.x < q.y) ? 1.570796327 : 0.0;
for (int i = 0; i < 4; i++) {
vec2 cs = vec2(cos(w), sin(w));
vec2 u = ab * vec2(cs.x, cs.y);
vec2 v = ab * vec2(-cs.y, cs.x);
w = w + dot(p - u, v) / (dot(p - u, u) + dot(v, v));
}
return ab * vec2(cos(w), sin(w)) * p_sign;
}
vec2 sdEllipseFarthestPoint(vec2 p, vec2 ab)
{
vec2 p_sign = msign(p);
// symmetry
p = abs(p);
// find root with Newton solver
vec2 q = ab * (p + ab);
float w = 3.1415 + ((q.x < q.y) ? 1.570796327 : 0.0);
for (int i = 0; i < 4; i++) {
vec2 cs = vec2(cos(w), sin(w));
vec2 u = ab * vec2(cs.x, cs.y);
vec2 v = ab * vec2(-cs.y, cs.x);
w = w + dot(p - u, v) / (dot(p - u, u) + dot(v, v));
}
return ab * vec2(cos(w), sin(w)) * p_sign;
}
/* from Real-Time Area Lighting: a Journey from Research to Production
* Stephen Hill and Eric Heitz */
vec3 light_edge_integral_vec(vec3 v1, vec3 v2)
{
float x = dot(v1, v2);
float y = abs(x);
float a = 0.8543985 + (0.4965155 + 0.0145206 * y) * y;
float b = 3.4175940 + (4.1616724 + y) * y;
float v = a / b;
float theta_sintheta = (x > 0.0) ? v : 0.5 * inversesqrt(max(1.0 - x * x, 1e-7)) - v;
return cross(v1, v2) * theta_sintheta;
}
float light_spread_angle_attenuation(LightData light, vec3 L, float dist)
{
vec3 lL = light_world_to_local(light, L * dist);
float distance_to_plane = abs(lL.z);
vec2 area_size = vec2(light._area_size_x, light._area_size_y);
if (light.type == LIGHT_RECT) {
vec2 r1 = lL.xy - area_size;
vec2 r2 = lL.xy + area_size;
vec3 corners[4];
corners[0] = vec3(area_size.x, -area_size.y, 0.0);
corners[1] = vec3(area_size.x, area_size.y, 0.0);
corners[2] = -corners[0];
corners[3] = -corners[1];
corners[0] = normalize(corners[0] + lL);
corners[1] = normalize(corners[1] + lL);
corners[2] = normalize(corners[2] + lL);
corners[3] = normalize(corners[3] + lL);
vec3 avg_dir;
avg_dir = light_edge_integral_vec(corners[0], corners[1]);
avg_dir += light_edge_integral_vec(corners[1], corners[2]);
avg_dir += light_edge_integral_vec(corners[2], corners[3]);
avg_dir += light_edge_integral_vec(corners[3], corners[0]);
float form_factor = length(avg_dir);
float avg_dir_z = (avg_dir / form_factor).z;
float half_light_angle = acos(1.0 - form_factor);
return light_cone_cone_ratio(light.spread_half_angle, half_light_angle, acos(avg_dir_z));
/* angle_1 is min angle of intersection with first edge of the lune.
* angle_2 is min angle of intersection with second edge of the lune. */
/* TODO(fclem): Port fast_atanf from cycles. */
vec2 angle_1 = atan(r1, vec2(distance_to_plane));
vec2 angle_2 = atan(r2, vec2(distance_to_plane));
float x = light_lune_cone_ratio(light.spread_half_angle, angle_1.x, angle_2.x);
float y = light_lune_cone_ratio(light.spread_half_angle, angle_1.y, angle_2.y);
return x * y;
}
float r1 = distance(sdEllipseNearestPoint(lL.xy, area_size), lL.xy);
float r2 = distance(sdEllipseFarthestPoint(lL.xy, area_size), lL.xy);
bool inside_ellipse = length_squared(lL.xy / area_size) < 1.0;
if (inside_ellipse) {
/* Signed distance. */
r1 = -r1;
}
/* TODO(fclem): Port fast_atanf from cycles. */
float angle_1 = atan(r1, distance_to_plane);
float angle_2 = atan(r2, distance_to_plane);
float half_light_angle = abs(angle_1 - angle_2) / 2.0;
float elevation_angle = (angle_1 + angle_2) / 2.0;
return light_cone_cone_ratio(light.spread_half_angle, half_light_angle, elevation_angle);
}
float light_attenuation_common(LightData light, const bool is_directional, vec3 L, float dist)
{
if (is_directional) {
return 1.0;
@ -111,7 +305,7 @@ float light_attenuation_common(LightData light, const bool is_directional, vec3
return light_spot_attenuation(light, L);
}
if (is_area_light(light.type)) {
return step(0.0, -dot(L, -light._back));
return step(0.0, -dot(L, -light._back)) * light_spread_angle_attenuation(light, L, dist);
}
return 1.0;
}
@ -144,14 +338,14 @@ vec2 light_attenuation_facing(LightData light, vec3 L, float distance_to_light,
vec2 light_attenuation_surface(LightData light, const bool is_directional, vec3 Ng, LightVector lv)
{
return light_attenuation_common(light, is_directional, lv.L) *
return light_attenuation_common(light, is_directional, lv.L, lv.dist) *
light_attenuation_facing(light, lv.L, lv.dist, Ng) *
light_influence_attenuation(lv.dist, light.influence_radius_invsqr_surface);
}
float light_attenuation_volume(LightData light, const bool is_directional, LightVector lv)
{
return light_attenuation_common(light, is_directional, lv.L) *
return light_attenuation_common(light, is_directional, lv.L, lv.dist) *
light_influence_attenuation(lv.dist, light.influence_radius_invsqr_volume);
}
@ -205,7 +399,7 @@ float light_ltc(
corners[2] = -corners[0];
corners[3] = -corners[1];
vec3 L = lv.L * lv.dist;
vec3 L = mix(light._back, lv.L * lv.dist, light.spread_mix_fac);
corners[0] += L;
corners[1] += L;
corners[2] += L;
@ -242,7 +436,7 @@ float light_ltc(
points[1] = Px * size.x + Py * -size.y;
points[2] = -points[0];
vec3 L = lv.L * lv.dist;
vec3 L = mix(light._back, lv.L * lv.dist, light.spread_mix_fac);
points[0] += L;
points[1] += L;
points[2] += L;