Refactor shadows rendering to use Shadow Map Atlas

This commit is contained in:
Wojtek Figat
2024-04-04 12:54:07 +02:00
parent 017def29d4
commit 61323f8526
31 changed files with 1115 additions and 1826 deletions

View File

@@ -16,7 +16,6 @@
#include "./Flax/ExponentialHeightFog.hlsl" #include "./Flax/ExponentialHeightFog.hlsl"
@2// Forward Shading: Constants @2// Forward Shading: Constants
LightData DirectionalLight; LightData DirectionalLight;
LightShadowData DirectionalLightShadow;
LightData SkyLight; LightData SkyLight;
ProbeData EnvironmentProbe; ProbeData EnvironmentProbe;
ExponentialHeightFogData ExponentialHeightFog; ExponentialHeightFogData ExponentialHeightFog;
@@ -26,9 +25,9 @@ LightData LocalLights[MAX_LOCAL_LIGHTS];
@3// Forward Shading: Resources @3// Forward Shading: Resources
TextureCube EnvProbe : register(t__SRV__); TextureCube EnvProbe : register(t__SRV__);
TextureCube SkyLightTexture : register(t__SRV__); TextureCube SkyLightTexture : register(t__SRV__);
Texture2DArray DirectionalLightShadowMap : register(t__SRV__); Buffer<float4> ShadowsBuffer : register(t__SRV__);
Texture2D<float> ShadowMap : register(t__SRV__);
@4// Forward Shading: Utilities @4// Forward Shading: Utilities
DECLARE_LIGHTSHADOWDATA_ACCESS(DirectionalLightShadow);
@5// Forward Shading: Shaders @5// Forward Shading: Shaders
// Pixel Shader function for Forward Pass // Pixel Shader function for Forward Pass
@@ -80,11 +79,8 @@ void PS_Forward(
// Calculate lighting from a single directional light // Calculate lighting from a single directional light
float4 shadowMask = 1.0f; float4 shadowMask = 1.0f;
if (DirectionalLight.CastShadows > 0) ShadowSample shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, gBuffer);
{ shadowMask = GetShadowMask(shadow);
LightShadowData directionalLightShadowData = GetDirectionalLightShadowData();
shadowMask.r = SampleShadow(DirectionalLight, directionalLightShadowData, DirectionalLightShadowMap, gBuffer, shadowMask.g);
}
float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false); float4 light = GetLighting(ViewPos, DirectionalLight, gBuffer, shadowMask, false, false);
// Calculate lighting from sky light // Calculate lighting from sky light

View File

@@ -23,3 +23,8 @@
#define OUT_OF_MEMORY Platform::OutOfMemory(__LINE__, __FILE__) #define OUT_OF_MEMORY Platform::OutOfMemory(__LINE__, __FILE__)
#define MISSING_CODE(info) Platform::MissingCode(__LINE__, __FILE__, info) #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 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; }

View File

@@ -10,7 +10,7 @@
/// <summary> /// <summary>
/// Current materials shader version. /// Current materials shader version.
/// </summary> /// </summary>
#define MATERIAL_GRAPH_VERSION 162 #define MATERIAL_GRAPH_VERSION 163
class Material; class Material;
class GPUShader; class GPUShader;

View File

@@ -21,7 +21,8 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
ASSERT_LOW_LAYER(cb.Length() >= sizeof(Data)); ASSERT_LOW_LAYER(cb.Length() >= sizeof(Data));
const int32 envProbeShaderRegisterIndex = srv + 0; const int32 envProbeShaderRegisterIndex = srv + 0;
const int32 skyLightShaderRegisterIndex = srv + 1; 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; const bool canUseShadow = view.Pass != DrawPass::Depth;
// Set fog input // Set fog input
@@ -39,24 +40,19 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
if (cache->DirectionalLights.HasItems()) if (cache->DirectionalLights.HasItems())
{ {
const auto& dirLight = cache->DirectionalLights.First(); const auto& dirLight = cache->DirectionalLights.First();
const auto shadowPass = ShadowsPass::Instance(); GPUTexture* shadowMapAtlas;
const bool useShadow = shadowPass->LastDirLightIndex == 0 && canUseShadow; GPUBufferView* shadowsBuffer;
if (useShadow) ShadowsPass::GetShadowAtlas(params.RenderContext.Buffers, shadowMapAtlas, shadowsBuffer);
{ const bool useShadow = shadowMapAtlas && canUseShadow && dirLight.HasShadow;
data.DirectionalLightShadow = shadowPass->LastDirLight;
params.GPUContext->BindSR(dirLightShaderRegisterIndex, shadowPass->LastDirLightShadowMap);
}
else
{
params.GPUContext->UnBindSR(dirLightShaderRegisterIndex);
}
dirLight.SetShaderData(data.DirectionalLight, useShadow); dirLight.SetShaderData(data.DirectionalLight, useShadow);
params.GPUContext->BindSR(shadowsBufferRegisterIndex, shadowsBuffer);
params.GPUContext->BindSR(shadowMapShaderRegisterIndex, shadowMapAtlas);
} }
else else
{ {
data.DirectionalLight.Color = Float3::Zero; Platform::MemoryClear(&data.DirectionalLight, sizeof(data.DirectionalLight));
data.DirectionalLight.CastShadows = 0.0f; params.GPUContext->UnBindSR(shadowsBufferRegisterIndex);
params.GPUContext->UnBindSR(dirLightShaderRegisterIndex); params.GPUContext->UnBindSR(shadowMapShaderRegisterIndex);
} }
// Set sky light // Set sky light

View File

@@ -23,12 +23,11 @@ struct ForwardShadingFeature : MaterialShaderFeature
{ {
enum { MaxLocalLights = 4 }; enum { MaxLocalLights = 4 };
enum { SRVs = 3 }; enum { SRVs = 4 };
PACK_STRUCT(struct Data PACK_STRUCT(struct Data
{ {
ShaderLightData DirectionalLight; ShaderLightData DirectionalLight;
ShaderLightShadowData DirectionalLightShadow;
ShaderLightData SkyLight; ShaderLightData SkyLight;
ShaderEnvProbeData EnvironmentProbe; ShaderEnvProbeData EnvironmentProbe;
ShaderExponentialHeightFogData ExponentialHeightFog; ShaderExponentialHeightFogData ExponentialHeightFog;

View File

@@ -38,7 +38,6 @@ void DirectionalLight::Draw(RenderContext& renderContext)
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.IndirectLightingIntensity = IndirectLightingIntensity; data.IndirectLightingIntensity = IndirectLightingIntensity;
data.CastVolumetricShadow = CastVolumetricShadow; data.CastVolumetricShadow = CastVolumetricShadow;
data.RenderedVolumetricFog = 0;
data.ShadowsMode = ShadowsMode; data.ShadowsMode = ShadowsMode;
data.CascadeCount = CascadeCount; data.CascadeCount = CascadeCount;
data.Cascade1Spacing = Cascade1Spacing; data.Cascade1Spacing = Cascade1Spacing;
@@ -49,6 +48,7 @@ void DirectionalLight::Draw(RenderContext& renderContext)
data.ContactShadowsLength = ContactShadowsLength; data.ContactShadowsLength = ContactShadowsLength;
data.StaticFlags = GetStaticFlags(); data.StaticFlags = GetStaticFlags();
data.ID = GetID(); data.ID = GetID();
data.ScreenSize = 1.0f;
renderContext.List->DirectionalLights.Add(data); renderContext.List->DirectionalLights.Add(data);
} }
} }

View File

@@ -2,6 +2,7 @@
#include "PointLight.h" #include "PointLight.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderView.h"
#include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/RenderList.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
@@ -102,7 +103,6 @@ void PointLight::Draw(RenderContext& renderContext)
data.ShadowsSharpness = ShadowsSharpness; data.ShadowsSharpness = ShadowsSharpness;
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.CastVolumetricShadow = CastVolumetricShadow; data.CastVolumetricShadow = CastVolumetricShadow;
data.RenderedVolumetricFog = 0;
data.ShadowsMode = ShadowsMode; data.ShadowsMode = ShadowsMode;
data.Radius = radius; data.Radius = radius;
data.FallOffExponent = FallOffExponent; data.FallOffExponent = FallOffExponent;
@@ -114,6 +114,7 @@ void PointLight::Draw(RenderContext& renderContext)
data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr; data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr;
data.StaticFlags = GetStaticFlags(); data.StaticFlags = GetStaticFlags();
data.ID = GetID(); data.ID = GetID();
data.ScreenSize = Math::Min(1.0f, Math::Sqrt(RenderTools::ComputeBoundsScreenRadiusSquared(position, (float)_sphere.Radius, renderContext.View)));
renderContext.List->PointLights.Add(data); renderContext.List->PointLights.Add(data);
} }
} }

View File

@@ -11,6 +11,7 @@
#include "Engine/Content/Content.h" #include "Engine/Content/Content.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
#include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
SkyLight::SkyLight(const SpawnParams& params) SkyLight::SkyLight(const SpawnParams& params)
@@ -118,13 +119,13 @@ void SkyLight::Draw(RenderContext& renderContext)
data.Color = Color.ToFloat3() * (Color.A * brightness); data.Color = Color.ToFloat3() * (Color.A * brightness);
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.CastVolumetricShadow = CastVolumetricShadow; data.CastVolumetricShadow = CastVolumetricShadow;
data.RenderedVolumetricFog = 0;
data.AdditiveColor = AdditiveColor.ToFloat3() * (AdditiveColor.A * brightness); data.AdditiveColor = AdditiveColor.ToFloat3() * (AdditiveColor.A * brightness);
data.IndirectLightingIntensity = IndirectLightingIntensity; data.IndirectLightingIntensity = IndirectLightingIntensity;
data.Radius = GetScaledRadius(); data.Radius = GetScaledRadius();
data.Image = GetSource(); data.Image = GetSource();
data.StaticFlags = GetStaticFlags(); data.StaticFlags = GetStaticFlags();
data.ID = GetID(); data.ID = GetID();
data.ScreenSize = Math::Min(1.0f, Math::Sqrt(RenderTools::ComputeBoundsScreenRadiusSquared(position, (float)_sphere.Radius, renderContext.View)));
renderContext.List->SkyLights.Add(data); renderContext.List->SkyLights.Add(data);
} }
} }

View File

@@ -5,6 +5,7 @@
#include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/RenderList.h"
#include "Engine/Content/Assets/IESProfile.h" #include "Engine/Content/Assets/IESProfile.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Level/Scene/SceneRendering.h"
@@ -152,7 +153,6 @@ void SpotLight::Draw(RenderContext& renderContext)
data.ShadowsSharpness = ShadowsSharpness; data.ShadowsSharpness = ShadowsSharpness;
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.CastVolumetricShadow = CastVolumetricShadow; data.CastVolumetricShadow = CastVolumetricShadow;
data.RenderedVolumetricFog = 0;
data.ShadowsMode = ShadowsMode; data.ShadowsMode = ShadowsMode;
data.Radius = radius; data.Radius = radius;
data.FallOffExponent = FallOffExponent; data.FallOffExponent = FallOffExponent;
@@ -167,6 +167,7 @@ void SpotLight::Draw(RenderContext& renderContext)
data.OuterConeAngle = outerConeAngle; data.OuterConeAngle = outerConeAngle;
data.StaticFlags = GetStaticFlags(); data.StaticFlags = GetStaticFlags();
data.ID = GetID(); data.ID = GetID();
data.ScreenSize = Math::Min(1.0f, Math::Sqrt(RenderTools::ComputeBoundsScreenRadiusSquared(position, (float)_sphere.Radius, renderContext.View)));
renderContext.List->SpotLights.Add(data); renderContext.List->SpotLights.Add(data);
} }
} }

View File

@@ -399,12 +399,6 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff
lightData.ShadowsSharpness = 1.0f; lightData.ShadowsSharpness = 1.0f;
lightData.UseInverseSquaredFalloff = false; lightData.UseInverseSquaredFalloff = false;
lightData.VolumetricScatteringIntensity = 1.0f; 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++) for (int32 particleIndex = 0; particleIndex < count; particleIndex++)
{ {

View File

@@ -77,28 +77,13 @@ PACK_STRUCT(struct ShaderLightData {
Float3 Color; Float3 Color;
float MinRoughness; float MinRoughness;
Float3 Position; Float3 Position;
float CastShadows; uint32 ShadowsBufferAddress;
Float3 Direction; Float3 Direction;
float Radius; float Radius;
float FalloffExponent; float FalloffExponent;
float InverseSquared; float InverseSquared;
float Dummy0;
float RadiusInv; float RadiusInv;
}); float Dummy0;
/// <summary>
/// Structure that contains information about light for shaders.
/// </summary>
PACK_STRUCT(struct ShaderLightShadowData {
Float2 ShadowMapSize;
float Sharpness;
float Fade;
float NormalOffsetScale;
float Bias;
float FadeDistance;
uint32 NumCascades;
Float4 CascadeSplits;
Matrix ShadowVP[6];
}); });
/// <summary> /// <summary>

View File

@@ -97,27 +97,7 @@ struct GlobalSurfaceAtlasObject
Platform::MemoryClear(this, sizeof(GlobalSurfaceAtlasObject)); Platform::MemoryClear(this, sizeof(GlobalSurfaceAtlasObject));
} }
GlobalSurfaceAtlasObject(const GlobalSurfaceAtlasObject& other) POD_COPYABLE(GlobalSurfaceAtlasObject);
{
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;
}
}; };
struct GlobalSurfaceAtlasLight struct GlobalSurfaceAtlasLight
@@ -130,9 +110,9 @@ class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer, publi
{ {
public: public:
int32 Resolution = 0; int32 Resolution = 0;
int32 AtlasPixelsUsed = 0;
uint64 LastFrameAtlasInsertFail = 0; uint64 LastFrameAtlasInsertFail = 0;
uint64 LastFrameAtlasDefragmentation = 0; uint64 LastFrameAtlasDefragmentation = 0;
int32 AtlasPixelsUsed = 0;
GPUTexture* AtlasDepth = nullptr; GPUTexture* AtlasDepth = nullptr;
GPUTexture* AtlasEmissive = nullptr; GPUTexture* AtlasEmissive = nullptr;
GPUTexture* AtlasGBuffer0 = nullptr; GPUTexture* AtlasGBuffer0 = nullptr;
@@ -163,7 +143,7 @@ public:
{ {
} }
FORCE_INLINE void ClearObjects() void ClearObjects()
{ {
CulledObjectsCounterIndex = -1; CulledObjectsCounterIndex = -1;
CulledObjectsUsageHistory.Clear(); CulledObjectsUsageHistory.Clear();
@@ -174,7 +154,7 @@ public:
Lights.Clear(); Lights.Clear();
} }
FORCE_INLINE void Clear() void Reset()
{ {
RenderTargetPool::Release(AtlasDepth); RenderTargetPool::Release(AtlasDepth);
RenderTargetPool::Release(AtlasEmissive); RenderTargetPool::Release(AtlasEmissive);
@@ -189,7 +169,7 @@ public:
{ {
SAFE_DELETE_GPU_RESOURCE(ChunksBuffer); SAFE_DELETE_GPU_RESOURCE(ChunksBuffer);
SAFE_DELETE_GPU_RESOURCE(CulledObjectsBuffer); SAFE_DELETE_GPU_RESOURCE(CulledObjectsBuffer);
Clear(); Reset();
} }
// [ISceneRenderingListener] // [ISceneRenderingListener]
@@ -400,7 +380,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
bool noCache = surfaceAtlasData.Resolution != resolution; bool noCache = surfaceAtlasData.Resolution != resolution;
if (noCache) if (noCache)
{ {
surfaceAtlasData.Clear(); surfaceAtlasData.Reset();
auto desc = GPUTextureDescription::New2D(resolution, resolution, PixelFormat::Unknown); auto desc = GPUTextureDescription::New2D(resolution, resolution, PixelFormat::Unknown);
uint64 memUsage = 0; uint64 memUsage = 0;
@@ -963,9 +943,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
if (_vertexBuffer->Data.Count() == 0) if (_vertexBuffer->Data.Count() == 0)
continue; continue;
// Draw draw light // Draw light
PROFILE_GPU_CPU_NAMED("Directional 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 // TODO: test perf/quality when using Shadow Map for directional light (ShadowsPass::Instance()->LastDirLightShadowMap) instead of Global SDF trace
light.SetShaderData(data.Light, useShadow); light.SetShaderData(data.Light, useShadow);
data.Light.Color *= light.IndirectLightingIntensity; data.Light.Color *= light.IndirectLightingIntensity;
@@ -997,9 +977,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
if (_vertexBuffer->Data.Count() == 0) if (_vertexBuffer->Data.Count() == 0)
continue; continue;
// Draw draw light // Draw light
PROFILE_GPU_CPU_NAMED("Point 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); light.SetShaderData(data.Light, useShadow);
data.Light.Color *= light.IndirectLightingIntensity; data.Light.Color *= light.IndirectLightingIntensity;
data.LightShadowsStrength = 1.0f - light.ShadowsStrength; data.LightShadowsStrength = 1.0f - light.ShadowsStrength;
@@ -1030,9 +1010,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
if (_vertexBuffer->Data.Count() == 0) if (_vertexBuffer->Data.Count() == 0)
continue; continue;
// Draw draw light // Draw light
PROFILE_GPU_CPU_NAMED("Spot 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); light.SetShaderData(data.Light, useShadow);
data.Light.Color *= light.IndirectLightingIntensity; data.Light.Color *= light.IndirectLightingIntensity;
data.LightShadowsStrength = 1.0f - light.ShadowsStrength; data.LightShadowsStrength = 1.0f - light.ShadowsStrength;
@@ -1048,7 +1028,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
surfaceAtlasData.Lights.Remove(it); surfaceAtlasData.Lights.Remove(it);
} }
// Draw draw indirect light from Global Illumination // Draw indirect light from Global Illumination
if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI)) if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI))
{ {
switch (giSettings.Mode) switch (giSettings.Mode)

View File

@@ -3,14 +3,15 @@
#include "LightPass.h" #include "LightPass.h"
#include "ShadowsPass.h" #include "ShadowsPass.h"
#include "GBufferPass.h" #include "GBufferPass.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Content/Assets/CubeTexture.h" #include "Engine/Content/Assets/CubeTexture.h"
#include "Engine/Content/Content.h" #include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/RenderTask.h"
PACK_STRUCT(struct PerLight{ PACK_STRUCT(struct PerLight{
ShaderLightData Light; ShaderLightData Light;
@@ -49,8 +50,9 @@ bool LightPass::Init()
_shader.Get()->OnReloading.Bind<LightPass, &LightPass::OnShaderReloading>(this); _shader.Get()->OnReloading.Bind<LightPass, &LightPass::OnShaderReloading>(this);
#endif #endif
// Pick the format for shadow mask (rendered shadow projection into screen-space)
auto format = PixelFormat::R8G8_UNorm; 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; format = PixelFormat::B8G8R8A8_UNorm;
} }
@@ -151,27 +153,48 @@ void LightPass::Dispose()
_sphereModel = nullptr; _sphereModel = nullptr;
} }
void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureView* lightBuffer) template<typename T = RenderLightData>
bool SortLights(T const& p1, T const& p2)
{
// Compare by screen size
int32 res = static_cast<int32>(p2.ScreenSize * 100 - p1.ScreenSize * 100);
if (res == 0)
{
// Compare by brightness
res = static_cast<int32>(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; const float sphereModelScale = 3.0f;
// Ensure to have valid data
if (checkIfSkipPass()) if (checkIfSkipPass())
{
// Resources are missing. Do not perform rendering.
return; return;
}
PROFILE_GPU_CPU("Lights"); PROFILE_GPU_CPU("Lights");
// Cache data // Cache data
auto device = GPUDevice::Instance; auto device = GPUDevice::Instance;
auto context = device->GetMainContext(); auto context = device->GetMainContext();
auto& renderContext = renderContextBatch.Contexts[0]; auto& renderContext = renderContextBatch.GetMainContext();
auto& view = renderContext.View; auto& view = renderContext.View;
auto mainCache = renderContext.List; auto mainCache = renderContext.List;
const auto lightShader = _shader->GetShader(); 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; const bool disableSpecular = (view.Flags & ViewFlags::SpecularLight) == ViewFlags::None;
// Check if debug lights // Check if debug lights
@@ -242,12 +265,9 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi
for (int32 lightIndex = 0; lightIndex < mainCache->PointLights.Count(); lightIndex++) for (int32 lightIndex = 0; lightIndex < mainCache->PointLights.Count(); lightIndex++)
{ {
PROFILE_GPU_CPU_NAMED("Point Light"); PROFILE_GPU_CPU_NAMED("Point Light");
// Cache data
auto& light = mainCache->PointLights[lightIndex]; auto& light = mainCache->PointLights[lightIndex];
float lightRadius = light.Radius; float lightRadius = light.Radius;
Float3 lightPosition = light.Position; Float3 lightPosition = light.Position;
const bool renderShadow = useShadows && light.ShadowDataIndex != -1;
bool useIES = light.IESTexture != nullptr; bool useIES = light.IESTexture != nullptr;
// Get distance from view center to light center less radius (check if view is inside a sphere) // 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(wvp, matrix, world);
Matrix::Multiply(world, view.ViewProjection(), wvp); Matrix::Multiply(world, view.ViewProjection(), wvp);
// Check if render shadow // Fullscreen shadow mask rendering
if (renderShadow) if (light.HasShadow)
{ {
GET_SHADOW_MASK(); GET_SHADOW_MASK();
ShadowsPass::Instance()->RenderShadow(renderContextBatch, light, shadowMaskView); ShadowsPass::Instance()->RenderShadowMask(renderContextBatch, light, shadowMaskView);
// Bind output
context->SetRenderTarget(depthBufferRTV, lightBuffer); context->SetRenderTarget(depthBufferRTV, lightBuffer);
// Set shadow mask
context->BindSR(5, shadowMaskView); context->BindSR(5, shadowMaskView);
} }
else else
context->UnBindSR(5); context->UnBindSR(5);
// Pack light properties buffer // Pack light properties buffer
light.SetShaderData(perLight.Light, renderShadow); light.SetShaderData(perLight.Light, light.HasShadow);
Matrix::Transpose(wvp, perLight.WVP); Matrix::Transpose(wvp, perLight.WVP);
if (useIES) if (useIES)
{ {
@@ -299,12 +315,9 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi
for (int32 lightIndex = 0; lightIndex < mainCache->SpotLights.Count(); lightIndex++) for (int32 lightIndex = 0; lightIndex < mainCache->SpotLights.Count(); lightIndex++)
{ {
PROFILE_GPU_CPU_NAMED("Spot Light"); PROFILE_GPU_CPU_NAMED("Spot Light");
// Cache data
auto& light = mainCache->SpotLights[lightIndex]; auto& light = mainCache->SpotLights[lightIndex];
float lightRadius = light.Radius; float lightRadius = light.Radius;
Float3 lightPosition = light.Position; Float3 lightPosition = light.Position;
const bool renderShadow = useShadows && light.ShadowDataIndex != -1;
bool useIES = light.IESTexture != nullptr; bool useIES = light.IESTexture != nullptr;
// Get distance from view center to light center less radius (check if view is inside a sphere) // 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(wvp, matrix, world);
Matrix::Multiply(world, view.ViewProjection(), wvp); Matrix::Multiply(world, view.ViewProjection(), wvp);
// Check if render shadow // Fullscreen shadow mask rendering
if (renderShadow) if (light.HasShadow)
{ {
GET_SHADOW_MASK(); GET_SHADOW_MASK();
ShadowsPass::Instance()->RenderShadow(renderContextBatch, light, shadowMaskView); ShadowsPass::Instance()->RenderShadowMask(renderContextBatch, light, shadowMaskView);
// Bind output
context->SetRenderTarget(depthBufferRTV, lightBuffer); context->SetRenderTarget(depthBufferRTV, lightBuffer);
// Set shadow mask
context->BindSR(5, shadowMaskView); context->BindSR(5, shadowMaskView);
} }
else else
context->UnBindSR(5); context->UnBindSR(5);
// Pack light properties buffer // Pack light properties buffer
light.SetShaderData(perLight.Light, renderShadow); light.SetShaderData(perLight.Light, light.HasShadow);
Matrix::Transpose(wvp, perLight.WVP); Matrix::Transpose(wvp, perLight.WVP);
if (useIES) if (useIES)
{ {
@@ -356,28 +365,21 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi
for (int32 lightIndex = 0; lightIndex < mainCache->DirectionalLights.Count(); lightIndex++) for (int32 lightIndex = 0; lightIndex < mainCache->DirectionalLights.Count(); lightIndex++)
{ {
PROFILE_GPU_CPU_NAMED("Directional Light"); PROFILE_GPU_CPU_NAMED("Directional Light");
// Cache data
auto& light = mainCache->DirectionalLights[lightIndex]; auto& light = mainCache->DirectionalLights[lightIndex];
const bool renderShadow = useShadows && light.ShadowDataIndex != -1;
// Check if render shadow // Fullscreen shadow mask rendering
if (renderShadow) if (light.HasShadow)
{ {
GET_SHADOW_MASK(); GET_SHADOW_MASK();
ShadowsPass::Instance()->RenderShadow(renderContextBatch, light, lightIndex, shadowMaskView); ShadowsPass::Instance()->RenderShadowMask(renderContextBatch, light, shadowMaskView);
// Bind output
context->SetRenderTarget(depthBufferRTV, lightBuffer); context->SetRenderTarget(depthBufferRTV, lightBuffer);
// Set shadow mask
context->BindSR(5, shadowMaskView); context->BindSR(5, shadowMaskView);
} }
else else
context->UnBindSR(5); context->UnBindSR(5);
// Pack light properties buffer // Pack light properties buffer
light.SetShaderData(perLight.Light, renderShadow); light.SetShaderData(perLight.Light, light.HasShadow);
// Calculate lighting // Calculate lighting
context->UpdateCB(cb0, &perLight); context->UpdateCB(cb0, &perLight);

View File

@@ -27,15 +27,19 @@ private:
PixelFormat _shadowMaskFormat; PixelFormat _shadowMaskFormat;
public: public:
/// <summary>
/// Setups the lights rendering for batched scene drawing.
/// </summary>
void SetupLights(RenderContext& renderContext, RenderContextBatch& renderContextBatch);
/// <summary> /// <summary>
/// Performs the lighting rendering for the input task. /// Performs the lighting rendering for the input task.
/// </summary> /// </summary>
/// <param name="renderContextBatch">The rendering context batch.</param> /// <param name="renderContextBatch">The rendering context batch.</param>
/// <param name="lightBuffer">The light accumulation buffer (input and output).</param> /// <param name="lightBuffer">The light accumulation buffer (input and output).</param>
void RenderLight(RenderContextBatch& renderContextBatch, GPUTextureView* lightBuffer); void RenderLights(RenderContextBatch& renderContextBatch, GPUTextureView* lightBuffer);
private: private:
#if COMPILE_WITH_DEV_ENV #if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj) void OnShaderReloading(Asset* obj)
{ {
@@ -51,14 +55,12 @@ private:
#endif #endif
public: public:
// [RendererPass] // [RendererPass]
String ToString() const override; String ToString() const override;
bool Init() override; bool Init() override;
void Dispose() override; void Dispose() override;
protected: protected:
// [RendererPass] // [RendererPass]
bool setupResources() override; bool setupResources() override;
}; };

View File

@@ -39,6 +39,24 @@ namespace
CriticalSection MemPoolLocker; 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 void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useShadow) const
{ {
data.SpotAngles.X = -2.0f; data.SpotAngles.X = -2.0f;
@@ -48,7 +66,7 @@ void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useSh
data.Color = Color; data.Color = Color;
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS); data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
data.Position = Float3::Zero; data.Position = Float3::Zero;
data.CastShadows = useShadow ? 1.0f : 0.0f; data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = -Direction; data.Direction = -Direction;
data.Radius = 0; data.Radius = 0;
data.FalloffExponent = 0; data.FalloffExponent = 0;
@@ -56,6 +74,15 @@ void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useSh
data.RadiusInv = 0; 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 void RenderSpotLightData::SetShaderData(ShaderLightData& data, bool useShadow) const
{ {
data.SpotAngles.X = CosOuterCone; data.SpotAngles.X = CosOuterCone;
@@ -65,7 +92,7 @@ void RenderSpotLightData::SetShaderData(ShaderLightData& data, bool useShadow) c
data.Color = Color; data.Color = Color;
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS); data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
data.Position = Position; data.Position = Position;
data.CastShadows = useShadow ? 1.0f : 0.0f; data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = Direction; data.Direction = Direction;
data.Radius = Radius; data.Radius = Radius;
data.FalloffExponent = FallOffExponent; data.FalloffExponent = FallOffExponent;
@@ -82,7 +109,7 @@ void RenderPointLightData::SetShaderData(ShaderLightData& data, bool useShadow)
data.Color = Color; data.Color = Color;
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS); data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
data.Position = Position; data.Position = Position;
data.CastShadows = useShadow ? 1.0f : 0.0f; data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = Direction; data.Direction = Direction;
data.Radius = Radius; data.Radius = Radius;
data.FalloffExponent = FallOffExponent; data.FalloffExponent = FallOffExponent;
@@ -99,7 +126,7 @@ void RenderSkyLightData::SetShaderData(ShaderLightData& data, bool useShadow) co
data.Color = Color; data.Color = Color;
data.MinRoughness = MIN_ROUGHNESS; data.MinRoughness = MIN_ROUGHNESS;
data.Position = Position; data.Position = Position;
data.CastShadows = useShadow ? 1.0f : 0.0f; data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = Float3::Forward; data.Direction = Float3::Forward;
data.Radius = Radius; data.Radius = Radius;
data.FalloffExponent = 0; data.FalloffExponent = 0;

View File

@@ -43,55 +43,82 @@ struct RenderLightData
StaticFlags StaticFlags; StaticFlags StaticFlags;
ShadowsCastingMode ShadowsMode; ShadowsCastingMode ShadowsMode;
float IndirectLightingIntensity; float IndirectLightingIntensity;
int16 ShadowDataIndex = -1; uint8 HasShadow : 1;
uint8 CastVolumetricShadow : 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 VolumetricScatteringIntensity;
float ContactShadowsLength; float ContactShadowsLength;
float ScreenSize;
uint32 ShadowsBufferAddress;
RenderLightData()
{
Platform::MemoryClear(this, sizeof(RenderLightData));
}
POD_COPYABLE(RenderLightData);
bool CanRenderShadow(const RenderView& view) const;
}; };
struct RenderDirectionalLightData : RenderLightData struct RenderDirectionalLightData : RenderLightData
{ {
PartitionMode PartitionMode;
int32 CascadeCount;
float Cascade1Spacing; float Cascade1Spacing;
float Cascade2Spacing; float Cascade2Spacing;
float Cascade3Spacing; float Cascade3Spacing;
float Cascade4Spacing; float Cascade4Spacing;
PartitionMode PartitionMode;
int32 CascadeCount;
RenderDirectionalLightData()
{
IsDirectionalLight = 1;
}
void SetShaderData(ShaderLightData& data, bool useShadow) const; void SetShaderData(ShaderLightData& data, bool useShadow) const;
}; };
struct RenderSpotLightData : RenderLightData struct RenderLocalLightData : RenderLightData
{ {
GPUTexture* IESTexture;
float Radius; float Radius;
float SourceRadius; float SourceRadius;
bool CanRenderShadow(const RenderView& view) const;
};
struct RenderSpotLightData : RenderLocalLightData
{
Float3 UpVector; Float3 UpVector;
float OuterConeAngle; float OuterConeAngle;
float CosOuterCone; float CosOuterCone;
float InvCosConeDifference; float InvCosConeDifference;
float FallOffExponent; float FallOffExponent;
uint8 UseInverseSquaredFalloff : 1;
GPUTexture* IESTexture; RenderSpotLightData()
{
IsSpotLight = 1;
}
void SetShaderData(ShaderLightData& data, bool useShadow) const; void SetShaderData(ShaderLightData& data, bool useShadow) const;
}; };
struct RenderPointLightData : RenderLightData struct RenderPointLightData : RenderLocalLightData
{ {
float Radius;
float SourceRadius;
float FallOffExponent; float FallOffExponent;
float SourceLength; float SourceLength;
uint8 UseInverseSquaredFalloff : 1;
GPUTexture* IESTexture; RenderPointLightData()
{
IsPointLight = 1;
}
void SetShaderData(ShaderLightData& data, bool useShadow) const; void SetShaderData(ShaderLightData& data, bool useShadow) const;
}; };
@@ -103,6 +130,11 @@ struct RenderSkyLightData : RenderLightData
CubeTexture* Image; CubeTexture* Image;
RenderSkyLightData()
{
IsSkyLight = 1;
}
void SetShaderData(ShaderLightData& data, bool useShadow) const; void SetShaderData(ShaderLightData& data, bool useShadow) const;
}; };

View File

@@ -348,7 +348,6 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
// Prepare // Prepare
renderContext.View.Prepare(renderContext); renderContext.View.Prepare(renderContext);
renderContext.Buffers->Prepare(); renderContext.Buffers->Prepare();
ShadowsPass::Instance()->Prepare();
// Build batch of render contexts (main view and shadow projections) // Build batch of render contexts (main view and shadow projections)
{ {
@@ -371,6 +370,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
drawShadows = false; drawShadows = false;
break; break;
} }
LightPass::Instance()->SetupLights(renderContext, renderContextBatch);
if (drawShadows) if (drawShadows)
ShadowsPass::Instance()->SetupShadows(renderContext, renderContextBatch); ShadowsPass::Instance()->SetupShadows(renderContext, renderContextBatch);
#if USE_EDITOR #if USE_EDITOR
@@ -404,7 +404,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::MotionVectors); renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::MotionVectors);
for (int32 i = 1; i < renderContextBatch.Contexts.Count(); i++) 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, DrawCallsListType::Depth, DrawPass::Depth);
shadowContext.List->SortDrawCalls(shadowContext, false, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, 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 // Render lighting
renderContextBatch.GetMainContext() = renderContext; // Sync render context in batch with the current value 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)) if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI))
{ {
switch (renderContext.List->Settings.GlobalIllumination.Mode) switch (renderContext.List->Settings.GlobalIllumination.Mode)

File diff suppressed because it is too large Load Diff

View File

@@ -9,121 +9,31 @@
#include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/Model.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
/// <summary>
/// Pixel format for fullscreen render target used for shadows calculations
/// </summary>
#define SHADOWS_PASS_SS_RR_FORMAT PixelFormat::R11G11B10_Float
template<typename T>
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;
}
/// <summary> /// <summary>
/// Shadows rendering service. /// Shadows rendering service.
/// </summary> /// </summary>
class ShadowsPass : public RendererPass<ShadowsPass> class ShadowsPass : public RendererPass<ShadowsPass>
{ {
private: private:
struct ShadowData
{
int32 ContextIndex;
int32 ContextCount;
bool BlendCSM;
ShaderLightShadowData Constants;
};
// Shader stuff
AssetReference<Shader> _shader; AssetReference<Shader> _shader;
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2 * 2> _psShadowDir; AssetReference<Model> _sphereModel;
GPUPipelineState* _psDepthClear = nullptr;
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowDir;
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowPoint; GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowPoint;
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowSpot; GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowSpot;
PixelFormat _shadowMapFormat; PixelFormat _shadowMapFormat; // Cached on initialization
int32 maxShadowsQuality = 0; // Cached state for the current frame rendering (setup via Prepare)
// Shadow maps stuff
int32 _shadowMapsSizeCSM;
int32 _shadowMapsSizeCube;
GPUTexture* _shadowMapCSM;
GPUTexture* _shadowMapCube;
Quality _currentShadowMapsQuality;
// Shadow map rendering stuff
AssetReference<Model> _sphereModel;
Array<ShadowData> _shadowData;
// Cached state for the current frame rendering (setup via Prepare)
int32 maxShadowsQuality;
public: public:
/// <summary>
/// Init
/// </summary>
ShadowsPass();
public:
/// <summary>
/// Gets current GPU memory usage by the shadow maps
/// </summary>
/// <returns>GPU memory used in bytes</returns>
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();
/// <summary> /// <summary>
/// Setups the shadows rendering for batched scene drawing. Checks which lights will cast a shadow. /// Setups the shadows rendering for batched scene drawing. Checks which lights will cast a shadow.
/// </summary> /// </summary>
void SetupShadows(RenderContext& renderContext, RenderContextBatch& renderContextBatch); void SetupShadows(RenderContext& renderContext, RenderContextBatch& renderContextBatch);
/// <summary> /// <summary>
/// Determines whether can render shadow for the specified light. /// Renders the shadow maps for all lights (into atlas).
/// </summary> /// </summary>
/// <param name="renderContext">The rendering context.</param> void RenderShadowMaps(RenderContextBatch& renderContextBatch);
/// <param name="light">The light.</param>
/// <returns><c>true</c> if can render shadow for the specified light; otherwise, <c>false</c>.</returns>
bool CanRenderShadow(const RenderContext& renderContext, const RenderPointLightData& light);
/// <summary>
/// Determines whether can render shadow for the specified light.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="light">The light.</param>
/// <returns><c>true</c> if can render shadow for the specified light; otherwise, <c>false</c>.</returns>
bool CanRenderShadow(const RenderContext& renderContext, const RenderSpotLightData& light);
/// <summary>
/// Determines whether can render shadow for the specified light.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="light">The light.</param>
/// <returns><c>true</c> if can render shadow for the specified light; otherwise, <c>false</c>.</returns>
bool CanRenderShadow(const RenderContext& renderContext, const RenderDirectionalLightData& light);
/// <summary> /// <summary>
/// Renders the shadow mask for the given light. /// Renders the shadow mask for the given light.
@@ -131,32 +41,23 @@ public:
/// <param name="renderContextBatch">The rendering context batch.</param> /// <param name="renderContextBatch">The rendering context batch.</param>
/// <param name="light">The light.</param> /// <param name="light">The light.</param>
/// <param name="shadowMask">The shadow mask (output).</param> /// <param name="shadowMask">The shadow mask (output).</param>
void RenderShadow(RenderContextBatch& renderContextBatch, RenderPointLightData& light, GPUTextureView* shadowMask); void RenderShadowMask(RenderContextBatch& renderContextBatch, RenderLightData& light, GPUTextureView* shadowMask);
/// <summary> /// <summary>
/// Renders the shadow mask for the given light. /// Gets the shadow atlas texture and shadows buffer for shadow projection in shaders.
/// </summary> /// </summary>
/// <param name="renderContextBatch">The rendering context batch.</param> /// <param name="renderBuffers">The render buffers that store frame context.</param>
/// <param name="light">The light.</param> /// <param name="shadowMapAtlas">The output shadow map atlas texture or null if unused.</param>
/// <param name="shadowMask">The shadow mask (output).</param> /// <param name="shadowsBuffer">The output shadows buffer or null if unused.</param>
void RenderShadow(RenderContextBatch& renderContextBatch, RenderSpotLightData& light, GPUTextureView* shadowMask); static void GetShadowAtlas(const RenderBuffers* renderBuffers, GPUTexture*& shadowMapAtlas, GPUBufferView*& shadowsBuffer);
/// <summary>
/// Renders the shadow mask for the given light.
/// </summary>
/// <param name="renderContextBatch">The rendering context batch.</param>
/// <param name="light">The light.</param>
/// <param name="index">The light index.</param>
/// <param name="shadowMask">The shadow mask (output).</param>
void RenderShadow(RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, int32 index, GPUTextureView* shadowMask);
private: private:
void updateShadowMapSize();
void SetupRenderContext(RenderContext& renderContext, RenderContext& shadowContext); void SetupRenderContext(RenderContext& renderContext, RenderContext& shadowContext);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light); void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLightData& light, struct ShadowAtlasLight& atlasLight);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light); void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light); 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 #if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj) void OnShaderReloading(Asset* obj)
@@ -169,14 +70,12 @@ private:
#endif #endif
public: public:
// [RendererPass] // [RendererPass]
String ToString() const override; String ToString() const override;
bool Init() override; bool Init() override;
void Dispose() override; void Dispose() override;
protected: protected:
// [RendererPass] // [RendererPass]
bool setupResources() override; bool setupResources() override;
}; };

View File

@@ -6,12 +6,12 @@
#include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/RenderTargetPool.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/Assets/CubeTexture.h"
#include "Engine/Content/Content.h" #include "Engine/Content/Content.h"
#include "Engine/Engine/Engine.h" #include "Engine/Engine/Engine.h"
#include "Engine/Graphics/GPUContext.h"
// Must match shader source // Must match shader source
int32 VolumetricFogGridInjectionGroupSize = 4; int32 VolumetricFogGridInjectionGroupSize = 4;
@@ -143,38 +143,30 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context,
switch (quality) switch (quality)
{ {
case Quality::Low: case Quality::Low:
{
_cache.GridPixelSize = 16; _cache.GridPixelSize = 16;
_cache.GridSizeZ = 64; _cache.GridSizeZ = 64;
_cache.FogJitter = false; _cache.FogJitter = false;
_cache.MissedHistorySamplesCount = 1; _cache.MissedHistorySamplesCount = 1;
break; break;
}
case Quality::Medium: case Quality::Medium:
{
_cache.GridPixelSize = 16; _cache.GridPixelSize = 16;
_cache.GridSizeZ = 64; _cache.GridSizeZ = 64;
_cache.FogJitter = true; _cache.FogJitter = true;
_cache.MissedHistorySamplesCount = 4; _cache.MissedHistorySamplesCount = 4;
break; break;
}
case Quality::High: case Quality::High:
{
_cache.GridPixelSize = 16; _cache.GridPixelSize = 16;
_cache.GridSizeZ = 128; _cache.GridSizeZ = 128;
_cache.FogJitter = true; _cache.FogJitter = true;
_cache.MissedHistorySamplesCount = 4; _cache.MissedHistorySamplesCount = 4;
break; break;
}
case Quality::Ultra: case Quality::Ultra:
{
_cache.GridPixelSize = 8; _cache.GridPixelSize = 8;
_cache.GridSizeZ = 256; _cache.GridSizeZ = 256;
_cache.FogJitter = true; _cache.FogJitter = true;
_cache.MissedHistorySamplesCount = 8; _cache.MissedHistorySamplesCount = 8;
break; break;
} }
}
// Prepare // Prepare
const int32 width = renderContext.Buffers->GetWidth(); const int32 width = renderContext.Buffers->GetWidth();
@@ -202,7 +194,6 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context,
_cache.Data.VolumetricFogMaxDistance = options.Distance; _cache.Data.VolumetricFogMaxDistance = options.Distance;
_cache.Data.MissedHistorySamplesCount = Math::Clamp(_cache.MissedHistorySamplesCount, 1, (int32)ARRAY_COUNT(_cache.Data.FrameJitterOffsets)); _cache.Data.MissedHistorySamplesCount = Math::Clamp(_cache.MissedHistorySamplesCount, 1, (int32)ARRAY_COUNT(_cache.Data.FrameJitterOffsets));
Matrix::Transpose(view.PrevViewProjection, _cache.Data.PrevWorldToClip); Matrix::Transpose(view.PrevViewProjection, _cache.Data.PrevWorldToClip);
_cache.Data.DirectionalLightShadow.NumCascades = 0;
_cache.Data.SkyLight.VolumetricScatteringIntensity = 0; _cache.Data.SkyLight.VolumetricScatteringIntensity = 0;
// Fill frame jitter history // Fill frame jitter history
@@ -262,83 +253,6 @@ GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext
return renderContext.Buffers->LocalShadowedLightScattering->ViewVolume(); return renderContext.Buffers->LocalShadowedLightScattering->ViewVolume();
} }
template<typename T>
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<typename T> template<typename T>
void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1) 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 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 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); const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f);
if (volumeZBoundsMin >= volumeZBoundsMax)
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)
return; 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) // Call rendering to the volume
{ const int32 psIndex = withShadow ? 1 : 0;
// Skip lights with no volumetric light influence or not casting volumetric shadow context->SetState(_psInjectLight.Get(psIndex));
if (light.VolumetricScatteringIntensity <= ZeroTolerance || !light.CastVolumetricShadow) const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin;
return; const int32 indexCount = _ibCircleRasterize->GetElementsCount();
ASSERT(shadowMap); context->BindVB(ToSpan(&_vbCircleRasterize, 1));
context->BindIB(_ibCircleRasterize);
context->BindSR(6, shadowMap); context->DrawIndexedInstanced(indexCount, instanceCount, 0);
RenderRadialLight(renderContext, context, light, shadow);
context->UnBindSR(6);
} }
void VolumetricFogPass::Render(RenderContext& renderContext) void VolumetricFogPass::Render(RenderContext& renderContext)
{ {
// Prepare
VolumetricFogOptions options; VolumetricFogOptions options;
auto context = GPUDevice::Instance->GetMainContext(); auto context = GPUDevice::Instance->GetMainContext();
if (Init(renderContext, context, options)) if (Init(renderContext, context, options))
return; return;
auto& view = renderContext.View; auto& view = renderContext.View;
auto& cache = _cache; auto& cache = _cache;
PROFILE_GPU_CPU("Volumetric Fog"); PROFILE_GPU_CPU("Volumetric Fog");
// TODO: test exponential depth distribution (should give better quality near the camera) // TODO: test exponential depth distribution (should give better quality near the camera)
// TODO: use tiled light culling and render unshadowed lights in single pass // 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 // Init directional light data
GPUTextureView* dirLightShadowMap = nullptr; Platform::MemoryClear(&_cache.Data.DirectionalLight, sizeof(_cache.Data.DirectionalLight));
if (renderContext.List->DirectionalLights.HasItems()) if (renderContext.List->DirectionalLights.HasItems())
{ {
const int32 dirLightIndex = (int32)renderContext.List->DirectionalLights.Count() - 1; const int32 dirLightIndex = (int32)renderContext.List->DirectionalLights.Count() - 1;
const auto& dirLight = renderContext.List->DirectionalLights[dirLightIndex]; const auto& dirLight = renderContext.List->DirectionalLights[dirLightIndex];
const float brightness = dirLight.VolumetricScatteringIntensity; const float brightness = dirLight.VolumetricScatteringIntensity;
if (brightness > ZeroTolerance) if (brightness > ZeroTolerance)
{ {
const auto shadowPass = ShadowsPass::Instance(); const bool useShadow = shadowMap && dirLight.CastVolumetricShadow && dirLight.HasShadow;
const bool useShadow = dirLight.CastVolumetricShadow && shadowPass->LastDirLightIndex == dirLightIndex;
dirLight.SetShaderData(_cache.Data.DirectionalLight, useShadow); dirLight.SetShaderData(_cache.Data.DirectionalLight, useShadow);
_cache.Data.DirectionalLight.Color *= brightness; _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 // Init sky light data
GPUTexture* skyLightImage = nullptr; GPUTexture* skyLightImage = nullptr;
Platform::MemoryClear(&_cache.Data.SkyLight, sizeof(_cache.Data.SkyLight));
if (renderContext.List->SkyLights.HasItems() && !useDDGI) if (renderContext.List->SkyLights.HasItems() && !useDDGI)
{ {
const auto& skyLight = renderContext.List->SkyLights.Last(); const auto& skyLight = renderContext.List->SkyLights.Last();
@@ -510,13 +386,10 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
// Initialize fog volume properties // Initialize fog volume properties
{ {
PROFILE_GPU("Initialize"); PROFILE_GPU("Initialize");
context->ResetRenderTarget(); context->ResetRenderTarget();
context->BindUA(0, vBufferA->ViewVolume()); context->BindUA(0, vBufferA->ViewVolume());
context->BindUA(1, vBufferB->ViewVolume()); context->BindUA(1, vBufferB->ViewVolume());
context->Dispatch(_csInitialize, groupCountX, groupCountY, groupCountZ); context->Dispatch(_csInitialize, groupCountX, groupCountY, groupCountZ);
context->ResetUA(); 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); const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f);
// Culling // 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; continue;
// Setup material shader data // Setup material shader data
@@ -598,25 +471,17 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
Array<const RenderSpotLightData*, InlinedAllocation<64, RendererAllocation>> spotLights; Array<const RenderSpotLightData*, InlinedAllocation<64, RendererAllocation>> spotLights;
for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++) for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++)
{ {
const auto& light = renderContext.List->PointLights[i]; const auto& light = renderContext.List->PointLights.Get()[i];
if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog) if (light.VolumetricScatteringIntensity > ZeroTolerance &&
{ (view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius))
if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius)) pointLights.Add(&light);
{
pointLights.Add(&light);
}
}
} }
for (int32 i = 0; i < renderContext.List->SpotLights.Count(); i++) for (int32 i = 0; i < renderContext.List->SpotLights.Count(); i++)
{ {
const auto& light = renderContext.List->SpotLights[i]; const auto& light = renderContext.List->SpotLights.Get()[i];
if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog) if (light.VolumetricScatteringIntensity > ZeroTolerance &&
{ (view.Position - light.Position).LengthSquared() < Math::Square(options.Distance + light.Radius))
if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius)) spotLights.Add(&light);
{
spotLights.Add(&light);
}
}
} }
// Skip if no lights to render // Skip if no lights to render
@@ -638,6 +503,8 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
context->SetViewportAndScissors((float)volumeDesc.Width, (float)volumeDesc.Height); context->SetViewportAndScissors((float)volumeDesc.Width, (float)volumeDesc.Height);
// Render them to the volume // Render them to the volume
context->BindSR(0, shadowMap);
context->BindSR(1, shadowsBuffer);
for (int32 i = 0; i < pointLights.Count(); i++) for (int32 i = 0; i < pointLights.Count(); i++)
RenderRadialLight(renderContext, context, view, options, *pointLights[i], perLight, cb1); RenderRadialLight(renderContext, context, view, options, *pointLights[i], perLight, cb1);
for (int32 i = 0; i < spotLights.Count(); i++) for (int32 i = 0; i < spotLights.Count(); i++)
@@ -666,19 +533,19 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
context->BindSR(1, vBufferB->ViewVolume()); context->BindSR(1, vBufferB->ViewVolume());
context->BindSR(2, lightScatteringHistory ? lightScatteringHistory->ViewVolume() : nullptr); context->BindSR(2, lightScatteringHistory ? lightScatteringHistory->ViewVolume() : nullptr);
context->BindSR(3, localShadowedLightScattering); context->BindSR(3, localShadowedLightScattering);
context->BindSR(4, dirLightShadowMap); context->BindSR(4, shadowMap);
context->BindSR(5, shadowsBuffer);
int32 csIndex; int32 csIndex;
if (useDDGI) if (useDDGI)
{ {
context->BindSR(5, bindingDataDDGI.ProbesData); context->BindSR(6, bindingDataDDGI.ProbesData);
context->BindSR(6, bindingDataDDGI.ProbesDistance); context->BindSR(7, bindingDataDDGI.ProbesDistance);
context->BindSR(7, bindingDataDDGI.ProbesIrradiance); context->BindSR(8, bindingDataDDGI.ProbesIrradiance);
csIndex = 1; csIndex = 1;
} }
else else
{ {
context->BindSR(5, skyLightImage); context->BindSR(6, skyLightImage);
csIndex = 0; csIndex = 0;
} }
context->Dispatch(_csLightScattering.Get(csIndex), groupCountX, groupCountY, groupCountZ); context->Dispatch(_csLightScattering.Get(csIndex), groupCountX, groupCountY, groupCountZ);

View File

@@ -32,7 +32,7 @@ private:
Float3 MultiplyColor; Float3 MultiplyColor;
float VolumetricScatteringIntensity; float VolumetricScatteringIntensity;
Float3 AdditiveColor; Float3 AdditiveColor;
float Dummt0; float Dummy0;
}); });
PACK_STRUCT(struct Data { PACK_STRUCT(struct Data {
@@ -63,7 +63,6 @@ private:
Float4 FrameJitterOffsets[8]; Float4 FrameJitterOffsets[8];
ShaderLightData DirectionalLight; ShaderLightData DirectionalLight;
ShaderLightShadowData DirectionalLightShadow;
SkyLightData SkyLight; SkyLightData SkyLight;
DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI; DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI;
}); });
@@ -77,7 +76,6 @@ private:
Matrix ViewToVolumeClip; Matrix ViewToVolumeClip;
ShaderLightData LocalLight; ShaderLightData LocalLight;
ShaderLightShadowData LocalLightShadow;
}); });
// Shader stuff // Shader stuff
@@ -147,27 +145,6 @@ public:
VolumetricFogPass(); VolumetricFogPass();
public: public:
/// <summary>
/// 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.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="context">The GPU commands context.</param>
/// <param name="light">The light.</param>
/// <param name="shadowMap">The shadow map.</param>
/// <param name="shadow">The light shadow data.</param>
void RenderLight(RenderContext& renderContext, GPUContext* context, RenderPointLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow);
/// <summary>
/// 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.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="context">The GPU commands context.</param>
/// <param name="light">The light.</param>
/// <param name="shadowMap">The shadow map.</param>
/// <param name="shadow">The light shadow data.</param>
void RenderLight(RenderContext& renderContext, GPUContext* context, RenderSpotLightData& light, GPUTextureView* shadowMap, ShaderLightShadowData& shadow);
/// <summary> /// <summary>
/// Renders the volumetric fog (generates integrated light scattering 3D texture). Does nothing if feature is disabled or not supported. /// Renders the volumetric fog (generates integrated light scattering 3D texture). Does nothing if feature is disabled or not supported.
/// </summary> /// </summary>
@@ -180,8 +157,6 @@ private:
GPUTextureView* GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) const; GPUTextureView* GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) const;
void InitCircleBuffer(); void InitCircleBuffer();
template<typename T> template<typename T>
void RenderRadialLight(RenderContext& renderContext, GPUContext* context, T& light, ShaderLightShadowData& shadow);
template<typename T>
void RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1); void RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1);
#if COMPILE_WITH_DEV_ENV #if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj) void OnShaderReloading(Asset* obj)

View File

@@ -159,7 +159,7 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target
float toLightDst = GLOBAL_SDF_WORLD_SIZE; float toLightDst = GLOBAL_SDF_WORLD_SIZE;
#endif #endif
float4 shadowMask = 1; float4 shadowMask = 1;
if (Light.CastShadows > 0) if (Light.ShadowsBufferAddress != 0)
{ {
float NoL = dot(gBuffer.Normal, L); float NoL = dot(gBuffer.Normal, L);
float shadowBias = 10.0f; float shadowBias = 10.0f;

View File

@@ -5,15 +5,15 @@
#include "./Flax/LightingCommon.hlsl" #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.SurfaceShadow = gBuffer.AO * shadowMask.r;
shadow.TransmissionShadow = shadowMask.g; shadow.TransmissionShadow = shadowMask.g;
return shadow; 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 diffuseColor = GetDiffuseColor(gBuffer);
float3 H = normalize(V + L); 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 NoH = saturate(dot(N, H));
float VoH = saturate(dot(V, H)); float VoH = saturate(dot(V, H));
LightingData lighting; LightSample lighting;
lighting.Diffuse = Diffuse_Lambert(diffuseColor); lighting.Diffuse = Diffuse_Lambert(diffuseColor);
#if LIGHTING_NO_SPECULAR #if LIGHTING_NO_SPECULAR
lighting.Specular = 0; lighting.Specular = 0;
@@ -37,9 +37,9 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa
return lighting; 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) #if defined(USE_GBUFFER_CUSTOM_DATA)
// Fake effect of the light going through the material // Fake effect of the light going through the material
float3 subsurfaceColor = gBuffer.CustomData.rgb; float3 subsurfaceColor = gBuffer.CustomData.rgb;
@@ -53,9 +53,9 @@ LightingData SubsurfaceShading(GBufferSample gBuffer, float energy, float3 L, fl
return lighting; 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) #if defined(USE_GBUFFER_CUSTOM_DATA)
// Fake effect of the light going through the thin foliage // Fake effect of the light going through the thin foliage
float3 subsurfaceColor = gBuffer.CustomData.rgb; float3 subsurfaceColor = gBuffer.CustomData.rgb;
@@ -67,7 +67,7 @@ LightingData FoliageShading(GBufferSample gBuffer, float energy, float3 L, float
return lighting; 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) switch (gBuffer.ShadingModel)
{ {
@@ -79,7 +79,7 @@ LightingData SurfaceShading(GBufferSample gBuffer, float energy, float3 L, float
case SHADING_MODEL_FOLIAGE: case SHADING_MODEL_FOLIAGE:
return FoliageShading(gBuffer, energy, L, V, N); return FoliageShading(gBuffer, energy, L, V, N);
default: default:
return (LightingData)0; return (LightSample)0;
} }
} }
@@ -121,7 +121,7 @@ float4 GetLighting(float3 viewPos, LightData lightData, GBufferSample gBuffer, f
float3 toLight = lightData.Direction; float3 toLight = lightData.Direction;
// Calculate shadow // Calculate shadow
ShadowData shadow = GetShadow(lightData, gBuffer, shadowMask); ShadowSample shadow = GetShadow(lightData, gBuffer, shadowMask);
// Calculate attenuation // Calculate attenuation
if (isRadial) 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); float energy = AreaLightSpecular(lightData, gBuffer.Roughness, toLight, L, V, N);
// Calculate direct lighting // Calculate direct lighting
LightingData lighting = SurfaceShading(gBuffer, energy, L, V, N); LightSample lighting = SurfaceShading(gBuffer, energy, L, V, N);
// Calculate final light color // Calculate final light color
float3 surfaceLight = (lighting.Diffuse + lighting.Specular) * shadow.SurfaceShadow; float3 surfaceLight = (lighting.Diffuse + lighting.Specular) * shadow.SurfaceShadow;

View File

@@ -27,26 +27,26 @@ struct LightData
float MinRoughness; float MinRoughness;
float3 Position; float3 Position;
float CastShadows; uint ShadowsBufferAddress;
float3 Direction; float3 Direction;
float Radius; float Radius;
float FalloffExponent; float FalloffExponent;
float InverseSquared; float InverseSquared;
float Dummy0;
float RadiusInv; float RadiusInv;
float Dummy0;
}; };
// Structure that contains information about shadow // Structure that contains information about shadow sampling result
struct ShadowData struct ShadowSample
{ {
float SurfaceShadow; float SurfaceShadow;
float TransmissionShadow; float TransmissionShadow;
}; };
// Structure that contains information about direct lighting calculations result // Structure that contains information about direct lighting calculations result
struct LightingData struct LightSample
{ {
float3 Diffuse; float3 Diffuse;
float3 Specular; float3 Specular;

View File

@@ -61,7 +61,7 @@ void PS_Directional(Quad_VS2PS input, out float4 output : SV_Target0)
// Sample shadow mask // Sample shadow mask
float4 shadowMask = 1; float4 shadowMask = 1;
BRANCH BRANCH
if (Light.CastShadows > 0) if (Light.ShadowsBufferAddress != 0)
{ {
shadowMask = SAMPLE_RT(Shadow, input.TexCoord); shadowMask = SAMPLE_RT(Shadow, input.TexCoord);
} }
@@ -98,7 +98,7 @@ void PS_Point(Model_VS2PS input, out float4 output : SV_Target0)
// Sample shadow mask // Sample shadow mask
float4 shadowMask = 1; float4 shadowMask = 1;
BRANCH BRANCH
if (Light.CastShadows > 0) if (Light.ShadowsBufferAddress != 0)
{ {
shadowMask = SAMPLE_RT(Shadow, uv); shadowMask = SAMPLE_RT(Shadow, uv);
} }
@@ -140,7 +140,7 @@ void PS_Spot(Model_VS2PS input, out float4 output : SV_Target0)
// Sample shadow mask // Sample shadow mask
float4 shadowMask = 1; float4 shadowMask = 1;
BRANCH BRANCH
if (Light.CastShadows > 0) if (Light.ShadowsBufferAddress != 0)
{ {
shadowMask = SAMPLE_RT(Shadow, uv); shadowMask = SAMPLE_RT(Shadow, uv);
} }

View File

@@ -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

View File

@@ -56,8 +56,6 @@ float4 PS_CopyLinear(Quad_VS2PS input) : SV_Target
#endif #endif
#ifdef _PS_Clear
// Pixel Shader for clearing a render target with a solid color // Pixel Shader for clearing a render target with a solid color
META_PS(true, FEATURE_LEVEL_ES2) META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Clear(Quad_VS2PS input) : SV_Target float4 PS_Clear(Quad_VS2PS input) : SV_Target
@@ -65,4 +63,9 @@ float4 PS_Clear(Quad_VS2PS input) : SV_Target
return Color; 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;
}

View File

@@ -10,7 +10,6 @@
META_CB_BEGIN(0, PerLight) META_CB_BEGIN(0, PerLight)
GBufferData GBuffer; GBufferData GBuffer;
LightData Light; LightData Light;
LightShadowData LightShadow;
float4x4 WVP; float4x4 WVP;
float4x4 ViewProjectionMatrix; float4x4 ViewProjectionMatrix;
float2 Dummy0; float2 Dummy0;
@@ -18,8 +17,10 @@ float ContactShadowsDistance;
float ContactShadowsLength; float ContactShadowsLength;
META_CB_END META_CB_END
Buffer<float4> ShadowsBuffer : register(t5);
Texture2D<float> ShadowMap : register(t6);
DECLARE_GBUFFERDATA_ACCESS(GBuffer) DECLARE_GBUFFERDATA_ACCESS(GBuffer)
DECLARE_LIGHTSHADOWDATA_ACCESS(LightShadow);
#if CONTACT_SHADOWS #if CONTACT_SHADOWS
@@ -67,10 +68,6 @@ Model_VS2PS VS_Model(ModelInput_PosOnly input)
return output; return output;
} }
#ifdef _PS_PointLight
TextureCube<float> ShadowMapPoint : register(t5);
// Pixel shader for point light shadow rendering // Pixel shader for point light shadow rendering
META_PS(true, FEATURE_LEVEL_ES2) META_PS(true, FEATURE_LEVEL_ES2)
META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0) 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) META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1)
float4 PS_PointLight(Model_VS2PS input) : SV_Target0 float4 PS_PointLight(Model_VS2PS input) : SV_Target0
{ {
float shadow = 1;
float subsurfaceShadow = 1;
// Obtain texture coordinates corresponding to the current pixel // 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); 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); GBufferSample gBuffer = SampleGBuffer(gBufferData, uv);
// Sample shadow // Sample shadow
LightShadowData lightShadowData = GetLightShadowData(); ShadowSample shadow = SamplePointLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer);
shadow = SampleShadow(Light, lightShadowData, ShadowMapPoint, gBuffer, subsurfaceShadow);
#if CONTACT_SHADOWS #if CONTACT_SHADOWS
// Calculate screen-space contact shadow // 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 #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 // Pixel shader for directional light shadow rendering
META_PS(true, FEATURE_LEVEL_ES2) META_PS(true, FEATURE_LEVEL_ES2)
META_PERMUTATION_3(SHADOWS_QUALITY=0,CSM_BLENDING=0,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0)
META_PERMUTATION_3(SHADOWS_QUALITY=1,CSM_BLENDING=0,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=1,CONTACT_SHADOWS=0)
META_PERMUTATION_3(SHADOWS_QUALITY=2,CSM_BLENDING=0,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=0)
META_PERMUTATION_3(SHADOWS_QUALITY=3,CSM_BLENDING=0,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=0)
META_PERMUTATION_3(SHADOWS_QUALITY=0,CSM_BLENDING=1,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=1)
META_PERMUTATION_3(SHADOWS_QUALITY=1,CSM_BLENDING=1,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=1,CONTACT_SHADOWS=1)
META_PERMUTATION_3(SHADOWS_QUALITY=2,CSM_BLENDING=1,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=1)
META_PERMUTATION_3(SHADOWS_QUALITY=3,CSM_BLENDING=1,CONTACT_SHADOWS=0) META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1)
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)
float4 PS_DirLight(Quad_VS2PS input) : SV_Target0 float4 PS_DirLight(Quad_VS2PS input) : SV_Target0
{ {
float shadow = 1;
float subsurfaceShadow = 1;
// Sample GBuffer // Sample GBuffer
GBufferData gBufferData = GetGBufferData(); GBufferData gBufferData = GetGBufferData();
GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord); GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord);
// Sample shadow // Sample shadow
LightShadowData lightShadowData = GetLightShadowData(); ShadowSample shadow = SampleDirectionalLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer);
shadow = SampleShadow(Light, lightShadowData, ShadowMapDir, gBuffer, subsurfaceShadow);
#if CONTACT_SHADOWS #if CONTACT_SHADOWS
// Calculate screen-space contact shadow // 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 #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 // Pixel shader for spot light shadow rendering
META_PS(true, FEATURE_LEVEL_ES2) META_PS(true, FEATURE_LEVEL_ES2)
META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0) 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) META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1)
float4 PS_SpotLight(Model_VS2PS input) : SV_Target0 float4 PS_SpotLight(Model_VS2PS input) : SV_Target0
{ {
float shadow = 1;
float subsurfaceShadow = 1;
// Obtain texture coordinates corresponding to the current pixel // 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); 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); GBufferSample gBuffer = SampleGBuffer(gBufferData, uv);
// Sample shadow // Sample shadow
LightShadowData lightShadowData = GetLightShadowData(); ShadowSample shadow = SampleSpotLightShadow(Light, ShadowsBuffer, ShadowMap, gBuffer);
shadow = SampleShadow(Light, lightShadowData, ShadowMapSpot, gBuffer, subsurfaceShadow);
#if CONTACT_SHADOWS #if CONTACT_SHADOWS
// Calculate screen-space contact shadow // 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 #endif
return float4(shadow, subsurfaceShadow, 1, 1); return GetShadowMask(shadow);
} }
#endif

View File

@@ -12,32 +12,57 @@
#ifndef SHADOWS_QUALITY #ifndef SHADOWS_QUALITY
#define SHADOWS_QUALITY 0 #define SHADOWS_QUALITY 0
#endif #endif
#ifndef CSM_BLENDING
#define CSM_BLENDING 0
#endif
// Structure that contains information about light // Shadow data for the light
struct LightShadowData struct ShadowData
{ {
float2 ShadowMapSize;
float Sharpness; float Sharpness;
float Fade; float Fade;
float FadeDistance;
float NormalOffsetScale; float NormalOffsetScale;
float Bias; float Bias;
float FadeDistance; uint TilesCount;
uint NumCascades;
float4 CascadeSplits; float4 CascadeSplits;
float4x4 ShadowVP[6];
}; };
#ifdef PLATFORM_ANDROID // Shadow projection tile data for the light
// #AdrenoVK_CB_STRUCT_MEMBER_ACCESS_BUG struct ShadowTileData
#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 float4 ShadowToAtlas;
#define DECLARE_LIGHTSHADOWDATA_ACCESS(uniformName) LightShadowData Get##uniformName##Data() { return uniformName; } float4x4 WorldToShadow;
#endif };
// Loads the shadow data of the light in the shadow buffer
ShadowData LoadShadowsBuffer(Buffer<float4> 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<float4> 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) 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 CalculateSubsurfaceOcclusion(float opacity, float sceneDepth, float shadowMapDepth)
{ {
float thickness = max(sceneDepth - shadowMapDepth, 0); 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; 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 #endif

View File

@@ -7,46 +7,24 @@
#include "./Flax/GBufferCommon.hlsl" #include "./Flax/GBufferCommon.hlsl"
#include "./Flax/LightingCommon.hlsl" #include "./Flax/LightingCommon.hlsl"
// Select shadows filter based on quality #if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
// Supported sampling kernel sizes fo each shadowing technique: #define SAMPLE_SHADOW_MAP(shadowMap, shadowUV, sceneDepth) shadowMap.SampleCmpLevelZero(ShadowSamplerLinear, shadowUV, sceneDepth)
// CSM: 2, 3, 5, 7, 9 #define SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowUV, texelOffset, sceneDepth) shadowMap.SampleCmpLevelZero(ShadowSamplerLinear, shadowUV, sceneDepth, texelOffset)
// Cube: 2, 5, 12, 29 #else
// Spot: 2, 5, 12, 29 #define SAMPLE_SHADOW_MAP(shadowMap, shadowUV, sceneDepth) (sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, shadowUV, 0).r)
#if SHADOWS_QUALITY == 0 #define SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowUV, texelOffset, sceneDepth) (sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, shadowUV, 0, texelOffset).r)
#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
#endif #endif
#if SHADOWS_QUALITY != 0 float4 GetShadowMask(ShadowSample shadow)
#include "./Flax/PCFKernels.hlsl" {
#endif 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 // 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) // Where: direction = normalize(worldPosition - lightPosition)
int GetCubeFaceIndex(float3 direction) uint GetCubeFaceIndex(float3 direction)
{ {
int cubeFaceIndex; uint cubeFaceIndex;
float3 absDirection = abs(direction); float3 absDirection = abs(direction);
float maxDirection = max(absDirection.x, max(absDirection.y, absDirection.z)); float maxDirection = max(absDirection.x, max(absDirection.y, absDirection.z));
if (maxDirection == absDirection.x) if (maxDirection == absDirection.x)
@@ -58,666 +36,230 @@ int GetCubeFaceIndex(float3 direction)
return cubeFaceIndex; return cubeFaceIndex;
} }
// Samples the shadow map with a fixed-size PCF kernel optimized with GatherCmpRed. float2 GetLightShadowAtlasUV(ShadowData shadow, ShadowTileData shadowTile, float3 samplePosition, out float4 shadowPosition)
// 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)
{ {
#if FilterSizeCSM == 2 // Project into shadow space (WorldToShadow is pre-multiplied to convert Clip Space to UV Space)
shadowPosition = mul(float4(samplePosition, 1.0f), shadowTile.WorldToShadow);
#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;
shadowPosition.z -= shadow.Bias; shadowPosition.z -= shadow.Bias;
shadowPosition.xyz /= shadowPosition.w;
// Sample shadow // UV Space -> Atlas Tile UV Space
float result = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex); 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 float SampleShadowMap(Texture2D<float> shadowMap, float2 shadowMapUV, float sceneDepth)
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f }; {
float sharpness = shadow.Sharpness * SharpnessScale[cascadeIndex]; // Single hardware sample with filtering
float result = SAMPLE_SHADOW_MAP(shadowMap, shadowMapUV, sceneDepth);
#if CSM_BLENDING
// Sample the next cascade, and blend between the two results to smooth the transition #if SHADOWS_QUALITY == 1
const float BlendThreshold = 0.1f; result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 0), sceneDepth);
float nextSplit = shadow.CascadeSplits[cascadeIndex]; result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, -1), sceneDepth);
float splitSize = cascadeIndex == 0 ? nextSplit : nextSplit - shadow.CascadeSplits[cascadeIndex - 1]; result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, 1), sceneDepth);
float splitDist = (nextSplit - viewDepth) / splitSize; result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 0), sceneDepth);
BRANCH result = result * (1.0f / 4.0);
if (splitDist <= BlendThreshold && cascadeIndex != shadow.NumCascades - 1) #elif SHADOWS_QUALITY == 2 || SHADOWS_QUALITY == 3
{ // TODO: implement Percentage-Closer Soft Shadows (PCSS) for Ultra quality
// Find the position of this pixel in light space of next cascade result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, -1), sceneDepth);
shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cascadeIndex + 1]); result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 0), sceneDepth);
shadowPosition.xy /= shadowPosition.w; result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 1), sceneDepth);
shadowPosition.z -= shadow.Bias; result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, -1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, 1), sceneDepth);
// Sample next cascade and blur result result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, -1), sceneDepth);
float nextSplitShadow = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex + 1); result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 0), sceneDepth);
float lerpAmount = smoothstep(0.0f, BlendThreshold, splitDist); result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 1), sceneDepth);
lerpAmount = splitDist / BlendThreshold; result = result * (1.0f / 9.0);
result = lerp(nextSplitShadow, result, lerpAmount);
// Blur sharpness as well
sharpness = lerp(shadow.Sharpness * SharpnessScale[cascadeIndex + 1], sharpness, lerpAmount);
}
#endif #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; return result;
} }
// Samples the shadow for the given directional light (cascaded shadow map sampling) for the material surface (supports subsurface shadowing) // Samples the shadow for the given directional light on the material surface (supports subsurface shadowing)
float SampleShadow(LightData light, LightShadowData shadow, Texture2DArray shadowMap, GBufferSample gBuffer, out float subsurfaceShadow) ShadowSample SampleDirectionalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> 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 #if !LIGHTING_NO_DIRECTIONAL
// Skip if surface is in a full shadow // Skip if surface is in a full shadow
float NoL = dot(gBuffer.Normal, light.Direction); float NoL = dot(gBuffer.Normal, light.Direction);
BRANCH BRANCH
if (NoL <= 0) if (NoL <= 0
{ #if defined(USE_GBUFFER_CUSTOM_DATA)
return 0; && !IsSubsurfaceMode(gBuffer.ShadingModel)
} #endif
)
// Apply normal offset bias return (ShadowSample)0;
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
#endif #endif
// Sample shadow ShadowSample result;
return SampleShadow(light, shadow, shadowMap, samplePosWS, viewDepth); 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) // Create a blend factor which is one before and at the fade plane
float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, float3 worldPosition) float viewDepth = gBuffer.ViewPos.z;
{ float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.TilesCount - 1] + shadow.FadeDistance) / shadow.FadeDistance);
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
BRANCH 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 float3 samplePosition = gBuffer.WorldPos;
toLight = -L; #if !LIGHTING_NO_DIRECTIONAL
// Apply normal offset bias
// Project into shadow space samplePosition += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
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);
#endif #endif
// Apply shadow fade and sharpness // Project position into shadow atlas UV
result = saturate((result - 0.5) * shadow.Sharpness + 0.5); float4 shadowPosition;
result = lerp(1.0f, result, shadow.Fade); 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; return result;
} }
// Samples the shadow for the given spot light (PCF shadow map sampling) for the material surface (supports subsurface shadowing) // Samples the shadow for the given local light on the material surface (supports subsurface shadowing)
float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, GBufferSample gBuffer, out float subsurfaceShadow) ShadowSample SampleLocalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, float3 L, float toLightLength, uint tileIndex)
{ {
subsurfaceShadow = 1; #if !LIGHTING_NO_DIRECTIONAL
float3 toLight = light.Position - gBuffer.WorldPos; // Skip if surface is in a full shadow
float toLightLength = length(toLight); float NoL = dot(gBuffer.Normal, L);
float3 L = toLight / toLightLength; BRANCH
#if LIGHTING_NO_DIRECTIONAL if (NoL <= 0
float dirCheck = 1.0f; #if defined(USE_GBUFFER_CUSTOM_DATA)
#else && !IsSubsurfaceMode(gBuffer.ShadingModel)
float dirCheck = dot(-light.Direction, L);
#endif #endif
)
return (ShadowSample)0;
#endif
ShadowSample result;
result.SurfaceShadow = 1;
result.TransmissionShadow = 1;
// Skip pixels outside of the light influence // Skip pixels outside of the light influence
BRANCH BRANCH
if (toLightLength > light.Radius || dirCheck < 0) if (toLightLength > light.Radius)
{ return result;
return 1;
} // 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) #if defined(USE_GBUFFER_CUSTOM_DATA)
// Subsurface shadowing // Subsurface shadowing
BRANCH BRANCH
if (IsSubsurfaceMode(gBuffer.ShadingModel)) if (IsSubsurfaceMode(gBuffer.ShadingModel))
{ {
// Get subsurface material info
float opacity = gBuffer.CustomData.a; float opacity = gBuffer.CustomData.a;
shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, gBuffer.WorldPos, shadowPosition);
// Project into shadow space float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowMapUV, 0).r;
float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[0]); result.TransmissionShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
shadowPosition.z -= shadow.Bias; result.TransmissionShadow = PostProcessShadow(shadow, result.TransmissionShadow);
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);
} }
#endif #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<float> 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; return result;
} }
// Samples the shadow for the given point light (PCF shadow map sampling) for the material surface (supports subsurface shadowing) // Samples the shadow for the given spot light on the material surface (supports subsurface shadowing)
float SampleShadow(LightData light, LightShadowData shadow, TextureCube<float> shadowMap, GBufferSample gBuffer, out float subsurfaceShadow) ShadowSample SampleSpotLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> 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<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer)
{ {
subsurfaceShadow = 1;
float3 toLight = light.Position - gBuffer.WorldPos; float3 toLight = light.Position - gBuffer.WorldPos;
float toLightLength = length(toLight); float toLightLength = length(toLight);
float3 L = toLight / toLightLength; 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 // Figure out which cube face we're sampling from
int cubeFaceIndex = GetCubeFaceIndex(toLight); uint cubeFaceIndex = GetCubeFaceIndex(-L);
#if defined(USE_GBUFFER_CUSTOM_DATA) return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, cubeFaceIndex);
// Subsurface shadowing }
BRANCH
if (IsSubsurfaceMode(gBuffer.ShadingModel))
{
// Get subsurface material info
float opacity = gBuffer.CustomData.a;
// Project into shadow space GBufferSample GetDummyGBufferSample(float3 worldPosition)
float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[cubeFaceIndex]); {
shadowPosition.z -= shadow.Bias; GBufferSample gBuffer = (GBufferSample)0;
shadowPosition.xyz /= shadowPosition.w; gBuffer.ShadingModel = SHADING_MODEL_LIT;
gBuffer.WorldPos = worldPosition;
return gBuffer;
}
// Sample shadow map (use single hardware sample with filtering) // Samples the shadow for the given directional light at custom location
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, toLight, 0).r; ShadowSample SampleDirectionalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, float3 worldPosition, float viewDepth)
subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth); {
GBufferSample gBuffer = GetDummyGBufferSample(worldPosition);
gBuffer.ViewPos.z = viewDepth;
return SampleDirectionalLightShadow(light, shadowsBuffer, shadowMap, gBuffer);
}
// Apply shadow fade // Samples the shadow for the given spot light at custom location
subsurfaceShadow = lerp(1.0f, subsurfaceShadow, shadow.Fade); ShadowSample SampleSpotLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, float3 worldPosition)
} {
#endif GBufferSample gBuffer = GetDummyGBufferSample(worldPosition);
return SampleSpotLightShadow(light, shadowsBuffer, shadowMap, gBuffer);
float3 samplePosWS = gBuffer.WorldPos; }
#if !LIGHTING_NO_DIRECTIONAL // Samples the shadow for the given point light at custom location
// Skip if surface is in a full shadow ShadowSample SamplePointLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, float3 worldPosition)
float NoL = dot(gBuffer.Normal, L); {
BRANCH GBufferSample gBuffer = GetDummyGBufferSample(worldPosition);
if (NoL <= 0) return SamplePointLightShadow(light, shadowsBuffer, shadowMap, gBuffer);
{
return 0;
}
// Apply normal offset bias
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
#endif
// Sample shadow
return SampleShadow(light, shadow, shadowMap, samplePosWS);
} }
#endif #endif

View File

@@ -6,6 +6,7 @@
// "Physically Based and Unified Volumetric Rendering in Frostbite" - Sebastien Hillaire at Siggraph 2015 // "Physically Based and Unified Volumetric Rendering in Frostbite" - Sebastien Hillaire at Siggraph 2015
#define NO_GBUFFER_SAMPLING #define NO_GBUFFER_SAMPLING
#define LIGHTING_NO_DIRECTIONAL 1
// Debug voxels world space positions // Debug voxels world space positions
#define DEBUG_VOXEL_WS_POS 0 #define DEBUG_VOXEL_WS_POS 0
@@ -24,11 +25,10 @@ struct SkyLightData
float3 MultiplyColor; float3 MultiplyColor;
float VolumetricScatteringIntensity; float VolumetricScatteringIntensity;
float3 AdditiveColor; float3 AdditiveColor;
float Dummt0; float Dummy0;
}; };
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
GBufferData GBuffer; GBufferData GBuffer;
float3 GlobalAlbedo; float3 GlobalAlbedo;
@@ -54,14 +54,11 @@ float4x4 PrevWorldToClip;
float4 FrameJitterOffsets[8]; float4 FrameJitterOffsets[8];
LightData DirectionalLight; LightData DirectionalLight;
LightShadowData DirectionalLightShadow;
SkyLightData SkyLight; SkyLightData SkyLight;
DDGIData DDGI; DDGIData DDGI;
META_CB_END META_CB_END
META_CB_BEGIN(1, PerLight) META_CB_BEGIN(1, PerLight)
float2 SliceToDepth; float2 SliceToDepth;
int MinZ; int MinZ;
float LocalLightScatteringIntensity; float LocalLightScatteringIntensity;
@@ -70,8 +67,6 @@ float4 ViewSpaceBoundingSphere;
float4x4 ViewToVolumeClip; float4x4 ViewToVolumeClip;
LightData LocalLight; LightData LocalLight;
LightShadowData LocalLightShadow;
META_CB_END META_CB_END
// The Henyey-Greenstein phase function // The Henyey-Greenstein phase function
@@ -162,28 +157,8 @@ void GS_WriteToSlice(triangle Quad_VS2GS input[3], inout TriangleStream<Quad_GS2
} }
#if USE_SHADOW #if USE_SHADOW
Texture2D<float> ShadowMap : register(t0);
// Shadow Maps Buffer<float4> ShadowsBuffer : register(t1);
TextureCube<float> 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;
}
#endif #endif
META_PS(true, FEATURE_LEVEL_SM5) 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); GetRadialLightAttenuation(LocalLight, isSpotLight, float3(0, 0, 1), distanceSqr, distanceBias * distanceBias, toLight, L, NoL, attenuation);
// Peek the shadow // Peek the shadow
float shadowFactor = 1.0f; float shadow = 1.0f;
#if USE_SHADOW #if USE_SHADOW
if (attenuation > 0) 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 #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; scattering.rgb /= (float)samplesCount;
@@ -281,13 +263,14 @@ Texture3D<float4> VBufferA : register(t0);
Texture3D<float4> VBufferB : register(t1); Texture3D<float4> VBufferB : register(t1);
Texture3D<float4> LightScatteringHistory : register(t2); Texture3D<float4> LightScatteringHistory : register(t2);
Texture3D<float4> LocalShadowedLightScattering : register(t3); Texture3D<float4> LocalShadowedLightScattering : register(t3);
Texture2DArray ShadowMapCSM : register(t4); Texture2D<float> ShadowMap : register(t4);
Buffer<float4> ShadowsBuffer : register(t5);
#if USE_DDGI #if USE_DDGI
Texture2D<snorm float4> ProbesData : register(t5); Texture2D<snorm float4> ProbesData : register(t6);
Texture2D<float4> ProbesDistance : register(t6); Texture2D<float4> ProbesDistance : register(t7);
Texture2D<float4> ProbesIrradiance : register(t7); Texture2D<float4> ProbesIrradiance : register(t8);
#else #else
TextureCube SkyLightImage : register(t5); TextureCube SkyLightImage : register(t6);
#endif #endif
META_CS(true, FEATURE_LEVEL_SM5) META_CS(true, FEATURE_LEVEL_SM5)
@@ -319,16 +302,8 @@ void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_
float3 cameraVectorNormalized = cameraVector / cameraVectorLength; float3 cameraVectorNormalized = cameraVector / cameraVectorLength;
// Directional light // Directional light
BRANCH {
if (DirectionalLightShadow.NumCascades < 10) // NumCascades==10 if no dir light float shadow = SampleDirectionalLightShadow(DirectionalLight, ShadowsBuffer, ShadowMap, positionWS, cameraVectorLength).SurfaceShadow;
{
// Try to sample CSM shadow at the voxel position
float shadow = 1;
if (DirectionalLightShadow.NumCascades > 0)
{
shadow = SampleShadow(DirectionalLight, DirectionalLightShadow, ShadowMapCSM, positionWS, cameraVectorLength);
}
lightScattering += DirectionalLight.Color * (8 * shadow * GetPhase(PhaseG, dot(DirectionalLight.Direction, cameraVectorNormalized))); lightScattering += DirectionalLight.Color * (8 * shadow * GetPhase(PhaseG, dot(DirectionalLight.Direction, cameraVectorNormalized)));
} }