EEVEE-Next: Jittered Soft Shadows #119753

Closed
Miguel Pozo wants to merge 46 commits from pragma37/blender:pull-eevee-jittered-shoft-shadows into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
6 changed files with 50 additions and 50 deletions
Showing only changes of commit 34c9c6e83a - Show all commits

View File

@ -785,8 +785,9 @@ void ShadowModule::init()
}
}
do_jittering_ = !inst_.is_viewport() || (scene.eevee.flag & SCE_EEVEE_SHADOW_JITTERED_VIEWPORT &&
!inst_.is_navigating() && !inst_.is_playback());
do_jittering_ = !inst_.is_viewport() ||
pragma37 marked this conversation as resolved Outdated

Style: Always add parenthesis between binary operators and logical operators (i.e: (a & b) && c. This avoids dumb reviewers to verify operator precedence and improves readability.

Style: Always add parenthesis between binary operators and logical operators (i.e: `(a & b) && c`. This avoids dumb reviewers to verify operator precedence and improves readability.

Done, but I personally tend to have more trouble parsing nested parenthesis than operator precedence.

Done, but I personally tend to have more trouble parsing nested parenthesis than operator precedence.
((scene.eevee.flag & SCE_EEVEE_SHADOW_JITTERED_VIEWPORT) &&
!inst_.is_navigating() && !inst_.is_playback());
data_.ray_count = clamp_i(inst_.scene->eevee.shadow_ray_count, 1, SHADOW_MAX_RAY);
data_.step_count = clamp_i(inst_.scene->eevee.shadow_step_count, 1, SHADOW_MAX_STEP);

View File

@ -70,7 +70,7 @@ void light_shadow_single(uint l_idx,
#endif
ShadowEvalResult result = shadow_eval(
light, is_directional, is_transmission, false, P, Ng, Ng, ray_count, ray_step_count);
light, is_directional, is_transmission, false, P, Ng, Ng, 0.0, ray_count, ray_step_count);
shadow_bits |= shadow_pack(result.light_visibilty, ray_count, shift);
shift += ray_count;
@ -252,16 +252,14 @@ void light_eval_single(uint l_idx,
shadow = shadow_unpack(packed_shadows, ray_count, shift);
shift += ray_count;
#else
vec3 L = shadow_vector_get(light, is_directional, P);
vec3 shadow_P = (is_transmission) ? P + L * stack.cl[0].shadow_offset : P;
ShadowEvalResult result = shadow_eval(light,
is_directional,
is_transmission,
is_translucent_with_thickness,
shadow_P,
P,
Ng,
L,
shadow_vector_get(light, is_directional, P),
stack.cl[0].shadow_offset,
ray_count,
ray_step_count);
pragma37 marked this conversation as resolved

At this point, just pass stack.cl[0].shadow_offset to shadow_eval. It will be needed to fix the SSS shadow anyway.

Also prefer calling shadow_dir instead of L. L is reserved for direction towards the light (except in small function / sampling context where it can just refer to outgoing ray direction). But I don't think it is worth changing all existing usage of it right now.

At this point, just pass `stack.cl[0].shadow_offset` to `shadow_eval`. It will be needed to fix the SSS shadow anyway. Also prefer calling `shadow_dir` instead of `L`. `L` is reserved for direction towards the light (except in small function / sampling context where it can just refer to outgoing ray direction). But I don't think it is worth changing all existing usage of it right now.
shadow = result.light_visibilty;

View File

@ -15,7 +15,7 @@
# define SHADOW_ATLAS_TYPE usampler2DArray
#endif
vec3 shadow_vector_get(LightData light, bool is_directional, vec3 P)
vec3 shadow_vector_get(LightData light, const bool is_directional, vec3 P)
{
if (is_directional) {
vec3 b = float3(0, 0, 1);
@ -28,7 +28,7 @@ vec3 shadow_vector_get(LightData light, bool is_directional, vec3 P)
}
}
vec3 light_local_to_shadow_local(LightData light, vec3 lP, bool is_directional)
vec3 light_local_to_shadow_local(LightData light, vec3 lP, const bool is_directional)

Use const bool is_directional for all three functions.

Use `const bool is_directional` for all three functions.

Done, but why?

Done, but why?
{
if (is_directional) {
return rotate(invert(as_quaternion(light_sun_data_get(light).shadow_projection_rotation)), lP);
@ -38,7 +38,7 @@ vec3 light_local_to_shadow_local(LightData light, vec3 lP, bool is_directional)
}
}
vec3 shadow_local_to_light_local(LightData light, vec3 lP, bool is_directional)
vec3 shadow_local_to_light_local(LightData light, vec3 lP, const bool is_directional)
{
if (is_directional) {
return rotate(as_quaternion(light_sun_data_get(light).shadow_projection_rotation), lP);

View File

@ -55,9 +55,9 @@ void main()
float local_min = FLT_MAX;
float local_max = -FLT_MAX;
vec3 L = shadow_vector_get(light, true, vec3(0));
vec3 shadow_forward_z_axis = -shadow_vector_get(light, true, vec3(0));
pragma37 marked this conversation as resolved Outdated

Negate and rename shadow_forward_z_axis.

Negate and rename `shadow_forward_z_axis`.
for (int i = 0; i < 8; i++) {
float z = dot(box.corners[i].xyz, -L);
float z = dot(box.corners[i].xyz, shadow_forward_z_axis);
local_min = min(local_min, z);
local_max = max(local_max, z);
}

View File

@ -185,16 +185,16 @@ ShadowMapTraceResult shadow_map_trace_finish(ShadowMapTracingState state)
/** \} */
/* If the ray direction `L` is below the horizon defined by N (normalized) at the shading point,
* push it just above the horizon so that this ray will never be below it and produce
/* If the ray direction `shadow_dir` is below the horizon defined by N (normalized) at the shading
* point, push it just above the horizon so that this ray will never be below it and produce
* over-shadowing (since light evaluation already clips the light shape). */
vec3 shadow_ray_above_horizon_ensure(vec3 L, vec3 N)
vec3 shadow_ray_above_horizon_ensure(vec3 shadow_dir, vec3 N)
{
float distance_to_plan = dot(L, -N);
float distance_to_plan = dot(shadow_dir, -N);
if (distance_to_plan > 0.0) {
L += N * (0.01 + distance_to_plan);
shadow_dir += N * (0.01 + distance_to_plan);
}
return L;
return shadow_dir;
}
/* ---------------------------------------------------------------------- */
@ -328,10 +328,10 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, vec2 random_2d,
}
else {
float dist;
vec3 L = normalize_and_get_length(lP, dist);
vec3 shadow_dir = normalize_and_get_length(lP, dist);
/* Disk rotated towards light vector. */
vec3 right, up;
make_orthonormal_basis(L, right, up);
make_orthonormal_basis(shadow_dir, right, up);
float shape_radius = light_spot_data_get(light).radius;
if (is_sphere_light(light.type)) {
@ -403,14 +403,14 @@ SHADOW_MAP_TRACE_FN(ShadowRayPunctual)
/* Compute the world space offset of the shading position required for
* stochastic percentage closer filtering of shadow-maps. */
vec3 shadow_pcf_offset(
LightData light, const bool is_directional, vec3 L, vec3 P, vec3 Ng, vec2 random)
LightData light, const bool is_directional, vec3 shadow_dir, vec3 P, vec3 Ng, vec2 random)
{
if (light.pcf_radius <= 0.001) {
/* Early return. */
return vec3(0.0);
}
if (dot(L, Ng) < 0.001) {
if (dot(shadow_dir, Ng) < 0.001) {
/* Don't apply PCF to almost perpendicular,
* since we can't project the offset to the surface. */
return vec3(0.0);
@ -479,22 +479,22 @@ vec3 shadow_pcf_offset(
#ifdef GPU_NVIDIA
/* Workaround for a bug in the Nvidia shader compiler.
* If we don't compute L here again, it breaks shadows on reflection probes. */
L = shadow_vector_get(light, is_directional, P);
* If we don't compute shadow_dir here again, it breaks shadows on reflection probes. */
shadow_dir = shadow_vector_get(light, is_directional, P);
#endif
if (abs(dot(Ng, L)) > 0.999) {
if (abs(dot(Ng, shadow_dir)) > 0.999) {
return ws_offset;
}
offset_P = line_plane_intersect(offset_P, L, P, Ng);
offset_P = line_plane_intersect(offset_P, shadow_dir, P, Ng);
ws_offset = offset_P - P;
if (dot(ws_offset, L) < 0.0) {
if (dot(ws_offset, shadow_dir) < 0.0) {
/* Project the offset position into the perpendicular plane, since it's closer to the light
* (avoids overshadowing at geometry angles). */
vec3 perpendicular_plane_normal = cross(Ng, normalize(cross(Ng, L)));
offset_P = line_plane_intersect(offset_P, L, P, perpendicular_plane_normal);
vec3 perpendicular_plane_normal = cross(Ng, normalize(cross(Ng, shadow_dir)));
offset_P = line_plane_intersect(offset_P, shadow_dir, P, perpendicular_plane_normal);
ws_offset = offset_P - P;
}
@ -510,7 +510,8 @@ ShadowEvalResult shadow_eval(LightData light,
bool is_translucent_with_thickness,
vec3 P,
vec3 Ng,
vec3 L,
vec3 shadow_dir,
float shadow_offset,
int ray_count,
int ray_step_count)
{
@ -532,13 +533,15 @@ ShadowEvalResult shadow_eval(LightData light,
float normal_offset = 0.02;
#endif
P += shadow_pcf_offset(light, is_directional, L, P, Ng, random_pcf_2d);
P = is_transmission ? (P + shadow_dir * shadow_offset) : P;
P += shadow_pcf_offset(light, is_directional, shadow_dir, P, Ng, random_pcf_2d);
/* We want to bias inside the object for transmission to go through the object itself.
* But doing so split the shadow in two different directions at the horizon. Also this
* doesn't fix the the aliasing issue. So we reflect the normal so that it always go towards
* the light. */
vec3 N_bias = is_transmission ? reflect(Ng, L) : Ng;
vec3 N_bias = is_transmission ? reflect(Ng, shadow_dir) : Ng;
/* Avoid self intersection. */
P = offset_ray(P, N_bias);

View File

@ -50,29 +50,14 @@ vec4 as_vec4(Quaternion quat)
return vec4(quat.x, quat.y, quat.z, quat.w);
}
Quaternion Quaternion_identity()
{
return Quaternion(1, 0, 0, 0);
}
Quaternion as_quaternion(vec4 quat)
{
return Quaternion(quat.x, quat.y, quat.z, quat.w);
}
Quaternion invert(Quaternion quat)
Quaternion Quaternion_identity()
pragma37 marked this conversation as resolved Outdated

Move this function next to as_vec4 as it is a conversion function.

Move this function next to `as_vec4` as it is a conversion function.
{
quat.x *= -1.0;
quat.y *= -1.0;
quat.z *= -1.0;
return quat;
}
vec3 rotate(Quaternion quat, vec3 vec)
{
vec4 q = as_vec4(quat);
vec3 t = cross(q.xyz, vec) * 2.0;
return vec + t * q.w + cross(q.xyz, t);
return Quaternion(1, 0, 0, 0);
}
struct EulerXYZ {

Use same names as in BLI_math_quaternion.hh. This is conjugate and not the inverse. Also move to the /** \name Rotation Functions block. The top of the file is for type definitions.

Use same names as in `BLI_math_quaternion.hh`. This is `conjugate` and not the inverse. Also move to the `/** \name Rotation Functions` block. The top of the file is for type definitions.
Review

It's the inverse if we assume we only use unit quaternion.
The same happens with the rotate function, it assumes a unit quaternion.

I think it's a fair assumption (at least in the GPU code), but if it's not then it's a problem for both functions.

It's the inverse if we assume we only use unit quaternion. The same happens with the `rotate` function, it assumes a unit quaternion. I think it's a fair assumption (at least in the GPU code), but if it's not then it's a problem for both functions.
@ -133,6 +118,19 @@ Quaternion interpolate(Quaternion a, Quaternion b, float t)
return Quaternion(UNPACK4(quat));
}
Quaternion invert(Quaternion quat)
{
return Quaternion(-quat.x, -quat.y, -quat.z, quat.w);
}
vec3 rotate(Quaternion quat, vec3 vec)
{
/* https://fgiesen.wordpress.com/2019/02/09/rotating-a-single-vector-using-a-quaternion/ */
vec4 q = as_vec4(quat);
vec3 t = cross(q.xyz, vec) * 2.0;
return vec + t * q.w + cross(q.xyz, t);
}
Quaternion to_quaternion(EulerXYZ eul)
{
float ti = eul.x * 0.5;