From 8f4f4e8649e34cf7f6b90d61674305ada4f4e2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Sun, 28 Jul 2024 13:42:12 +0200 Subject: [PATCH] soft shadow disk sampling (#906) --- Editor/GraphicsWindow.cpp | 19 - Editor/GraphicsWindow.h | 1 - WickedEngine/shaders/ShaderInterop_Renderer.h | 2 +- WickedEngine/shaders/globals.hlsli | 25 ++ WickedEngine/shaders/lightingHF.hlsli | 350 +++++++++--------- WickedEngine/shaders/shadowHF.hlsli | 87 +++-- .../volumetricLight_DirectionalPS.hlsl | 3 +- .../shaders/volumetricLight_PointPS.hlsl | 3 +- .../shaders/volumetricLight_SpotPS.hlsl | 3 +- WickedEngine/wiRenderer.cpp | 17 +- WickedEngine/wiRenderer.h | 3 - WickedEngine/wiVersion.cpp | 2 +- 12 files changed, 262 insertions(+), 253 deletions(-) diff --git a/Editor/GraphicsWindow.cpp b/Editor/GraphicsWindow.cpp index 5a6eecc11..63192a91c 100644 --- a/Editor/GraphicsWindow.cpp +++ b/Editor/GraphicsWindow.cpp @@ -415,22 +415,6 @@ void GraphicsWindow::Create(EditorComponent* _editor) }); AddWidget(&speedMultiplierSlider); - transparentShadowsCheckBox.Create("Transparent Shadows: "); - transparentShadowsCheckBox.SetTooltip("Enables color tinted shadows and refraction caustics effects for transparent objects and water."); - transparentShadowsCheckBox.SetPos(XMFLOAT2(x, y += step)); - transparentShadowsCheckBox.SetSize(XMFLOAT2(itemheight, itemheight)); - if (editor->main->config.GetSection("graphics").Has("transparent_shadows")) - { - wi::renderer::SetTransparentShadowsEnabled(editor->main->config.GetSection("graphics").GetBool("transparent_shadows")); - } - transparentShadowsCheckBox.SetCheck(wi::renderer::GetTransparentShadowsEnabled()); - transparentShadowsCheckBox.OnClick([=](wi::gui::EventArgs args) { - wi::renderer::SetTransparentShadowsEnabled(args.bValue); - editor->main->config.GetSection("graphics").Set("transparent_shadows", args.bValue); - editor->main->config.Commit(); - }); - AddWidget(&transparentShadowsCheckBox); - shadowTypeComboBox.Create("Shadow type: "); shadowTypeComboBox.SetSize(XMFLOAT2(wid, itemheight)); shadowTypeComboBox.SetPos(XMFLOAT2(x, y += step)); @@ -1685,7 +1669,6 @@ void GraphicsWindow::ResizeLayout() occlusionCullingCheckBox.SetVisible(false); visibilityComputeShadingCheckBox.SetVisible(false); tessellationCheckBox.SetVisible(false); - transparentShadowsCheckBox.SetVisible(false); } else { @@ -1702,7 +1685,6 @@ void GraphicsWindow::ResizeLayout() occlusionCullingCheckBox.SetVisible(true); visibilityComputeShadingCheckBox.SetVisible(true); tessellationCheckBox.SetVisible(true); - transparentShadowsCheckBox.SetVisible(true); add(shadowTypeComboBox); add(shadowProps2DComboBox); @@ -1717,7 +1699,6 @@ void GraphicsWindow::ResizeLayout() add_right(occlusionCullingCheckBox); add_right(visibilityComputeShadingCheckBox); add_right(tessellationCheckBox); - add_right(transparentShadowsCheckBox); } y += jump; diff --git a/Editor/GraphicsWindow.h b/Editor/GraphicsWindow.h index 80e627f7c..a23d9af94 100644 --- a/Editor/GraphicsWindow.h +++ b/Editor/GraphicsWindow.h @@ -35,7 +35,6 @@ public: wi::gui::Slider vxgiRayStepSizeSlider; wi::gui::Slider vxgiMaxDistanceSlider; wi::gui::Slider speedMultiplierSlider; - wi::gui::CheckBox transparentShadowsCheckBox; wi::gui::ComboBox shadowTypeComboBox; wi::gui::ComboBox shadowProps2DComboBox; wi::gui::ComboBox shadowPropsCubeComboBox; diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h index 3f5f28f9c..867f95d42 100644 --- a/WickedEngine/shaders/ShaderInterop_Renderer.h +++ b/WickedEngine/shaders/ShaderInterop_Renderer.h @@ -918,7 +918,7 @@ static const int impostorCaptureAngles = 36; enum FRAME_OPTIONS { OPTION_BIT_TEMPORALAA_ENABLED = 1 << 0, - OPTION_BIT_TRANSPARENTSHADOWS_ENABLED = 1 << 1, + //OPTION_BIT_TRANSPARENTSHADOWS_ENABLED = 1 << 1, OPTION_BIT_VXGI_ENABLED = 1 << 2, OPTION_BIT_VXGI_REFLECTIONS_ENABLED = 1 << 3, OPTION_BIT_REALISTIC_SKY = 1 << 6, diff --git a/WickedEngine/shaders/globals.hlsli b/WickedEngine/shaders/globals.hlsli index 4ee72a57c..931256e3d 100644 --- a/WickedEngine/shaders/globals.hlsli +++ b/WickedEngine/shaders/globals.hlsli @@ -708,6 +708,7 @@ inline uint2 GetInternalResolution() { return GetCamera().internal_resolution; } inline float GetDeltaTime() { return GetFrame().delta_time; } inline float GetTime() { return GetFrame().time; } inline float GetTimePrev() { return GetFrame().time_previous; } +inline float GetFrameCount() { return GetFrame().frame_count; } inline uint2 GetTemporalAASampleRotation() { return uint2((GetFrame().temporalaa_samplerotation >> 0u) & 0x000000FF, (GetFrame().temporalaa_samplerotation >> 8) & 0x000000FF); } inline bool IsStaticSky() { return GetScene().globalenvmap >= 0; } @@ -1391,6 +1392,30 @@ inline half dither(in float2 pixel) return ditherMask8(pixel); } +// For every value of BayerMatrix8, this contains: half2(sin(value * 2 * PI), cos(value * 2 * PI)) +static const half2 BayerMatrix8_sincos[8][8] = { + {half2(0.096514, 0.995332),half2(-0.999708, 0.024164),half2(0.951057, 0.309017),half2(-0.377095, 0.926175),half2(0.377095, 0.926175),half2(-0.951056, 0.309017),half2(0.999708, 0.024164),half2(-0.096514, 0.995332),}, + {half2(-0.048314, -0.998832),half2(0.997373, -0.072435),half2(-0.935016, -0.354605),half2(0.331908, -0.943312),half2(-0.331908, -0.943312),half2(0.935016, -0.354605),half2(-0.997373, -0.072434),half2(0.048313, -0.998832),}, + {half2(0.764316, 0.644842),half2(-0.698511, 0.715599),half2(0.464723, 0.885456),half2(-0.916792, 0.399365),half2(0.916792, 0.399364),half2(-0.464723, 0.885456),half2(0.698511, 0.715599),half2(-0.764316, 0.644842),}, + {half2(-0.732269, -0.681016),half2(0.663123, -0.748511),half2(-0.421401, -0.906874),half2(0.896427, -0.443192),half2(-0.896427, -0.443191),half2(0.421401, -0.906874),half2(-0.663123, -0.748511),half2(0.732269, -0.681016),}, + {half2(0.285946, 0.958246),half2(-0.976441, 0.215784),half2(0.992709, 0.120537),half2(-0.192127, 0.981370),half2(0.192127, 0.981370),half2(-0.992709, 0.120537),half2(0.976441, 0.215784),half2(-0.285946, 0.958246),}, + {half2(-0.239316, -0.970942),half2(0.964876, -0.262708),half2(-0.985726, -0.168357),half2(0.144489, -0.989506),half2(-0.144489, -0.989506),half2(0.985726, -0.168357),half2(-0.964876, -0.262707),half2(0.239316, -0.970942),}, + {half2(0.873968, 0.485983),half2(-0.548012, 0.836470),half2(0.626185, 0.779674),half2(-0.822984, 0.568065),half2(0.822984, 0.568065),half2(-0.626185, 0.779675),half2(0.548013, 0.836470),half2(-0.873968, 0.485984),}, + {half2(-0.849468, -0.527640),half2(0.506960, -0.861970),half2(-0.587786, -0.809017),half2(0.794578, -0.607163),half2(-0.794578, -0.607162),half2(0.587785, -0.809017),half2(-0.506960, -0.861970),half2(0.849468, -0.527640),}, +}; +inline half2 dither_sincos(in float2 pixel) +{ + return BayerMatrix8_sincos[pixel.x % 8][pixel.y % 8]; +} +inline half2x2 dither_rot2x2(in float2 pixel) +{ + half2 sincos = dither_sincos(pixel); + return half2x2( + sincos.y, -sincos.x, + sincos.x, sincos.y + ); +} + // Quaternion multiplication // http://mathworld.wolfram.com/Quaternion.html float4 qmul(float4 q1, float4 q2) diff --git a/WickedEngine/shaders/lightingHF.hlsli b/WickedEngine/shaders/lightingHF.hlsli index 433e40bdc..a0e71817b 100644 --- a/WickedEngine/shaders/lightingHF.hlsli +++ b/WickedEngine/shaders/lightingHF.hlsli @@ -14,6 +14,14 @@ #define LIGHTING_SCATTER #endif // WATER +#if __SHADER_TARGET_STAGE == __SHADER_STAGE_PIXEL +// Average shadow within quad, this smooths out the dithering a bit: +// Note that I don't implement this in shadowHF.hlsli because we need to +// make sure that when averaging, all lanes in the quad are coherent +// It wouldn't be good if some waves are not sampling shadows or sampling different slices +#define SHADOW_QUAD_BLUR +#endif // __SHADER_STAGE_PIXEL + struct LightingPart { half3 diffuse; @@ -49,100 +57,100 @@ inline void ApplyLighting(in Surface surface, in Lighting lighting, inout half4 inline void light_directional(in ShaderEntity light, in Surface surface, inout Lighting lighting, in half shadow_mask = 1) { - half3 L = light.GetDirection(); + half3 shadow = shadow_mask; + [branch] + if (light.IsCastingShadow() && surface.IsReceiveShadow()) + { + if (GetFrame().options & OPTION_BIT_VOLUMETRICCLOUDS_CAST_SHADOW) + { + shadow *= shadow_2D_volumetricclouds(surface.P); + } + +#if defined(SHADOW_MASK_ENABLED) && !defined(TRANSPARENT) + [branch] + if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0 || (GetCamera().options & SHADERCAMERA_OPTION_USE_SHADOW_MASK) == 0) +#endif // SHADOW_MASK_ENABLED + { + // Loop through cascades from closest (smallest) to furthest (largest) + [loop] + for (uint cascade = 0; cascade < light.GetShadowCascadeCount(); ++cascade) + { + // Project into shadow map space (no need to divide by .w because ortho projection!): + const float4x4 cascade_projection = load_entitymatrix(light.GetMatrixIndex() + cascade); + const float3 shadow_pos = mul(cascade_projection, float4(surface.P, 1)).xyz; + const float3 shadow_uv = clipspace_to_uv(shadow_pos); + + // Determine if pixel is inside current cascade bounds and compute shadow if it is: + [branch] + if (is_saturated(shadow_uv)) + { + const half2 cascade_edgefactor = saturate(saturate(abs(shadow_pos.xy)) - 0.8) * 5.0; // fade will be on edge and inwards 10% + const half cascade_fade = max(cascade_edgefactor.x, cascade_edgefactor.y); + + // If we are on cascade edge threshold and not the last cascade, then fallback to a larger cascade: + [branch] + if (cascade_fade > 0 && dither(surface.pixel + GetTemporalAASampleRotation()) < cascade_fade) + continue; + + shadow *= shadow_2D(light, shadow_pos, shadow_uv.xy, cascade, surface.pixel); + break; + } + } + } + +#ifdef SHADOW_QUAD_BLUR + shadow = (shadow + QuadReadAcrossX(shadow) + QuadReadAcrossY(shadow) + QuadReadAcrossDiagonal(shadow)) / 4.0; +#endif // SHADOW_QUAD_BLUR + } + if(!any(shadow)) + return; + + half3 L = light.GetDirection(); SurfaceToLight surface_to_light; surface_to_light.create(surface, L); [branch] if (any(surface_to_light.NdotL_sss)) { - half3 shadow = shadow_mask; + half3 light_color = light.GetColor().rgb * shadow; [branch] - if (light.IsCastingShadow() && surface.IsReceiveShadow()) + if (GetFrame().options & OPTION_BIT_REALISTIC_SKY) { - if (GetFrame().options & OPTION_BIT_VOLUMETRICCLOUDS_CAST_SHADOW) - { - shadow *= shadow_2D_volumetricclouds(surface.P); - } - -#if defined(SHADOW_MASK_ENABLED) && !defined(TRANSPARENT) - [branch] - if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0 || (GetCamera().options & SHADERCAMERA_OPTION_USE_SHADOW_MASK) == 0) -#endif // SHADOW_MASK_ENABLED - { - // Loop through cascades from closest (smallest) to furthest (largest) - [loop] - for (uint cascade = 0; cascade < light.GetShadowCascadeCount(); ++cascade) - { - // Project into shadow map space (no need to divide by .w because ortho projection!): - const float4x4 cascade_projection = load_entitymatrix(light.GetMatrixIndex() + cascade); - const float3 shadow_pos = mul(cascade_projection, float4(surface.P, 1)).xyz; - const float3 shadow_uv = clipspace_to_uv(shadow_pos); - - // Determine if pixel is inside current cascade bounds and compute shadow if it is: - [branch] - if (is_saturated(shadow_uv)) - { - const half2 cascade_edgefactor = saturate(saturate(abs(shadow_pos.xy)) - 0.8) * 5.0; // fade will be on edge and inwards 10% - const half cascade_fade = max(cascade_edgefactor.x, cascade_edgefactor.y); - - // If we are on cascade edge threshold and not the last cascade, then fallback to a larger cascade: - [branch] - if (cascade_fade > 0 && dither(surface.pixel + GetTemporalAASampleRotation()) < cascade_fade) - continue; - - shadow *= shadow_2D(light, shadow_pos, shadow_uv.xy, cascade); - break; - } - } - } + light_color *= GetAtmosphericLightTransmittance(GetWeather().atmosphere, surface.P, L, texture_transmittancelut); } - [branch] - if (any(shadow)) - { - half3 light_color = light.GetColor().rgb * shadow; - - [branch] - if (GetFrame().options & OPTION_BIT_REALISTIC_SKY) - { - light_color *= GetAtmosphericLightTransmittance(GetWeather().atmosphere, surface.P, L, texture_transmittancelut); - } - - lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); - lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); + lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); + lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); #ifdef LIGHTING_SCATTER - const half scattering = ComputeScattering(saturate(dot(L, -surface.V))); - lighting.indirect.specular += scattering * light_color * (1 - surface.extinction) * (1 - sqr(1 - saturate(1 - surface.N.y))); + const half scattering = ComputeScattering(saturate(dot(L, -surface.V))); + lighting.indirect.specular += scattering * light_color * (1 - surface.extinction) * (1 - sqr(1 - saturate(1 - surface.N.y))); #endif // LIGHTING_SCATTER #ifndef WATER - // On non-water surfaces there can be procedural caustic if it's under ocean: - const ShaderOcean ocean = GetWeather().ocean; - if (ocean.texture_displacementmap >= 0) + // On non-water surfaces there can be procedural caustic if it's under ocean: + const ShaderOcean ocean = GetWeather().ocean; + if (ocean.texture_displacementmap >= 0) + { + Texture2D displacementmap = bindless_textures[ocean.texture_displacementmap]; + float2 ocean_uv = surface.P.xz * ocean.patch_size_rcp; + float3 displacement = displacementmap.SampleLevel(sampler_linear_wrap, ocean_uv, 0).xzy; + float water_height = ocean.water_height + displacement.y; + if (surface.P.y < water_height) { - Texture2D displacementmap = bindless_textures[ocean.texture_displacementmap]; - float2 ocean_uv = surface.P.xz * ocean.patch_size_rcp; - float3 displacement = displacementmap.SampleLevel(sampler_linear_wrap, ocean_uv, 0).xzy; - float water_height = ocean.water_height + displacement.y; - if (surface.P.y < water_height) - { - half3 caustic = texture_caustics.SampleLevel(sampler_linear_mirror, ocean_uv, 0).rgb; - caustic *= sqr(saturate((water_height - surface.P.y) * 0.5)); // fade out at shoreline - caustic *= light_color; - lighting.indirect.diffuse += caustic; + half3 caustic = texture_caustics.SampleLevel(sampler_linear_mirror, ocean_uv, 0).rgb; + caustic *= sqr(saturate((water_height - surface.P.y) * 0.5)); // fade out at shoreline + caustic *= light_color; + lighting.indirect.diffuse += caustic; - // fade out specular at depth, it looks weird when specular appears under ocean from wetmap - half water_depth = water_height - surface.P.y; - lighting.direct.specular *= saturate(exp(-water_depth * 10)); - } + // fade out specular at depth, it looks weird when specular appears under ocean from wetmap + half water_depth = water_height - surface.P.y; + lighting.direct.specular *= saturate(exp(-water_depth * 10)); } -#endif // WATER - } +#endif // WATER } } @@ -159,7 +167,25 @@ inline half attenuation_pointlight(in half dist2, in half range, in half range2) inline void light_point(in ShaderEntity light, in Surface surface, inout Lighting lighting, in half shadow_mask = 1) { float3 Lunnormalized = light.position - surface.P; - float3 LunnormalizedShadow = Lunnormalized; + half3 shadow = shadow_mask; + + [branch] + if (light.IsCastingShadow() && surface.IsReceiveShadow()) + { +#if defined(SHADOW_MASK_ENABLED) && !defined(TRANSPARENT) + [branch] + if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0 || (GetCamera().options & SHADERCAMERA_OPTION_USE_SHADOW_MASK) == 0) +#endif // SHADOW_MASK_ENABLED + { + shadow *= shadow_cube(light, Lunnormalized, surface.pixel); + } + +#ifdef SHADOW_QUAD_BLUR + shadow = (shadow + QuadReadAcrossX(shadow) + QuadReadAcrossY(shadow) + QuadReadAcrossDiagonal(shadow)) / 4.0; +#endif // SHADOW_QUAD_BLUR + } + if(!any(shadow)) + return; #ifndef DISABLE_AREA_LIGHTS if (light.GetLength() > 0) @@ -190,68 +216,50 @@ inline void light_point(in ShaderEntity light, in Surface surface, inout Lightin [branch] if (any(surface_to_light.NdotL_sss)) { - half3 shadow = shadow_mask; + half3 light_color = light.GetColor().rgb * shadow; + light_color *= attenuation_pointlight(dist2, range, range2); - [branch] - if (light.IsCastingShadow() && surface.IsReceiveShadow()) - { -#if defined(SHADOW_MASK_ENABLED) && !defined(TRANSPARENT) - [branch] - if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0 || (GetCamera().options & SHADERCAMERA_OPTION_USE_SHADOW_MASK) == 0) -#endif // SHADOW_MASK_ENABLED - { - shadow *= shadow_cube(light, LunnormalizedShadow); - } - } - - [branch] - if (any(shadow)) - { - half3 light_color = light.GetColor().rgb * shadow; - light_color *= attenuation_pointlight(dist2, range, range2); - - lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); + lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); #ifndef DISABLE_AREA_LIGHTS - if (light.GetLength() > 0) - { - // Specular representative point on line: - float3 P0 = light.position - light.GetDirection() * light.GetLength() * 0.5; - float3 P1 = light.position + light.GetDirection() * light.GetLength() * 0.5; - float3 L0 = P0 - surface.P; - float3 L1 = P1 - surface.P; - float3 Ld = L1 - L0; - float RdotLd = dot(surface.R, Ld); - float t = dot(surface.R, L0) * RdotLd - dot(L0, Ld); - t /= dot(Ld, Ld) - RdotLd * RdotLd; - Lunnormalized = (L0 + saturate(t) * Ld); - } - else - { - Lunnormalized = light.position - surface.P; - } - if(light.GetRadius() > 0) - { - // Specular representative point on sphere: - float3 centerToRay = mad(dot(Lunnormalized, surface.R), surface.R, -Lunnormalized); - Lunnormalized = mad(centerToRay, saturate(light.GetRadius() / length(centerToRay)), Lunnormalized); - // Energy conservation for radius: - light_color /= max(1, sphere_volume(light.GetRadius())); - } - if (light.GetLength() > 0 || light.GetRadius() > 0) - { - L = normalize(Lunnormalized); - surface_to_light.create(surface, L); // recompute all surface-light vectors - } + if (light.GetLength() > 0) + { + // Specular representative point on line: + float3 P0 = light.position - light.GetDirection() * light.GetLength() * 0.5; + float3 P1 = light.position + light.GetDirection() * light.GetLength() * 0.5; + float3 L0 = P0 - surface.P; + float3 L1 = P1 - surface.P; + float3 Ld = L1 - L0; + float RdotLd = dot(surface.R, Ld); + float t = dot(surface.R, L0) * RdotLd - dot(L0, Ld); + t /= dot(Ld, Ld) - RdotLd * RdotLd; + Lunnormalized = (L0 + saturate(t) * Ld); + } + else + { + Lunnormalized = light.position - surface.P; + } + if(light.GetRadius() > 0) + { + // Specular representative point on sphere: + float3 centerToRay = mad(dot(Lunnormalized, surface.R), surface.R, -Lunnormalized); + Lunnormalized = mad(centerToRay, saturate(light.GetRadius() / length(centerToRay)), Lunnormalized); + // Energy conservation for radius: + light_color /= max(1, sphere_volume(light.GetRadius())); + } + if (light.GetLength() > 0 || light.GetRadius() > 0) + { + L = normalize(Lunnormalized); + surface_to_light.create(surface, L); // recompute all surface-light vectors + } #endif // DISABLE_AREA_LIGHTS - lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); + lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); #ifdef LIGHTING_SCATTER - const half scattering = ComputeScattering(saturate(dot(L, -surface.V))); - lighting.indirect.specular += scattering * light_color * (1 - surface.extinction) * (1 - sqr(1 - saturate(1 - surface.N.y))); + const half scattering = ComputeScattering(saturate(dot(L, -surface.V))); + lighting.indirect.specular += scattering * light_color * (1 - surface.extinction) * (1 - sqr(1 - saturate(1 - surface.N.y))); #endif // LIGHTING_SCATTER - } } } } @@ -266,6 +274,33 @@ inline half attenuation_spotlight(in half dist2, in half range, in half range2, } inline void light_spot(in ShaderEntity light, in Surface surface, inout Lighting lighting, in half shadow_mask = 1) { + half3 shadow = shadow_mask; + + [branch] + if (light.IsCastingShadow() && surface.IsReceiveShadow()) + { +#if defined(SHADOW_MASK_ENABLED) && !defined(TRANSPARENT) + [branch] + if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0 || (GetCamera().options & SHADERCAMERA_OPTION_USE_SHADOW_MASK) == 0) +#endif // SHADOW_MASK_ENABLED + { + float4 shadow_pos = mul(load_entitymatrix(light.GetMatrixIndex() + 0), float4(surface.P, 1)); + shadow_pos.xyz /= shadow_pos.w; + float2 shadow_uv = clipspace_to_uv(shadow_pos.xy); + [branch] + if (is_saturated(shadow_uv)) + { + shadow *= shadow_2D(light, shadow_pos.xyz, shadow_uv.xy, 0, surface.pixel); + } + } + +#ifdef SHADOW_QUAD_BLUR + shadow = (shadow + QuadReadAcrossX(shadow) + QuadReadAcrossY(shadow) + QuadReadAcrossDiagonal(shadow)) / 4.0; +#endif // SHADOW_QUAD_BLUR + } + if(!any(shadow)) + return; + float3 Lunnormalized = light.position - surface.P; const half dist2 = dot(Lunnormalized, Lunnormalized); const half range = light.GetRange(); @@ -289,56 +324,31 @@ inline void light_spot(in ShaderEntity light, in Surface surface, inout Lighting [branch] if (spot_factor > spot_cutoff) { - half3 shadow = shadow_mask; + half3 light_color = light.GetColor().rgb * shadow; + light_color *= attenuation_spotlight(dist2, range, range2, spot_factor, light.GetAngleScale(), light.GetAngleOffset()); - [branch] - if (light.IsCastingShadow() && surface.IsReceiveShadow()) - { -#if defined(SHADOW_MASK_ENABLED) && !defined(TRANSPARENT) - [branch] - if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0 || (GetCamera().options & SHADERCAMERA_OPTION_USE_SHADOW_MASK) == 0) -#endif // SHADOW_MASK_ENABLED - { - float4 shadow_pos = mul(load_entitymatrix(light.GetMatrixIndex() + 0), float4(surface.P, 1)); - shadow_pos.xyz /= shadow_pos.w; - float2 shadow_uv = clipspace_to_uv(shadow_pos.xy); - [branch] - if (is_saturated(shadow_uv)) - { - shadow *= shadow_2D(light, shadow_pos.xyz, shadow_uv.xy, 0); - } - } - } - - [branch] - if (any(shadow)) - { - half3 light_color = light.GetColor().rgb * shadow; - light_color *= attenuation_spotlight(dist2, range, range2, spot_factor, light.GetAngleScale(), light.GetAngleOffset()); - - lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); + lighting.direct.diffuse = mad(light_color, BRDF_GetDiffuse(surface, surface_to_light), lighting.direct.diffuse); #ifndef DISABLE_AREA_LIGHTS - if (light.GetRadius() > 0) - { - // Specular representative point on sphere: - Lunnormalized = light.position - surface.P; - float3 centerToRay = mad(dot(Lunnormalized, surface.R), surface.R, -Lunnormalized); - Lunnormalized = mad(centerToRay, saturate(light.GetRadius() / length(centerToRay)), Lunnormalized); - L = normalize(Lunnormalized); - surface_to_light.create(surface, L); // recompute all surface-light vectors - // Energy conservation for radius: - light_color /= max(1, sphere_volume(light.GetRadius())); - } + if (light.GetRadius() > 0) + { + // Specular representative point on sphere: + Lunnormalized = light.position - surface.P; + float3 centerToRay = mad(dot(Lunnormalized, surface.R), surface.R, -Lunnormalized); + Lunnormalized = mad(centerToRay, saturate(light.GetRadius() / length(centerToRay)), Lunnormalized); + L = normalize(Lunnormalized); + surface_to_light.create(surface, L); // recompute all surface-light vectors + // Energy conservation for radius: + light_color /= max(1, sphere_volume(light.GetRadius())); + } #endif // DISABLE_AREA_LIGHTS - lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); + lighting.direct.specular = mad(light_color, BRDF_GetSpecular(surface, surface_to_light), lighting.direct.specular); #ifdef LIGHTING_SCATTER - const half scattering = ComputeScattering(saturate(dot(L, -surface.V))); - lighting.indirect.specular += scattering * light_color * (1 - surface.extinction) * (1 - sqr(1 - saturate(1 - surface.N.y))); + const half scattering = ComputeScattering(saturate(dot(L, -surface.V))); + lighting.indirect.specular += scattering * light_color * (1 - surface.extinction) * (1 - sqr(1 - saturate(1 - surface.N.y))); #endif // LIGHTING_SCATTER - } } } } diff --git a/WickedEngine/shaders/shadowHF.hlsli b/WickedEngine/shaders/shadowHF.hlsli index 2dad762c9..2c87d2ccc 100644 --- a/WickedEngine/shaders/shadowHF.hlsli +++ b/WickedEngine/shaders/shadowHF.hlsli @@ -2,75 +2,82 @@ #define WI_SHADOW_HF #include "globals.hlsli" -inline half3 sample_shadow(float2 uv, float cmp) -{ - [branch] - if (GetFrame().texture_shadowatlas_index < 0) - return 1; +// "Vogel disk" sampling pattern based on: https://github.com/corporateshark/poisson-disk-generator/blob/master/PoissonGenerator.h +// Baked values are remapped from [0, 1] range into [-1, 1] range by doing: value * 2 - 1 +static const float2 vogel_points[] = { + float2(0.353553, 0.000000), + float2(-0.451560, 0.413635), + float2(0.069174, -0.787537), + float2(0.569060, 0.742409), +}; +static const min16uint soft_shadow_sample_count = arraysize(vogel_points); +static const half soft_shadow_sample_count_rcp = rcp((half)soft_shadow_sample_count); + +inline half3 sample_shadow(float2 uv, float cmp, float4 uv_clamping, float spread, uint2 pixel) +{ Texture2D texture_shadowatlas = bindless_textures[GetFrame().texture_shadowatlas_index]; - half3 shadow = (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp).r; + Texture2D texture_shadowatlas_transparent = bindless_textures[GetFrame().texture_shadowatlas_transparent_index]; + + half3 shadow = 0; #ifndef DISABLE_SOFT_SHADOWMAP - // sample along a rectangle pattern around center: - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(-1, -1)).r; - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(-1, 0)).r; - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(-1, 1)).r; - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(0, -1)).r; - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(0, 1)).r; - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(1, -1)).r; - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(1, 0)).r; - shadow.x += (half)texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, uv, cmp, int2(1, 1)).r; - shadow = shadow.xxx / 9.0; + const float2 spread_offset = GetFrame().shadow_atlas_resolution_rcp.xy * (2 + spread * 8); // remap spread to try to match ray traced shadow result + const half2x2 rot = dither_rot2x2(pixel + GetTemporalAASampleRotation()); // per pixel rotation for every sample + for (min16uint i = 0; i < soft_shadow_sample_count; ++i) + { + float2 sample_uv = uv + mul(vogel_points[i], rot) * spread_offset; +#else + float2 sample_uv = uv; #endif // DISABLE_SOFT_SHADOWMAP -#ifndef DISABLE_TRANSPARENT_SHADOWMAP - [branch] - if (GetFrame().options & OPTION_BIT_TRANSPARENTSHADOWS_ENABLED && GetFrame().texture_shadowatlas_transparent_index) - { - Texture2D texture_shadowatlas_transparent = bindless_textures[GetFrame().texture_shadowatlas_transparent_index]; - half4 transparent_shadow = (half4)texture_shadowatlas_transparent.SampleLevel(sampler_linear_clamp, uv, 0); -#ifdef TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK - if (transparent_shadow.a > cmp) -#endif // TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK + sample_uv = clamp(sample_uv, uv_clamping.xy, uv_clamping.zw); + half3 pcf = texture_shadowatlas.SampleCmpLevelZero(sampler_cmp_depth, sample_uv, cmp).rrr; + if(pcf.x > 0) { - shadow *= transparent_shadow.rgb; + half4 transparent_shadow = texture_shadowatlas_transparent.SampleLevel(sampler_linear_clamp, sample_uv, 0); +#ifdef TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK + if (transparent_shadow.a > cmp) +#endif // TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK + { + pcf *= transparent_shadow.rgb; + } + shadow += pcf; } + +#ifndef DISABLE_SOFT_SHADOWMAP } -#endif //DISABLE_TRANSPARENT_SHADOWMAP + shadow *= soft_shadow_sample_count_rcp; +#endif // DISABLE_SOFT_SHADOWMAP return shadow; } // This is used to clamp the uvs to last texel center to avoid sampling on the border and overfiltering into a different shadow -inline void shadow_border_shrink(in ShaderEntity light, inout float2 shadow_uv) +inline float4 shadow_border_clamp(in ShaderEntity light, in float slice) { const float2 shadow_resolution = light.shadowAtlasMulAdd.xy * GetFrame().shadow_atlas_resolution; -#ifdef DISABLE_SOFT_SHADOWMAP - const float border_size = 0.5; -#else - const float border_size = 1.5; -#endif // DISABLE_SOFT_SHADOWMAP - shadow_uv = clamp(shadow_uv * shadow_resolution, border_size, shadow_resolution - border_size) / shadow_resolution; + const float border_size = 0.5 * GetFrame().shadow_atlas_resolution_rcp; + const float2 topleft = mad(float2(slice, 0), light.shadowAtlasMulAdd.xy, light.shadowAtlasMulAdd.zw) + border_size; + const float2 bottomright = mad(float2(slice + 1, 1), light.shadowAtlasMulAdd.xy, light.shadowAtlasMulAdd.zw) - border_size; + return float4(topleft, bottomright); } -inline half3 shadow_2D(in ShaderEntity light, in float3 shadow_pos, in float2 shadow_uv, in uint cascade) +inline half3 shadow_2D(in ShaderEntity light, in float3 shadow_pos, in float2 shadow_uv, in uint cascade, uint2 pixel = 0) { - shadow_border_shrink(light, shadow_uv); shadow_uv.x += cascade; shadow_uv = mad(shadow_uv, light.shadowAtlasMulAdd.xy, light.shadowAtlasMulAdd.zw); - return sample_shadow(shadow_uv, shadow_pos.z); + return sample_shadow(shadow_uv, shadow_pos.z, shadow_border_clamp(light, cascade), light.GetRadius(), pixel); } -inline half3 shadow_cube(in ShaderEntity light, in float3 Lunnormalized) +inline half3 shadow_cube(in ShaderEntity light, in float3 Lunnormalized, uint2 pixel = 0) { const float remapped_distance = light.GetCubemapDepthRemapNear() + light.GetCubemapDepthRemapFar() / (max(max(abs(Lunnormalized.x), abs(Lunnormalized.y)), abs(Lunnormalized.z)) * 0.989); // little bias to avoid artifact const float3 uv_slice = cubemap_to_uv(-Lunnormalized); float2 shadow_uv = uv_slice.xy; - shadow_border_shrink(light, shadow_uv); shadow_uv.x += uv_slice.z; shadow_uv = mad(shadow_uv, light.shadowAtlasMulAdd.xy, light.shadowAtlasMulAdd.zw); - return sample_shadow(shadow_uv, remapped_distance); + return sample_shadow(shadow_uv, remapped_distance, shadow_border_clamp(light, uv_slice.z), light.GetRadius(), pixel); } inline half shadow_2D_volumetricclouds(float3 P) diff --git a/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl b/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl index 8e0a24f32..ed972d52a 100644 --- a/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl +++ b/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl @@ -1,4 +1,3 @@ -#define DISABLE_SOFT_SHADOWMAP #define TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK // fix the lack of depth testing #include "volumetricLightHF.hlsli" #include "volumetricCloudsHF.hlsli" @@ -59,7 +58,7 @@ float4 main(VertexToPixel input) : SV_TARGET [branch] if (is_saturated(shadow_uv)) { - shadow *= shadow_2D(light, shadow_pos, shadow_uv.xy, cascade); + shadow *= shadow_2D(light, shadow_pos, shadow_uv.xy, cascade, input.pos.xy); break; } } diff --git a/WickedEngine/shaders/volumetricLight_PointPS.hlsl b/WickedEngine/shaders/volumetricLight_PointPS.hlsl index 9f4a26e67..09c543ed9 100644 --- a/WickedEngine/shaders/volumetricLight_PointPS.hlsl +++ b/WickedEngine/shaders/volumetricLight_PointPS.hlsl @@ -1,4 +1,3 @@ -#define DISABLE_SOFT_SHADOWMAP #define TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK // fix the lack of depth testing #include "volumetricLightHF.hlsli" #include "fogHF.hlsli" @@ -63,7 +62,7 @@ float4 main(VertexToPixel input) : SV_TARGET [branch] if (light.IsCastingShadow()) { - attenuation *= shadow_cube(light, Lunnormalized); + attenuation *= shadow_cube(light, Lunnormalized, input.pos.xy); } // Evaluate sample height for height fog calculation, given 0 for V: diff --git a/WickedEngine/shaders/volumetricLight_SpotPS.hlsl b/WickedEngine/shaders/volumetricLight_SpotPS.hlsl index 44359e5e0..6440277a8 100644 --- a/WickedEngine/shaders/volumetricLight_SpotPS.hlsl +++ b/WickedEngine/shaders/volumetricLight_SpotPS.hlsl @@ -1,4 +1,3 @@ -#define DISABLE_SOFT_SHADOWMAP #define TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK // fix the lack of depth testing #include "volumetricLightHF.hlsli" #include "fogHF.hlsli" @@ -112,7 +111,7 @@ float4 main(VertexToPixel input) : SV_TARGET [branch] if ((saturate(shadow_uv.x) == shadow_uv.x) && (saturate(shadow_uv.y) == shadow_uv.y)) { - attenuation *= shadow_2D(light, shadow_pos.xyz, shadow_uv.xy, 0); + attenuation *= shadow_2D(light, shadow_pos.xyz, shadow_uv.xy, 0, input.pos.xy); } } diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index cb2a86bf5..fad68c65d 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -96,7 +96,6 @@ void barrier_stack_flush(CommandList cmd) barrier_stack.clear(); } -bool TRANSPARENTSHADOWSENABLED = true; bool wireRender = false; bool debugBoneLines = false; bool debugPartitionTree = false; @@ -2069,7 +2068,7 @@ void SetUpStates() rs.front_counter_clockwise = true; rs.depth_bias = -1; rs.depth_bias_clamp = 0; - rs.slope_scaled_depth_bias = -2.0f; + rs.slope_scaled_depth_bias = -4.0f; rs.depth_clip_enable = true; rs.multisample_enable = false; rs.antialiased_line_enable = false; @@ -3757,10 +3756,6 @@ void UpdatePerFrameData( { frameCB.options |= OPTION_BIT_TEMPORALAA_ENABLED; } - if (GetTransparentShadowsEnabled()) - { - frameCB.options |= OPTION_BIT_TRANSPARENTSHADOWS_ENABLED; - } if (GetVXGIEnabled()) { frameCB.options |= OPTION_BIT_VXGI_ENABLED; @@ -5871,7 +5866,7 @@ void DrawShadowmaps( renderQueue.sort_opaque(); RenderMeshes(vis, renderQueue, RENDERPASS_SHADOW, FILTER_OPAQUE, cmd, 0, cascade_count); - if (GetTransparentShadowsEnabled() && transparentShadowsRequested) + if (transparentShadowsRequested) { RenderMeshes(vis, renderQueue, RENDERPASS_SHADOW, FILTER_TRANSPARENT | FILTER_WATER, cmd, 0, (uint32_t)cascade_count); } @@ -5880,7 +5875,7 @@ void DrawShadowmaps( if (!vis.visibleHairs.empty()) { cb.cameras[0].position = vis.camera->Eye; - for (uint32_t cascade = 0; cascade < cascade_count; ++cascade) + for (uint32_t cascade = 0; cascade < std::min(2u, cascade_count); ++cascade) { XMStoreFloat4x4(&cb.cameras[0].view_projection, shcams[cascade].view_projection); device->BindDynamicConstantBuffer(cb, CBSLOT_RENDERER_CAMERA, cmd); @@ -5975,7 +5970,7 @@ void DrawShadowmaps( renderQueue.sort_opaque(); RenderMeshes(vis, renderQueue, RENDERPASS_SHADOW, FILTER_OPAQUE, cmd); - if (GetTransparentShadowsEnabled() && transparentShadowsRequested) + if (transparentShadowsRequested) { RenderMeshes(vis, renderQueue, RENDERPASS_SHADOW, FILTER_TRANSPARENT | FILTER_WATER, cmd); } @@ -6112,7 +6107,7 @@ void DrawShadowmaps( renderQueue.sort_opaque(); RenderMeshes(vis, renderQueue, RENDERPASS_SHADOW, FILTER_OPAQUE, cmd, 0, camera_count); - if (GetTransparentShadowsEnabled() && transparentShadowsRequested) + if (transparentShadowsRequested) { RenderMeshes(vis, renderQueue, RENDERPASS_SHADOW, FILTER_TRANSPARENT | FILTER_WATER, cmd, 0, camera_count); } @@ -17504,8 +17499,6 @@ void AddDeferredBlockCompression(const wi::graphics::Texture& texture_src, const -void SetTransparentShadowsEnabled(bool value) { TRANSPARENTSHADOWSENABLED = value; } -float GetTransparentShadowsEnabled() { return TRANSPARENTSHADOWSENABLED; } void SetWireRender(bool value) { wireRender = value; } bool IsWireRender() { return wireRender; } void SetToDrawDebugBoneLines(bool param) { debugBoneLines = param; } diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h index 6cbf30a95..714969be0 100644 --- a/WickedEngine/wiRenderer.h +++ b/WickedEngine/wiRenderer.h @@ -1025,9 +1025,6 @@ namespace wi::renderer void SetShadowPropsCube(int max_resolution); - - void SetTransparentShadowsEnabled(bool value); - float GetTransparentShadowsEnabled(); void SetWireRender(bool value); bool IsWireRender(); void SetToDrawDebugBoneLines(bool param); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index d70acae13..efa50fc0d 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 523; + const int revision = 524; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);