From e300217253048cebcd82b3c7098637ea74e2f11c Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Mon, 18 Oct 2021 17:08:06 +0200 Subject: [PATCH] Add a project setting to enable 3D shadow dithering This improves overall shadow quality at the cost of a grainy appearance when up close. Dithering is disabled by default to preserve the current appearance in existing projects, but it will be enabled by default in Godot 4.0. This setting works in both GLES3 and GLES2 and with any shadow filter mode (even Disabled). --- doc/classes/ProjectSettings.xml | 5 +- drivers/gles2/rasterizer_scene_gles2.cpp | 7 +++ drivers/gles2/rasterizer_scene_gles2.h | 2 + drivers/gles2/shaders/scene.glsl | 14 +++++ drivers/gles3/rasterizer_scene_gles3.cpp | 6 +++ drivers/gles3/rasterizer_scene_gles3.h | 2 + drivers/gles3/shaders/scene.glsl | 65 +++++++++++++----------- servers/visual_server.cpp | 1 + 8 files changed, 70 insertions(+), 32 deletions(-) diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b1f26cec6604..bb74949a81a1 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1801,12 +1801,15 @@ Lower-end override for [member rendering/quality/shadow_atlas/size] on mobile devices, due to performance concerns or driver support. - Shadow filter mode. Higher-quality settings result in smoother shadows that flicker less when moving. "Disabled" is the fastest option, but also has the lowest quality. "PCF5" is smoother but is also slower. "PCF13" is the smoothest option, but is also the slowest. + Shadow filter mode in 3D. Higher-quality settings result in smoother shadows that flicker less when moving. "Disabled" is the fastest option, but also has the lowest quality. "PCF5" is smoother but is also slower. "PCF13" is the smoothest option, but is also the slowest. See also [member rendering/quality/shadows/use_dithering]. [b]Note:[/b] When using the GLES2 backend, the "PCF13" option actually uses 16 samples to emulate linear filtering in the shader. This results in a shadow appearance similar to the one produced by the GLES3 backend. Lower-end override for [member rendering/quality/shadows/filter_mode] on mobile devices, due to performance concerns or driver support. + + If [code]true[/code], enables dithering for 3D shadows. Dithering improves overall shadow quality, but introduces a grainy effect that can be noticeable in scenes with large flat color areas. This dithering effect becomes less noticeable as textures become more complex and as the 3D rendering resolution increases. Disabling dithering can also slightly improve shadow rendering performance. See also [member rendering/quality/shadows/filter_mode]. + Forces [MeshInstance] to always perform skinning on the CPU (applies to both GLES2 and GLES3). See also [member rendering/quality/skinning/software_skinning_fallback]. diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index c685deddabf4..9d4a20b3f3d1 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -1911,6 +1911,7 @@ void RasterizerSceneGLES2::_setup_light_type(LightInstance *p_light, ShadowAtlas state.scene_shader.set_conditional(SceneShaderGLES2::USE_SHADOW, false); state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_5, false); state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_13, false); + state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_USE_DITHERING, false); state.scene_shader.set_conditional(SceneShaderGLES2::LIGHT_MODE_DIRECTIONAL, false); state.scene_shader.set_conditional(SceneShaderGLES2::LIGHT_MODE_OMNI, false); state.scene_shader.set_conditional(SceneShaderGLES2::LIGHT_MODE_SPOT, false); @@ -1952,6 +1953,7 @@ void RasterizerSceneGLES2::_setup_light_type(LightInstance *p_light, ShadowAtlas } state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_5, shadow_filter_mode == SHADOW_FILTER_PCF5); state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_13, shadow_filter_mode == SHADOW_FILTER_PCF13); + state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_USE_DITHERING, shadow_use_dithering); } } break; @@ -1967,6 +1969,7 @@ void RasterizerSceneGLES2::_setup_light_type(LightInstance *p_light, ShadowAtlas } state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_5, shadow_filter_mode == SHADOW_FILTER_PCF5); state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_13, shadow_filter_mode == SHADOW_FILTER_PCF13); + state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_USE_DITHERING, shadow_use_dithering); } } break; case VS::LIGHT_SPOT: { @@ -1981,6 +1984,7 @@ void RasterizerSceneGLES2::_setup_light_type(LightInstance *p_light, ShadowAtlas } state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_5, shadow_filter_mode == SHADOW_FILTER_PCF5); state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_MODE_PCF_13, shadow_filter_mode == SHADOW_FILTER_PCF13); + state.scene_shader.set_conditional(SceneShaderGLES2::SHADOW_USE_DITHERING, shadow_use_dithering); } } break; } @@ -4103,6 +4107,7 @@ void RasterizerSceneGLES2::initialize() { } shadow_filter_mode = SHADOW_FILTER_NEAREST; + shadow_use_dithering = false; glFrontFace(GL_CW); } @@ -4115,6 +4120,8 @@ void RasterizerSceneGLES2::iteration() { directional_shadow_size = directional_shadow_size_new; directional_shadow_create(); } + + shadow_use_dithering = bool(GLOBAL_GET("rendering/quality/shadows/use_dithering")); } void RasterizerSceneGLES2::finalize() { diff --git a/drivers/gles2/rasterizer_scene_gles2.h b/drivers/gles2/rasterizer_scene_gles2.h index 7e5976d948b2..9f328a26ebd7 100644 --- a/drivers/gles2/rasterizer_scene_gles2.h +++ b/drivers/gles2/rasterizer_scene_gles2.h @@ -66,6 +66,8 @@ class RasterizerSceneGLES2 : public RasterizerScene { ShadowFilterMode shadow_filter_mode; + bool shadow_use_dithering; + RID default_material; RID default_material_twosided; RID default_shader; diff --git a/drivers/gles2/shaders/scene.glsl b/drivers/gles2/shaders/scene.glsl index a41cda80e1f7..7919addaabe9 100644 --- a/drivers/gles2/shaders/scene.glsl +++ b/drivers/gles2/shaders/scene.glsl @@ -1500,9 +1500,23 @@ LIGHT_SHADER_CODE #define SAMPLE_SHADOW_TEXEL(p_shadow, p_pos, p_depth) step(p_depth, SHADOW_DEPTH(texture2D(p_shadow, p_pos))) +#ifdef SHADOW_USE_DITHERING +// Interleaved Gradient Noise +// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare +float quick_hash(vec2 pos) { + const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f); + return fract(magic.z * fract(dot(pos, magic.xy))); +} +#endif + float sample_shadow(highp sampler2D shadow, highp vec4 spos) { spos.xyz /= spos.w; +#ifdef SHADOW_USE_DITHERING + vec2 dither = (-vec2(0.5) + quick_hash(gl_FragCoord.xy)) * shadow_pixel_size; + vec2 pos = spos.xy + dither; +#else vec2 pos = spos.xy; +#endif float depth = spos.z; #ifdef SHADOW_MODE_PCF_13 diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 769e4eed5024..a6eeb6e65e84 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -2045,6 +2045,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::LIGHT_USE_PSSM_BLEND, false); state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_5, false); state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false); + state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_USE_DITHERING, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false); @@ -2071,6 +2072,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::LIGHT_USE_PSSM_BLEND, false); state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_5, shadow_filter_mode == SHADOW_FILTER_PCF5); state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, shadow_filter_mode == SHADOW_FILTER_PCF13); + state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_USE_DITHERING, shadow_use_dithering); state.scene_shader.set_conditional(SceneShaderGLES3::USE_RADIANCE_MAP, use_radiance_map); state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, state.used_contact_shadows); @@ -2232,6 +2234,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::SHADELESS, false); state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_5, false); state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false); + state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_USE_DITHERING, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_LAYERED, false); @@ -5187,6 +5190,7 @@ void RasterizerSceneGLES3::initialize() { } shadow_filter_mode = SHADOW_FILTER_NEAREST; + shadow_use_dithering = false; { //reflection cubemaps int max_reflection_cubemap_sampler_size = 512; @@ -5334,6 +5338,8 @@ void RasterizerSceneGLES3::iteration() { directional_shadow_create(); } + shadow_use_dithering = bool(GLOBAL_GET("rendering/quality/shadows/use_dithering")); + subsurface_scatter_follow_surface = GLOBAL_GET("rendering/quality/subsurface_scattering/follow_surface"); subsurface_scatter_weight_samples = GLOBAL_GET("rendering/quality/subsurface_scattering/weight_samples"); subsurface_scatter_quality = SubSurfaceScatterQuality(int(GLOBAL_GET("rendering/quality/subsurface_scattering/quality"))); diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 4263da657b3f..fe648617f28c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -56,6 +56,8 @@ class RasterizerSceneGLES3 : public RasterizerScene { ShadowFilterMode shadow_filter_mode; + bool shadow_use_dithering; + uint64_t shadow_atlas_realloc_tolerance_msec; enum SubSurfaceScatterQuality { diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index b0134c8a2581..9ee56e897ed0 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -1260,49 +1260,52 @@ LIGHT_SHADER_CODE #endif //defined(USE_LIGHT_SHADER_CODE) } -float sample_shadow(highp sampler2DShadow shadow, vec2 shadow_pixel_size, vec2 pos, float depth, vec4 clamp_rect) { -#ifdef SHADOW_MODE_PCF_13 //ubershader-runtime +#ifdef SHADOW_USE_DITHERING +// Interleaved Gradient Noise +// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare +float quick_hash(vec2 pos) { + const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f); + return fract(magic.z * fract(dot(pos, magic.xy))); +} +#endif - float avg = textureProj(shadow, vec4(pos + vec2(shadow_pixel_size.x * 2.0, 0.0), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(-shadow_pixel_size.x * 2.0, 0.0), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(0.0, shadow_pixel_size.y * 2.0), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(0.0, -shadow_pixel_size.y * 2.0), depth, 1.0)); - // Early bail if distant samples are fully shaded (or none are shaded) to improve performance. - if (avg <= 0.000001) { - // None shaded at all. - return 0.0; - } else if (avg >= 3.999999) { - // All fully shaded. - return 1.0; - } +float sample_shadow(highp sampler2DShadow shadow, vec2 shadow_pixel_size, vec2 pos, float depth, vec4 clamp_rect) { +#ifdef SHADOW_USE_DITHERING + vec2 dither = (-vec2(0.5) + quick_hash(gl_FragCoord.xy)) * shadow_pixel_size; +#else + vec2 dither = vec2(0.0); +#endif - avg += textureProj(shadow, vec4(pos, depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(shadow_pixel_size.x, 0.0), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(-shadow_pixel_size.x, 0.0), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(0.0, shadow_pixel_size.y), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(0.0, -shadow_pixel_size.y), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(shadow_pixel_size.x, shadow_pixel_size.y), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(-shadow_pixel_size.x, shadow_pixel_size.y), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(shadow_pixel_size.x, -shadow_pixel_size.y), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(-shadow_pixel_size.x, -shadow_pixel_size.y), depth, 1.0)); +#ifdef SHADOW_MODE_PCF_13 //ubershader-runtime + float avg = textureProj(shadow, vec4(pos + dither, depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(shadow_pixel_size.x, 0.0), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(-shadow_pixel_size.x, 0.0), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(0.0, shadow_pixel_size.y), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(0.0, -shadow_pixel_size.y), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(shadow_pixel_size.x, shadow_pixel_size.y), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(-shadow_pixel_size.x, shadow_pixel_size.y), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(shadow_pixel_size.x, -shadow_pixel_size.y), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(-shadow_pixel_size.x, -shadow_pixel_size.y), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(shadow_pixel_size.x * 2.0, 0.0), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(-shadow_pixel_size.x * 2.0, 0.0), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(0.0, shadow_pixel_size.y * 2.0), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(0.0, -shadow_pixel_size.y * 2.0), depth, 1.0)); return avg * (1.0 / 13.0); #endif //ubershader-runtime #ifdef SHADOW_MODE_PCF_5 //ubershader-runtime - - float avg = textureProj(shadow, vec4(pos, depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(shadow_pixel_size.x, 0.0), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(-shadow_pixel_size.x, 0.0), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(0.0, shadow_pixel_size.y), depth, 1.0)); - avg += textureProj(shadow, vec4(pos + vec2(0.0, -shadow_pixel_size.y), depth, 1.0)); + float avg = textureProj(shadow, vec4(pos + dither, depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(shadow_pixel_size.x, 0.0), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(-shadow_pixel_size.x, 0.0), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(0.0, shadow_pixel_size.y), depth, 1.0)); + avg += textureProj(shadow, vec4(pos + dither + vec2(0.0, -shadow_pixel_size.y), depth, 1.0)); return avg * (1.0 / 5.0); #endif //ubershader-runtime #ifndef SHADOW_MODE_PCF_5 //ubershader-runtime #ifndef SHADOW_MODE_PCF_13 //ubershader-runtime - - return textureProj(shadow, vec4(pos, depth, 1.0)); + return textureProj(shadow, vec4(pos + dither, depth, 1.0)); #endif //ubershader-runtime #endif //ubershader-runtime diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 7a2e00cb9643..8bb012d80917 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -2656,6 +2656,7 @@ VisualServer::VisualServer() { GLOBAL_DEF("rendering/quality/shadows/filter_mode", 1); GLOBAL_DEF("rendering/quality/shadows/filter_mode.mobile", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/shadows/filter_mode", PropertyInfo(Variant::INT, "rendering/quality/shadows/filter_mode", PROPERTY_HINT_ENUM, "Disabled,PCF5,PCF13")); + GLOBAL_DEF("rendering/quality/shadows/use_dithering", false); GLOBAL_DEF("rendering/quality/reflections/texture_array_reflections", true); GLOBAL_DEF("rendering/quality/reflections/texture_array_reflections.mobile", false);