From 61323f85264a40becad17f19f533846803cdd1ed Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 4 Apr 2024 12:54:07 +0200 Subject: [PATCH] Refactor shadows rendering to use Shadow Map Atlas --- .../Features/ForwardShading.hlsl | 12 +- Source/Engine/Core/Core.h | 5 + .../Graphics/Materials/MaterialShader.h | 2 +- .../Materials/MaterialShaderFeatures.cpp | 26 +- .../Materials/MaterialShaderFeatures.h | 3 +- .../Engine/Level/Actors/DirectionalLight.cpp | 2 +- Source/Engine/Level/Actors/PointLight.cpp | 3 +- Source/Engine/Level/Actors/SkyLight.cpp | 3 +- Source/Engine/Level/Actors/SpotLight.cpp | 3 +- .../Graph/CPU/ParticleEmitterGraph.CPU.cpp | 6 - Source/Engine/Renderer/Config.h | 19 +- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 46 +- Source/Engine/Renderer/LightPass.cpp | 92 +- Source/Engine/Renderer/LightPass.h | 10 +- Source/Engine/Renderer/RenderList.cpp | 35 +- Source/Engine/Renderer/RenderList.h | 60 +- Source/Engine/Renderer/Renderer.cpp | 7 +- Source/Engine/Renderer/ShadowsPass.cpp | 989 ++++++++++-------- Source/Engine/Renderer/ShadowsPass.h | 137 +-- Source/Engine/Renderer/VolumetricFogPass.cpp | 235 +---- Source/Engine/Renderer/VolumetricFogPass.h | 27 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 2 +- Source/Shaders/Lighting.hlsl | 24 +- Source/Shaders/LightingCommon.hlsl | 10 +- Source/Shaders/Lights.shader | 6 +- Source/Shaders/PCFKernels.hlsl | 135 --- Source/Shaders/Quad.shader | 9 +- Source/Shaders/Shadows.shader | 77 +- Source/Shaders/ShadowsCommon.hlsl | 69 +- Source/Shaders/ShadowsSampling.hlsl | 818 ++++----------- Source/Shaders/VolumetricFog.shader | 69 +- 31 files changed, 1115 insertions(+), 1826 deletions(-) delete mode 100644 Source/Shaders/PCFKernels.hlsl diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 9dbc19369..7f7d16545 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -16,7 +16,6 @@ #include "./Flax/ExponentialHeightFog.hlsl" @2// Forward Shading: Constants LightData DirectionalLight; -LightShadowData DirectionalLightShadow; LightData SkyLight; ProbeData EnvironmentProbe; ExponentialHeightFogData ExponentialHeightFog; @@ -26,9 +25,9 @@ LightData LocalLights[MAX_LOCAL_LIGHTS]; @3// Forward Shading: Resources TextureCube EnvProbe : register(t__SRV__); TextureCube SkyLightTexture : register(t__SRV__); -Texture2DArray DirectionalLightShadowMap : register(t__SRV__); +Buffer ShadowsBuffer : register(t__SRV__); +Texture2D ShadowMap : register(t__SRV__); @4// Forward Shading: Utilities -DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow); @5// Forward Shading: Shaders // Pixel Shader function for Forward Pass @@ -80,11 +79,8 @@ void PS_Forward( // Calculate lighting from a single directional light float4 shadowMask = 1.0f; - if (DirectionalLight.CastShadows > 0) - { - LightShadowData directionalLightShadowData = GetDirectionalLightShadowData(); - shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g); - } + ShadowSample shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, gBuffer); + shadowMask = GetShadowMask(shadow); float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); // Calculate lighting from sky light diff --git a/Source/Engine/Core/Core.h b/Source/Engine/Core/Core.h index 01f80b7b2..cfc8471d1 100644 --- a/Source/Engine/Core/Core.h +++ b/Source/Engine/Core/Core.h @@ -23,3 +23,8 @@ #define OUT_OF_MEMORY Platform::OutOfMemory(__LINE__, __FILE__) #define MISSING_CODE(info) Platform::MissingCode(__LINE__, __FILE__, info) #define NON_COPYABLE(type) type(type&&) = delete; type(const type&) = delete; type& operator=(const type&) = delete; type& operator=(type&&) = delete; +#define POD_COPYABLE(type) \ + type(const type& other) { Platform::MemoryCopy(this, &other, sizeof(type)); } \ + type(type&& other) noexcept { Platform::MemoryCopy(this, &other, sizeof(type)); } \ + type& operator=(const type& other) { Platform::MemoryCopy(this, &other, sizeof(type)); return *this; } \ + type& operator=(type&& other) noexcept { Platform::MemoryCopy(this, &other, sizeof(type)); return *this; } diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 7a5b842e2..5a4cec20c 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 162 +#define MATERIAL_GRAPH_VERSION 163 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index e2e07b397..b6d7ef23a 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -21,7 +21,8 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span= sizeof(Data)); const int32 envProbeShaderRegisterIndex = srv + 0; const int32 skyLightShaderRegisterIndex = srv + 1; - const int32 dirLightShaderRegisterIndex = srv + 2; + const int32 shadowsBufferRegisterIndex = srv + 2; + const int32 shadowMapShaderRegisterIndex = srv + 3; const bool canUseShadow = view.Pass != DrawPass::Depth; // Set fog input @@ -39,24 +40,19 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanDirectionalLights.HasItems()) { const auto& dirLight = cache->DirectionalLights.First(); - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = shadowPass->LastDirLightIndex == 0 && canUseShadow; - if (useShadow) - { - data.DirectionalLightShadow = shadowPass->LastDirLight; - params.GPUContext->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap); - } - else - { - params.GPUContext->UnBindSR(dirLightShaderRegisterIndex); - } + GPUTexture* shadowMapAtlas; + GPUBufferView* shadowsBuffer; + ShadowsPass::GetShadowAtlas(params.RenderContext.Buffers, shadowMapAtlas, shadowsBuffer); + const bool useShadow = shadowMapAtlas && canUseShadow && dirLight.HasShadow; dirLight.SetShaderData(data.DirectionalLight, useShadow); + params.GPUContext->BindSR(shadowsBufferRegisterIndex, shadowsBuffer); + params.GPUContext->BindSR(shadowMapShaderRegisterIndex, shadowMapAtlas); } else { - data.DirectionalLight.Color = Float3::Zero; - data.DirectionalLight.CastShadows = 0.0f; - params.GPUContext->UnBindSR(dirLightShaderRegisterIndex); + Platform::MemoryClear(&data.DirectionalLight, sizeof(data.DirectionalLight)); + params.GPUContext->UnBindSR(shadowsBufferRegisterIndex); + params.GPUContext->UnBindSR(shadowMapShaderRegisterIndex); } // Set sky light diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h index f48c6821c..81a6e260d 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -23,12 +23,11 @@ struct ForwardShadingFeature : MaterialShaderFeature { enum { MaxLocalLights = 4 }; - enum { SRVs = 3 }; + enum { SRVs = 4 }; PACK_STRUCT(struct Data { ShaderLightData DirectionalLight; - ShaderLightShadowData DirectionalLightShadow; ShaderLightData SkyLight; ShaderEnvProbeData EnvironmentProbe; ShaderExponentialHeightFogData ExponentialHeightFog; diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index f44375074..3a2a44e45 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -38,7 +38,6 @@ void DirectionalLight::Draw(RenderContext& renderContext) data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.IndirectLightingIntensity = IndirectLightingIntensity; data.CastVolumetricShadow = CastVolumetricShadow; - data.RenderedVolumetricFog = 0; data.ShadowsMode = ShadowsMode; data.CascadeCount = CascadeCount; data.Cascade1Spacing = Cascade1Spacing; @@ -49,6 +48,7 @@ void DirectionalLight::Draw(RenderContext& renderContext) data.ContactShadowsLength = ContactShadowsLength; data.StaticFlags = GetStaticFlags(); data.ID = GetID(); + data.ScreenSize = 1.0f; renderContext.List->DirectionalLights.Add(data); } } diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 0a4f607ae..ff12f8dfd 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -2,6 +2,7 @@ #include "PointLight.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Serialization/Serialization.h" @@ -102,7 +103,6 @@ void PointLight::Draw(RenderContext& renderContext) data.ShadowsSharpness = ShadowsSharpness; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.CastVolumetricShadow = CastVolumetricShadow; - data.RenderedVolumetricFog = 0; data.ShadowsMode = ShadowsMode; data.Radius = radius; data.FallOffExponent = FallOffExponent; @@ -114,6 +114,7 @@ void PointLight::Draw(RenderContext& renderContext) data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr; data.StaticFlags = GetStaticFlags(); data.ID = GetID(); + data.ScreenSize = Math::Min(1.0f, Math::Sqrt(RenderTools::ComputeBoundsScreenRadiusSquared(position, (float)_sphere.Radius, renderContext.View))); renderContext.List->PointLights.Add(data); } } diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index 67116fa13..19152a557 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -11,6 +11,7 @@ #include "Engine/Content/Content.h" #include "Engine/Serialization/Serialization.h" #include "Engine/ContentImporters/AssetsImportingManager.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Level/Scene/Scene.h" SkyLight::SkyLight(const SpawnParams& params) @@ -118,13 +119,13 @@ void SkyLight::Draw(RenderContext& renderContext) data.Color = Color.ToFloat3() * (Color.A * brightness); data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.CastVolumetricShadow = CastVolumetricShadow; - data.RenderedVolumetricFog = 0; data.AdditiveColor = AdditiveColor.ToFloat3() * (AdditiveColor.A * brightness); data.IndirectLightingIntensity = IndirectLightingIntensity; data.Radius = GetScaledRadius(); data.Image = GetSource(); data.StaticFlags = GetStaticFlags(); data.ID = GetID(); + data.ScreenSize = Math::Min(1.0f, Math::Sqrt(RenderTools::ComputeBoundsScreenRadiusSquared(position, (float)_sphere.Radius, renderContext.View))); renderContext.List->SkyLights.Add(data); } } diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index a80c012a5..ab9cde364 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -5,6 +5,7 @@ #include "Engine/Renderer/RenderList.h" #include "Engine/Content/Assets/IESProfile.h" #include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Serialization/Serialization.h" #include "Engine/Level/Scene/SceneRendering.h" @@ -152,7 +153,6 @@ void SpotLight::Draw(RenderContext& renderContext) data.ShadowsSharpness = ShadowsSharpness; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.CastVolumetricShadow = CastVolumetricShadow; - data.RenderedVolumetricFog = 0; data.ShadowsMode = ShadowsMode; data.Radius = radius; data.FallOffExponent = FallOffExponent; @@ -167,6 +167,7 @@ void SpotLight::Draw(RenderContext& renderContext) data.OuterConeAngle = outerConeAngle; data.StaticFlags = GetStaticFlags(); data.ID = GetID(); + data.ScreenSize = Math::Min(1.0f, Math::Sqrt(RenderTools::ComputeBoundsScreenRadiusSquared(position, (float)_sphere.Radius, renderContext.View))); renderContext.List->SpotLights.Add(data); } } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index 9c847fd91..d7441c67d 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -399,12 +399,6 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff lightData.ShadowsSharpness = 1.0f; lightData.UseInverseSquaredFalloff = false; lightData.VolumetricScatteringIntensity = 1.0f; - lightData.CastVolumetricShadow = false; - lightData.RenderedVolumetricFog = 0; - lightData.ShadowsMode = ShadowsCastingMode::None; - lightData.SourceRadius = 0.0f; - lightData.SourceLength = 0.0f; - lightData.IESTexture = nullptr; for (int32 particleIndex = 0; particleIndex < count; particleIndex++) { diff --git a/Source/Engine/Renderer/Config.h b/Source/Engine/Renderer/Config.h index b74cb20da..c62941b67 100644 --- a/Source/Engine/Renderer/Config.h +++ b/Source/Engine/Renderer/Config.h @@ -77,28 +77,13 @@ PACK_STRUCT(struct ShaderLightData { Float3 Color; float MinRoughness; Float3 Position; - float CastShadows; + uint32 ShadowsBufferAddress; Float3 Direction; float Radius; float FalloffExponent; float InverseSquared; - float Dummy0; float RadiusInv; - }); - -/// -/// Structure that contains information about light for shaders. -/// -PACK_STRUCT(struct ShaderLightShadowData { - Float2 ShadowMapSize; - float Sharpness; - float Fade; - float NormalOffsetScale; - float Bias; - float FadeDistance; - uint32 NumCascades; - Float4 CascadeSplits; - Matrix ShadowVP[6]; + float Dummy0; }); /// diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 58b5ffde0..d4e212222 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -97,27 +97,7 @@ struct GlobalSurfaceAtlasObject Platform::MemoryClear(this, sizeof(GlobalSurfaceAtlasObject)); } - GlobalSurfaceAtlasObject(const GlobalSurfaceAtlasObject& other) - { - Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); - } - - GlobalSurfaceAtlasObject(GlobalSurfaceAtlasObject&& other) noexcept - { - Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); - } - - GlobalSurfaceAtlasObject& operator=(const GlobalSurfaceAtlasObject& other) - { - Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); - return *this; - } - - GlobalSurfaceAtlasObject& operator=(GlobalSurfaceAtlasObject&& other) noexcept - { - Platform::MemoryCopy(this, &other, sizeof(GlobalSurfaceAtlasObject)); - return *this; - } + POD_COPYABLE(GlobalSurfaceAtlasObject); }; struct GlobalSurfaceAtlasLight @@ -130,9 +110,9 @@ class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer, publi { public: int32 Resolution = 0; + int32 AtlasPixelsUsed = 0; uint64 LastFrameAtlasInsertFail = 0; uint64 LastFrameAtlasDefragmentation = 0; - int32 AtlasPixelsUsed = 0; GPUTexture* AtlasDepth = nullptr; GPUTexture* AtlasEmissive = nullptr; GPUTexture* AtlasGBuffer0 = nullptr; @@ -163,7 +143,7 @@ public: { } - FORCE_INLINE void ClearObjects() + void ClearObjects() { CulledObjectsCounterIndex = -1; CulledObjectsUsageHistory.Clear(); @@ -174,7 +154,7 @@ public: Lights.Clear(); } - FORCE_INLINE void Clear() + void Reset() { RenderTargetPool::Release(AtlasDepth); RenderTargetPool::Release(AtlasEmissive); @@ -189,7 +169,7 @@ public: { SAFE_DELETE_GPU_RESOURCE(ChunksBuffer); SAFE_DELETE_GPU_RESOURCE(CulledObjectsBuffer); - Clear(); + Reset(); } // [ISceneRenderingListener] @@ -400,7 +380,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co bool noCache = surfaceAtlasData.Resolution != resolution; if (noCache) { - surfaceAtlasData.Clear(); + surfaceAtlasData.Reset(); auto desc = GPUTextureDescription::New2D(resolution, resolution, PixelFormat::Unknown); uint64 memUsage = 0; @@ -963,9 +943,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co if (_vertexBuffer->Data.Count() == 0) continue; - // Draw draw light + // Draw light PROFILE_GPU_CPU_NAMED("Directional Light"); - const bool useShadow = CanRenderShadow(renderContext.View, light); + const bool useShadow = light.CanRenderShadow(renderContext.View); // TODO: test perf/quality when using Shadow Map for directional light (ShadowsPass::Instance()->LastDirLightShadowMap) instead of Global SDF trace light.SetShaderData(data.Light, useShadow); data.Light.Color *= light.IndirectLightingIntensity; @@ -997,9 +977,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co if (_vertexBuffer->Data.Count() == 0) continue; - // Draw draw light + // Draw light PROFILE_GPU_CPU_NAMED("Point Light"); - const bool useShadow = CanRenderShadow(renderContext.View, light); + const bool useShadow = light.CanRenderShadow(renderContext.View); light.SetShaderData(data.Light, useShadow); data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; @@ -1030,9 +1010,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co if (_vertexBuffer->Data.Count() == 0) continue; - // Draw draw light + // Draw light PROFILE_GPU_CPU_NAMED("Spot Light"); - const bool useShadow = CanRenderShadow(renderContext.View, light); + const bool useShadow = light.CanRenderShadow(renderContext.View); light.SetShaderData(data.Light, useShadow); data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; @@ -1048,7 +1028,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.Lights.Remove(it); } - // Draw draw indirect light from Global Illumination + // Draw indirect light from Global Illumination if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI)) { switch (giSettings.Mode) diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index bdbe22be8..e1330ece5 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -3,14 +3,15 @@ #include "LightPass.h" #include "ShadowsPass.h" #include "GBufferPass.h" +#include "Engine/Core/Collections/Sorting.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTools.h" +#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Content/Assets/CubeTexture.h" #include "Engine/Content/Content.h" -#include "Engine/Graphics/GPUContext.h" -#include "Engine/Graphics/RenderTask.h" PACK_STRUCT(struct PerLight{ ShaderLightData Light; @@ -49,8 +50,9 @@ bool LightPass::Init() _shader.Get()->OnReloading.Bind(this); #endif + // Pick the format for shadow mask (rendered shadow projection into screen-space) auto format = PixelFormat::R8G8_UNorm; - if (EnumHasNoneFlags(GPUDevice::Instance->GetFormatFeatures(format).Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D))) + if (EnumHasNoneFlags(GPUDevice::Instance->GetFormatFeatures(format).Support, FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)) { format = PixelFormat::B8G8R8A8_UNorm; } @@ -151,27 +153,48 @@ void LightPass::Dispose() _sphereModel = nullptr; } -void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureView* lightBuffer) +template +bool SortLights(T const& p1, T const& p2) +{ + // Compare by screen size + int32 res = static_cast(p2.ScreenSize * 100 - p1.ScreenSize * 100); + if (res == 0) + { + // Compare by brightness + res = static_cast(p2.Color.SumValues() * 100 - p1.Color.SumValues() * 100); + if (res == 0) + { + // Compare by ID to stabilize order + res = GetHash(p2.ID) - GetHash(p1.ID); + } + } + return res < 0; +} + +void LightPass::SetupLights(RenderContext& renderContext, RenderContextBatch& renderContextBatch) +{ + PROFILE_CPU(); + + // Sort lights + Sorting::QuickSort(renderContext.List->DirectionalLights.Get(), renderContext.List->DirectionalLights.Count(), &SortLights); + Sorting::QuickSort(renderContext.List->PointLights.Get(), renderContext.List->PointLights.Count(), &SortLights); + Sorting::QuickSort(renderContext.List->SpotLights.Get(), renderContext.List->SpotLights.Count(), &SortLights); +} + +void LightPass::RenderLights(RenderContextBatch& renderContextBatch, GPUTextureView* lightBuffer) { const float sphereModelScale = 3.0f; - - // Ensure to have valid data if (checkIfSkipPass()) - { - // Resources are missing. Do not perform rendering. return; - } - PROFILE_GPU_CPU("Lights"); // Cache data auto device = GPUDevice::Instance; auto context = device->GetMainContext(); - auto& renderContext = renderContextBatch.Contexts[0]; + auto& renderContext = renderContextBatch.GetMainContext(); auto& view = renderContext.View; auto mainCache = renderContext.List; const auto lightShader = _shader->GetShader(); - const bool useShadows = ShadowsPass::Instance()->IsReady() && EnumHasAnyFlags(view.Flags, ViewFlags::Shadows); const bool disableSpecular = (view.Flags & ViewFlags::SpecularLight) == ViewFlags::None; // Check if debug lights @@ -242,12 +265,9 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi for (int32 lightIndex = 0; lightIndex < mainCache->PointLights.Count(); lightIndex++) { PROFILE_GPU_CPU_NAMED("Point Light"); - - // Cache data auto& light = mainCache->PointLights[lightIndex]; float lightRadius = light.Radius; Float3 lightPosition = light.Position; - const bool renderShadow = useShadows && light.ShadowDataIndex != -1; bool useIES = light.IESTexture != nullptr; // Get distance from view center to light center less radius (check if view is inside a sphere) @@ -261,23 +281,19 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi Matrix::Multiply(wvp, matrix, world); Matrix::Multiply(world, view.ViewProjection(), wvp); - // Check if render shadow - if (renderShadow) + // Fullscreen shadow mask rendering + if (light.HasShadow) { GET_SHADOW_MASK(); - ShadowsPass::Instance()->RenderShadow(renderContextBatch, light, shadowMaskView); - - // Bind output + ShadowsPass::Instance()->RenderShadowMask(renderContextBatch, light, shadowMaskView); context->SetRenderTarget(depthBufferRTV, lightBuffer); - - // Set shadow mask context->BindSR(5, shadowMaskView); } else context->UnBindSR(5); // Pack light properties buffer - light.SetShaderData(perLight.Light, renderShadow); + light.SetShaderData(perLight.Light, light.HasShadow); Matrix::Transpose(wvp, perLight.WVP); if (useIES) { @@ -299,12 +315,9 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi for (int32 lightIndex = 0; lightIndex < mainCache->SpotLights.Count(); lightIndex++) { PROFILE_GPU_CPU_NAMED("Spot Light"); - - // Cache data auto& light = mainCache->SpotLights[lightIndex]; float lightRadius = light.Radius; Float3 lightPosition = light.Position; - const bool renderShadow = useShadows && light.ShadowDataIndex != -1; bool useIES = light.IESTexture != nullptr; // Get distance from view center to light center less radius (check if view is inside a sphere) @@ -318,23 +331,19 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi Matrix::Multiply(wvp, matrix, world); Matrix::Multiply(world, view.ViewProjection(), wvp); - // Check if render shadow - if (renderShadow) + // Fullscreen shadow mask rendering + if (light.HasShadow) { GET_SHADOW_MASK(); - ShadowsPass::Instance()->RenderShadow(renderContextBatch, light, shadowMaskView); - - // Bind output + ShadowsPass::Instance()->RenderShadowMask(renderContextBatch, light, shadowMaskView); context->SetRenderTarget(depthBufferRTV, lightBuffer); - - // Set shadow mask context->BindSR(5, shadowMaskView); } else context->UnBindSR(5); // Pack light properties buffer - light.SetShaderData(perLight.Light, renderShadow); + light.SetShaderData(perLight.Light, light.HasShadow); Matrix::Transpose(wvp, perLight.WVP); if (useIES) { @@ -356,28 +365,21 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi for (int32 lightIndex = 0; lightIndex < mainCache->DirectionalLights.Count(); lightIndex++) { PROFILE_GPU_CPU_NAMED("Directional Light"); - - // Cache data auto& light = mainCache->DirectionalLights[lightIndex]; - const bool renderShadow = useShadows && light.ShadowDataIndex != -1; - // Check if render shadow - if (renderShadow) + // Fullscreen shadow mask rendering + if (light.HasShadow) { GET_SHADOW_MASK(); - ShadowsPass::Instance()->RenderShadow(renderContextBatch, light, lightIndex, shadowMaskView); - - // Bind output + ShadowsPass::Instance()->RenderShadowMask(renderContextBatch, light, shadowMaskView); context->SetRenderTarget(depthBufferRTV, lightBuffer); - - // Set shadow mask context->BindSR(5, shadowMaskView); } else context->UnBindSR(5); // Pack light properties buffer - light.SetShaderData(perLight.Light, renderShadow); + light.SetShaderData(perLight.Light, light.HasShadow); // Calculate lighting context->UpdateCB(cb0, &perLight); diff --git a/Source/Engine/Renderer/LightPass.h b/Source/Engine/Renderer/LightPass.h index 118f22363..a399c7bfd 100644 --- a/Source/Engine/Renderer/LightPass.h +++ b/Source/Engine/Renderer/LightPass.h @@ -27,15 +27,19 @@ private: PixelFormat _shadowMaskFormat; public: + /// + /// Setups the lights rendering for batched scene drawing. + /// + void SetupLights(RenderContext& renderContext, RenderContextBatch& renderContextBatch); + /// /// Performs the lighting rendering for the input task. /// /// The rendering context batch. /// The light accumulation buffer (input and output). - void RenderLight(RenderContextBatch& renderContextBatch, GPUTextureView* lightBuffer); + void RenderLights(RenderContextBatch& renderContextBatch, GPUTextureView* lightBuffer); private: - #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { @@ -51,14 +55,12 @@ private: #endif public: - // [RendererPass] String ToString() const override; bool Init() override; void Dispose() override; protected: - // [RendererPass] bool setupResources() override; }; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index efe3edd2a..8f07180f3 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -39,6 +39,24 @@ namespace CriticalSection MemPoolLocker; } +bool RenderLightData::CanRenderShadow(const RenderView& view) const +{ + bool result = false; + switch (ShadowsMode) + { + case ShadowsCastingMode::StaticOnly: + result = view.IsOfflinePass; + break; + case ShadowsCastingMode::DynamicOnly: + result = !view.IsOfflinePass; + break; + case ShadowsCastingMode::All: + result = true; + break; + } + return result && ShadowsStrength > ZeroTolerance; +} + void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useShadow) const { data.SpotAngles.X = -2.0f; @@ -48,7 +66,7 @@ void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useSh data.Color = Color; data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS); data.Position = Float3::Zero; - data.CastShadows = useShadow ? 1.0f : 0.0f; + data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0; data.Direction = -Direction; data.Radius = 0; data.FalloffExponent = 0; @@ -56,6 +74,15 @@ void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useSh data.RadiusInv = 0; } +bool RenderLocalLightData::CanRenderShadow(const RenderView& view) const +{ + // Fade shadow on distance + const float fadeDistance = Math::Max(ShadowsFadeDistance, 0.1f); + const float dstLightToView = Float3::Distance(Position, view.Position); + const float fade = 1 - Math::Saturate((dstLightToView - Radius - ShadowsDistance + fadeDistance) / fadeDistance); + return fade > ZeroTolerance && RenderLightData::CanRenderShadow(view); +} + void RenderSpotLightData::SetShaderData(ShaderLightData& data, bool useShadow) const { data.SpotAngles.X = CosOuterCone; @@ -65,7 +92,7 @@ void RenderSpotLightData::SetShaderData(ShaderLightData& data, bool useShadow) c data.Color = Color; data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS); data.Position = Position; - data.CastShadows = useShadow ? 1.0f : 0.0f; + data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0; data.Direction = Direction; data.Radius = Radius; data.FalloffExponent = FallOffExponent; @@ -82,7 +109,7 @@ void RenderPointLightData::SetShaderData(ShaderLightData& data, bool useShadow) data.Color = Color; data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS); data.Position = Position; - data.CastShadows = useShadow ? 1.0f : 0.0f; + data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0; data.Direction = Direction; data.Radius = Radius; data.FalloffExponent = FallOffExponent; @@ -99,7 +126,7 @@ void RenderSkyLightData::SetShaderData(ShaderLightData& data, bool useShadow) co data.Color = Color; data.MinRoughness = MIN_ROUGHNESS; data.Position = Position; - data.CastShadows = useShadow ? 1.0f : 0.0f; + data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0; data.Direction = Float3::Forward; data.Radius = Radius; data.FalloffExponent = 0; diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 15b5a9842..3a7ab373e 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -43,55 +43,82 @@ struct RenderLightData StaticFlags StaticFlags; ShadowsCastingMode ShadowsMode; float IndirectLightingIntensity; - int16 ShadowDataIndex = -1; + uint8 HasShadow : 1; uint8 CastVolumetricShadow : 1; - uint8 RenderedVolumetricFog : 1; + uint8 UseInverseSquaredFalloff : 1; + uint8 IsDirectionalLight : 1; + uint8 IsPointLight : 1; + uint8 IsSpotLight : 1; + uint8 IsSkyLight : 1; float VolumetricScatteringIntensity; float ContactShadowsLength; + float ScreenSize; + uint32 ShadowsBufferAddress; + + RenderLightData() + { + Platform::MemoryClear(this, sizeof(RenderLightData)); + } + + POD_COPYABLE(RenderLightData); + bool CanRenderShadow(const RenderView& view) const; }; struct RenderDirectionalLightData : RenderLightData { - PartitionMode PartitionMode; - int32 CascadeCount; - float Cascade1Spacing; float Cascade2Spacing; float Cascade3Spacing; float Cascade4Spacing; + PartitionMode PartitionMode; + int32 CascadeCount; + + RenderDirectionalLightData() + { + IsDirectionalLight = 1; + } + void SetShaderData(ShaderLightData& data, bool useShadow) const; }; -struct RenderSpotLightData : RenderLightData +struct RenderLocalLightData : RenderLightData { + GPUTexture* IESTexture; + float Radius; float SourceRadius; + bool CanRenderShadow(const RenderView& view) const; +}; + +struct RenderSpotLightData : RenderLocalLightData +{ Float3 UpVector; float OuterConeAngle; float CosOuterCone; float InvCosConeDifference; float FallOffExponent; - uint8 UseInverseSquaredFalloff : 1; - GPUTexture* IESTexture; + RenderSpotLightData() + { + IsSpotLight = 1; + } void SetShaderData(ShaderLightData& data, bool useShadow) const; }; -struct RenderPointLightData : RenderLightData +struct RenderPointLightData : RenderLocalLightData { - float Radius; - float SourceRadius; - float FallOffExponent; float SourceLength; - uint8 UseInverseSquaredFalloff : 1; - GPUTexture* IESTexture; + RenderPointLightData() + { + IsPointLight = 1; + } void SetShaderData(ShaderLightData& data, bool useShadow) const; }; @@ -103,6 +130,11 @@ struct RenderSkyLightData : RenderLightData CubeTexture* Image; + RenderSkyLightData() + { + IsSkyLight = 1; + } + void SetShaderData(ShaderLightData& data, bool useShadow) const; }; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 7500b2f1e..8b97bc1d5 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -348,7 +348,6 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Prepare renderContext.View.Prepare(renderContext); renderContext.Buffers->Prepare(); - ShadowsPass::Instance()->Prepare(); // Build batch of render contexts (main view and shadow projections) { @@ -371,6 +370,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont drawShadows = false; break; } + LightPass::Instance()->SetupLights(renderContext, renderContextBatch); if (drawShadows) ShadowsPass::Instance()->SetupShadows(renderContext, renderContextBatch); #if USE_EDITOR @@ -404,7 +404,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::MotionVectors); for (int32 i = 1; i < renderContextBatch.Contexts.Count(); i++) { - auto& shadowContext = renderContextBatch.Contexts[i]; + auto& shadowContext = renderContextBatch.Contexts.Get()[i]; shadowContext.List->SortDrawCalls(shadowContext, false, DrawCallsListType::Depth, DrawPass::Depth); shadowContext.List->SortDrawCalls(shadowContext, false, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, DrawPass::Depth); } @@ -487,7 +487,8 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Render lighting renderContextBatch.GetMainContext() = renderContext; // Sync render context in batch with the current value - LightPass::Instance()->RenderLight(renderContextBatch, *lightBuffer); + ShadowsPass::Instance()->RenderShadowMaps(renderContextBatch); + LightPass::Instance()->RenderLights(renderContextBatch, *lightBuffer); if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI)) { switch (renderContext.List->Settings.GlobalIllumination.Mode) diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index de1d0c878..bbd036592 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -9,19 +9,22 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Content/Content.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Scripting/Enums.h" +#include "Engine/Utilities/RectPack.h" #if USE_EDITOR #include "Engine/Renderer/Lightmaps.h" #endif +#define MaxTiles 6 #define NormalOffsetScaleTweak 100.0f -#define SpotLight_NearPlane 10.0f -#define PointLight_NearPlane 10.0f +#define LocalLightNearPlane 10.0f PACK_STRUCT(struct Data{ ShaderGBufferData GBuffer; ShaderLightData Light; - ShaderLightShadowData LightShadow; Matrix WVP; Matrix ViewProjectionMatrix; Float2 Dummy0; @@ -29,28 +32,116 @@ PACK_STRUCT(struct Data{ float ContactShadowsLength; }); -ShadowsPass::ShadowsPass() - : _shader(nullptr) - , _shadowMapsSizeCSM(0) - , _shadowMapsSizeCube(0) - , _shadowMapCSM(nullptr) - , _shadowMapCube(nullptr) - , _currentShadowMapsQuality((Quality)((int32)Quality::Ultra + 1)) - , _sphereModel(nullptr) - , maxShadowsQuality(0) +struct ShadowsAtlasTile : RectPack { + ShadowsAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height) + : RectPack(x, y, width, height) + { + } + + void OnInsert(class ShadowsCustomBuffer* buffer); + void OnFree(ShadowsCustomBuffer* buffer); +}; + +uint16 QuantizeResolution(float input) +{ + uint16 output = Math::FloorToInt(input); + uint16 alignment = 16; + if (output >= 512) + alignment = 64; + else if (output >= 256) + alignment = 32; + output = Math::AlignDown(output, alignment); + return output; } -uint64 ShadowsPass::GetShadowMapsMemoryUsage() const +struct ShadowAtlasLight { - uint64 result = 0; + uint64 LastFrameUsed; + int32 ContextIndex; + int32 ContextCount; + uint16 Resolution; + uint16 TilesNeeded; + bool BlendCSM; + float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance; + Float4 CascadeSplits; + ShadowsAtlasTile* Tiles[MaxTiles]; + Matrix WorldToShadow[MaxTiles]; - if (_shadowMapCSM) - result += _shadowMapCSM->GetMemoryUsage(); - if (_shadowMapCube) - result += _shadowMapCube->GetMemoryUsage(); + ShadowAtlasLight() + { + Platform::MemoryClear(this, sizeof(ShadowAtlasLight)); + } - return result; + POD_COPYABLE(ShadowAtlasLight); + + void SetWorldToShadow(int32 index, const Matrix& shadowViewProjection) + { + // Transform Clip Space [-1,+1]^2 to UV Space [0,1]^2 (saves MAD instruction in shader) + const Matrix ClipToUV( + 0.5f, 0.0f, 0.0f, 0.0f, + 0.0f, -0.5f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 1.0f); + Matrix m; + Matrix::Multiply(shadowViewProjection, ClipToUV, m); + Matrix::Transpose(m, WorldToShadow[index]); + } +}; + +class ShadowsCustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + int32 Resolution = 0; + int32 AtlasPixelsUsed = 0; + mutable bool ClearShadowMapAtlas = true; + Vector3 ViewOrigin; + GPUTexture* ShadowMapAtlas = nullptr; + DynamicTypedBuffer ShadowsBuffer; + GPUBufferView* ShadowsBufferView = nullptr; + ShadowsAtlasTile* AtlasTiles = nullptr; // TODO: optimize with a single allocation for atlas tiles + Dictionary Lights; + + ShadowsCustomBuffer() + : ShadowsBuffer(1024, PixelFormat::R32G32B32A32_Float, false, TEXT("ShadowsBuffer")) + { + ShadowMapAtlas = GPUDevice::Instance->CreateTexture(TEXT("Shadow Map Atlas")); + } + + void ClearTiles() + { + ClearShadowMapAtlas = true; + AtlasPixelsUsed = 0; + SAFE_DELETE(AtlasTiles); + for (auto it = Lights.Begin(); it.IsNotEnd(); ++it) + { + auto& atlasLight = it->Value; + Platform::MemoryClear(atlasLight.Tiles, sizeof(atlasLight.Tiles)); + } + } + + void Reset() + { + Lights.Clear(); + ClearTiles(); + ViewOrigin = Vector3::Zero; + } + + ~ShadowsCustomBuffer() + { + Reset(); + SAFE_DELETE_GPU_RESOURCE(ShadowMapAtlas); + } +}; + +void ShadowsAtlasTile::OnInsert(ShadowsCustomBuffer* buffer) +{ + buffer->AtlasPixelsUsed += (int32)Width * (int32)Height; +} + +void ShadowsAtlasTile::OnFree(ShadowsCustomBuffer* buffer) +{ + buffer->AtlasPixelsUsed -= (int32)Width * (int32)Height; } String ShadowsPass::ToString() const @@ -69,13 +160,7 @@ bool ShadowsPass::Init() _shader = Content::LoadAsyncInternal(TEXT("Shaders/Shadows")); _sphereModel = Content::LoadAsyncInternal(TEXT("Engine/Models/SphereLowPoly")); if (_shader == nullptr || _sphereModel == nullptr) - { return true; - } - - // Create shadow maps - _shadowMapCSM = GPUDevice::Instance->CreateTexture(TEXT("Shadow Map CSM")); - _shadowMapCube = GPUDevice::Instance->CreateTexture(TEXT("Shadow Map Cube")); #if COMPILE_WITH_DEV_ENV _shader.Get()->OnReloading.Bind(this); @@ -141,62 +226,22 @@ bool ShadowsPass::setupResources() if (_psShadowSpot.Create(psDesc, shader, "PS_SpotLight")) return true; } + if (_psDepthClear == nullptr) + { + psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + psDesc.PS = GPUDevice::Instance->QuadShader->GetPS("PS_DepthClear"); + psDesc.DepthEnable = true; + psDesc.DepthWriteEnable = true; + psDesc.DepthFunc = ComparisonFunc::Always; + psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::None; + _psDepthClear = GPUDevice::Instance->CreatePipelineState(); + if (_psDepthClear->Init(psDesc)) + return true; + } return false; } -void ShadowsPass::updateShadowMapSize() -{ - // Temporary data - int32 newSizeCSM = 0; - int32 newSizeCube = 0; - - // Select new size - _currentShadowMapsQuality = Graphics::ShadowMapsQuality; - if (_shadowMapFormat != PixelFormat::Unknown) - { - switch (_currentShadowMapsQuality) - { - case Quality::Ultra: - newSizeCSM = 2048; - newSizeCube = 1024; - break; - case Quality::High: - newSizeCSM = 1024; - newSizeCube = 1024; - break; - case Quality::Medium: - newSizeCSM = 1024; - newSizeCube = 512; - break; - case Quality::Low: - newSizeCSM = 512; - newSizeCube = 256; - break; - } - } - - // Check if size will change - if (newSizeCSM > 0 && newSizeCSM != _shadowMapsSizeCSM) - { - if (_shadowMapCSM->Init(GPUTextureDescription::New2D(newSizeCSM, newSizeCSM, _shadowMapFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil, 1, MAX_CSM_CASCADES))) - { - LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("CSM"), newSizeCSM, ScriptingEnum::ToString(_shadowMapFormat)); - return; - } - _shadowMapsSizeCSM = newSizeCSM; - } - if (newSizeCube > 0 && newSizeCube != _shadowMapsSizeCube) - { - if (_shadowMapCube->Init(GPUTextureDescription::NewCube(newSizeCube, _shadowMapFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil))) - { - LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("Cube"), newSizeCube, ScriptingEnum::ToString(_shadowMapFormat)); - return; - } - _shadowMapsSizeCube = newSizeCube; - } -} - void ShadowsPass::SetupRenderContext(RenderContext& renderContext, RenderContext& shadowContext) { const auto& view = renderContext.View; @@ -219,15 +264,38 @@ void ShadowsPass::SetupRenderContext(RenderContext& renderContext, RenderContext shadowContext.Task = renderContext.Task; } -void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light) +void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLightData& light, ShadowAtlasLight& atlasLight) { + // Copy light properties + atlasLight.Sharpness = light.ShadowsSharpness; + atlasLight.Fade = light.ShadowsStrength; + atlasLight.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / (float)atlasLight.Resolution); + atlasLight.Bias = light.ShadowsDepthBias; + atlasLight.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); +} + +void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight) +{ + SetupLight(renderContext, renderContextBatch, (RenderLightData&)light, atlasLight); + + // Fade shadow on distance + const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); + const float dstLightToView = Float3::Distance(light.Position, renderContext.View.Position); + const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); + atlasLight.Fade *= fade; +} + +void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight) +{ + SetupLight(renderContext, renderContextBatch, (RenderLightData&)light, atlasLight); + const RenderView& view = renderContext.View; auto mainCache = renderContext.List; Float3 lightDirection = light.Direction; float shadowsDistance = Math::Min(view.Far, light.ShadowsDistance); int32 csmCount = Math::Clamp(light.CascadeCount, 0, MAX_CSM_CASCADES); bool blendCSM = Graphics::AllowCSMBlending; - const auto shadowMapsSizeCSM = (float)_shadowMapsSizeCSM; + const auto shadowMapsSize = (float)atlasLight.Resolution; #if USE_EDITOR if (IsRunningRadiancePass) blendCSM = false; @@ -248,7 +316,6 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r float maxDistance; float cascadeSplits[MAX_CSM_CASCADES]; { - // TODO: use HiZ and get view min/max range to fit cascades better minDistance = cameraNear; maxDistance = cameraNear + shadowsDistance; @@ -306,6 +373,7 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r for (int32 i = 0; i < MAX_CSM_CASCADES; i++) cascadeSplits[i] = (cascadeSplits[i] - cameraNear) / cameraRange; } + atlasLight.CascadeSplits = view.Near + Float4(cascadeSplits) * cameraRange; // Select best Up vector Float3 side = Float3::UnitX; @@ -327,12 +395,10 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r Matrix shadowView, shadowProjection, shadowVP; // Init shadow data - light.ShadowDataIndex = _shadowData.Count(); - auto& shadowData = _shadowData.AddOne(); - shadowData.ContextIndex = renderContextBatch.Contexts.Count(); - shadowData.ContextCount = csmCount; - shadowData.BlendCSM = blendCSM; - renderContextBatch.Contexts.AddDefault(shadowData.ContextCount); + atlasLight.ContextIndex = renderContextBatch.Contexts.Count(); + atlasLight.ContextCount = csmCount; + atlasLight.BlendCSM = blendCSM; + renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount); // Create the different view and projection matrices for each split float splitMinRatio = 0; @@ -380,7 +446,7 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r if (stabilization == ViewSnapping) { // Snap the target to the texel units (reference: ShaderX7 - Practical Cascaded Shadows Maps) - float shadowMapHalfSize = shadowMapsSizeCSM * 0.5f; + float shadowMapHalfSize = shadowMapsSize * 0.5f; float x = Math::Ceil(Float3::Dot(target, upDirection) * shadowMapHalfSize / boundingVSRadius) * boundingVSRadius / shadowMapHalfSize; float y = Math::Ceil(Float3::Dot(target, side) * shadowMapHalfSize / boundingVSRadius) * boundingVSRadius / shadowMapHalfSize; float z = Float3::Dot(target, lightDirection); @@ -411,125 +477,78 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r // Stabilize the shadow matrix on the projection if (stabilization == ProjectionSnapping) { - Float3 shadowPixelPosition = shadowVP.GetTranslation() * (shadowMapsSizeCSM * 0.5f); + Float3 shadowPixelPosition = shadowVP.GetTranslation() * (shadowMapsSize * 0.5f); shadowPixelPosition.Z = 0; const Float3 shadowPixelPositionRounded(Math::Round(shadowPixelPosition.X), Math::Round(shadowPixelPosition.Y), 0.0f); - const Float4 shadowPixelOffset((shadowPixelPositionRounded - shadowPixelPosition) * (2.0f / shadowMapsSizeCSM), 0.0f); + const Float4 shadowPixelOffset((shadowPixelPositionRounded - shadowPixelPosition) * (2.0f / shadowMapsSize), 0.0f); shadowProjection.SetRow4(shadowProjection.GetRow4() + shadowPixelOffset); Matrix::Multiply(shadowView, shadowProjection, shadowVP); } - // Transform NDC space [-1,+1]^2 to texture space [0,1]^2 - { - const Matrix T( - 0.5f, 0.0f, 0.0f, 0.0f, - 0.0f, -0.5f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.5f, 0.5f, 0.0f, 1.0f); - Matrix m; - Matrix::Multiply(shadowVP, T, m); - Matrix::Transpose(m, shadowData.Constants.ShadowVP[cascadeIndex]); - } + atlasLight.SetWorldToShadow(cascadeIndex, shadowVP); // Setup context for cascade - auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + cascadeIndex]; + auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + cascadeIndex]; SetupRenderContext(renderContext, shadowContext); shadowContext.List->Clear(); shadowContext.View.Position = -lightDirection * shadowsDistance + view.Position; shadowContext.View.Direction = lightDirection; shadowContext.View.SetUp(shadowView, shadowProjection); shadowContext.View.CullingFrustum.SetMatrix(cullingVP); - shadowContext.View.PrepareCache(shadowContext, shadowMapsSizeCSM, shadowMapsSizeCSM, Float2::Zero, &view); + shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &view); } - - // Setup constant buffer data - shadowData.Constants.ShadowMapSize = shadowMapsSizeCSM; - shadowData.Constants.Sharpness = light.ShadowsSharpness; - shadowData.Constants.Fade = Math::Saturate(light.ShadowsStrength); - shadowData.Constants.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / shadowMapsSizeCSM); - shadowData.Constants.Bias = light.ShadowsDepthBias; - shadowData.Constants.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); - shadowData.Constants.NumCascades = csmCount; - shadowData.Constants.CascadeSplits = view.Near + Float4(cascadeSplits) * cameraRange; } -void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light) +void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight) { + SetupLight(renderContext, renderContextBatch, (RenderLocalLightData&)light, atlasLight); + // Init shadow data - light.ShadowDataIndex = _shadowData.Count(); - auto& shadowData = _shadowData.AddOne(); - shadowData.ContextIndex = renderContextBatch.Contexts.Count(); - shadowData.ContextCount = 6; - renderContextBatch.Contexts.AddDefault(shadowData.ContextCount); + atlasLight.ContextIndex = renderContextBatch.Contexts.Count(); + atlasLight.ContextCount = 6; + renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount); const auto& view = renderContext.View; - const auto shadowMapsSizeCube = (float)_shadowMapsSizeCube; + const auto shadowMapsSize = (float)atlasLight.Resolution; // Fade shadow on distance const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); const float dstLightToView = Float3::Distance(light.Position, view.Position); const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); + atlasLight.Fade *= fade; // Render depth to all 6 faces of the cube map for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) { - auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + faceIndex]; + auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + faceIndex]; SetupRenderContext(renderContext, shadowContext); shadowContext.List->Clear(); - shadowContext.View.SetUpCube(PointLight_NearPlane, light.Radius, light.Position); + shadowContext.View.SetUpCube(LocalLightNearPlane, light.Radius, light.Position); shadowContext.View.SetFace(faceIndex); - shadowContext.View.PrepareCache(shadowContext, shadowMapsSizeCube, shadowMapsSizeCube, Float2::Zero, &view); - Matrix::Transpose(shadowContext.View.ViewProjection(), shadowData.Constants.ShadowVP[faceIndex]); + shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &view); + atlasLight.SetWorldToShadow(faceIndex, shadowContext.View.ViewProjection()); } - - // Setup constant buffer data - shadowData.Constants.ShadowMapSize = shadowMapsSizeCube; - shadowData.Constants.Sharpness = light.ShadowsSharpness; - shadowData.Constants.Fade = Math::Saturate(light.ShadowsStrength * fade); - shadowData.Constants.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / shadowMapsSizeCube); - shadowData.Constants.Bias = light.ShadowsDepthBias; - shadowData.Constants.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); - shadowData.Constants.NumCascades = 1; - shadowData.Constants.CascadeSplits = Float4::Zero; } -void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light) +void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight) { + SetupLight(renderContext, renderContextBatch, (RenderLocalLightData&)light, atlasLight); + // Init shadow data - light.ShadowDataIndex = _shadowData.Count(); - auto& shadowData = _shadowData.AddOne(); - shadowData.ContextIndex = renderContextBatch.Contexts.Count(); - shadowData.ContextCount = 1; - renderContextBatch.Contexts.AddDefault(shadowData.ContextCount); + atlasLight.ContextIndex = renderContextBatch.Contexts.Count(); + atlasLight.ContextCount = 1; + renderContextBatch.Contexts.AddDefault(atlasLight.ContextCount); const auto& view = renderContext.View; - const auto shadowMapsSizeCube = (float)_shadowMapsSizeCube; + const auto shadowMapsSize = (float)atlasLight.Resolution; - // Fade shadow on distance - const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); - const float dstLightToView = Float3::Distance(light.Position, view.Position); - const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); - - // Render depth to all 1 face of the cube map - constexpr int32 faceIndex = 0; - { - auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + faceIndex]; - SetupRenderContext(renderContext, shadowContext); - shadowContext.List->Clear(); - shadowContext.View.SetProjector(SpotLight_NearPlane, light.Radius, light.Position, light.Direction, light.UpVector, light.OuterConeAngle * 2.0f); - shadowContext.View.PrepareCache(shadowContext, shadowMapsSizeCube, shadowMapsSizeCube, Float2::Zero, &view); - Matrix::Transpose(shadowContext.View.ViewProjection(), shadowData.Constants.ShadowVP[faceIndex]); - } - - // Setup constant buffer data - shadowData.Constants.ShadowMapSize = shadowMapsSizeCube; - shadowData.Constants.Sharpness = light.ShadowsSharpness; - shadowData.Constants.Fade = Math::Saturate(light.ShadowsStrength * fade); - shadowData.Constants.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / shadowMapsSizeCube); - shadowData.Constants.Bias = light.ShadowsDepthBias; - shadowData.Constants.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); - shadowData.Constants.NumCascades = 1; - shadowData.Constants.CascadeSplits = Float4::Zero; + // Render depth to a single projection + auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex]; + SetupRenderContext(renderContext, shadowContext); + shadowContext.List->Clear(); + shadowContext.View.SetProjector(LocalLightNearPlane, light.Radius, light.Position, light.Direction, light.UpVector, light.OuterConeAngle * 2.0f); + shadowContext.View.PrepareCache(shadowContext, shadowMapsSize, shadowMapsSize, Float2::Zero, &view); + atlasLight.SetWorldToShadow(0, shadowContext.View.ViewProjection()); } void ShadowsPass::Dispose() @@ -543,294 +562,420 @@ void ShadowsPass::Dispose() _psShadowSpot.Delete(); _shader = nullptr; _sphereModel = nullptr; - SAFE_DELETE_GPU_RESOURCE(_shadowMapCSM); - SAFE_DELETE_GPU_RESOURCE(_shadowMapCube); -} - -void ShadowsPass::Prepare() -{ - // Clear cached data - _shadowData.Clear(); - LastDirLightIndex = -1; - LastDirLightShadowMap = nullptr; + SAFE_DELETE_GPU_RESOURCE(_psDepthClear); } void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch& renderContextBatch) { PROFILE_CPU(); - auto& view = renderContext.View; + maxShadowsQuality = Math::Clamp(Math::Min((int32)Graphics::ShadowsQuality, (int32)renderContext.View.MaxShadowsQuality), 0, (int32)Quality::MAX - 1); - // Update shadow map - const auto shadowMapsQuality = Graphics::ShadowMapsQuality; - if (shadowMapsQuality != _currentShadowMapsQuality) - updateShadowMapSize(); - auto shadowsQuality = Graphics::ShadowsQuality; - maxShadowsQuality = Math::Clamp(Math::Min(static_cast(shadowsQuality), static_cast(view.MaxShadowsQuality)), 0, static_cast(Quality::MAX) - 1); - - // Create shadow projections for lights + // Early out and skip shadows setup if no lights is actively casting shadows + // RenderBuffers will automatically free any old ShadowsCustomBuffer after a few frames if we don't update LastFrameUsed + if (_shadowMapFormat == PixelFormat::Unknown || checkIfSkipPass() || EnumHasNoneFlags(renderContext.View.Flags, ViewFlags::Shadows)) + return; + Array shadowedLights; for (auto& light : renderContext.List->DirectionalLights) { - if (::CanRenderShadow(view, light) && CanRenderShadow(renderContext, light)) - SetupLight(renderContext, renderContextBatch, light); - } - for (auto& light : renderContext.List->PointLights) - { - if (::CanRenderShadow(view, light) && CanRenderShadow(renderContext, light)) - SetupLight(renderContext, renderContextBatch, light); + if (light.CanRenderShadow(renderContext.View)) + shadowedLights.Add(&light); } for (auto& light : renderContext.List->SpotLights) { - if (::CanRenderShadow(view, light) && CanRenderShadow(renderContext, light)) - SetupLight(renderContext, renderContextBatch, light); + if (light.CanRenderShadow(renderContext.View)) + shadowedLights.Add(&light); } -} - -bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const RenderPointLightData& light) -{ - const Float3 lightPosition = light.Position; - const float dstLightToView = Float3::Distance(lightPosition, renderContext.View.Position); - - // Fade shadow on distance - const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); - const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); - - return fade > ZeroTolerance && _shadowMapFormat != PixelFormat::Unknown; -} - -bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const RenderSpotLightData& light) -{ - const Float3 lightPosition = light.Position; - const float dstLightToView = Float3::Distance(lightPosition, renderContext.View.Position); - - // Fade shadow on distance - const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f); - const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance); - - return fade > ZeroTolerance && _shadowMapFormat != PixelFormat::Unknown; -} - -bool ShadowsPass::CanRenderShadow(const RenderContext& renderContext, const RenderDirectionalLightData& light) -{ - return _shadowMapFormat != PixelFormat::Unknown; -} - -void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RenderPointLightData& light, GPUTextureView* shadowMask) -{ - if (light.ShadowDataIndex == -1) - return; - PROFILE_GPU_CPU("Shadow"); - GPUContext* context = GPUDevice::Instance->GetMainContext(); - RenderContext& renderContext = renderContextBatch.GetMainContext(); - ShadowData& shadowData = _shadowData[light.ShadowDataIndex]; - const float sphereModelScale = 3.0f; - auto& view = renderContext.View; - auto shader = _shader->GetShader(); - - // TODO: here we can use lower shadows quality based on light distance to view (LOD switching) and per light setting for max quality - int32 shadowQuality = maxShadowsQuality; - - // Set up GPU context and render view - const auto shadowMapsSizeCube = (float)_shadowMapsSizeCube; - context->SetViewportAndScissors(shadowMapsSizeCube, shadowMapsSizeCube); - - // Render depth to all 6 faces of the cube map - for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) + for (auto& light : renderContext.List->PointLights) { - auto rt = _shadowMapCube->View(faceIndex); - context->ResetSR(); - context->SetRenderTarget(rt, static_cast(nullptr)); - context->ClearDepth(rt); - auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + faceIndex]; - shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth); - shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, nullptr); + if (light.CanRenderShadow(renderContext.View)) + shadowedLights.Add(&light); + } + if (shadowedLights.IsEmpty()) + return; + + // Initialize shadow atlas + auto& shadows = *renderContext.Buffers->GetCustomBuffer(TEXT("Shadows")); + const auto currentFrame = Engine::FrameCount; + shadows.LastFrameUsed = currentFrame; + int32 atlasResolution; + switch (Graphics::ShadowMapsQuality) + { + case Quality::Low: + atlasResolution = 1024; + break; + case Quality::Medium: + atlasResolution = 2048; + break; + case Quality::High: + atlasResolution = 4096; + break; + case Quality::Ultra: + atlasResolution = 8192; + break; + default: + return; + } + const int32 baseLightResolution = atlasResolution / MAX_CSM_CASCADES; // Allow to store 4 CSM cascades in a single row in all cases + if (shadows.Resolution != atlasResolution) + { + shadows.Reset(); + auto desc = GPUTextureDescription::New2D(atlasResolution, atlasResolution, _shadowMapFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil); + if (shadows.ShadowMapAtlas->Init(desc)) + { + LOG(Fatal, "Failed to setup shadow map of size {0}x{1} and format {2}", desc.Width, desc.Height, ScriptingEnum::ToString(desc.Format)); + return; + } + shadows.ClearShadowMapAtlas = true; + shadows.Resolution = atlasResolution; + } + if (renderContext.View.Origin != shadows.ViewOrigin) + { + // Large Worlds chunk movement so invalidate cached shadows + shadows.Reset(); + shadows.ViewOrigin = renderContext.View.Origin; + } + if (!shadows.AtlasTiles) + shadows.AtlasTiles = New(0, 0, atlasResolution, atlasResolution); + + // Update/add lights + for (const RenderLightData* light : shadowedLights) + { + auto& atlasLight = shadows.Lights[light->ID]; + + // Calculate resolution for this light + // TODO: add support for fixed shadow map resolution assigned per-light + float lightResolutionFloat = baseLightResolution * light->ScreenSize; + atlasLight.Resolution = QuantizeResolution(lightResolutionFloat); + + // Cull too small lights + constexpr uint16 MinResolution = 16; + if (atlasLight.Resolution < MinResolution) + continue; + + if (light->IsDirectionalLight) + atlasLight.TilesNeeded = Math::Clamp(((const RenderDirectionalLightData*)light)->CascadeCount, 0, MAX_CSM_CASCADES); + else if (light->IsPointLight) + atlasLight.TilesNeeded = 6; + else + atlasLight.TilesNeeded = 1; + atlasLight.LastFrameUsed = currentFrame; + } + + // Remove unused lights (before inserting any new ones to make space in the atlas) + for (auto it = shadows.Lights.Begin(); it.IsNotEnd(); ++it) + { + if (it->Value.LastFrameUsed != currentFrame) + { + for (auto& tile : it->Value.Tiles) + { + if (tile) + tile->Free(&shadows); + } + shadows.Lights.Remove(it); + } + } + + // Calculate size requirements for atlas + int32 atlasPixelsNeeded = 0; + for (auto it = shadows.Lights.Begin(); it.IsNotEnd(); ++it) + { + const auto& atlasLight = it->Value; + atlasPixelsNeeded += atlasLight.Resolution * atlasLight.Resolution * atlasLight.TilesNeeded; + } + const int32 atlasPixelsAllowed = atlasResolution * atlasResolution; + const float atlasPixelsCoverage = (float)atlasPixelsNeeded / atlasPixelsAllowed; + + // If atlas is overflown then scale down the shadows resolution + float resolutionScale = 1.0f; + if (atlasPixelsCoverage > 1.0f) + resolutionScale /= atlasPixelsCoverage; + float finalScale = 1.0f; + bool defragDone = false; +RETRY_ATLAS_SETUP: + + // Apply additional scale to the shadows resolution + if (!Math::IsOne(resolutionScale)) + { + finalScale *= resolutionScale; + for (const RenderLightData* light : shadowedLights) + { + auto& atlasLight = shadows.Lights[light->ID]; + if (light->IsDirectionalLight && !defragDone) + continue; // Reduce scaling on directional light shadows (before defrag) + atlasLight.Resolution = QuantizeResolution(atlasLight.Resolution * resolutionScale); + } + } + + // Macro checks if light has proper amount of tiles already assigned and the resolution is matching +#define IS_LIGHT_TILE_REUSABLE (atlasLight.ContextCount == atlasLight.TilesNeeded && atlasLight.Tiles[0] && atlasLight.Tiles[0]->Width == atlasLight.Resolution) + + // Remove incorrect tiles before allocating new ones + for (RenderLightData* light : shadowedLights) + { + auto& atlasLight = shadows.Lights[light->ID]; + if (IS_LIGHT_TILE_REUSABLE) + continue; + + // Remove existing tiles + for (auto& tile : atlasLight.Tiles) + { + if (tile) + { + tile->Free(&shadows); + tile = nullptr; + } + } + } + + // Insert tiles into the atlas (already sorted to favor the first ones) + for (RenderLightData* light : shadowedLights) + { + auto& atlasLight = shadows.Lights[light->ID]; + if (IS_LIGHT_TILE_REUSABLE || atlasLight.Resolution < 16) + continue; + + // Try to insert tiles + bool failedToInsert = false; + for (int32 tileIndex = 0; tileIndex < atlasLight.TilesNeeded; tileIndex++) + { + auto tile = shadows.AtlasTiles->Insert(atlasLight.Resolution, atlasLight.Resolution, 0, &shadows); + if (!tile) + { + // Free any previous tiles that were added + for (int32 i = 0; i < tileIndex; i++) + { + atlasLight.Tiles[i]->Free(&shadows); + atlasLight.Tiles[i] = nullptr; + } + failedToInsert = true; + break; + } + atlasLight.Tiles[tileIndex] = tile; + } + if (failedToInsert) + { + if (defragDone) + { + // Already defragmented atlas so scale it down + resolutionScale = 0.8f; + } + else + { + // Defragment atlas without changing scale + defragDone = true; + resolutionScale = 1.0f; + } + + // Rebuild atlas + shadows.ClearTiles(); + shadows.AtlasTiles = New(0, 0, atlasResolution, atlasResolution); + goto RETRY_ATLAS_SETUP; + } + } + + // Setup shadows for all lights + for (RenderLightData* light : shadowedLights) + { + auto& atlasLight = shadows.Lights[light->ID]; + if (atlasLight.Tiles[0] && atlasLight.Tiles[0]->Width == atlasLight.Resolution) + { + light->HasShadow = true; + if (light->IsPointLight) + SetupLight(renderContext, renderContextBatch, *(RenderPointLightData*)light, atlasLight); + else if (light->IsSpotLight) + SetupLight(renderContext, renderContextBatch, *(RenderSpotLightData*)light, atlasLight); + else //if (light->IsDirectionalLight) + SetupLight(renderContext, renderContextBatch, *(RenderDirectionalLightData*)light, atlasLight); + } + } + +#undef IS_LIGHT_TILE_REUSABLE + + // Update shadows buffer (contains packed data with all shadow projections in the atlas) + const float atlasResolutionInv = 1.0f / (float)atlasResolution; + shadows.ShadowsBuffer.Clear(); + shadows.ShadowsBuffer.Write(Float4::Zero); // Insert dummy prefix so ShadowsBufferAddress=0 indicates no shadow + for (RenderLightData* light : shadowedLights) + { + auto& atlasLight = shadows.Lights[light->ID]; + if (atlasLight.Tiles[0] == nullptr) + { + light->ShadowsBufferAddress = 0; // Clear to indicate no shadow + continue; + } + + // Cache start of the shadow data for this light + light->ShadowsBufferAddress = shadows.ShadowsBuffer.Data.Count() / sizeof(Float4); + + // Write shadow data (this must match HLSL) + const int32 tilesCount = atlasLight.ContextCount; + { + // Shadow info + auto* packed = shadows.ShadowsBuffer.WriteReserve(2); + Color32 packed0x((byte)(atlasLight.Sharpness * (255.0f / 10.0f)), (byte)(atlasLight.Fade * 255.0f), tilesCount, 0); + packed[0] = Float4(*(const float*)&packed0x, atlasLight.FadeDistance, atlasLight.NormalOffsetScale, atlasLight.Bias); + packed[1] = atlasLight.CascadeSplits; + } + for (int32 tileIndex = 0; tileIndex < tilesCount; tileIndex++) + { + // Shadow projection info + const ShadowsAtlasTile* tile = atlasLight.Tiles[tileIndex]; + ASSERT(tile); + const Matrix& worldToShadow = atlasLight.WorldToShadow[tileIndex]; + auto* packed = shadows.ShadowsBuffer.WriteReserve(5); + packed[0] = Float4(tile->Width, tile->Height, tile->X, tile->Y) * atlasResolutionInv; // UV to AtlasUV via a single MAD instruction + packed[1] = worldToShadow.GetColumn1(); + packed[2] = worldToShadow.GetColumn2(); + packed[3] = worldToShadow.GetColumn3(); + packed[4] = worldToShadow.GetColumn4(); + } + } + GPUContext* context = GPUDevice::Instance->GetMainContext(); + shadows.ShadowsBuffer.Flush(context); + shadows.ShadowsBufferView = shadows.ShadowsBuffer.GetBuffer()->View(); +} + +void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch) +{ + const RenderContext& renderContext = renderContextBatch.GetMainContext(); + const ShadowsCustomBuffer* shadowsPtr = renderContext.Buffers->FindCustomBuffer(TEXT("Shadows")); + if (shadowsPtr == nullptr || shadowsPtr->Lights.IsEmpty() || shadowsPtr->LastFrameUsed != Engine::FrameCount) + return; + PROFILE_GPU_CPU("ShadowMaps"); + const ShadowsCustomBuffer& shadows = *shadowsPtr; + GPUContext* context = GPUDevice::Instance->GetMainContext(); + context->ResetSR(); + context->SetRenderTarget(shadows.ShadowMapAtlas->View(), (GPUTextureView*)nullptr); + GPUConstantBuffer* quadShaderCB; + if (shadows.ClearShadowMapAtlas) + { + context->ClearDepth(shadows.ShadowMapAtlas->View()); + } + else + { + QuadShaderData quadShaderData; + quadShaderData.Color = Float4::One; // Color.r is used by PS_DepthClear in Quad shader to clear depth + quadShaderCB = GPUDevice::Instance->QuadShader->GetCB(0); + context->UpdateCB(quadShaderCB, &quadShaderData); + } + + // Render depth to all shadow map tiles + for (auto& e : shadows.Lights) + { + const ShadowAtlasLight& atlasLight = e.Value; + for (int32 tileIndex = 0; tileIndex < atlasLight.ContextCount; tileIndex++) + { + const ShadowsAtlasTile* tile = atlasLight.Tiles[tileIndex]; + if (!tile) + break; + + // Set viewport for tile + context->SetViewportAndScissors(Viewport(tile->X, tile->Y, tile->Width, tile->Height)); + + if (!shadows.ClearShadowMapAtlas) + { + // Clear tile depth + context->BindCB(0, quadShaderCB); + context->SetState(_psDepthClear); + context->DrawFullscreenTriangle(); + } + + // Draw objects depth + auto& shadowContext = renderContextBatch.Contexts[atlasLight.ContextIndex + tileIndex]; + shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth); + shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, nullptr); + } } // Restore GPU context context->ResetSR(); context->ResetRenderTarget(); - const Viewport viewport = renderContext.Task->GetViewport(); - GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; - GPUTextureView* depthBufferSRV = EnumHasAnyFlags(depthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); - context->SetViewportAndScissors(viewport); - context->BindSR(0, renderContext.Buffers->GBuffer0); - context->BindSR(1, renderContext.Buffers->GBuffer1); - context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, depthBufferSRV); - context->BindSR(4, renderContext.Buffers->GBuffer3); - - // Setup shader data - Data sperLight; - GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetShaderData(sperLight.Light, true); - sperLight.LightShadow = shadowData.Constants; - Matrix::Transpose(view.ViewProjection(), sperLight.ViewProjectionMatrix); - sperLight.ContactShadowsDistance = light.ShadowsDistance; - sperLight.ContactShadowsLength = EnumHasAnyFlags(view.Flags, ViewFlags::ContactShadows) ? light.ContactShadowsLength : 0.0f; - - // Calculate world view projection matrix for the light sphere - Matrix world, wvp, matrix; - Matrix::Scaling(light.Radius * sphereModelScale, wvp); - Matrix::Translation(light.Position, matrix); - Matrix::Multiply(wvp, matrix, world); - Matrix::Multiply(world, view.ViewProjection(), wvp); - Matrix::Transpose(wvp, sperLight.WVP); - - // Render shadow in screen space - context->UpdateCB(shader->GetCB(0), &sperLight); - context->BindCB(0, shader->GetCB(0)); - context->BindCB(1, shader->GetCB(1)); - context->BindSR(5, _shadowMapCube->ViewArray()); - context->SetRenderTarget(shadowMask); - context->SetState(_psShadowPoint.Get(shadowQuality + (sperLight.ContactShadowsLength > ZeroTolerance ? 4 : 0))); - _sphereModel->Render(context); - - // Cleanup - context->ResetRenderTarget(); - context->UnBindSR(5); - - // Render volumetric light with shadow - VolumetricFogPass::Instance()->RenderLight(renderContext, context, light, _shadowMapCube->ViewArray(), sperLight.LightShadow); -} - -void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RenderSpotLightData& light, GPUTextureView* shadowMask) -{ - if (light.ShadowDataIndex == -1) - return; - PROFILE_GPU_CPU("Shadow"); - GPUContext* context = GPUDevice::Instance->GetMainContext(); - RenderContext& renderContext = renderContextBatch.GetMainContext(); - ShadowData& shadowData = _shadowData[light.ShadowDataIndex]; - const float sphereModelScale = 3.0f; - auto& view = renderContext.View; - auto shader = _shader->GetShader(); - - // TODO: here we can use lower shadows quality based on light distance to view (LOD switching) and per light setting for max quality - int32 shadowQuality = maxShadowsQuality; - - // Set up GPU context and render view - const auto shadowMapsSizeCube = (float)_shadowMapsSizeCube; - context->SetViewportAndScissors(shadowMapsSizeCube, shadowMapsSizeCube); - - // Render depth to all 1 face of the cube map - constexpr int32 faceIndex = 0; - { - auto rt = _shadowMapCube->View(faceIndex); - context->ResetSR(); - context->SetRenderTarget(rt, static_cast(nullptr)); - context->ClearDepth(rt); - auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + faceIndex]; - shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth); - shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, nullptr); - } - - // Restore GPU context - context->ResetSR(); - context->ResetRenderTarget(); - const Viewport viewport = renderContext.Task->GetViewport(); - GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; - GPUTextureView* depthBufferSRV = EnumHasAllFlags(depthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); - context->SetViewportAndScissors(viewport); - context->BindSR(0, renderContext.Buffers->GBuffer0); - context->BindSR(1, renderContext.Buffers->GBuffer1); - context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, depthBufferSRV); - context->BindSR(4, renderContext.Buffers->GBuffer3); - - // Setup shader data - Data sperLight; - GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetShaderData(sperLight.Light, true); - sperLight.LightShadow = shadowData.Constants; - Matrix::Transpose(view.ViewProjection(), sperLight.ViewProjectionMatrix); - sperLight.ContactShadowsDistance = light.ShadowsDistance; - sperLight.ContactShadowsLength = EnumHasAnyFlags(view.Flags, ViewFlags::ContactShadows) ? light.ContactShadowsLength : 0.0f; - - // Calculate world view projection matrix for the light sphere - Matrix world, wvp, matrix; - Matrix::Scaling(light.Radius * sphereModelScale, wvp); - Matrix::Translation(light.Position, matrix); - Matrix::Multiply(wvp, matrix, world); - Matrix::Multiply(world, view.ViewProjection(), wvp); - Matrix::Transpose(wvp, sperLight.WVP); - - // Render shadow in screen space - context->UpdateCB(shader->GetCB(0), &sperLight); - context->BindCB(0, shader->GetCB(0)); - context->BindCB(1, shader->GetCB(1)); - context->BindSR(5, _shadowMapCube->View(faceIndex)); - context->SetRenderTarget(shadowMask); - context->SetState(_psShadowSpot.Get(shadowQuality + (sperLight.ContactShadowsLength > ZeroTolerance ? 4 : 0))); - _sphereModel->Render(context); - - // Cleanup - context->ResetRenderTarget(); - context->UnBindSR(5); - - // Render volumetric light with shadow - VolumetricFogPass::Instance()->RenderLight(renderContext, context, light, _shadowMapCube->View(faceIndex), sperLight.LightShadow); -} - -void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, int32 index, GPUTextureView* shadowMask) -{ - if (light.ShadowDataIndex == -1) - return; - PROFILE_GPU_CPU("Shadow"); - GPUContext* context = GPUDevice::Instance->GetMainContext(); - RenderContext& renderContext = renderContextBatch.GetMainContext(); - ShadowData& shadowData = _shadowData[light.ShadowDataIndex]; - const float shadowMapsSizeCSM = (float)_shadowMapsSizeCSM; - context->SetViewportAndScissors(shadowMapsSizeCSM, shadowMapsSizeCSM); - - // Render shadow map for each projection - for (int32 cascadeIndex = 0; cascadeIndex < shadowData.ContextCount; cascadeIndex++) - { - const auto rt = _shadowMapCSM->View(cascadeIndex); - context->ResetSR(); - context->SetRenderTarget(rt, static_cast(nullptr)); - context->ClearDepth(rt); - auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + cascadeIndex]; - shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth); - shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, nullptr); - } - - // Restore GPU context - context->ResetSR(); - context->ResetRenderTarget(); - GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; - GPUTextureView* depthBufferSRV = EnumHasAnyFlags(depthBuffer->Flags(), GPUTextureFlags::ReadOnlyDepthView) ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); context->SetViewportAndScissors(renderContext.Task->GetViewport()); - context->BindSR(0, renderContext.Buffers->GBuffer0); - context->BindSR(1, renderContext.Buffers->GBuffer1); - context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, depthBufferSRV); - context->BindSR(4, renderContext.Buffers->GBuffer3); + shadows.ClearShadowMapAtlas = false; +} + +void ShadowsPass::RenderShadowMask(RenderContextBatch& renderContextBatch, RenderLightData& light, GPUTextureView* shadowMask) +{ + ASSERT(light.HasShadow); + PROFILE_GPU_CPU("Shadow"); + GPUContext* context = GPUDevice::Instance->GetMainContext(); + RenderContext& renderContext = renderContextBatch.GetMainContext(); + const ShadowsCustomBuffer& shadows = *renderContext.Buffers->FindCustomBuffer(TEXT("Shadows")); + ASSERT(shadows.LastFrameUsed == Engine::FrameCount); + const ShadowAtlasLight& atlasLight = shadows.Lights.At(light.ID); + const float sphereModelScale = 3.0f; + auto& view = renderContext.View; + auto shader = _shader->GetShader(); + const bool isLocalLight = light.IsPointLight || light.IsSpotLight; + + // TODO: here we can use lower shadows quality based on light distance to view (LOD switching) and per light setting for max quality + int32 shadowQuality = maxShadowsQuality; // Setup shader data Data sperLight; - auto& view = renderContext.View; GBufferPass::SetInputs(view, sperLight.GBuffer); - light.SetShaderData(sperLight.Light, true); - sperLight.LightShadow = shadowData.Constants; + if (light.IsDirectionalLight) + ((RenderDirectionalLightData&)light).SetShaderData(sperLight.Light, true); + else if (light.IsPointLight) + ((RenderPointLightData&)light).SetShaderData(sperLight.Light, true); + else if (light.IsSpotLight) + ((RenderSpotLightData&)light).SetShaderData(sperLight.Light, true); Matrix::Transpose(view.ViewProjection(), sperLight.ViewProjectionMatrix); sperLight.ContactShadowsDistance = light.ShadowsDistance; sperLight.ContactShadowsLength = EnumHasAnyFlags(view.Flags, ViewFlags::ContactShadows) ? light.ContactShadowsLength : 0.0f; + if (isLocalLight) + { + // Calculate world view projection matrix for the light sphere + Matrix world, wvp, matrix; + Matrix::Scaling(((RenderLocalLightData&)light).Radius * sphereModelScale, wvp); + Matrix::Translation(light.Position, matrix); + Matrix::Multiply(wvp, matrix, world); + Matrix::Multiply(world, view.ViewProjection(), wvp); + Matrix::Transpose(wvp, sperLight.WVP); + } + // TODO: reimplement cascades blending for directional lights (but with dithering) // Render shadow in screen space - auto shader = _shader->GetShader(); - context->UpdateCB(shader->GetCB(0), &sperLight); - context->BindCB(0, shader->GetCB(0)); - context->BindCB(1, shader->GetCB(1)); - context->BindSR(5, _shadowMapCSM->ViewArray()); + GPUConstantBuffer* cb0 = shader->GetCB(0); + context->UpdateCB(cb0, &sperLight); + context->BindCB(0, cb0); + context->BindSR(5, shadows.ShadowsBufferView); + context->BindSR(6, shadows.ShadowMapAtlas); + const int32 permutationIndex = shadowQuality + (sperLight.ContactShadowsLength > ZeroTolerance ? 4 : 0); context->SetRenderTarget(shadowMask); - context->SetState(_psShadowDir.Get(maxShadowsQuality + static_cast(Quality::MAX) * shadowData.BlendCSM + (sperLight.ContactShadowsLength > ZeroTolerance ? 8 : 0))); - context->DrawFullscreenTriangle(); + if (light.IsPointLight) + { + context->SetState(_psShadowPoint.Get(permutationIndex)); + _sphereModel->Render(context); + } + else if (light.IsSpotLight) + { + context->SetState(_psShadowSpot.Get(permutationIndex)); + _sphereModel->Render(context); + } + else //if (light.IsDirectionalLight) + { + context->SetState(_psShadowDir.Get(permutationIndex)); + context->DrawFullscreenTriangle(); + } // Cleanup context->ResetRenderTarget(); context->UnBindSR(5); - - // Cache params for the volumetric fog or other effects that use dir light shadow sampling - LastDirLightIndex = index; - LastDirLightShadowMap = _shadowMapCSM->ViewArray(); - LastDirLight = sperLight.LightShadow; + context->UnBindSR(6); +} + +void ShadowsPass::GetShadowAtlas(const RenderBuffers* renderBuffers, GPUTexture*& shadowMapAtlas, GPUBufferView*& shadowsBuffer) +{ + const ShadowsCustomBuffer* shadowsPtr = renderBuffers->FindCustomBuffer(TEXT("Shadows")); + if (shadowsPtr && shadowsPtr->ShadowMapAtlas && shadowsPtr->LastFrameUsed == Engine::FrameCount) + { + shadowMapAtlas = shadowsPtr->ShadowMapAtlas; + shadowsBuffer = shadowsPtr->ShadowsBufferView; + } + else + { + shadowMapAtlas = nullptr; + shadowsBuffer = nullptr; + } } diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h index fd96900ca..17d21b6d6 100644 --- a/Source/Engine/Renderer/ShadowsPass.h +++ b/Source/Engine/Renderer/ShadowsPass.h @@ -9,121 +9,31 @@ #include "Engine/Content/Assets/Model.h" #include "Engine/Graphics/RenderTask.h" -/// -/// Pixel format for fullscreen render target used for shadows calculations -/// -#define SHADOWS_PASS_SS_RR_FORMAT PixelFormat::R11G11B10_Float - -template -bool CanRenderShadow(const RenderView& view, const T& light) -{ - bool result = false; - switch ((ShadowsCastingMode)light.ShadowsMode) - { - case ShadowsCastingMode::StaticOnly: - result = view.IsOfflinePass; - break; - case ShadowsCastingMode::DynamicOnly: - result = !view.IsOfflinePass; - break; - case ShadowsCastingMode::All: - result = true; - break; - default: - break; - } - return result && light.ShadowsStrength > ZeroTolerance; -} - /// /// Shadows rendering service. /// class ShadowsPass : public RendererPass { private: - - struct ShadowData - { - int32 ContextIndex; - int32 ContextCount; - bool BlendCSM; - ShaderLightShadowData Constants; - }; - - // Shader stuff AssetReference _shader; - GPUPipelineStatePermutationsPs(Quality::MAX) * 2 * 2> _psShadowDir; + AssetReference _sphereModel; + GPUPipelineState* _psDepthClear = nullptr; + GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowDir; GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowPoint; GPUPipelineStatePermutationsPs(Quality::MAX) * 2> _psShadowSpot; - PixelFormat _shadowMapFormat; - - // Shadow maps stuff - int32 _shadowMapsSizeCSM; - int32 _shadowMapsSizeCube; - GPUTexture* _shadowMapCSM; - GPUTexture* _shadowMapCube; - Quality _currentShadowMapsQuality; - - // Shadow map rendering stuff - AssetReference _sphereModel; - Array _shadowData; - - // Cached state for the current frame rendering (setup via Prepare) - int32 maxShadowsQuality; + PixelFormat _shadowMapFormat; // Cached on initialization + int32 maxShadowsQuality = 0; // Cached state for the current frame rendering (setup via Prepare) public: - - /// - /// Init - /// - ShadowsPass(); - -public: - - /// - /// Gets current GPU memory usage by the shadow maps - /// - /// GPU memory used in bytes - uint64 GetShadowMapsMemoryUsage() const; - -public: - - // TODO: use full scene shadow map atlas with dynamic slots allocation - int32 LastDirLightIndex = -1; - GPUTextureView* LastDirLightShadowMap = nullptr; - ShaderLightShadowData LastDirLight; - -public: - void Prepare(); - /// /// Setups the shadows rendering for batched scene drawing. Checks which lights will cast a shadow. /// void SetupShadows(RenderContext& renderContext, RenderContextBatch& renderContextBatch); /// - /// Determines whether can render shadow for the specified light. + /// Renders the shadow maps for all lights (into atlas). /// - /// The rendering context. - /// The light. - /// true if can render shadow for the specified light; otherwise, false. - bool CanRenderShadow(const RenderContext& renderContext, const RenderPointLightData& light); - - /// - /// Determines whether can render shadow for the specified light. - /// - /// The rendering context. - /// The light. - /// true if can render shadow for the specified light; otherwise, false. - bool CanRenderShadow(const RenderContext& renderContext, const RenderSpotLightData& light); - - /// - /// Determines whether can render shadow for the specified light. - /// - /// The rendering context. - /// The light. - /// true if can render shadow for the specified light; otherwise, false. - bool CanRenderShadow(const RenderContext& renderContext, const RenderDirectionalLightData& light); + void RenderShadowMaps(RenderContextBatch& renderContextBatch); /// /// Renders the shadow mask for the given light. @@ -131,32 +41,23 @@ public: /// The rendering context batch. /// The light. /// The shadow mask (output). - void RenderShadow(RenderContextBatch& renderContextBatch, RenderPointLightData& light, GPUTextureView* shadowMask); + void RenderShadowMask(RenderContextBatch& renderContextBatch, RenderLightData& light, GPUTextureView* shadowMask); /// - /// Renders the shadow mask for the given light. + /// Gets the shadow atlas texture and shadows buffer for shadow projection in shaders. /// - /// The rendering context batch. - /// The light. - /// The shadow mask (output). - void RenderShadow(RenderContextBatch& renderContextBatch, RenderSpotLightData& light, GPUTextureView* shadowMask); - - /// - /// Renders the shadow mask for the given light. - /// - /// The rendering context batch. - /// The light. - /// The light index. - /// The shadow mask (output). - void RenderShadow(RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, int32 index, GPUTextureView* shadowMask); + /// The render buffers that store frame context. + /// The output shadow map atlas texture or null if unused. + /// The output shadows buffer or null if unused. + static void GetShadowAtlas(const RenderBuffers* renderBuffers, GPUTexture*& shadowMapAtlas, GPUBufferView*& shadowsBuffer); private: - - void updateShadowMapSize(); void SetupRenderContext(RenderContext& renderContext, RenderContext& shadowContext); - void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light); - void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light); - void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light); + void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLightData& light, struct ShadowAtlasLight& atlasLight); + void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight); + void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight); + void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight); + void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight); #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) @@ -169,14 +70,12 @@ private: #endif public: - // [RendererPass] String ToString() const override; bool Init() override; void Dispose() override; protected: - // [RendererPass] bool setupResources() override; }; diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index adbe4c837..fe3b2c2e8 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -6,12 +6,12 @@ #include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" -#include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/GPULimits.h" +#include "Engine/Graphics/GPUContext.h" #include "Engine/Content/Assets/CubeTexture.h" #include "Engine/Content/Content.h" #include "Engine/Engine/Engine.h" -#include "Engine/Graphics/GPUContext.h" // Must match shader source int32 VolumetricFogGridInjectionGroupSize = 4; @@ -143,38 +143,30 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, switch (quality) { case Quality::Low: - { _cache.GridPixelSize = 16; _cache.GridSizeZ = 64; _cache.FogJitter = false; _cache.MissedHistorySamplesCount = 1; break; - } case Quality::Medium: - { _cache.GridPixelSize = 16; _cache.GridSizeZ = 64; _cache.FogJitter = true; _cache.MissedHistorySamplesCount = 4; break; - } case Quality::High: - { _cache.GridPixelSize = 16; _cache.GridSizeZ = 128; _cache.FogJitter = true; _cache.MissedHistorySamplesCount = 4; break; - } case Quality::Ultra: - { _cache.GridPixelSize = 8; _cache.GridSizeZ = 256; _cache.FogJitter = true; _cache.MissedHistorySamplesCount = 8; break; } - } // Prepare const int32 width = renderContext.Buffers->GetWidth(); @@ -202,7 +194,6 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, _cache.Data.VolumetricFogMaxDistance = options.Distance; _cache.Data.MissedHistorySamplesCount = Math::Clamp(_cache.MissedHistorySamplesCount, 1, (int32)ARRAY_COUNT(_cache.Data.FrameJitterOffsets)); Matrix::Transpose(view.PrevViewProjection, _cache.Data.PrevWorldToClip); - _cache.Data.DirectionalLightShadow.NumCascades = 0; _cache.Data.SkyLight.VolumetricScatteringIntensity = 0; // Fill frame jitter history @@ -262,83 +253,6 @@ GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext return renderContext.Buffers->LocalShadowedLightScattering->ViewVolume(); } -template -void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, T& light, ShaderLightShadowData& shadow) -{ - // Prepare - VolumetricFogOptions options; - if (Init(renderContext, context, options)) - return; - auto& view = renderContext.View; - - // Calculate light volume bounds in camera frustum depth range (min and max) - const Float3 center = light.Position; - const float radius = light.Radius; - Float3 viewSpaceLightBoundsOrigin = Float3::Transform(center, view.View); - float furthestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z + radius, options, _cache.GridSizeZ); - float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - radius, options, _cache.GridSizeZ); - int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, _cache.GridSize.Z - 1.0f); - int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, _cache.GridSize.Z - 1.0f); - - // Cull light - if ((view.Position - center).LengthSquared() >= (options.Distance + radius) * (options.Distance + radius) || volumeZBoundsMin >= volumeZBoundsMax) - return; - - PROFILE_GPU_CPU("Volumetric Fog Light"); - - // Allocate temporary buffer for light scattering injection - auto localShadowedLightScattering = GetLocalShadowedLightScattering(renderContext, context, options); - - // Prepare - PerLight perLight; - auto cb0 = _shader->GetShader()->GetCB(0); - auto cb1 = _shader->GetShader()->GetCB(1); - - // Bind the output - context->SetRenderTarget(localShadowedLightScattering); - context->SetViewportAndScissors(_cache.Data.GridSize.X, _cache.Data.GridSize.Y); - - // Setup data - perLight.SliceToDepth.X = _cache.Data.GridSize.Z; - perLight.SliceToDepth.Y = _cache.Data.VolumetricFogMaxDistance; - perLight.MinZ = volumeZBoundsMin; - perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; - perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius); - Matrix::Transpose(view.Projection, perLight.ViewToVolumeClip); - light.SetShaderData(perLight.LocalLight, true); - perLight.LocalLightShadow = shadow; - - // Upload data - context->UpdateCB(cb1, &perLight); - context->BindCB(0, cb0); - context->BindCB(1, cb1); - - // Ensure to have valid buffers created - if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr) - InitCircleBuffer(); - - // Call rendering to the volume - const int32 psIndex = 1; - context->SetState(_psInjectLight.Get(psIndex)); - const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin; - const int32 indexCount = _ibCircleRasterize->GetElementsCount(); - ASSERT(instanceCount > 0); - context->BindVB(ToSpan(&_vbCircleRasterize, 1)); - context->BindIB(_ibCircleRasterize); - context->DrawIndexedInstanced(indexCount, instanceCount, 0); - - // Cleanup - context->UnBindCB(0); - context->UnBindCB(1); - auto viewport = renderContext.Task->GetViewport(); - context->SetViewportAndScissors(viewport); - context->ResetRenderTarget(); - context->FlushState(); - - // Mark as rendered - light.RenderedVolumetricFog = 1; -} - template void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1) { @@ -353,106 +267,67 @@ void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUConte const float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - radius, options, cache.GridSizeZ); const int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f); const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f); - - if (volumeZBoundsMin < volumeZBoundsMax) - { - // TODO: use full scene shadows atlas and render point/spot lights with shadow into a fog volume - bool withShadow = false; - - // Setup data - perLight.SliceToDepth.X = cache.Data.GridSize.Z; - perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; - perLight.MinZ = volumeZBoundsMin; - perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; - perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius); - Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip); - light.SetShaderData(perLight.LocalLight, withShadow); - - // Upload data - context->UpdateCB(cb1, &perLight); - context->BindCB(1, cb1); - - // Ensure to have valid buffers created - if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr) - InitCircleBuffer(); - - // Call rendering to the volume - const int32 psIndex = withShadow ? 1 : 0; - context->SetState(_psInjectLight.Get(psIndex)); - const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin; - const int32 indexCount = _ibCircleRasterize->GetElementsCount(); - context->BindVB(ToSpan(&_vbCircleRasterize, 1)); - context->BindIB(_ibCircleRasterize); - context->DrawIndexedInstanced(indexCount, instanceCount, 0); - } -} - -void VolumetricFogPass::RenderLight(RenderContext& renderContext, GPUContext* context, RenderPointLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow) -{ - // Skip lights with no volumetric light influence or not casting volumetric shadow - if (light.VolumetricScatteringIntensity <= ZeroTolerance || !light.CastVolumetricShadow) + if (volumeZBoundsMin >= volumeZBoundsMax) return; - ASSERT(shadowMap); - context->BindSR(5, shadowMap); + // Setup data + perLight.SliceToDepth.X = cache.Data.GridSize.Z; + perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; + perLight.MinZ = volumeZBoundsMin; + perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; + perLight.ViewSpaceBoundingSphere = Float4(viewSpaceLightBoundsOrigin, radius); + Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip); + const bool withShadow = light.CastVolumetricShadow && light.HasShadow; + light.SetShaderData(perLight.LocalLight, withShadow); - RenderRadialLight(renderContext, context, light, shadow); + // Upload data + context->UpdateCB(cb1, &perLight); + context->BindCB(1, cb1); - context->UnBindSR(5); -} + // Ensure to have valid buffers created + if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr) + InitCircleBuffer(); -void VolumetricFogPass::RenderLight(RenderContext& renderContext, GPUContext* context, RenderSpotLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow) -{ - // Skip lights with no volumetric light influence or not casting volumetric shadow - if (light.VolumetricScatteringIntensity <= ZeroTolerance || !light.CastVolumetricShadow) - return; - ASSERT(shadowMap); - - context->BindSR(6, shadowMap); - - RenderRadialLight(renderContext, context, light, shadow); - - context->UnBindSR(6); + // Call rendering to the volume + const int32 psIndex = withShadow ? 1 : 0; + context->SetState(_psInjectLight.Get(psIndex)); + const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin; + const int32 indexCount = _ibCircleRasterize->GetElementsCount(); + context->BindVB(ToSpan(&_vbCircleRasterize, 1)); + context->BindIB(_ibCircleRasterize); + context->DrawIndexedInstanced(indexCount, instanceCount, 0); } void VolumetricFogPass::Render(RenderContext& renderContext) { - // Prepare VolumetricFogOptions options; auto context = GPUDevice::Instance->GetMainContext(); if (Init(renderContext, context, options)) return; auto& view = renderContext.View; auto& cache = _cache; - PROFILE_GPU_CPU("Volumetric Fog"); // TODO: test exponential depth distribution (should give better quality near the camera) // TODO: use tiled light culling and render unshadowed lights in single pass + // Try to get shadows atlas + GPUTexture* shadowMap; + GPUBufferView* shadowsBuffer; + ShadowsPass::GetShadowAtlas(renderContext.Buffers, shadowMap, shadowsBuffer); + // Init directional light data - GPUTextureView* dirLightShadowMap = nullptr; + Platform::MemoryClear(&_cache.Data.DirectionalLight, sizeof(_cache.Data.DirectionalLight)); if (renderContext.List->DirectionalLights.HasItems()) { const int32 dirLightIndex = (int32)renderContext.List->DirectionalLights.Count() - 1; const auto& dirLight = renderContext.List->DirectionalLights[dirLightIndex]; const float brightness = dirLight.VolumetricScatteringIntensity; - if (brightness > ZeroTolerance) { - const auto shadowPass = ShadowsPass::Instance(); - const bool useShadow = dirLight.CastVolumetricShadow && shadowPass->LastDirLightIndex == dirLightIndex; + const bool useShadow = shadowMap && dirLight.CastVolumetricShadow && dirLight.HasShadow; dirLight.SetShaderData(_cache.Data.DirectionalLight, useShadow); _cache.Data.DirectionalLight.Color *= brightness; - if (useShadow) - { - _cache.Data.DirectionalLightShadow = shadowPass->LastDirLight; - dirLightShadowMap = shadowPass->LastDirLightShadowMap; - } - else - { - _cache.Data.DirectionalLightShadow.NumCascades = 0; - } } } @@ -475,6 +350,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) // Init sky light data GPUTexture* skyLightImage = nullptr; + Platform::MemoryClear(&_cache.Data.SkyLight, sizeof(_cache.Data.SkyLight)); if (renderContext.List->SkyLights.HasItems() && !useDDGI) { const auto& skyLight = renderContext.List->SkyLights.Last(); @@ -510,13 +386,10 @@ void VolumetricFogPass::Render(RenderContext& renderContext) // Initialize fog volume properties { PROFILE_GPU("Initialize"); - context->ResetRenderTarget(); context->BindUA(0, vBufferA->ViewVolume()); context->BindUA(1, vBufferB->ViewVolume()); - context->Dispatch(_csInitialize, groupCountX, groupCountY, groupCountZ); - context->ResetUA(); } @@ -557,7 +430,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f); // Culling - if ((view.Position - center).LengthSquared() >= (options.Distance + radius) * (options.Distance + radius) || volumeZBoundsMin >= volumeZBoundsMax) + if ((view.Position - center).LengthSquared() >= Math::Square(options.Distance + radius) || volumeZBoundsMin >= volumeZBoundsMax) continue; // Setup material shader data @@ -598,25 +471,17 @@ void VolumetricFogPass::Render(RenderContext& renderContext) Array> spotLights; for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++) { - const auto& light = renderContext.List->PointLights[i]; - if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog) - { - if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius)) - { - pointLights.Add(&light); - } - } + const auto& light = renderContext.List->PointLights.Get()[i]; + if (light.VolumetricScatteringIntensity > ZeroTolerance && + (view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius)) + pointLights.Add(&light); } for (int32 i = 0; i < renderContext.List->SpotLights.Count(); i++) { - const auto& light = renderContext.List->SpotLights[i]; - if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog) - { - if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius)) - { - spotLights.Add(&light); - } - } + const auto& light = renderContext.List->SpotLights.Get()[i]; + if (light.VolumetricScatteringIntensity > ZeroTolerance && + (view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius)) + spotLights.Add(&light); } // Skip if no lights to render @@ -638,6 +503,8 @@ void VolumetricFogPass::Render(RenderContext& renderContext) context->SetViewportAndScissors((float)volumeDesc.Width, (float)volumeDesc.Height); // Render them to the volume + context->BindSR(0, shadowMap); + context->BindSR(1, shadowsBuffer); for (int32 i = 0; i < pointLights.Count(); i++) RenderRadialLight(renderContext, context, view, options, *pointLights[i], perLight, cb1); for (int32 i = 0; i < spotLights.Count(); i++) @@ -666,19 +533,19 @@ void VolumetricFogPass::Render(RenderContext& renderContext) context->BindSR(1, vBufferB->ViewVolume()); context->BindSR(2, lightScatteringHistory ? lightScatteringHistory->ViewVolume() : nullptr); context->BindSR(3, localShadowedLightScattering); - context->BindSR(4, dirLightShadowMap); - + context->BindSR(4, shadowMap); + context->BindSR(5, shadowsBuffer); int32 csIndex; if (useDDGI) { - context->BindSR(5, bindingDataDDGI.ProbesData); - context->BindSR(6, bindingDataDDGI.ProbesDistance); - context->BindSR(7, bindingDataDDGI.ProbesIrradiance); + context->BindSR(6, bindingDataDDGI.ProbesData); + context->BindSR(7, bindingDataDDGI.ProbesDistance); + context->BindSR(8, bindingDataDDGI.ProbesIrradiance); csIndex = 1; } else { - context->BindSR(5, skyLightImage); + context->BindSR(6, skyLightImage); csIndex = 0; } context->Dispatch(_csLightScattering.Get(csIndex), groupCountX, groupCountY, groupCountZ); diff --git a/Source/Engine/Renderer/VolumetricFogPass.h b/Source/Engine/Renderer/VolumetricFogPass.h index cbec860fc..47757cf47 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.h +++ b/Source/Engine/Renderer/VolumetricFogPass.h @@ -32,7 +32,7 @@ private: Float3 MultiplyColor; float VolumetricScatteringIntensity; Float3 AdditiveColor; - float Dummt0; + float Dummy0; }); PACK_STRUCT(struct Data { @@ -63,7 +63,6 @@ private: Float4 FrameJitterOffsets[8]; ShaderLightData DirectionalLight; - ShaderLightShadowData DirectionalLightShadow; SkyLightData SkyLight; DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI; }); @@ -77,7 +76,6 @@ private: Matrix ViewToVolumeClip; ShaderLightData LocalLight; - ShaderLightShadowData LocalLightShadow; }); // Shader stuff @@ -147,27 +145,6 @@ public: VolumetricFogPass(); public: - - /// - /// Renders the light to the volumetric fog light scattering volume texture. Called by the light pass after shadow map rendering. Used by the shadows casting lights. - /// - /// The rendering context. - /// The GPU commands context. - /// The light. - /// The shadow map. - /// The light shadow data. - void RenderLight(RenderContext& renderContext, GPUContext* context, RenderPointLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow); - - /// - /// Renders the light to the volumetric fog light scattering volume texture. Called by the light pass after shadow map rendering. Used by the shadows casting lights. - /// - /// The rendering context. - /// The GPU commands context. - /// The light. - /// The shadow map. - /// The light shadow data. - void RenderLight(RenderContext& renderContext, GPUContext* context, RenderSpotLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow); - /// /// Renders the volumetric fog (generates integrated light scattering 3D texture). Does nothing if feature is disabled or not supported. /// @@ -180,8 +157,6 @@ private: GPUTextureView* GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) const; void InitCircleBuffer(); template - void RenderRadialLight(RenderContext& renderContext, GPUContext* context, T& light, ShaderLightShadowData& shadow); - template void RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1); #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 4765614ae..1af8880bc 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -159,7 +159,7 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target float toLightDst = GLOBAL_SDF_WORLD_SIZE; #endif float4 shadowMask = 1; - if (Light.CastShadows > 0) + if (Light.ShadowsBufferAddress != 0) { float NoL = dot(gBuffer.Normal, L); float shadowBias = 10.0f; diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 42512752e..736d6a29c 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -5,15 +5,15 @@ #include "./Flax/LightingCommon.hlsl" -ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMask) +ShadowSample GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMask) { - ShadowData shadow; + ShadowSample shadow; shadow.SurfaceShadow = gBuffer.AO * shadowMask.r; shadow.TransmissionShadow = shadowMask.g; return shadow; } -LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) +LightSample StandardShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { float3 diffuseColor = GetDiffuseColor(gBuffer); float3 H = normalize(V + L); @@ -22,7 +22,7 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa float NoH = saturate(dot(N, H)); float VoH = saturate(dot(V, H)); - LightingData lighting; + LightSample lighting; lighting.Diffuse = Diffuse_Lambert(diffuseColor); #if LIGHTING_NO_SPECULAR lighting.Specular = 0; @@ -37,9 +37,9 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa return lighting; } -LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) +LightSample SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { - LightingData lighting = StandardShading(gBuffer, energy, L, V, N); + LightSample lighting = StandardShading(gBuffer, energy, L, V, N); #if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the material float3 subsurfaceColor = gBuffer.CustomData.rgb; @@ -53,9 +53,9 @@ LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, fl return lighting; } -LightingData FoliageShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) +LightSample FoliageShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { - LightingData lighting = StandardShading(gBuffer, energy, L, V, N); + LightSample lighting = StandardShading(gBuffer, energy, L, V, N); #if defined(USE_GBUFFER_CUSTOM_DATA) // Fake effect of the light going through the thin foliage float3 subsurfaceColor = gBuffer.CustomData.rgb; @@ -67,7 +67,7 @@ LightingData FoliageShading(GBufferSample gBuffer, float energy, float3 L, float return lighting; } -LightingData SurfaceShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) +LightSample SurfaceShading(GBufferSample gBuffer, float energy, float3 L, float3 V, half3 N) { switch (gBuffer.ShadingModel) { @@ -79,7 +79,7 @@ LightingData SurfaceShading(GBufferSample gBuffer, float energy, float3 L, float case SHADING_MODEL_FOLIAGE: return FoliageShading(gBuffer, energy, L, V, N); default: - return (LightingData)0; + return (LightSample)0; } } @@ -121,7 +121,7 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f float3 toLight = lightData.Direction; // Calculate shadow - ShadowData shadow = GetShadow(lightData, gBuffer, shadowMask); + ShadowSample shadow = GetShadow(lightData, gBuffer, shadowMask); // Calculate attenuation if (isRadial) @@ -147,7 +147,7 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f float energy = AreaLightSpecular(lightData, gBuffer.Roughness, toLight, L, V, N); // Calculate direct lighting - LightingData lighting = SurfaceShading(gBuffer, energy, L, V, N); + LightSample lighting = SurfaceShading(gBuffer, energy, L, V, N); // Calculate final light color float3 surfaceLight = (lighting.Diffuse + lighting.Specular) * shadow.SurfaceShadow; diff --git a/Source/Shaders/LightingCommon.hlsl b/Source/Shaders/LightingCommon.hlsl index 239f2a076..7011acb5b 100644 --- a/Source/Shaders/LightingCommon.hlsl +++ b/Source/Shaders/LightingCommon.hlsl @@ -27,26 +27,26 @@ struct LightData float MinRoughness; float3 Position; - float CastShadows; + uint ShadowsBufferAddress; float3 Direction; float Radius; float FalloffExponent; float InverseSquared; - float Dummy0; float RadiusInv; + float Dummy0; }; -// Structure that contains information about shadow -struct ShadowData +// Structure that contains information about shadow sampling result +struct ShadowSample { float SurfaceShadow; float TransmissionShadow; }; // Structure that contains information about direct lighting calculations result -struct LightingData +struct LightSample { float3 Diffuse; float3 Specular; diff --git a/Source/Shaders/Lights.shader b/Source/Shaders/Lights.shader index 45d2a027f..eb11dc8a8 100644 --- a/Source/Shaders/Lights.shader +++ b/Source/Shaders/Lights.shader @@ -61,7 +61,7 @@ void PS_Directional(Quad_VS2PS input, out float4 output : SV_Target0) // Sample shadow mask float4 shadowMask = 1; BRANCH - if (Light.CastShadows > 0) + if (Light.ShadowsBufferAddress != 0) { shadowMask = SAMPLE_RT(Shadow, input.TexCoord); } @@ -98,7 +98,7 @@ void PS_Point(Model_VS2PS input, out float4 output : SV_Target0) // Sample shadow mask float4 shadowMask = 1; BRANCH - if (Light.CastShadows > 0) + if (Light.ShadowsBufferAddress != 0) { shadowMask = SAMPLE_RT(Shadow, uv); } @@ -140,7 +140,7 @@ void PS_Spot(Model_VS2PS input, out float4 output : SV_Target0) // Sample shadow mask float4 shadowMask = 1; BRANCH - if (Light.CastShadows > 0) + if (Light.ShadowsBufferAddress != 0) { shadowMask = SAMPLE_RT(Shadow, uv); } diff --git a/Source/Shaders/PCFKernels.hlsl b/Source/Shaders/PCFKernels.hlsl deleted file mode 100644 index 6629f9497..000000000 --- a/Source/Shaders/PCFKernels.hlsl +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. - -#ifndef __PCF_KERNELS__ -#define __PCF_KERNELS__ - -// Cascades Shadow Mapping - -#if FilterSizeCSM == 2 - -#elif FilterSizeCSM == 3 - -static const float CSMFilterWeightsSum = 7; -static const float CSMFilterWeights[3][3] = -{ - { 0.5,1.0,0.5 }, - { 1.0,1.0,1.0 }, - { 0.5,1.0,0.5 } -}; - -#elif FilterSizeCSM == 5 - -static const float CSMFilterWeightsSum = 17; -static const float CSMFilterWeights[5][5] = -{ - { 0.0,0.5,1.0,0.5,0.0 }, - { 0.5,1.0,1.0,1.0,0.5 }, - { 1.0,1.0,1.0,1.0,1.0 }, - { 0.5,1.0,1.0,1.0,0.5 }, - { 0.0,0.5,1.0,0.5,0.0 } -}; - -#elif FilterSizeCSM == 7 - -static const float CSMFilterWeightsSum = 33; -static const float CSMFilterWeights[7][7] = -{ - { 0.0,0.0,0.5,1.0,0.5,0.0,0.0 }, - { 0.0,1.0,1.0,1.0,1.0,1.0,0.0 }, - { 0.5,1.0,1.0,1.0,1.0,1.0,0.5 }, - { 1.0,1.0,1.0,1.0,1.0,1.0,1.0 }, - { 0.5,1.0,1.0,1.0,1.0,1.0,0.5 }, - { 0.0,1.0,1.0,1.0,1.0,1.0,0.0 }, - { 0.0,0.0,0.5,1.0,0.5,0.0,0.0 } -}; - -#elif FilterSizeCSM == 9 - -static const float CSMFilterWeightsSum = 53; -static const float CSMFilterWeights[9][9] = -{ - { 0.0,0.0,0.0,0.5,1.0,0.5,0.0,0.0,0.0 }, - { 0.0,0.0,1.0,1.0,1.0,1.0,1.0,0.0,0.0 }, - { 0.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0 }, - { 0.5,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.5 }, - { 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 }, - { 0.5,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.5 }, - { 0.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,0.0 }, - { 0.0,0.0,1.0,1.0,1.0,1.0,1.0,0.0,0.0 }, - { 0.0,0.0,0.0,0.5,1.0,0.5,0.0,0.0,0.0 } -}; - -#endif - -// Cube Map Shadows - -#if FilterSizeCube == 5 - -// 5 random points in disc with radius 2.5 -static const float2 PCFDiscSamples[5] = -{ - float2(0.000000, 2.500000), - float2(2.377641, 0.772542), - float2(1.469463, -2.022543), - float2(-1.469463, -2.022542), - float2(-2.377641, 0.772543), -}; - -#elif FilterSizeCube == 12 - -// 12 random points in disc with radius 2.5 -static const float2 PCFDiscSamples[12] = -{ - float2(0.000000, 2.500000), - float2(1.767767, 1.767767), - float2(2.500000, -0.000000), - float2(1.767767, -1.767767), - float2(-0.000000, -2.500000), - float2(-1.767767, -1.767767), - float2(-2.500000, 0.000000), - float2(-1.767766, 1.767768), - float2(-1.006119, -0.396207), - float2(1.000015, 0.427335), - float2(0.416807, -1.006577), - float2(-0.408872, 1.024430), -}; - -#elif FilterSizeCube == 29 - -// 29 random points in disc with radius 2.5 -static const float2 PCFDiscSamples[29] = -{ - float2(0.000000, 2.500000), - float2(1.016842, 2.283864), - float2(1.857862, 1.672826), - float2(2.377641, 0.772542), - float2(2.486305, -0.261321), - float2(2.165063, -1.250000), - float2(1.469463, -2.022543), - float2(0.519779, -2.445369), - float2(-0.519779, -2.445369), - float2(-1.469463, -2.022542), - float2(-2.165064, -1.250000), - float2(-2.486305, -0.261321), - float2(-2.377641, 0.772543), - float2(-1.857862, 1.672827), - float2(-1.016841, 2.283864), - float2(0.091021, -0.642186), - float2(0.698035, 0.100940), - float2(0.959731, -1.169393), - float2(-1.053880, 1.180380), - float2(-1.479156, -0.606937), - float2(-0.839488, -1.320002), - float2(1.438566, 0.705359), - float2(0.067064, -1.605197), - float2(0.728706, 1.344722), - float2(1.521424, -0.380184), - float2(-0.199515, 1.590091), - float2(-1.524323, 0.364010), - float2(-0.692694, -0.086749), - float2(-0.082476, 0.654088), -}; - -#endif - -#endif diff --git a/Source/Shaders/Quad.shader b/Source/Shaders/Quad.shader index 9bebc93c1..da6fce92c 100644 --- a/Source/Shaders/Quad.shader +++ b/Source/Shaders/Quad.shader @@ -56,8 +56,6 @@ float4 PS_CopyLinear(Quad_VS2PS input) : SV_Target #endif -#ifdef _PS_Clear - // Pixel Shader for clearing a render target with a solid color META_PS(true, FEATURE_LEVEL_ES2) float4 PS_Clear(Quad_VS2PS input) : SV_Target @@ -65,4 +63,9 @@ float4 PS_Clear(Quad_VS2PS input) : SV_Target return Color; } -#endif +// Pixel Shader for clearing depth buffer +META_PS(true, FEATURE_LEVEL_ES2) +float PS_DepthClear(Quad_VS2PS input) : SV_Depth +{ + return Color.r; +} diff --git a/Source/Shaders/Shadows.shader b/Source/Shaders/Shadows.shader index 9db800f33..aff46ac8e 100644 --- a/Source/Shaders/Shadows.shader +++ b/Source/Shaders/Shadows.shader @@ -10,7 +10,6 @@ META_CB_BEGIN(0, PerLight) GBufferData GBuffer; LightData Light; -LightShadowData LightShadow; float4x4 WVP; float4x4 ViewProjectionMatrix; float2 Dummy0; @@ -18,8 +17,10 @@ float ContactShadowsDistance; float ContactShadowsLength; META_CB_END +Buffer ShadowsBuffer : register(t5); +Texture2D ShadowMap : register(t6); + DECLARE_GBUFFERDATA_ACCESS(GBuffer) -DECLARE_LIGHTSHADOWDATA_ACCESS(LightShadow); #if CONTACT_SHADOWS @@ -67,10 +68,6 @@ Model_VS2PS VS_Model(ModelInput_PosOnly input) return output; } -#ifdef _PS_PointLight - -TextureCube ShadowMapPoint : register(t5); - // Pixel shader for point light shadow rendering META_PS(true, FEATURE_LEVEL_ES2) META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0) @@ -83,9 +80,6 @@ META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=1) META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1) float4 PS_PointLight(Model_VS2PS input) : SV_Target0 { - float shadow = 1; - float subsurfaceShadow = 1; - // Obtain texture coordinates corresponding to the current pixel float2 uv = (input.ScreenPos.xy / input.ScreenPos.w) * float2(0.5, -0.5) + float2(0.5, 0.5); @@ -94,68 +88,43 @@ float4 PS_PointLight(Model_VS2PS input) : SV_Target0 GBufferSample gBuffer = SampleGBuffer(gBufferData, uv); // Sample shadow - LightShadowData lightShadowData = GetLightShadowData(); - shadow = SampleShadow(Light, lightShadowData, ShadowMapPoint, gBuffer, subsurfaceShadow); + ShadowSample shadow = SamplePointLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer); #if CONTACT_SHADOWS // Calculate screen-space contact shadow - shadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, normalize(Light.Position - gBuffer.WorldPos), ContactShadowsLength); + shadow.SurfaceShadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, normalize(Light.Position - gBuffer.WorldPos), ContactShadowsLength); #endif - return float4(shadow, subsurfaceShadow, 1, 1); + return GetShadowMask(shadow); } -#endif - -#ifdef _PS_DirLight - -Texture2DArray ShadowMapDir : register(t5); - // Pixel shader for directional light shadow rendering META_PS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_3(SHADOWS_QUALITY=0,CSM_BLENDING=0,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=1,CSM_BLENDING=0,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=2,CSM_BLENDING=0,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=3,CSM_BLENDING=0,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=0,CSM_BLENDING=1,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=1,CSM_BLENDING=1,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=2,CSM_BLENDING=1,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=3,CSM_BLENDING=1,CONTACT_SHADOWS=0) -META_PERMUTATION_3(SHADOWS_QUALITY=0,CSM_BLENDING=0,CONTACT_SHADOWS=1) -META_PERMUTATION_3(SHADOWS_QUALITY=1,CSM_BLENDING=0,CONTACT_SHADOWS=1) -META_PERMUTATION_3(SHADOWS_QUALITY=2,CSM_BLENDING=0,CONTACT_SHADOWS=1) -META_PERMUTATION_3(SHADOWS_QUALITY=3,CSM_BLENDING=0,CONTACT_SHADOWS=1) -META_PERMUTATION_3(SHADOWS_QUALITY=0,CSM_BLENDING=1,CONTACT_SHADOWS=1) -META_PERMUTATION_3(SHADOWS_QUALITY=1,CSM_BLENDING=1,CONTACT_SHADOWS=1) -META_PERMUTATION_3(SHADOWS_QUALITY=2,CSM_BLENDING=1,CONTACT_SHADOWS=1) -META_PERMUTATION_3(SHADOWS_QUALITY=3,CSM_BLENDING=1,CONTACT_SHADOWS=1) +META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0) +META_PERMUTATION_2(SHADOWS_QUALITY=1,CONTACT_SHADOWS=0) +META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=0) +META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=0) +META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=1) +META_PERMUTATION_2(SHADOWS_QUALITY=1,CONTACT_SHADOWS=1) +META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=1) +META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1) float4 PS_DirLight(Quad_VS2PS input) : SV_Target0 { - float shadow = 1; - float subsurfaceShadow = 1; - // Sample GBuffer GBufferData gBufferData = GetGBufferData(); GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord); // Sample shadow - LightShadowData lightShadowData = GetLightShadowData(); - shadow = SampleShadow(Light, lightShadowData, ShadowMapDir, gBuffer, subsurfaceShadow); + ShadowSample shadow = SampleDirectionalLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer); #if CONTACT_SHADOWS // Calculate screen-space contact shadow - shadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, Light.Direction, ContactShadowsLength); + shadow.SurfaceShadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, Light.Direction, ContactShadowsLength); #endif - return float4(shadow, subsurfaceShadow, 1, 1); + return GetShadowMask(shadow); } -#endif - -#ifdef _PS_SpotLight - -Texture2D ShadowMapSpot : register(t5); - // Pixel shader for spot light shadow rendering META_PS(true, FEATURE_LEVEL_ES2) META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0) @@ -168,9 +137,6 @@ META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=1) META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1) float4 PS_SpotLight(Model_VS2PS input) : SV_Target0 { - float shadow = 1; - float subsurfaceShadow = 1; - // Obtain texture coordinates corresponding to the current pixel float2 uv = (input.ScreenPos.xy / input.ScreenPos.w) * float2(0.5, -0.5) + float2(0.5, 0.5); @@ -179,15 +145,12 @@ float4 PS_SpotLight(Model_VS2PS input) : SV_Target0 GBufferSample gBuffer = SampleGBuffer(gBufferData, uv); // Sample shadow - LightShadowData lightShadowData = GetLightShadowData(); - shadow = SampleShadow(Light, lightShadowData, ShadowMapSpot, gBuffer, subsurfaceShadow); + ShadowSample shadow = SampleSpotLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer); #if CONTACT_SHADOWS // Calculate screen-space contact shadow - shadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, normalize(Light.Position - gBuffer.WorldPos), ContactShadowsLength); + shadow.SurfaceShadow *= RayCastScreenSpaceShadow(gBufferData, gBuffer, gBuffer.WorldPos, normalize(Light.Position - gBuffer.WorldPos), ContactShadowsLength); #endif - return float4(shadow, subsurfaceShadow, 1, 1); + return GetShadowMask(shadow); } - -#endif diff --git a/Source/Shaders/ShadowsCommon.hlsl b/Source/Shaders/ShadowsCommon.hlsl index 23ddf4214..43fd0c2d5 100644 --- a/Source/Shaders/ShadowsCommon.hlsl +++ b/Source/Shaders/ShadowsCommon.hlsl @@ -12,32 +12,57 @@ #ifndef SHADOWS_QUALITY #define SHADOWS_QUALITY 0 #endif -#ifndef CSM_BLENDING -#define CSM_BLENDING 0 -#endif -// Structure that contains information about light -struct LightShadowData +// Shadow data for the light +struct ShadowData { - float2 ShadowMapSize; float Sharpness; float Fade; - + float FadeDistance; float NormalOffsetScale; float Bias; - float FadeDistance; - uint NumCascades; - + uint TilesCount; float4 CascadeSplits; - float4x4 ShadowVP[6]; }; -#ifdef PLATFORM_ANDROID -// #AdrenoVK_CB_STRUCT_MEMBER_ACCESS_BUG -#define DECLARE_LIGHTSHADOWDATA_ACCESS(uniformName) LightShadowData Get##uniformName##Data() { LightShadowData tmp; tmp.ShadowMapSize = uniformName.ShadowMapSize; tmp.Sharpness = uniformName.Sharpness; tmp.Fade = uniformName.Fade; tmp.NormalOffsetScale = uniformName.NormalOffsetScale; tmp.Bias = uniformName.Bias; tmp.FadeDistance = uniformName.FadeDistance; tmp.NumCascades = uniformName.NumCascades; tmp.CascadeSplits = uniformName.CascadeSplits; tmp.ShadowVP[0] = uniformName.ShadowVP[0]; tmp.ShadowVP[1] = uniformName.ShadowVP[1]; tmp.ShadowVP[2] = uniformName.ShadowVP[2]; tmp.ShadowVP[3] = uniformName.ShadowVP[3]; tmp.ShadowVP[4] = uniformName.ShadowVP[4]; tmp.ShadowVP[5] = uniformName.ShadowVP[5]; return tmp; } -#else -#define DECLARE_LIGHTSHADOWDATA_ACCESS(uniformName) LightShadowData Get##uniformName##Data() { return uniformName; } -#endif +// Shadow projection tile data for the light +struct ShadowTileData +{ + float4 ShadowToAtlas; + float4x4 WorldToShadow; +}; + +// Loads the shadow data of the light in the shadow buffer +ShadowData LoadShadowsBuffer(Buffer shadowsBuffer, uint shadowsBufferAddress) +{ + // This must match C++ + float4 vector0 = shadowsBuffer.Load(shadowsBufferAddress + 0); + float4 vector1 = shadowsBuffer.Load(shadowsBufferAddress + 1); + ShadowData shadow; + uint packed0x = asuint(vector0.x); + shadow.Sharpness = (packed0x & 0x000000ff) * (10.0f / 255.0f); + shadow.Fade = ((packed0x & 0x0000ff00) >> 8) * (1.0f / 255.0f); + shadow.TilesCount = ((packed0x & 0x00ff0000) >> 16); + shadow.FadeDistance = vector0.y; + shadow.NormalOffsetScale = vector0.z; + shadow.Bias = vector0.w; + shadow.CascadeSplits = vector1; + return shadow; +} + +// Loads the shadow tile data of the light in the shadow buffer +ShadowTileData LoadShadowsBufferTile(Buffer shadowsBuffer, uint shadowsBufferAddress, uint tileIndex) +{ + // This must match C++ + shadowsBufferAddress += tileIndex * 5 + 2; + ShadowTileData tile; + tile.ShadowToAtlas = shadowsBuffer.Load(shadowsBufferAddress + 0); + tile.WorldToShadow[0] = shadowsBuffer.Load(shadowsBufferAddress + 1); + tile.WorldToShadow[1] = shadowsBuffer.Load(shadowsBufferAddress + 2); + tile.WorldToShadow[2] = shadowsBuffer.Load(shadowsBufferAddress + 3); + tile.WorldToShadow[3] = shadowsBuffer.Load(shadowsBufferAddress + 4); + return tile; +} float3 GetShadowPositionOffset(float offsetScale, float NoL, float3 normal) { @@ -48,8 +73,16 @@ float3 GetShadowPositionOffset(float offsetScale, float NoL, float3 normal) float CalculateSubsurfaceOcclusion(float opacity, float sceneDepth, float shadowMapDepth) { float thickness = max(sceneDepth - shadowMapDepth, 0); - float occlusion = 1 - thickness * lerp(1.0f, 100.0f, opacity); + float occlusion = 1 - saturate(thickness * lerp(1.0f, 100.0f, opacity)); return shadowMapDepth > 0.99f ? 1 : occlusion; } +float PostProcessShadow(ShadowData lightShadow, float shadow) +{ + // Apply shadow fade and sharpness + shadow = saturate((shadow - 0.5) * lightShadow.Sharpness + 0.5); + shadow = lerp(1.0f, shadow, lightShadow.Fade); + return shadow; +} + #endif diff --git a/Source/Shaders/ShadowsSampling.hlsl b/Source/Shaders/ShadowsSampling.hlsl index e7d10a7b9..d8510011a 100644 --- a/Source/Shaders/ShadowsSampling.hlsl +++ b/Source/Shaders/ShadowsSampling.hlsl @@ -7,46 +7,24 @@ #include "./Flax/GBufferCommon.hlsl" #include "./Flax/LightingCommon.hlsl" -// Select shadows filter based on quality -// Supported sampling kernel sizes fo each shadowing technique: -// CSM: 2, 3, 5, 7, 9 -// Cube: 2, 5, 12, 29 -// Spot: 2, 5, 12, 29 -#if SHADOWS_QUALITY == 0 - -#define FilterSizeCSM 2 -#define FilterSizeCube 2 -#define FilterSizeSpot 2 - -#elif SHADOWS_QUALITY == 1 - - #define FilterSizeCSM 3 - #define FilterSizeCube 5 - #define FilterSizeSpot 5 - -#elif SHADOWS_QUALITY == 2 - - #define FilterSizeCSM 5 - #define FilterSizeCube 12 - #define FilterSizeSpot 12 - -#else // SHADOWS_QUALITY == 3 - - #define FilterSizeCSM 7 - #define FilterSizeCube 12 - #define FilterSizeSpot 12 - +#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5 +#define SAMPLE_SHADOW_MAP(shadowMap, shadowUV, sceneDepth) shadowMap.SampleCmpLevelZero(ShadowSamplerLinear, shadowUV, sceneDepth) +#define SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowUV, texelOffset, sceneDepth) shadowMap.SampleCmpLevelZero(ShadowSamplerLinear, shadowUV, sceneDepth, texelOffset) +#else +#define SAMPLE_SHADOW_MAP(shadowMap, shadowUV, sceneDepth) (sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, shadowUV, 0).r) +#define SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowUV, texelOffset, sceneDepth) (sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, shadowUV, 0, texelOffset).r) #endif -#if SHADOWS_QUALITY != 0 -#include "./Flax/PCFKernels.hlsl" -#endif +float4 GetShadowMask(ShadowSample shadow) +{ + return float4(shadow.SurfaceShadow, shadow.TransmissionShadow, 1, 1); +} // Gets the cube texture face index to use for shadow map sampling for the given view-to-light direction vector // Where: direction = normalize(worldPosition - lightPosition) -int GetCubeFaceIndex(float3 direction) +uint GetCubeFaceIndex(float3 direction) { - int cubeFaceIndex; + uint cubeFaceIndex; float3 absDirection = abs(direction); float maxDirection = max(absDirection.x, max(absDirection.y, absDirection.z)); if (maxDirection == absDirection.x) @@ -58,666 +36,230 @@ int GetCubeFaceIndex(float3 direction) return cubeFaceIndex; } -// Samples the shadow map with a fixed-size PCF kernel optimized with GatherCmpRed. -// Uses code from "Fast Conventional Shadow Filtering" by Holger Gruen, in GPU Pro. -float SampleShadowMapFixedSizePCF(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPos, uint cascadeIndex) +float2 GetLightShadowAtlasUV(ShadowData shadow, ShadowTileData shadowTile, float3 samplePosition, out float4 shadowPosition) { -#if FilterSizeCSM == 2 - -#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5 - - return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(shadowPos.xy, cascadeIndex), sceneDepth); - -#else - - return sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, float3(shadowPos.xy, cascadeIndex), 0).r; - -#endif - -#else - - const int FS_2 = FilterSizeCSM / 2; - float2 tc = shadowPos.xy; - float4 s = 0.0f; - float2 stc = (shadowMapSize * tc.xy) + float2(0.5f, 0.5f); - float2 tcs = floor(stc); - float2 fc; - int row; - int col; - float4 v1[FS_2 + 1]; - float2 v0[FS_2 + 1]; - float3 baseUV = float3(tc.xy, cascadeIndex); - float2 shadowMapSizeInv = 1.0f / shadowMapSize; - - fc.xy = stc - tcs; - tc.xy = tcs * shadowMapSizeInv; - - // Loop over the rows - UNROLL - for (row = -FS_2; row <= FS_2; row += 2) - { - UNROLL - for (col = -FS_2; col <= FS_2; col += 2) - { - float value = CSMFilterWeights[row + FS_2][col + FS_2]; - - if (col > -FS_2) - value += CSMFilterWeights[row + FS_2][col + FS_2 - 1]; - - if (col < FS_2) - value += CSMFilterWeights[row + FS_2][col + FS_2 + 1]; - - if (row > -FS_2) { - value += CSMFilterWeights[row + FS_2 - 1][col + FS_2]; - - if (col < FS_2) - value += CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]; - - if (col > -FS_2) - value += CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]; - } - - if (value != 0.0f) - { - // Gather returns xyzw which is counter clockwise order starting with the sample to the lower left of the queried location -#if CAN_USE_GATHER - - v1[(col + FS_2) / 2] = shadowMap.GatherCmp(ShadowSampler, baseUV, sceneDepth, int2(col, row)); - -#else - - float4 gather; - - gather.x = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(0, 1) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r; - gather.y = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(1, 1) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r; - gather.z = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(1, 0) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r; - gather.w = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(0, 0) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r; - - v1[(col + FS_2) / 2] = gather; - -#endif - } - else - v1[(col + FS_2) / 2] = 0.0f; - - if (col == -FS_2) - { - s.x += (1.0f - fc.y) * (v1[0].w * (CSMFilterWeights[row + FS_2][col + FS_2] - - CSMFilterWeights[row + FS_2][col + FS_2] * fc.x) - + v1[0].z * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2] - - CSMFilterWeights[row + FS_2][col + FS_2 + 1]) - + CSMFilterWeights[row + FS_2][col + FS_2 + 1])); - s.y += fc.y * (v1[0].x * (CSMFilterWeights[row + FS_2][col + FS_2] - - CSMFilterWeights[row + FS_2][col + FS_2] * fc.x) - + v1[0].y * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2] - - CSMFilterWeights[row + FS_2][col + FS_2 + 1]) - + CSMFilterWeights[row + FS_2][col + FS_2 + 1])); - if(row > -FS_2) - { - s.z += (1.0f - fc.y) * (v0[0].x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2] * fc.x) - + v0[0].y * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) - + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])); - s.w += fc.y * (v1[0].w * (CSMFilterWeights[row + FS_2 - 1][col + FS_2] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2] * fc.x) - + v1[0].z * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) - + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])); - } - } - else if (col == FS_2) - { - s.x += (1 - fc.y) * (v1[FS_2].w * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1] - - CSMFilterWeights[row + FS_2][col + FS_2]) + CSMFilterWeights[row + FS_2][col + FS_2]) - + v1[FS_2].z * fc.x * CSMFilterWeights[row + FS_2][col + FS_2]); - s.y += fc.y * (v1[FS_2].x * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1] - - CSMFilterWeights[row + FS_2][col + FS_2] ) + CSMFilterWeights[row + FS_2][col + FS_2]) - + v1[FS_2].y * fc.x * CSMFilterWeights[row + FS_2][col + FS_2]); - if(row > -FS_2) { - s.z += (1 - fc.y) * (v0[FS_2].x * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2]) - + CSMFilterWeights[row + FS_2 - 1][col + FS_2]) - + v0[FS_2].y * fc.x * CSMFilterWeights[row + FS_2 - 1][col + FS_2]); - s.w += fc.y * (v1[FS_2].w * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2]) - + CSMFilterWeights[row + FS_2 - 1][col + FS_2]) - + v1[FS_2].z * fc.x * CSMFilterWeights[row + FS_2 - 1][col + FS_2]); - } - } - else - { - s.x += (1 - fc.y) * (v1[(col + FS_2) / 2].w * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1] - - CSMFilterWeights[row + FS_2][col + FS_2 + 0] ) + CSMFilterWeights[row + FS_2][col + FS_2 + 0]) - + v1[(col + FS_2) / 2].z * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 0] - - CSMFilterWeights[row + FS_2][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2][col + FS_2 + 1])); - s.y += fc.y * (v1[(col + FS_2) / 2].x * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2-1] - - CSMFilterWeights[row + FS_2][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2][col + FS_2 + 0]) - + v1[(col + FS_2) / 2].y * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 0] - - CSMFilterWeights[row + FS_2][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2][col + FS_2 + 1])); - if(row > -FS_2) { - s.z += (1 - fc.y) * (v0[(col + FS_2) / 2].x * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) - + v0[(col + FS_2) / 2].y * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 0] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])); - s.w += fc.y * (v1[(col + FS_2) / 2].w * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) - + v1[(col + FS_2) / 2].z * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 0] - - CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])); - } - } - - if (row != FS_2) - v0[(col + FS_2) / 2] = v1[(col + FS_2) / 2].xy; - } - } - - return dot(s, 1.0f) / CSMFilterWeightsSum; - -#endif -} - -// Helper function for SampleShadowMapOptimizedPCF -float SampleShadowMap(Texture2DArray shadowMap, float2 baseUv, float u, float v, float2 shadowMapSizeInv, uint cascadeIndex, float depth) -{ - float2 uv = baseUv + float2(u, v) * shadowMapSizeInv; - return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(uv, cascadeIndex), depth); -} - -// The method used in The Witness -float SampleShadowMapOptimizedPCF(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPos, uint cascadeIndex) -{ - float2 uv = shadowPos.xy * shadowMapSize; // 1 unit - 1 texel - float2 shadowMapSizeInv = 1.0f / shadowMapSize; - - float2 baseUv; - baseUv.x = floor(uv.x + 0.5); - baseUv.y = floor(uv.y + 0.5); - float s = (uv.x + 0.5 - baseUv.x); - float t = (uv.y + 0.5 - baseUv.y); - baseUv -= float2(0.5, 0.5); - baseUv *= shadowMapSizeInv; - - float sum = 0; - -#if FilterSizeCSM == 2 - - return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(shadowPos.xy, cascadeIndex), sceneDepth); - -#elif FilterSizeCSM == 3 - - float uw0 = (3 - 2 * s); - float uw1 = (1 + 2 * s); - - float u0 = (2 - s) / uw0 - 1; - float u1 = s / uw1 + 1; - - float vw0 = (3 - 2 * t); - float vw1 = (1 + 2 * t); - - float v0 = (2 - t) / vw0 - 1; - float v1 = t / vw1 + 1; - - sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - - return sum * 1.0f / 16; - -#elif FilterSizeCSM == 5 - - float uw0 = (4 - 3 * s); - float uw1 = 7; - float uw2 = (1 + 3 * s); - - float u0 = (3 - 2 * s) / uw0 - 2; - float u1 = (3 + s) / uw1; - float u2 = s / uw2 + 2; - - float vw0 = (4 - 3 * t); - float vw1 = 7; - float vw2 = (1 + 3 * t); - - float v0 = (3 - 2 * t) / vw0 - 2; - float v1 = (3 + t) / vw1; - float v2 = t / vw2 + 2; - - sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw2 * vw0 * SampleShadowMap(shadowMap, baseUv, u2, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - - sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw2 * vw1 * SampleShadowMap(shadowMap, baseUv, u2, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - - sum += uw0 * vw2 * SampleShadowMap(shadowMap, baseUv, u0, v2, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw2 * SampleShadowMap(shadowMap, baseUv, u1, v2, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw2 * vw2 * SampleShadowMap(shadowMap, baseUv, u2, v2, shadowMapSizeInv, cascadeIndex, sceneDepth); - - return sum * 1.0f / 144; - -#else // FilterSizeCSM == 7 - - float uw0 = (5 * s - 6); - float uw1 = (11 * s - 28); - float uw2 = -(11 * s + 17); - float uw3 = -(5 * s + 1); - - float u0 = (4 * s - 5) / uw0 - 3; - float u1 = (4 * s - 16) / uw1 - 1; - float u2 = -(7 * s + 5) / uw2 + 1; - float u3 = -s / uw3 + 3; - - float vw0 = (5 * t - 6); - float vw1 = (11 * t - 28); - float vw2 = -(11 * t + 17); - float vw3 = -(5 * t + 1); - - float v0 = (4 * t - 5) / vw0 - 3; - float v1 = (4 * t - 16) / vw1 - 1; - float v2 = -(7 * t + 5) / vw2 + 1; - float v3 = -t / vw3 + 3; - - sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw2 * vw0 * SampleShadowMap(shadowMap, baseUv, u2, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw3 * vw0 * SampleShadowMap(shadowMap, baseUv, u3, v0, shadowMapSizeInv, cascadeIndex, sceneDepth); - - sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw2 * vw1 * SampleShadowMap(shadowMap, baseUv, u2, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw3 * vw1 * SampleShadowMap(shadowMap, baseUv, u3, v1, shadowMapSizeInv, cascadeIndex, sceneDepth); - - sum += uw0 * vw2 * SampleShadowMap(shadowMap, baseUv, u0, v2, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw2 * SampleShadowMap(shadowMap, baseUv, u1, v2, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw2 * vw2 * SampleShadowMap(shadowMap, baseUv, u2, v2, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw3 * vw2 * SampleShadowMap(shadowMap, baseUv, u3, v2, shadowMapSizeInv, cascadeIndex, sceneDepth); - - sum += uw0 * vw3 * SampleShadowMap(shadowMap, baseUv, u0, v3, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw1 * vw3 * SampleShadowMap(shadowMap, baseUv, u1, v3, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw2 * vw3 * SampleShadowMap(shadowMap, baseUv, u2, v3, shadowMapSizeInv, cascadeIndex, sceneDepth); - sum += uw3 * vw3 * SampleShadowMap(shadowMap, baseUv, u3, v3, shadowMapSizeInv, cascadeIndex, sceneDepth); - - return sum * (1.0f / 2704); - -#endif -} - -// Samples the shadow from the shadow map cascade -float SampleShadowCascade(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPosition, uint cascadeIndex) -{ - float shadow = SampleShadowMapFixedSizePCF(shadowMap, shadowMapSize, sceneDepth, shadowPosition, cascadeIndex); - //float shadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapSize, sceneDepth, shadowPosition, cascadeIndex); - return shadow; -} - -// Samples the shadow for the given directional light (cascaded shadow map sampling) -float SampleShadow(LightData light, LightShadowData shadow, Texture2DArray shadowMap, float3 worldPosition, float viewDepth) -{ - // Create a blend factor which is one before and at the fade plane - float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.NumCascades - 1] + shadow.FadeDistance) / shadow.FadeDistance); - BRANCH - if (fade >= 1.0) - { - return 1; - } - - // Figure out which cascade to sample from - uint cascadeIndex = 0; - for (uint i = 0; i < shadow.NumCascades - 1; i++) - { - if (viewDepth > shadow.CascadeSplits[i]) - cascadeIndex = i + 1; - } - - // Project into shadow space - float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cascadeIndex]); - shadowPosition.xy /= shadowPosition.w; + // Project into shadow space (WorldToShadow is pre-multiplied to convert Clip Space to UV Space) + shadowPosition = mul(float4(samplePosition, 1.0f), shadowTile.WorldToShadow); shadowPosition.z -= shadow.Bias; + shadowPosition.xyz /= shadowPosition.w; - // Sample shadow - float result = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex); + // UV Space -> Atlas Tile UV Space + float2 shadowMapUV = saturate(shadowPosition.xy); + shadowMapUV = shadowMapUV * shadowTile.ShadowToAtlas.xy + shadowTile.ShadowToAtlas.zw; + return shadowMapUV; +} - // Increase the sharpness for higher cascades to match the filter radius - const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f }; - float sharpness = shadow.Sharpness * SharpnessScale[cascadeIndex]; - -#if CSM_BLENDING - // Sample the next cascade, and blend between the two results to smooth the transition - const float BlendThreshold = 0.1f; - float nextSplit = shadow.CascadeSplits[cascadeIndex]; - float splitSize = cascadeIndex == 0 ? nextSplit : nextSplit - shadow.CascadeSplits[cascadeIndex - 1]; - float splitDist = (nextSplit - viewDepth) / splitSize; - BRANCH - if (splitDist <= BlendThreshold && cascadeIndex != shadow.NumCascades - 1) - { - // Find the position of this pixel in light space of next cascade - shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cascadeIndex + 1]); - shadowPosition.xy /= shadowPosition.w; - shadowPosition.z -= shadow.Bias; - - // Sample next cascade and blur result - float nextSplitShadow = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex + 1); - float lerpAmount = smoothstep(0.0f, BlendThreshold, splitDist); - lerpAmount = splitDist / BlendThreshold; - result = lerp(nextSplitShadow, result, lerpAmount); - - // Blur sharpness as well - sharpness = lerp(shadow.Sharpness * SharpnessScale[cascadeIndex + 1], sharpness, lerpAmount); - } +float SampleShadowMap(Texture2D shadowMap, float2 shadowMapUV, float sceneDepth) +{ + // Single hardware sample with filtering + float result = SAMPLE_SHADOW_MAP(shadowMap, shadowMapUV, sceneDepth); + +#if SHADOWS_QUALITY == 1 + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 0), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, -1), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, 1), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 0), sceneDepth); + result = result * (1.0f / 4.0); +#elif SHADOWS_QUALITY == 2 || SHADOWS_QUALITY == 3 + // TODO: implement Percentage-Closer Soft Shadows (PCSS) for Ultra quality + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, -1), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 0), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 1), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, -1), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, 1), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, -1), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 0), sceneDepth); + result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 1), sceneDepth); + result = result * (1.0f / 9.0); #endif - // Apply shadow fade and sharpness - result = saturate((result - 0.5) * sharpness + 0.5); - result = lerp(1.0f, result, (1 - fade) * shadow.Fade); return result; } -// Samples the shadow for the given directional light (cascaded shadow map sampling) for the material surface (supports subsurface shadowing) -float SampleShadow(LightData light, LightShadowData shadow, Texture2DArray shadowMap, GBufferSample gBuffer, out float subsurfaceShadow) +// Samples the shadow for the given directional light on the material surface (supports subsurface shadowing) +ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, GBufferSample gBuffer) { - subsurfaceShadow = 1; - - // Create a blend factor which is one before and at the fade plane - float viewDepth = gBuffer.ViewPos.z; - float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.NumCascades - 1] + shadow.FadeDistance) / shadow.FadeDistance); - BRANCH - if (fade >= 1.0) - { - return 1; - } - - // Figure out which cascade to sample from - uint cascadeIndex = 0; - for (uint i = 0; i < shadow.NumCascades - 1; i++) - { - if (viewDepth > shadow.CascadeSplits[i]) - cascadeIndex = i + 1; - } - -#if defined(USE_GBUFFER_CUSTOM_DATA) - // Subsurface shadowing - BRANCH - if (IsSubsurfaceMode(gBuffer.ShadingModel)) - { - // Get subsurface material info - float opacity = gBuffer.CustomData.a; - - // Project into shadow space - float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[cascadeIndex]); - shadowPosition.xy /= shadowPosition.w; - shadowPosition.z -= shadow.Bias; - - // Sample shadow map (single hardware sample with hardware filtering) - float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, float3(shadowPosition.xy, cascadeIndex), 0).r; - subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth); - - // Apply shadow fade - subsurfaceShadow = lerp(1.0f, subsurfaceShadow, (1 - fade) * shadow.Fade); - } -#endif - - float3 samplePosWS = gBuffer.WorldPos; - #if !LIGHTING_NO_DIRECTIONAL // Skip if surface is in a full shadow float NoL = dot(gBuffer.Normal, light.Direction); BRANCH - if (NoL <= 0) - { - return 0; - } - - // Apply normal offset bias - samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal); + if (NoL <= 0 +#if defined(USE_GBUFFER_CUSTOM_DATA) + && !IsSubsurfaceMode(gBuffer.ShadingModel) +#endif + ) + return (ShadowSample)0; #endif - // Sample shadow - return SampleShadow(light, shadow, shadowMap, samplePosWS, viewDepth); -} + ShadowSample result; + result.SurfaceShadow = 1; + result.TransmissionShadow = 1; + + // Load shadow data + if (light.ShadowsBufferAddress == 0) + return result; // No shadow assigned + ShadowData shadow = LoadShadowsBuffer(shadowsBuffer, light.ShadowsBufferAddress); -// Samples the shadow for the given spot light (PCF shadow map sampling) -float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, float3 worldPosition) -{ - float3 toLight = light.Position - worldPosition; - float toLightLength = length(toLight); - float3 L = toLight / toLightLength; -#if LIGHTING_NO_DIRECTIONAL - float dirCheck = 1.0f; -#else - float dirCheck = dot(-light.Direction, L); -#endif - - // Skip pixels outside of the light influence + // Create a blend factor which is one before and at the fade plane + float viewDepth = gBuffer.ViewPos.z; + float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.TilesCount - 1] + shadow.FadeDistance) / shadow.FadeDistance); BRANCH - if (toLightLength > light.Radius || dirCheck < 0) + if (fade >= 1.0) + return result; + + // Figure out which cascade to sample from + uint cascadeIndex = 0; + for (uint i = 0; i < shadow.TilesCount - 1; i++) { - return 1; + if (viewDepth > shadow.CascadeSplits[i]) + cascadeIndex = i + 1; } + ShadowTileData shadowTile = LoadShadowsBufferTile(shadowsBuffer, light.ShadowsBufferAddress, cascadeIndex); - // Negate direction and use normalized value - toLight = -L; - - // Project into shadow space - float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[0]); - shadowPosition.z -= shadow.Bias; - shadowPosition.xyz /= shadowPosition.w; - - float2 shadowMapUVs = shadowPosition.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f); - -#if FilterSizeSpot == 2 - - // Use single hardware sample with filtering - float result = shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, shadowMapUVs, shadowPosition.z); - -#else - - float3 sideVector = normalize(cross(toLight, float3(0, 0, 1))); - float3 upVector = cross(sideVector, toLight); - - float shadowMapSizeInv = 1.0f / shadow.ShadowMapSize.x; - sideVector *= shadowMapSizeInv; - upVector *= shadowMapSizeInv; - - // Use PCF filter - float result = 0; - UNROLL - for(int i = 0; i < FilterSizeCube; i++) - { - float2 samplePos = shadowMapUVs + sideVector.xy * PCFDiscSamples[i].x + upVector.xy * PCFDiscSamples[i].y; - result += shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, samplePos, shadowPosition.z); - } - result *= (1.0f / FilterSizeCube); - + float3 samplePosition = gBuffer.WorldPos; +#if !LIGHTING_NO_DIRECTIONAL + // Apply normal offset bias + samplePosition += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal); #endif - // Apply shadow fade and sharpness - result = saturate((result - 0.5) * shadow.Sharpness + 0.5); - result = lerp(1.0f, result, shadow.Fade); + // Project position into shadow atlas UV + float4 shadowPosition; + float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition); + // Sample shadow map + result.SurfaceShadow = SampleShadowMap(shadowMap, shadowMapUV, shadowPosition.z); + + // Increase the sharpness for higher cascades to match the filter radius + const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f }; + shadow.Sharpness *= SharpnessScale[cascadeIndex]; + +#if defined(USE_GBUFFER_CUSTOM_DATA) + // Subsurface shadowing + BRANCH + if (IsSubsurfaceMode(gBuffer.ShadingModel)) + { + float opacity = gBuffer.CustomData.a; + shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, gBuffer.WorldPos, shadowPosition); + float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowMapUV, 0).r; + result.TransmissionShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth); + result.TransmissionShadow = PostProcessShadow(shadow, result.TransmissionShadow); + } +#endif + + result.SurfaceShadow = PostProcessShadow(shadow, result.SurfaceShadow); return result; } -// Samples the shadow for the given spot light (PCF shadow map sampling) for the material surface (supports subsurface shadowing) -float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, GBufferSample gBuffer, out float subsurfaceShadow) +// Samples the shadow for the given local light on the material surface (supports subsurface shadowing) +ShadowSample SampleLocalLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, GBufferSample gBuffer, float3 L, float toLightLength, uint tileIndex) { - subsurfaceShadow = 1; - float3 toLight = light.Position - gBuffer.WorldPos; - float toLightLength = length(toLight); - float3 L = toLight / toLightLength; -#if LIGHTING_NO_DIRECTIONAL - float dirCheck = 1.0f; -#else - float dirCheck = dot(-light.Direction, L); +#if !LIGHTING_NO_DIRECTIONAL + // Skip if surface is in a full shadow + float NoL = dot(gBuffer.Normal, L); + BRANCH + if (NoL <= 0 +#if defined(USE_GBUFFER_CUSTOM_DATA) + && !IsSubsurfaceMode(gBuffer.ShadingModel) #endif + ) + return (ShadowSample)0; +#endif + + ShadowSample result; + result.SurfaceShadow = 1; + result.TransmissionShadow = 1; // Skip pixels outside of the light influence BRANCH - if (toLightLength > light.Radius || dirCheck < 0) - { - return 1; - } + if (toLightLength > light.Radius) + return result; + + // Load shadow data + if (light.ShadowsBufferAddress == 0) + return result; // No shadow assigned + ShadowData shadow = LoadShadowsBuffer(shadowsBuffer, light.ShadowsBufferAddress); + ShadowTileData shadowTile = LoadShadowsBufferTile(shadowsBuffer, light.ShadowsBufferAddress, tileIndex); + + float3 samplePosition = gBuffer.WorldPos; +#if !LIGHTING_NO_DIRECTIONAL + // Apply normal offset bias + samplePosition += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal); +#endif + + // Project position into shadow atlas UV + float4 shadowPosition; + float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition); + + // Sample shadow map + result.SurfaceShadow = SampleShadowMap(shadowMap, shadowMapUV, shadowPosition.z); #if defined(USE_GBUFFER_CUSTOM_DATA) // Subsurface shadowing BRANCH if (IsSubsurfaceMode(gBuffer.ShadingModel)) { - // Get subsurface material info float opacity = gBuffer.CustomData.a; - - // Project into shadow space - float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[0]); - shadowPosition.z -= shadow.Bias; - shadowPosition.xyz /= shadowPosition.w; - - // Sample shadow map (use single hardware sample with filtering) - float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowPosition.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f), 0).r; - subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth); - - // Apply shadow fade - subsurfaceShadow = lerp(1.0f, subsurfaceShadow, shadow.Fade); + shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, gBuffer.WorldPos, shadowPosition); + float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowMapUV, 0).r; + result.TransmissionShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth); + result.TransmissionShadow = PostProcessShadow(shadow, result.TransmissionShadow); } #endif - - float3 samplePosWS = gBuffer.WorldPos; - -#if !LIGHTING_NO_DIRECTIONAL - // Skip if surface is in a full shadow - float NoL = dot(gBuffer.Normal, L); - BRANCH - if (NoL <= 0) - { - return 0; - } - - // Apply normal offset bias - samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal); -#endif - - // Sample shadow - return SampleShadow(light, shadow, shadowMap, samplePosWS); -} - -// Samples the shadow for the given point light (PCF shadow map sampling) -float SampleShadow(LightData light, LightShadowData shadow, TextureCube shadowMap, float3 worldPosition) -{ - float3 toLight = light.Position - worldPosition; - float toLightLength = length(toLight); - float3 L = toLight / toLightLength; - - // Skip pixels outside of the light influence - BRANCH - if (toLightLength > light.Radius) - { - return 1; - } - - // Negate direction and use normalized value - toLight = -L; - - // Figure out which cube face we're sampling from - int cubeFaceIndex = GetCubeFaceIndex(toLight); - - // Project into shadow space - float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cubeFaceIndex]); - shadowPosition.z -= shadow.Bias; - shadowPosition.xyz /= shadowPosition.w; - -#if FilterSizeCube == 2 - - // Use single hardware sample with filtering - float result = shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, toLight, shadowPosition.z); - -#else - - float3 sideVector = normalize(cross(toLight, float3(0, 0, 1))); - float3 upVector = cross(sideVector, toLight); - - float shadowMapSizeInv = 1.0f / shadow.ShadowMapSize.x; - sideVector *= shadowMapSizeInv; - upVector *= shadowMapSizeInv; - - // Use PCF filter - float result = 0; - UNROLL - for (int i = 0; i < FilterSizeCube; i++) - { - float3 cubeSamplePos = toLight + sideVector * PCFDiscSamples[i].x + upVector * PCFDiscSamples[i].y; - result += shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, cubeSamplePos, shadowPosition.z); - } - result *= (1.0f / FilterSizeCube); - -#endif - - // Apply shadow fade and sharpness - result = saturate((result - 0.5) * shadow.Sharpness + 0.5); - result = lerp(1.0f, result, shadow.Fade); + result.SurfaceShadow = PostProcessShadow(shadow, result.SurfaceShadow); return result; } -// Samples the shadow for the given point light (PCF shadow map sampling) for the material surface (supports subsurface shadowing) -float SampleShadow(LightData light, LightShadowData shadow, TextureCube shadowMap, GBufferSample gBuffer, out float subsurfaceShadow) +// Samples the shadow for the given spot light on the material surface (supports subsurface shadowing) +ShadowSample SampleSpotLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, GBufferSample gBuffer) +{ + float3 toLight = light.Position - gBuffer.WorldPos; + float toLightLength = length(toLight); + float3 L = toLight / toLightLength; + return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, 0); +} + +// Samples the shadow for the given point light on the material surface (supports subsurface shadowing) +ShadowSample SamplePointLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, GBufferSample gBuffer) { - subsurfaceShadow = 1; float3 toLight = light.Position - gBuffer.WorldPos; float toLightLength = length(toLight); float3 L = toLight / toLightLength; - // Skip pixels outside of the light influence - BRANCH - if (toLightLength > light.Radius) - { - return 1; - } - - // Negate direction and use normalized value - toLight = -L; - // Figure out which cube face we're sampling from - int cubeFaceIndex = GetCubeFaceIndex(toLight); + uint cubeFaceIndex = GetCubeFaceIndex(-L); -#if defined(USE_GBUFFER_CUSTOM_DATA) - // Subsurface shadowing - BRANCH - if (IsSubsurfaceMode(gBuffer.ShadingModel)) - { - // Get subsurface material info - float opacity = gBuffer.CustomData.a; + return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, cubeFaceIndex); +} - // Project into shadow space - float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[cubeFaceIndex]); - shadowPosition.z -= shadow.Bias; - shadowPosition.xyz /= shadowPosition.w; +GBufferSample GetDummyGBufferSample(float3 worldPosition) +{ + GBufferSample gBuffer = (GBufferSample)0; + gBuffer.ShadingModel = SHADING_MODEL_LIT; + gBuffer.WorldPos = worldPosition; + return gBuffer; +} - // Sample shadow map (use single hardware sample with filtering) - float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, toLight, 0).r; - subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth); +// Samples the shadow for the given directional light at custom location +ShadowSample SampleDirectionalLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, float3 worldPosition, float viewDepth) +{ + GBufferSample gBuffer = GetDummyGBufferSample(worldPosition); + gBuffer.ViewPos.z = viewDepth; + return SampleDirectionalLightShadow(light, shadowsBuffer, shadowMap, gBuffer); +} - // Apply shadow fade - subsurfaceShadow = lerp(1.0f, subsurfaceShadow, shadow.Fade); - } -#endif - - float3 samplePosWS = gBuffer.WorldPos; +// Samples the shadow for the given spot light at custom location +ShadowSample SampleSpotLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, float3 worldPosition) +{ + GBufferSample gBuffer = GetDummyGBufferSample(worldPosition); + return SampleSpotLightShadow(light, shadowsBuffer, shadowMap, gBuffer); +} -#if !LIGHTING_NO_DIRECTIONAL - // Skip if surface is in a full shadow - float NoL = dot(gBuffer.Normal, L); - BRANCH - if (NoL <= 0) - { - return 0; - } - - // Apply normal offset bias - samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal); -#endif - - // Sample shadow - return SampleShadow(light, shadow, shadowMap, samplePosWS); +// Samples the shadow for the given point light at custom location +ShadowSample SamplePointLightShadow(LightData light, Buffer shadowsBuffer, Texture2D shadowMap, float3 worldPosition) +{ + GBufferSample gBuffer = GetDummyGBufferSample(worldPosition); + return SamplePointLightShadow(light, shadowsBuffer, shadowMap, gBuffer); } #endif diff --git a/Source/Shaders/VolumetricFog.shader b/Source/Shaders/VolumetricFog.shader index 49f50a8eb..0dbbb208f 100644 --- a/Source/Shaders/VolumetricFog.shader +++ b/Source/Shaders/VolumetricFog.shader @@ -6,6 +6,7 @@ // "Physically Based and Unified Volumetric Rendering in Frostbite" - Sebastien Hillaire at Siggraph 2015 #define NO_GBUFFER_SAMPLING +#define LIGHTING_NO_DIRECTIONAL 1 // Debug voxels world space positions #define DEBUG_VOXEL_WS_POS 0 @@ -24,11 +25,10 @@ struct SkyLightData float3 MultiplyColor; float VolumetricScatteringIntensity; float3 AdditiveColor; - float Dummt0; + float Dummy0; }; META_CB_BEGIN(0, Data) - GBufferData GBuffer; float3 GlobalAlbedo; @@ -54,14 +54,11 @@ float4x4 PrevWorldToClip; float4 FrameJitterOffsets[8]; LightData DirectionalLight; -LightShadowData DirectionalLightShadow; SkyLightData SkyLight; DDGIData DDGI; - META_CB_END META_CB_BEGIN(1, PerLight) - float2 SliceToDepth; int MinZ; float LocalLightScatteringIntensity; @@ -70,8 +67,6 @@ float4 ViewSpaceBoundingSphere; float4x4 ViewToVolumeClip; LightData LocalLight; -LightShadowData LocalLightShadow; - META_CB_END // The Henyey-Greenstein phase function @@ -162,28 +157,8 @@ void GS_WriteToSlice(triangle Quad_VS2GS input[3], inout TriangleStream ShadowMapCube : register(t5); -Texture2D ShadowMapSpot : register(t6); - -float ComputeVolumeShadowing(float3 worldPosition, bool isSpotLight) -{ - float shadow = 1; - - // TODO: use single shadowmaps atlas for whole scene (with slots) - same code path for spot and point lights - if (isSpotLight) - { - shadow = SampleShadow(LocalLight, LocalLightShadow, ShadowMapSpot, worldPosition); - } - else - { - shadow = SampleShadow(LocalLight, LocalLightShadow, ShadowMapCube, worldPosition); - } - - return shadow; -} - +Texture2D ShadowMap : register(t0); +Buffer ShadowsBuffer : register(t1); #endif META_PS(true, FEATURE_LEVEL_SM5) @@ -225,15 +200,22 @@ float4 PS_InjectLight(Quad_GS2PS input) : SV_Target0 GetRadialLightAttenuation(LocalLight, isSpotLight, float3(0, 0, 1), distanceSqr, distanceBias * distanceBias, toLight, L, NoL, attenuation); // Peek the shadow - float shadowFactor = 1.0f; + float shadow = 1.0f; #if USE_SHADOW if (attenuation > 0) { - shadowFactor = ComputeVolumeShadowing(positionWS, isSpotLight); + if (isSpotLight) + { + shadow = SampleSpotLightShadow(LocalLight, ShadowsBuffer, ShadowMap, positionWS).SurfaceShadow; + } + else + { + shadow = SamplePointLightShadow(LocalLight, ShadowsBuffer, ShadowMap, positionWS).SurfaceShadow; + } } #endif - scattering.rgb += LocalLight.Color * (GetPhase(PhaseG, dot(L, -cameraVector)) * attenuation * shadowFactor * LocalLightScatteringIntensity); + scattering.rgb += LocalLight.Color * (GetPhase(PhaseG, dot(L, -cameraVector)) * attenuation * shadow * LocalLightScatteringIntensity); } scattering.rgb /= (float)samplesCount; @@ -281,13 +263,14 @@ Texture3D VBufferA : register(t0); Texture3D VBufferB : register(t1); Texture3D LightScatteringHistory : register(t2); Texture3D LocalShadowedLightScattering : register(t3); -Texture2DArray ShadowMapCSM : register(t4); +Texture2D ShadowMap : register(t4); +Buffer ShadowsBuffer : register(t5); #if USE_DDGI -Texture2D ProbesData : register(t5); -Texture2D ProbesDistance : register(t6); -Texture2D ProbesIrradiance : register(t7); +Texture2D ProbesData : register(t6); +Texture2D ProbesDistance : register(t7); +Texture2D ProbesIrradiance : register(t8); #else -TextureCube SkyLightImage : register(t5); +TextureCube SkyLightImage : register(t6); #endif META_CS(true, FEATURE_LEVEL_SM5) @@ -319,16 +302,8 @@ void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_ float3 cameraVectorNormalized = cameraVector / cameraVectorLength; // Directional light - BRANCH - if (DirectionalLightShadow.NumCascades < 10) // NumCascades==10 if no dir light - { - // Try to sample CSM shadow at the voxel position - float shadow = 1; - if (DirectionalLightShadow.NumCascades > 0) - { - shadow = SampleShadow(DirectionalLight, DirectionalLightShadow, ShadowMapCSM, positionWS, cameraVectorLength); - } - + { + float shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, positionWS, cameraVectorLength).SurfaceShadow; lightScattering += DirectionalLight.Color * (8 * shadow * GetPhase(PhaseG, dot(DirectionalLight.Direction, cameraVectorNormalized))); }