Improvements to SpotLight3D and OmniLight3D's shadows

OmniLight3D:
* Fixed lack of precision in cube map mode by scaling the projection's
  znear.
* Fixed aliasing issues by making the paraboloids use two square regions instead of two half
  squares.
* Fixed shadowmap atlas bleeding by adding padding.
* Fixed sihadow blur's inconsistent radius and unclamped sampling.

SpotLight3D:
* Fixed lack of precision by scaling the projection's znear.
* Fixed normal biasing.

Both:
* Tweaked biasing to make sure it works out of the box in most situations.
This commit is contained in:
jfons 2021-08-04 17:18:06 +02:00
parent 8b6c168b3a
commit 55e7832d7b
12 changed files with 308 additions and 172 deletions

View file

@ -7,8 +7,7 @@
layout(push_constant, binding = 1, std430) uniform Params {
float z_far;
float z_near;
bool z_flip;
uint pad;
vec2 texel_size;
vec4 screen_rect;
}
params;
@ -35,22 +34,23 @@ layout(set = 0, binding = 0) uniform samplerCube source_cube;
layout(push_constant, binding = 1, std430) uniform Params {
float z_far;
float z_near;
bool z_flip;
uint pad;
vec2 texel_size;
vec4 screen_rect;
}
params;
void main() {
vec2 uv = uv_interp;
vec2 texel_size = abs(params.texel_size);
uv = clamp(uv * (1.0 + 2.0 * texel_size) - texel_size, vec2(0.0), vec2(1.0));
vec3 normal = vec3(uv * 2.0 - 1.0, 0.0);
normal.z = 0.5 - 0.5 * ((normal.x * normal.x) + (normal.y * normal.y));
normal.z = 0.5 * (1.0 - dot(normal.xy, normal.xy)); // z = 1/2 - 1/2 * (x^2 + y^2)
normal = normalize(normal);
normal.y = -normal.y; //needs to be flipped to match projection matrix
if (!params.z_flip) {
if (params.texel_size.x >= 0.0) { // Sign is used to encode Z flip
normal.z = -normal.z;
}

View file

@ -1601,7 +1601,7 @@ void main() {
continue; // Statically baked light and object uses lightmap, skip
}
float shadow = light_process_omni_shadow(light_index, vertex, view);
float shadow = light_process_omni_shadow(light_index, vertex, normal);
shadow = blur_shadow(shadow);
@ -1677,7 +1677,7 @@ void main() {
continue; // Statically baked light and object uses lightmap, skip
}
float shadow = light_process_spot_shadow(light_index, vertex, view);
float shadow = light_process_spot_shadow(light_index, vertex, normal);
shadow = blur_shadow(shadow);

View file

@ -279,7 +279,7 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float atte
}
#ifdef USE_SHADOW_TO_OPACITY
alpha = min(alpha, clamp(1.0 - attenuation), 0.0, 1.0));
alpha = min(alpha, clamp(1.0 - attenuation, 0.0, 1.0));
#endif
#endif //defined(LIGHT_CODE_USED)
@ -320,7 +320,7 @@ float sample_directional_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, ve
return avg * (1.0 / float(sc_directional_soft_shadow_samples));
}
float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) {
float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec3 coord) {
vec2 pos = coord.xy;
float depth = coord.z;
@ -346,6 +346,49 @@ float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) {
return avg * (1.0 / float(sc_soft_shadow_samples));
}
float sample_omni_pcf_shadow(texture2D shadow, float blur_scale, vec2 coord, vec4 uv_rect, vec2 flip_offset, float depth) {
//if only one sample is taken, take it from the center
if (sc_soft_shadow_samples == 1) {
vec2 pos = coord * 0.5 + 0.5;
pos = uv_rect.xy + pos * uv_rect.zw;
return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
}
mat2 disk_rotation;
{
float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI;
float sr = sin(r);
float cr = cos(r);
disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
}
float avg = 0.0;
vec2 offset_scale = blur_scale * 2.0 * scene_data.shadow_atlas_pixel_size / uv_rect.zw;
for (uint i = 0; i < sc_soft_shadow_samples; i++) {
vec2 offset = offset_scale * (disk_rotation * scene_data.soft_shadow_kernel[i].xy);
vec2 sample_coord = coord + offset;
float sample_coord_length_sqaured = dot(sample_coord, sample_coord);
bool do_flip = sample_coord_length_sqaured > 1.0;
if (do_flip) {
float len = sqrt(sample_coord_length_sqaured);
sample_coord = sample_coord * (2.0 / len - 1.0);
}
sample_coord = sample_coord * 0.5 + 0.5;
sample_coord = uv_rect.xy + sample_coord * uv_rect.zw;
if (do_flip) {
sample_coord += flip_offset;
}
avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(sample_coord, depth, 1.0));
}
return avg * (1.0 / float(sc_soft_shadow_samples));
}
float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex_scale) {
//find blocker
float blocker_count = 0.0;
@ -403,15 +446,21 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
#ifndef USE_NO_SHADOWS
if (omni_lights.data[idx].shadow_enabled) {
// there is a shadowmap
vec2 texel_size = scene_data.shadow_atlas_pixel_size;
vec4 base_uv_rect = omni_lights.data[idx].atlas_rect;
base_uv_rect.xy += texel_size;
base_uv_rect.zw -= texel_size * 2.0;
vec3 light_rel_vec = omni_lights.data[idx].position - vertex;
float light_length = length(light_rel_vec);
// Omni lights use direction.xy to store to store the offset between the two paraboloid regions
vec2 flip_offset = omni_lights.data[idx].direction.xy;
vec4 v = vec4(vertex, 1.0);
vec3 local_vert = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz;
vec4 splane = (omni_lights.data[idx].shadow_matrix * v);
float shadow_len = length(local_vert); //need to remember shadow len from here
vec3 shadow_dir = normalize(local_vert);
float shadow_len = length(splane.xyz); //need to remember shadow len from here
vec3 local_normal = normalize(mat3(omni_lights.data[idx].shadow_matrix) * normal);
vec3 normal_bias = local_normal * omni_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(local_normal, shadow_dir)));
float shadow;
@ -431,10 +480,10 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
}
vec3 normal = normalize(splane.xyz);
vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
vec3 tangent = normalize(cross(v0, normal));
vec3 bitangent = normalize(cross(tangent, normal));
vec3 basis_normal = shadow_dir;
vec3 v0 = abs(basis_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);
vec3 tangent = normalize(cross(v0, basis_normal));
vec3 bitangent = normalize(cross(tangent, basis_normal));
float z_norm = shadow_len * omni_lights.data[idx].inv_radius;
tangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale;
@ -443,18 +492,17 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy;
vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y;
vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y;
pos = normalize(pos);
vec4 uv_rect = omni_lights.data[idx].atlas_rect;
vec4 uv_rect = base_uv_rect;
if (pos.z >= 0.0) {
pos.z += 1.0;
uv_rect.y += uv_rect.w;
} else {
pos.z = 1.0 - pos.z;
uv_rect.xy += flip_offset;
}
pos.z = 1.0 + abs(pos.z);
pos.xy /= pos.z;
pos.xy = pos.xy * 0.5 + 0.5;
@ -479,18 +527,18 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
shadow = 0.0;
for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy;
vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y;
vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y;
pos = normalize(pos);
vec4 uv_rect = omni_lights.data[idx].atlas_rect;
pos = normalize(pos + normal_bias);
vec4 uv_rect = base_uv_rect;
if (pos.z >= 0.0) {
pos.z += 1.0;
uv_rect.y += uv_rect.w;
} else {
pos.z = 1.0 - pos.z;
uv_rect.xy += flip_offset;
}
pos.z = 1.0 + abs(pos.z);
pos.xy /= pos.z;
pos.xy = pos.xy * 0.5 + 0.5;
@ -505,26 +553,19 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
shadow = 1.0;
}
} else {
splane.xyz = normalize(splane.xyz);
vec4 clamp_rect = omni_lights.data[idx].atlas_rect;
vec4 uv_rect = base_uv_rect;
if (splane.z >= 0.0) {
splane.z += 1.0;
clamp_rect.y += clamp_rect.w;
} else {
splane.z = 1.0 - splane.z;
vec3 shadow_sample = normalize(shadow_dir + normal_bias);
if (shadow_sample.z >= 0.0) {
uv_rect.xy += flip_offset;
flip_offset *= -1.0;
}
splane.xy /= splane.z;
splane.xy = splane.xy * 0.5 + 0.5;
splane.z = shadow_len * omni_lights.data[idx].inv_radius;
splane.z -= omni_lights.data[idx].shadow_bias;
splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw;
splane.w = 1.0; //needed? i think it should be 1 already
shadow = sample_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, splane);
shadow_sample.z = 1.0 + abs(shadow_sample.z);
vec2 pos = shadow_sample.xy / shadow_sample.z;
float depth = shadow_len - omni_lights.data[idx].shadow_bias;
depth *= omni_lights.data[idx].inv_radius;
shadow = sample_omni_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale / shadow_sample.z, pos, uv_rect, flip_offset, depth);
}
return shadow;
@ -608,13 +649,11 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
vec4 atlas_rect = omni_lights.data[idx].projector_rect;
if (local_v.z >= 0.0) {
local_v.z += 1.0;
atlas_rect.y += atlas_rect.w;
} else {
local_v.z = 1.0 - local_v.z;
}
local_v.z = 1.0 + abs(local_v.z);
local_v.xy /= local_v.z;
local_v.xy = local_v.xy * 0.5 + 0.5;
vec2 proj_uv = local_v.xy * atlas_rect.zw;
@ -694,15 +733,18 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
vec3 light_rel_vec = spot_lights.data[idx].position - vertex;
float light_length = length(light_rel_vec);
vec3 spot_dir = spot_lights.data[idx].direction;
//there is a shadowmap
vec4 v = vec4(vertex, 1.0);
float shadow;
vec3 shadow_dir = light_rel_vec / light_length;
vec3 normal_bias = normal * light_length * spot_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(normal, shadow_dir)));
//there is a shadowmap
vec4 v = vec4(vertex + normal_bias, 1.0);
vec4 splane = (spot_lights.data[idx].shadow_matrix * v);
splane.z -= spot_lights.data[idx].shadow_bias / (light_length * spot_lights.data[idx].inv_radius);
splane /= splane.w;
splane.z -= spot_lights.data[idx].shadow_bias;
float shadow;
if (sc_use_light_soft_shadows && spot_lights.data[idx].soft_shadow_size > 0.0) {
//soft shadow
@ -753,11 +795,9 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
//no blockers found, so no shadow
shadow = 1.0;
}
} else {
//hard shadow
vec4 shadow_uv = vec4(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z, 1.0);
vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z);
shadow = sample_pcf_shadow(shadow_atlas, spot_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, shadow_uv);
}

View file

@ -1387,7 +1387,7 @@ void main() {
break;
}
float shadow = light_process_omni_shadow(light_index, vertex, view);
float shadow = light_process_omni_shadow(light_index, vertex, normal);
shadow = blur_shadow(shadow);
@ -1435,7 +1435,7 @@ void main() {
break;
}
float shadow = light_process_spot_shadow(light_index, vertex, view);
float shadow = light_process_spot_shadow(light_index, vertex, normal);
shadow = blur_shadow(shadow);