EEVEE-Next: Jittered Soft Shadows #119753
|
@ -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
|
||||
((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);
|
||||
|
|
|
@ -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
Clément Foucault
commented
At this point, just pass Also prefer calling 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;
|
||||
|
|
|
@ -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)
|
||||
Clément Foucault
commented
Use Use `const bool is_directional` for all three functions.
Miguel Pozo
commented
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);
|
||||
|
|
|
@ -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
Clément Foucault
commented
Negate and rename 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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
Clément Foucault
commented
Move this function next to 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 {
|
||||
Clément Foucault
commented
Use same names as in 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.
Miguel Pozo
commented
It's the inverse if we assume we only use 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;
|
||||
|
|
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.