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

View File

@@ -23,3 +23,8 @@
#define OUT_OF_MEMORY Platform::OutOfMemory(__LINE__, __FILE__)
#define MISSING_CODE(info) Platform::MissingCode(__LINE__, __FILE__, info)
#define NON_COPYABLE(type) type(type&&) = delete; type(const type&) = delete; type& operator=(const type&) = delete; type& operator=(type&&) = delete;
#define POD_COPYABLE(type) \
type(const type& other) { Platform::MemoryCopy(this, &other, sizeof(type)); } \
type(type&& other) noexcept { Platform::MemoryCopy(this, &other, sizeof(type)); } \
type& operator=(const type& other) { Platform::MemoryCopy(this, &other, sizeof(type)); return *this; } \
type& operator=(type&& other) noexcept { Platform::MemoryCopy(this, &other, sizeof(type)); return *this; }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -399,12 +399,6 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff
lightData.ShadowsSharpness = 1.0f;
lightData.UseInverseSquaredFalloff = false;
lightData.VolumetricScatteringIntensity = 1.0f;
lightData.CastVolumetricShadow = false;
lightData.RenderedVolumetricFog = 0;
lightData.ShadowsMode = ShadowsCastingMode::None;
lightData.SourceRadius = 0.0f;
lightData.SourceLength = 0.0f;
lightData.IESTexture = nullptr;
for (int32 particleIndex = 0; particleIndex < count; particleIndex++)
{

View File

@@ -77,28 +77,13 @@ PACK_STRUCT(struct ShaderLightData {
Float3 Color;
float MinRoughness;
Float3 Position;
float CastShadows;
uint32 ShadowsBufferAddress;
Float3 Direction;
float Radius;
float FalloffExponent;
float InverseSquared;
float Dummy0;
float RadiusInv;
});
/// <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];
float Dummy0;
});
/// <summary>

View File

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

View File

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

View File

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

View File

@@ -39,6 +39,24 @@ namespace
CriticalSection MemPoolLocker;
}
bool RenderLightData::CanRenderShadow(const RenderView& view) const
{
bool result = false;
switch (ShadowsMode)
{
case ShadowsCastingMode::StaticOnly:
result = view.IsOfflinePass;
break;
case ShadowsCastingMode::DynamicOnly:
result = !view.IsOfflinePass;
break;
case ShadowsCastingMode::All:
result = true;
break;
}
return result && ShadowsStrength > ZeroTolerance;
}
void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useShadow) const
{
data.SpotAngles.X = -2.0f;
@@ -48,7 +66,7 @@ void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useSh
data.Color = Color;
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
data.Position = Float3::Zero;
data.CastShadows = useShadow ? 1.0f : 0.0f;
data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = -Direction;
data.Radius = 0;
data.FalloffExponent = 0;
@@ -56,6 +74,15 @@ void RenderDirectionalLightData::SetShaderData(ShaderLightData& data, bool useSh
data.RadiusInv = 0;
}
bool RenderLocalLightData::CanRenderShadow(const RenderView& view) const
{
// Fade shadow on distance
const float fadeDistance = Math::Max(ShadowsFadeDistance, 0.1f);
const float dstLightToView = Float3::Distance(Position, view.Position);
const float fade = 1 - Math::Saturate((dstLightToView - Radius - ShadowsDistance + fadeDistance) / fadeDistance);
return fade > ZeroTolerance && RenderLightData::CanRenderShadow(view);
}
void RenderSpotLightData::SetShaderData(ShaderLightData& data, bool useShadow) const
{
data.SpotAngles.X = CosOuterCone;
@@ -65,7 +92,7 @@ void RenderSpotLightData::SetShaderData(ShaderLightData& data, bool useShadow) c
data.Color = Color;
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
data.Position = Position;
data.CastShadows = useShadow ? 1.0f : 0.0f;
data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = Direction;
data.Radius = Radius;
data.FalloffExponent = FallOffExponent;
@@ -82,7 +109,7 @@ void RenderPointLightData::SetShaderData(ShaderLightData& data, bool useShadow)
data.Color = Color;
data.MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
data.Position = Position;
data.CastShadows = useShadow ? 1.0f : 0.0f;
data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = Direction;
data.Radius = Radius;
data.FalloffExponent = FallOffExponent;
@@ -99,7 +126,7 @@ void RenderSkyLightData::SetShaderData(ShaderLightData& data, bool useShadow) co
data.Color = Color;
data.MinRoughness = MIN_ROUGHNESS;
data.Position = Position;
data.CastShadows = useShadow ? 1.0f : 0.0f;
data.ShadowsBufferAddress = useShadow ? ShadowsBufferAddress : 0;
data.Direction = Float3::Forward;
data.Radius = Radius;
data.FalloffExponent = 0;

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -9,121 +9,31 @@
#include "Engine/Content/Assets/Model.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>
/// Shadows rendering service.
/// </summary>
class ShadowsPass : public RendererPass<ShadowsPass>
{
private:
struct ShadowData
{
int32 ContextIndex;
int32 ContextCount;
bool BlendCSM;
ShaderLightShadowData Constants;
};
// Shader stuff
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> _psShadowSpot;
PixelFormat _shadowMapFormat;
// 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;
PixelFormat _shadowMapFormat; // Cached on initialization
int32 maxShadowsQuality = 0; // Cached state for the current frame rendering (setup via Prepare)
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>
/// Setups the shadows rendering for batched scene drawing. Checks which lights will cast a shadow.
/// </summary>
void SetupShadows(RenderContext& renderContext, RenderContextBatch& renderContextBatch);
/// <summary>
/// Determines whether can render shadow for the specified light.
/// Renders the shadow maps for all lights (into atlas).
/// </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 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);
void RenderShadowMaps(RenderContextBatch& renderContextBatch);
/// <summary>
/// Renders the shadow mask for the given light.
@@ -131,32 +41,23 @@ public:
/// <param name="renderContextBatch">The rendering context batch.</param>
/// <param name="light">The light.</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>
/// Renders the shadow mask for the given light.
/// Gets the shadow atlas texture and shadows buffer for shadow projection in shaders.
/// </summary>
/// <param name="renderContextBatch">The rendering context batch.</param>
/// <param name="light">The light.</param>
/// <param name="shadowMask">The shadow mask (output).</param>
void RenderShadow(RenderContextBatch& renderContextBatch, RenderSpotLightData& light, GPUTextureView* shadowMask);
/// <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);
/// <param name="renderBuffers">The render buffers that store frame context.</param>
/// <param name="shadowMapAtlas">The output shadow map atlas texture or null if unused.</param>
/// <param name="shadowsBuffer">The output shadows buffer or null if unused.</param>
static void GetShadowAtlas(const RenderBuffers* renderBuffers, GPUTexture*& shadowMapAtlas, GPUBufferView*& shadowsBuffer);
private:
void updateShadowMapSize();
void SetupRenderContext(RenderContext& renderContext, RenderContext& shadowContext);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLightData& light, struct ShadowAtlasLight& atlasLight);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderLocalLightData& light, ShadowAtlasLight& atlasLight);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight);
void SetupLight(RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight);
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj)
@@ -169,14 +70,12 @@ private:
#endif
public:
// [RendererPass]
String ToString() const override;
bool Init() override;
void Dispose() override;
protected:
// [RendererPass]
bool setupResources() override;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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
#ifdef _PS_Clear
// Pixel Shader for clearing a render target with a solid color
META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_Clear(Quad_VS2PS input) : SV_Target
@@ -65,4 +63,9 @@ float4 PS_Clear(Quad_VS2PS input) : SV_Target
return Color;
}
#endif
// Pixel Shader for clearing depth buffer
META_PS(true, FEATURE_LEVEL_ES2)
float PS_DepthClear(Quad_VS2PS input) : SV_Depth
{
return Color.r;
}

View File

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

View File

@@ -12,32 +12,57 @@
#ifndef SHADOWS_QUALITY
#define SHADOWS_QUALITY 0
#endif
#ifndef CSM_BLENDING
#define CSM_BLENDING 0
#endif
// Structure that contains information about light
struct LightShadowData
// Shadow data for the light
struct ShadowData
{
float2 ShadowMapSize;
float Sharpness;
float Fade;
float FadeDistance;
float NormalOffsetScale;
float Bias;
float FadeDistance;
uint NumCascades;
uint TilesCount;
float4 CascadeSplits;
float4x4 ShadowVP[6];
};
#ifdef PLATFORM_ANDROID
// #AdrenoVK_CB_STRUCT_MEMBER_ACCESS_BUG
#define DECLARE_LIGHTSHADOWDATA_ACCESS(uniformName) LightShadowData Get##uniformName##Data() { LightShadowData tmp; tmp.ShadowMapSize = uniformName.ShadowMapSize; tmp.Sharpness = uniformName.Sharpness; tmp.Fade = uniformName.Fade; tmp.NormalOffsetScale = uniformName.NormalOffsetScale; tmp.Bias = uniformName.Bias; tmp.FadeDistance = uniformName.FadeDistance; tmp.NumCascades = uniformName.NumCascades; tmp.CascadeSplits = uniformName.CascadeSplits; tmp.ShadowVP[0] = uniformName.ShadowVP[0]; tmp.ShadowVP[1] = uniformName.ShadowVP[1]; tmp.ShadowVP[2] = uniformName.ShadowVP[2]; tmp.ShadowVP[3] = uniformName.ShadowVP[3]; tmp.ShadowVP[4] = uniformName.ShadowVP[4]; tmp.ShadowVP[5] = uniformName.ShadowVP[5]; return tmp; }
#else
#define DECLARE_LIGHTSHADOWDATA_ACCESS(uniformName) LightShadowData Get##uniformName##Data() { return uniformName; }
#endif
// Shadow projection tile data for the light
struct ShadowTileData
{
float4 ShadowToAtlas;
float4x4 WorldToShadow;
};
// Loads the shadow data of the light in the shadow buffer
ShadowData LoadShadowsBuffer(Buffer<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)
{
@@ -48,8 +73,16 @@ float3 GetShadowPositionOffset(float offsetScale, float NoL, float3 normal)
float CalculateSubsurfaceOcclusion(float opacity, float sceneDepth, float shadowMapDepth)
{
float thickness = max(sceneDepth - shadowMapDepth, 0);
float occlusion = 1 - thickness * lerp(1.0f, 100.0f, opacity);
float occlusion = 1 - saturate(thickness * lerp(1.0f, 100.0f, opacity));
return shadowMapDepth > 0.99f ? 1 : occlusion;
}
float PostProcessShadow(ShadowData lightShadow, float shadow)
{
// Apply shadow fade and sharpness
shadow = saturate((shadow - 0.5) * lightShadow.Sharpness + 0.5);
shadow = lerp(1.0f, shadow, lightShadow.Fade);
return shadow;
}
#endif

View File

@@ -7,46 +7,24 @@
#include "./Flax/GBufferCommon.hlsl"
#include "./Flax/LightingCommon.hlsl"
// Select shadows filter based on quality
// Supported sampling kernel sizes fo each shadowing technique:
// CSM: 2, 3, 5, 7, 9
// Cube: 2, 5, 12, 29
// Spot: 2, 5, 12, 29
#if SHADOWS_QUALITY == 0
#define FilterSizeCSM 2
#define FilterSizeCube 2
#define FilterSizeSpot 2
#elif SHADOWS_QUALITY == 1
#define FilterSizeCSM 3
#define FilterSizeCube 5
#define FilterSizeSpot 5
#elif SHADOWS_QUALITY == 2
#define FilterSizeCSM 5
#define FilterSizeCube 12
#define FilterSizeSpot 12
#else // SHADOWS_QUALITY == 3
#define FilterSizeCSM 7
#define FilterSizeCube 12
#define FilterSizeSpot 12
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
#define SAMPLE_SHADOW_MAP(shadowMap, shadowUV, sceneDepth) shadowMap.SampleCmpLevelZero(ShadowSamplerLinear, shadowUV, sceneDepth)
#define SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowUV, texelOffset, sceneDepth) shadowMap.SampleCmpLevelZero(ShadowSamplerLinear, shadowUV, sceneDepth, texelOffset)
#else
#define SAMPLE_SHADOW_MAP(shadowMap, shadowUV, sceneDepth) (sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, shadowUV, 0).r)
#define SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowUV, texelOffset, sceneDepth) (sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, shadowUV, 0, texelOffset).r)
#endif
#if SHADOWS_QUALITY != 0
#include "./Flax/PCFKernels.hlsl"
#endif
float4 GetShadowMask(ShadowSample shadow)
{
return float4(shadow.SurfaceShadow, shadow.TransmissionShadow, 1, 1);
}
// Gets the cube texture face index to use for shadow map sampling for the given view-to-light direction vector
// Where: direction = normalize(worldPosition - lightPosition)
int GetCubeFaceIndex(float3 direction)
uint GetCubeFaceIndex(float3 direction)
{
int cubeFaceIndex;
uint cubeFaceIndex;
float3 absDirection = abs(direction);
float maxDirection = max(absDirection.x, max(absDirection.y, absDirection.z));
if (maxDirection == absDirection.x)
@@ -58,666 +36,230 @@ int GetCubeFaceIndex(float3 direction)
return cubeFaceIndex;
}
// Samples the shadow map with a fixed-size PCF kernel optimized with GatherCmpRed.
// Uses code from "Fast Conventional Shadow Filtering" by Holger Gruen, in GPU Pro.
float SampleShadowMapFixedSizePCF(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPos, uint cascadeIndex)
float2 GetLightShadowAtlasUV(ShadowData shadow, ShadowTileData shadowTile, float3 samplePosition, out float4 shadowPosition)
{
#if FilterSizeCSM == 2
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(shadowPos.xy, cascadeIndex), sceneDepth);
#else
return sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, float3(shadowPos.xy, cascadeIndex), 0).r;
#endif
#else
const int FS_2 = FilterSizeCSM / 2;
float2 tc = shadowPos.xy;
float4 s = 0.0f;
float2 stc = (shadowMapSize * tc.xy) + float2(0.5f, 0.5f);
float2 tcs = floor(stc);
float2 fc;
int row;
int col;
float4 v1[FS_2 + 1];
float2 v0[FS_2 + 1];
float3 baseUV = float3(tc.xy, cascadeIndex);
float2 shadowMapSizeInv = 1.0f / shadowMapSize;
fc.xy = stc - tcs;
tc.xy = tcs * shadowMapSizeInv;
// Loop over the rows
UNROLL
for (row = -FS_2; row <= FS_2; row += 2)
{
UNROLL
for (col = -FS_2; col <= FS_2; col += 2)
{
float value = CSMFilterWeights[row + FS_2][col + FS_2];
if (col > -FS_2)
value += CSMFilterWeights[row + FS_2][col + FS_2 - 1];
if (col < FS_2)
value += CSMFilterWeights[row + FS_2][col + FS_2 + 1];
if (row > -FS_2) {
value += CSMFilterWeights[row + FS_2 - 1][col + FS_2];
if (col < FS_2)
value += CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1];
if (col > -FS_2)
value += CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1];
}
if (value != 0.0f)
{
// Gather returns xyzw which is counter clockwise order starting with the sample to the lower left of the queried location
#if CAN_USE_GATHER
v1[(col + FS_2) / 2] = shadowMap.GatherCmp(ShadowSampler, baseUV, sceneDepth, int2(col, row));
#else
float4 gather;
gather.x = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(0, 1) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
gather.y = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(1, 1) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
gather.z = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(1, 0) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
gather.w = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(0, 0) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
v1[(col + FS_2) / 2] = gather;
#endif
}
else
v1[(col + FS_2) / 2] = 0.0f;
if (col == -FS_2)
{
s.x += (1.0f - fc.y) * (v1[0].w * (CSMFilterWeights[row + FS_2][col + FS_2]
- CSMFilterWeights[row + FS_2][col + FS_2] * fc.x)
+ v1[0].z * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2]
- CSMFilterWeights[row + FS_2][col + FS_2 + 1])
+ CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
s.y += fc.y * (v1[0].x * (CSMFilterWeights[row + FS_2][col + FS_2]
- CSMFilterWeights[row + FS_2][col + FS_2] * fc.x)
+ v1[0].y * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2]
- CSMFilterWeights[row + FS_2][col + FS_2 + 1])
+ CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
if(row > -FS_2)
{
s.z += (1.0f - fc.y) * (v0[0].x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2] * fc.x)
+ v0[0].y * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
s.w += fc.y * (v1[0].w * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2] * fc.x)
+ v1[0].z * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
}
}
else if (col == FS_2)
{
s.x += (1 - fc.y) * (v1[FS_2].w * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1]
- CSMFilterWeights[row + FS_2][col + FS_2]) + CSMFilterWeights[row + FS_2][col + FS_2])
+ v1[FS_2].z * fc.x * CSMFilterWeights[row + FS_2][col + FS_2]);
s.y += fc.y * (v1[FS_2].x * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1]
- CSMFilterWeights[row + FS_2][col + FS_2] ) + CSMFilterWeights[row + FS_2][col + FS_2])
+ v1[FS_2].y * fc.x * CSMFilterWeights[row + FS_2][col + FS_2]);
if(row > -FS_2) {
s.z += (1 - fc.y) * (v0[FS_2].x * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2])
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2])
+ v0[FS_2].y * fc.x * CSMFilterWeights[row + FS_2 - 1][col + FS_2]);
s.w += fc.y * (v1[FS_2].w * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2])
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2])
+ v1[FS_2].z * fc.x * CSMFilterWeights[row + FS_2 - 1][col + FS_2]);
}
}
else
{
s.x += (1 - fc.y) * (v1[(col + FS_2) / 2].w * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1]
- CSMFilterWeights[row + FS_2][col + FS_2 + 0] ) + CSMFilterWeights[row + FS_2][col + FS_2 + 0])
+ v1[(col + FS_2) / 2].z * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 0]
- CSMFilterWeights[row + FS_2][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
s.y += fc.y * (v1[(col + FS_2) / 2].x * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2-1]
- CSMFilterWeights[row + FS_2][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2][col + FS_2 + 0])
+ v1[(col + FS_2) / 2].y * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 0]
- CSMFilterWeights[row + FS_2][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
if(row > -FS_2) {
s.z += (1 - fc.y) * (v0[(col + FS_2) / 2].x * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0])
+ v0[(col + FS_2) / 2].y * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 0]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
s.w += fc.y * (v1[(col + FS_2) / 2].w * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0])
+ v1[(col + FS_2) / 2].z * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 0]
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
}
}
if (row != FS_2)
v0[(col + FS_2) / 2] = v1[(col + FS_2) / 2].xy;
}
}
return dot(s, 1.0f) / CSMFilterWeightsSum;
#endif
}
// Helper function for SampleShadowMapOptimizedPCF
float SampleShadowMap(Texture2DArray shadowMap, float2 baseUv, float u, float v, float2 shadowMapSizeInv, uint cascadeIndex, float depth)
{
float2 uv = baseUv + float2(u, v) * shadowMapSizeInv;
return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(uv, cascadeIndex), depth);
}
// The method used in The Witness
float SampleShadowMapOptimizedPCF(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPos, uint cascadeIndex)
{
float2 uv = shadowPos.xy * shadowMapSize; // 1 unit - 1 texel
float2 shadowMapSizeInv = 1.0f / shadowMapSize;
float2 baseUv;
baseUv.x = floor(uv.x + 0.5);
baseUv.y = floor(uv.y + 0.5);
float s = (uv.x + 0.5 - baseUv.x);
float t = (uv.y + 0.5 - baseUv.y);
baseUv -= float2(0.5, 0.5);
baseUv *= shadowMapSizeInv;
float sum = 0;
#if FilterSizeCSM == 2
return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(shadowPos.xy, cascadeIndex), sceneDepth);
#elif FilterSizeCSM == 3
float uw0 = (3 - 2 * s);
float uw1 = (1 + 2 * s);
float u0 = (2 - s) / uw0 - 1;
float u1 = s / uw1 + 1;
float vw0 = (3 - 2 * t);
float vw1 = (1 + 2 * t);
float v0 = (2 - t) / vw0 - 1;
float v1 = t / vw1 + 1;
sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
return sum * 1.0f / 16;
#elif FilterSizeCSM == 5
float uw0 = (4 - 3 * s);
float uw1 = 7;
float uw2 = (1 + 3 * s);
float u0 = (3 - 2 * s) / uw0 - 2;
float u1 = (3 + s) / uw1;
float u2 = s / uw2 + 2;
float vw0 = (4 - 3 * t);
float vw1 = 7;
float vw2 = (1 + 3 * t);
float v0 = (3 - 2 * t) / vw0 - 2;
float v1 = (3 + t) / vw1;
float v2 = t / vw2 + 2;
sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw2 * vw0 * SampleShadowMap(shadowMap, baseUv, u2, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw2 * vw1 * SampleShadowMap(shadowMap, baseUv, u2, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw0 * vw2 * SampleShadowMap(shadowMap, baseUv, u0, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw2 * SampleShadowMap(shadowMap, baseUv, u1, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw2 * vw2 * SampleShadowMap(shadowMap, baseUv, u2, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
return sum * 1.0f / 144;
#else // FilterSizeCSM == 7
float uw0 = (5 * s - 6);
float uw1 = (11 * s - 28);
float uw2 = -(11 * s + 17);
float uw3 = -(5 * s + 1);
float u0 = (4 * s - 5) / uw0 - 3;
float u1 = (4 * s - 16) / uw1 - 1;
float u2 = -(7 * s + 5) / uw2 + 1;
float u3 = -s / uw3 + 3;
float vw0 = (5 * t - 6);
float vw1 = (11 * t - 28);
float vw2 = -(11 * t + 17);
float vw3 = -(5 * t + 1);
float v0 = (4 * t - 5) / vw0 - 3;
float v1 = (4 * t - 16) / vw1 - 1;
float v2 = -(7 * t + 5) / vw2 + 1;
float v3 = -t / vw3 + 3;
sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw2 * vw0 * SampleShadowMap(shadowMap, baseUv, u2, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw3 * vw0 * SampleShadowMap(shadowMap, baseUv, u3, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw2 * vw1 * SampleShadowMap(shadowMap, baseUv, u2, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw3 * vw1 * SampleShadowMap(shadowMap, baseUv, u3, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw0 * vw2 * SampleShadowMap(shadowMap, baseUv, u0, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw2 * SampleShadowMap(shadowMap, baseUv, u1, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw2 * vw2 * SampleShadowMap(shadowMap, baseUv, u2, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw3 * vw2 * SampleShadowMap(shadowMap, baseUv, u3, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw0 * vw3 * SampleShadowMap(shadowMap, baseUv, u0, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw1 * vw3 * SampleShadowMap(shadowMap, baseUv, u1, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw2 * vw3 * SampleShadowMap(shadowMap, baseUv, u2, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
sum += uw3 * vw3 * SampleShadowMap(shadowMap, baseUv, u3, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
return sum * (1.0f / 2704);
#endif
}
// Samples the shadow from the shadow map cascade
float SampleShadowCascade(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPosition, uint cascadeIndex)
{
float shadow = SampleShadowMapFixedSizePCF(shadowMap, shadowMapSize, sceneDepth, shadowPosition, cascadeIndex);
//float shadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapSize, sceneDepth, shadowPosition, cascadeIndex);
return shadow;
}
// Samples the shadow for the given directional light (cascaded shadow map sampling)
float SampleShadow(LightData light, LightShadowData shadow, Texture2DArray shadowMap, float3 worldPosition, float viewDepth)
{
// Create a blend factor which is one before and at the fade plane
float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.NumCascades - 1] + shadow.FadeDistance) / shadow.FadeDistance);
BRANCH
if (fade >= 1.0)
{
return 1;
}
// Figure out which cascade to sample from
uint cascadeIndex = 0;
for (uint i = 0; i < shadow.NumCascades - 1; i++)
{
if (viewDepth > shadow.CascadeSplits[i])
cascadeIndex = i + 1;
}
// Project into shadow space
float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cascadeIndex]);
shadowPosition.xy /= shadowPosition.w;
// Project into shadow space (WorldToShadow is pre-multiplied to convert Clip Space to UV Space)
shadowPosition = mul(float4(samplePosition, 1.0f), shadowTile.WorldToShadow);
shadowPosition.z -= shadow.Bias;
shadowPosition.xyz /= shadowPosition.w;
// Sample shadow
float result = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex);
// UV Space -> Atlas Tile UV Space
float2 shadowMapUV = saturate(shadowPosition.xy);
shadowMapUV = shadowMapUV * shadowTile.ShadowToAtlas.xy + shadowTile.ShadowToAtlas.zw;
return shadowMapUV;
}
// Increase the sharpness for higher cascades to match the filter radius
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
float sharpness = shadow.Sharpness * SharpnessScale[cascadeIndex];
#if CSM_BLENDING
// Sample the next cascade, and blend between the two results to smooth the transition
const float BlendThreshold = 0.1f;
float nextSplit = shadow.CascadeSplits[cascadeIndex];
float splitSize = cascadeIndex == 0 ? nextSplit : nextSplit - shadow.CascadeSplits[cascadeIndex - 1];
float splitDist = (nextSplit - viewDepth) / splitSize;
BRANCH
if (splitDist <= BlendThreshold && cascadeIndex != shadow.NumCascades - 1)
{
// Find the position of this pixel in light space of next cascade
shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cascadeIndex + 1]);
shadowPosition.xy /= shadowPosition.w;
shadowPosition.z -= shadow.Bias;
// Sample next cascade and blur result
float nextSplitShadow = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex + 1);
float lerpAmount = smoothstep(0.0f, BlendThreshold, splitDist);
lerpAmount = splitDist / BlendThreshold;
result = lerp(nextSplitShadow, result, lerpAmount);
// Blur sharpness as well
sharpness = lerp(shadow.Sharpness * SharpnessScale[cascadeIndex + 1], sharpness, lerpAmount);
}
float SampleShadowMap(Texture2D<float> shadowMap, float2 shadowMapUV, float sceneDepth)
{
// Single hardware sample with filtering
float result = SAMPLE_SHADOW_MAP(shadowMap, shadowMapUV, sceneDepth);
#if SHADOWS_QUALITY == 1
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 0), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, -1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, 1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 0), sceneDepth);
result = result * (1.0f / 4.0);
#elif SHADOWS_QUALITY == 2 || SHADOWS_QUALITY == 3
// TODO: implement Percentage-Closer Soft Shadows (PCSS) for Ultra quality
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, -1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 0), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(-1, 1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, -1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(0, 1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, -1), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 0), sceneDepth);
result += SAMPLE_SHADOW_MAP_OFFSET(shadowMap, shadowMapUV, int2(1, 1), sceneDepth);
result = result * (1.0f / 9.0);
#endif
// Apply shadow fade and sharpness
result = saturate((result - 0.5) * sharpness + 0.5);
result = lerp(1.0f, result, (1 - fade) * shadow.Fade);
return result;
}
// Samples the shadow for the given directional light (cascaded shadow map sampling) for the material surface (supports subsurface shadowing)
float SampleShadow(LightData light, LightShadowData shadow, Texture2DArray shadowMap, GBufferSample gBuffer, out float subsurfaceShadow)
// Samples the shadow for the given directional light on the material surface (supports subsurface shadowing)
ShadowSample SampleDirectionalLightShadow(LightData light, Buffer<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
// Skip if surface is in a full shadow
float NoL = dot(gBuffer.Normal, light.Direction);
BRANCH
if (NoL <= 0)
{
return 0;
}
// Apply normal offset bias
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
if (NoL <= 0
#if defined(USE_GBUFFER_CUSTOM_DATA)
&& !IsSubsurfaceMode(gBuffer.ShadingModel)
#endif
)
return (ShadowSample)0;
#endif
// Sample shadow
return SampleShadow(light, shadow, shadowMap, samplePosWS, viewDepth);
}
ShadowSample result;
result.SurfaceShadow = 1;
result.TransmissionShadow = 1;
// Load shadow data
if (light.ShadowsBufferAddress == 0)
return result; // No shadow assigned
ShadowData shadow = LoadShadowsBuffer(shadowsBuffer, light.ShadowsBufferAddress);
// Samples the shadow for the given spot light (PCF shadow map sampling)
float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, float3 worldPosition)
{
float3 toLight = light.Position - worldPosition;
float toLightLength = length(toLight);
float3 L = toLight / toLightLength;
#if LIGHTING_NO_DIRECTIONAL
float dirCheck = 1.0f;
#else
float dirCheck = dot(-light.Direction, L);
#endif
// Skip pixels outside of the light influence
// Create a blend factor which is one before and at the fade plane
float viewDepth = gBuffer.ViewPos.z;
float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.TilesCount - 1] + shadow.FadeDistance) / shadow.FadeDistance);
BRANCH
if (toLightLength > light.Radius || dirCheck < 0)
if (fade >= 1.0)
return result;
// Figure out which cascade to sample from
uint cascadeIndex = 0;
for (uint i = 0; i < shadow.TilesCount - 1; i++)
{
return 1;
if (viewDepth > shadow.CascadeSplits[i])
cascadeIndex = i + 1;
}
ShadowTileData shadowTile = LoadShadowsBufferTile(shadowsBuffer, light.ShadowsBufferAddress, cascadeIndex);
// Negate direction and use normalized value
toLight = -L;
// Project into shadow space
float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[0]);
shadowPosition.z -= shadow.Bias;
shadowPosition.xyz /= shadowPosition.w;
float2 shadowMapUVs = shadowPosition.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f);
#if FilterSizeSpot == 2
// Use single hardware sample with filtering
float result = shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, shadowMapUVs, shadowPosition.z);
#else
float3 sideVector = normalize(cross(toLight, float3(0, 0, 1)));
float3 upVector = cross(sideVector, toLight);
float shadowMapSizeInv = 1.0f / shadow.ShadowMapSize.x;
sideVector *= shadowMapSizeInv;
upVector *= shadowMapSizeInv;
// Use PCF filter
float result = 0;
UNROLL
for(int i = 0; i < FilterSizeCube; i++)
{
float2 samplePos = shadowMapUVs + sideVector.xy * PCFDiscSamples[i].x + upVector.xy * PCFDiscSamples[i].y;
result += shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, samplePos, shadowPosition.z);
}
result *= (1.0f / FilterSizeCube);
float3 samplePosition = gBuffer.WorldPos;
#if !LIGHTING_NO_DIRECTIONAL
// Apply normal offset bias
samplePosition += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
#endif
// Apply shadow fade and sharpness
result = saturate((result - 0.5) * shadow.Sharpness + 0.5);
result = lerp(1.0f, result, shadow.Fade);
// Project position into shadow atlas UV
float4 shadowPosition;
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
// Sample shadow map
result.SurfaceShadow = SampleShadowMap(shadowMap, shadowMapUV, shadowPosition.z);
// Increase the sharpness for higher cascades to match the filter radius
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
shadow.Sharpness *= SharpnessScale[cascadeIndex];
#if defined(USE_GBUFFER_CUSTOM_DATA)
// Subsurface shadowing
BRANCH
if (IsSubsurfaceMode(gBuffer.ShadingModel))
{
float opacity = gBuffer.CustomData.a;
shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, gBuffer.WorldPos, shadowPosition);
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowMapUV, 0).r;
result.TransmissionShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
result.TransmissionShadow = PostProcessShadow(shadow, result.TransmissionShadow);
}
#endif
result.SurfaceShadow = PostProcessShadow(shadow, result.SurfaceShadow);
return result;
}
// Samples the shadow for the given spot light (PCF shadow map sampling) for the material surface (supports subsurface shadowing)
float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, GBufferSample gBuffer, out float subsurfaceShadow)
// Samples the shadow for the given local light on the material surface (supports subsurface shadowing)
ShadowSample SampleLocalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, float3 L, float toLightLength, uint tileIndex)
{
subsurfaceShadow = 1;
float3 toLight = light.Position - gBuffer.WorldPos;
float toLightLength = length(toLight);
float3 L = toLight / toLightLength;
#if LIGHTING_NO_DIRECTIONAL
float dirCheck = 1.0f;
#else
float dirCheck = dot(-light.Direction, L);
#if !LIGHTING_NO_DIRECTIONAL
// Skip if surface is in a full shadow
float NoL = dot(gBuffer.Normal, L);
BRANCH
if (NoL <= 0
#if defined(USE_GBUFFER_CUSTOM_DATA)
&& !IsSubsurfaceMode(gBuffer.ShadingModel)
#endif
)
return (ShadowSample)0;
#endif
ShadowSample result;
result.SurfaceShadow = 1;
result.TransmissionShadow = 1;
// Skip pixels outside of the light influence
BRANCH
if (toLightLength > light.Radius || dirCheck < 0)
{
return 1;
}
if (toLightLength > light.Radius)
return result;
// Load shadow data
if (light.ShadowsBufferAddress == 0)
return result; // No shadow assigned
ShadowData shadow = LoadShadowsBuffer(shadowsBuffer, light.ShadowsBufferAddress);
ShadowTileData shadowTile = LoadShadowsBufferTile(shadowsBuffer, light.ShadowsBufferAddress, tileIndex);
float3 samplePosition = gBuffer.WorldPos;
#if !LIGHTING_NO_DIRECTIONAL
// Apply normal offset bias
samplePosition += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
#endif
// Project position into shadow atlas UV
float4 shadowPosition;
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
// Sample shadow map
result.SurfaceShadow = SampleShadowMap(shadowMap, shadowMapUV, shadowPosition.z);
#if defined(USE_GBUFFER_CUSTOM_DATA)
// Subsurface shadowing
BRANCH
if (IsSubsurfaceMode(gBuffer.ShadingModel))
{
// Get subsurface material info
float opacity = gBuffer.CustomData.a;
// Project into shadow space
float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[0]);
shadowPosition.z -= shadow.Bias;
shadowPosition.xyz /= shadowPosition.w;
// Sample shadow map (use single hardware sample with filtering)
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowPosition.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f), 0).r;
subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
// Apply shadow fade
subsurfaceShadow = lerp(1.0f, subsurfaceShadow, shadow.Fade);
shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, gBuffer.WorldPos, shadowPosition);
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowMapUV, 0).r;
result.TransmissionShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
result.TransmissionShadow = PostProcessShadow(shadow, result.TransmissionShadow);
}
#endif
float3 samplePosWS = gBuffer.WorldPos;
#if !LIGHTING_NO_DIRECTIONAL
// Skip if surface is in a full shadow
float NoL = dot(gBuffer.Normal, L);
BRANCH
if (NoL <= 0)
{
return 0;
}
// Apply normal offset bias
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
#endif
// Sample shadow
return SampleShadow(light, shadow, shadowMap, samplePosWS);
}
// Samples the shadow for the given point light (PCF shadow map sampling)
float SampleShadow(LightData light, LightShadowData shadow, TextureCube<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;
}
// Samples the shadow for the given point light (PCF shadow map sampling) for the material surface (supports subsurface shadowing)
float SampleShadow(LightData light, LightShadowData shadow, TextureCube<float> shadowMap, GBufferSample gBuffer, out float subsurfaceShadow)
// Samples the shadow for the given spot light on the material surface (supports subsurface shadowing)
ShadowSample SampleSpotLightShadow(LightData light, Buffer<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;
float toLightLength = length(toLight);
float3 L = toLight / toLightLength;
// Skip pixels outside of the light influence
BRANCH
if (toLightLength > light.Radius)
{
return 1;
}
// Negate direction and use normalized value
toLight = -L;
// Figure out which cube face we're sampling from
int cubeFaceIndex = GetCubeFaceIndex(toLight);
uint cubeFaceIndex = GetCubeFaceIndex(-L);
#if defined(USE_GBUFFER_CUSTOM_DATA)
// Subsurface shadowing
BRANCH
if (IsSubsurfaceMode(gBuffer.ShadingModel))
{
// Get subsurface material info
float opacity = gBuffer.CustomData.a;
return SampleLocalLightShadow(light, shadowsBuffer, shadowMap, gBuffer, L, toLightLength, cubeFaceIndex);
}
// Project into shadow space
float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[cubeFaceIndex]);
shadowPosition.z -= shadow.Bias;
shadowPosition.xyz /= shadowPosition.w;
GBufferSample GetDummyGBufferSample(float3 worldPosition)
{
GBufferSample gBuffer = (GBufferSample)0;
gBuffer.ShadingModel = SHADING_MODEL_LIT;
gBuffer.WorldPos = worldPosition;
return gBuffer;
}
// Sample shadow map (use single hardware sample with filtering)
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, toLight, 0).r;
subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
// Samples the shadow for the given directional light at custom location
ShadowSample SampleDirectionalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, float3 worldPosition, float viewDepth)
{
GBufferSample gBuffer = GetDummyGBufferSample(worldPosition);
gBuffer.ViewPos.z = viewDepth;
return SampleDirectionalLightShadow(light, shadowsBuffer, shadowMap, gBuffer);
}
// Apply shadow fade
subsurfaceShadow = lerp(1.0f, subsurfaceShadow, shadow.Fade);
}
#endif
float3 samplePosWS = gBuffer.WorldPos;
// Samples the shadow for the given spot light at custom location
ShadowSample SampleSpotLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, float3 worldPosition)
{
GBufferSample gBuffer = GetDummyGBufferSample(worldPosition);
return SampleSpotLightShadow(light, shadowsBuffer, shadowMap, gBuffer);
}
#if !LIGHTING_NO_DIRECTIONAL
// Skip if surface is in a full shadow
float NoL = dot(gBuffer.Normal, L);
BRANCH
if (NoL <= 0)
{
return 0;
}
// Apply normal offset bias
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
#endif
// Sample shadow
return SampleShadow(light, shadow, shadowMap, samplePosWS);
// Samples the shadow for the given point light at custom location
ShadowSample SamplePointLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, float3 worldPosition)
{
GBufferSample gBuffer = GetDummyGBufferSample(worldPosition);
return SamplePointLightShadow(light, shadowsBuffer, shadowMap, gBuffer);
}
#endif

View File

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