From d8300f931015c17884ba2aa84fa1ee32cdd4d8dd Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 2 Aug 2026 21:21:01 +0200 Subject: [PATCH 01/13] Add depth min-max reduction texture resources --- Engine_Master_UPC/ModuleResources.cpp | 28 +++++++++++++++++++++++++++ Engine_Master_UPC/ModuleResources.h | 1 + 2 files changed, 29 insertions(+) diff --git a/Engine_Master_UPC/ModuleResources.cpp b/Engine_Master_UPC/ModuleResources.cpp index 68a11e32..01c8dcd9 100644 --- a/Engine_Master_UPC/ModuleResources.cpp +++ b/Engine_Master_UPC/ModuleResources.cpp @@ -26,6 +26,8 @@ #include #include "MD5Fwd.h" +#include + ModuleResources::ModuleResources(ComPtr device, CommandQueue* queue) { @@ -166,6 +168,32 @@ Texture* ModuleResources::createShadowMap(uint32_t size) return shadowMap; } +Texture* ModuleResources::createDepthMinMaxTexture(uint32_t width, uint32_t height) +{ + TextureDesc desc{}; + + desc.format = DXGI_FORMAT_R32G32_FLOAT; + desc.srvFormat = DXGI_FORMAT_R32G32_FLOAT; + desc.uavFormat = DXGI_FORMAT_R32G32_FLOAT; + + desc.width = std::max(1u, width); + desc.height = std::max(1u, height); + + desc.views = TextureView::SRV | TextureView::UAV; + + desc.initialState = + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + + desc.shaderVisibleSRV = true; + + Texture* texture = + new Texture(GenerateUID(), *m_device.Get(), desc); + + texture->setName(L"DepthMinMaxReduction"); + + return texture; +} + Texture* ModuleResources::createRenderTexture(float width, float height) { TextureDesc desc{}; diff --git a/Engine_Master_UPC/ModuleResources.h b/Engine_Master_UPC/ModuleResources.h index 9616a56d..eb25386e 100644 --- a/Engine_Master_UPC/ModuleResources.h +++ b/Engine_Master_UPC/ModuleResources.h @@ -57,6 +57,7 @@ class ModuleResources : public Module Texture* createDepthBuffer(float width, float height); Texture* createShadowMap(uint32_t size); + Texture* createDepthMinMaxTexture(uint32_t width, uint32_t height); Texture* createRenderTexture(float width, float height); Texture* createHDRRenderTexture(float width, float height); From c51c11c6807eccc7812189479e13711a239bbed5 Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 2 Aug 2026 21:37:14 +0200 Subject: [PATCH 02/13] Implement GPU depth min-max reduction pass --- .../DepthMinMaxInitialComputeShader.hlsl | 76 ++++ .../DepthMinMaxReduceComputeShader.hlsl | 75 ++++ Engine_Master_UPC/DepthReductionPass.cpp | 366 ++++++++++++++++++ Engine_Master_UPC/DepthReductionPass.h | 87 +++++ Engine_Master_UPC/Engine.vcxproj | 30 ++ Engine_Master_UPC/ModuleRender.cpp | 2 + Engine_Master_UPC/ModuleRender.h | 2 + 7 files changed, 638 insertions(+) create mode 100644 Engine_Master_UPC/DepthMinMaxInitialComputeShader.hlsl create mode 100644 Engine_Master_UPC/DepthMinMaxReduceComputeShader.hlsl create mode 100644 Engine_Master_UPC/DepthReductionPass.cpp create mode 100644 Engine_Master_UPC/DepthReductionPass.h diff --git a/Engine_Master_UPC/DepthMinMaxInitialComputeShader.hlsl b/Engine_Master_UPC/DepthMinMaxInitialComputeShader.hlsl new file mode 100644 index 00000000..4f65e758 --- /dev/null +++ b/Engine_Master_UPC/DepthMinMaxInitialComputeShader.hlsl @@ -0,0 +1,76 @@ +Texture2D inputDepth : register(t0); +RWTexture2D outputMinMax : register(u0); + +cbuffer ReductionParams : register(b0) +{ + uint2 inputSize; + uint2 padding; +}; + +groupshared uint groupMinDepth; +groupshared uint groupMaxDepth; +groupshared uint groupValidCount; + +[numthreads(8, 8, 1)] +void main( + uint3 globalIndex : SV_DispatchThreadID, + uint3 localIndex3D : SV_GroupThreadID, + uint3 groupIndex : SV_GroupID) +{ + uint localIndex = + localIndex3D.y * 8 + localIndex3D.x; + + if (localIndex == 0) + { + groupMinDepth = asuint(1.0f); + groupMaxDepth = asuint(0.0f); + groupValidCount = 0; + } + + GroupMemoryBarrierWithGroupSync(); + + if (globalIndex.x < inputSize.x && + globalIndex.y < inputSize.y) + { + float depth = + inputDepth.Load(int3(globalIndex.xy, 0)); + + // The camera depth buffer is cleared to 1.0. + // Clear pixels do not represent visible geometry. + if (depth >= 0.0f && depth < 1.0f) + { + uint depthBits = asuint(depth); + + InterlockedMin( + groupMinDepth, + depthBits); + + InterlockedMax( + groupMaxDepth, + depthBits); + + InterlockedAdd( + groupValidCount, + 1); + } + } + + GroupMemoryBarrierWithGroupSync(); + + if (localIndex == 0) + { + if (groupValidCount > 0) + { + outputMinMax[groupIndex.xy] = + float2( + asfloat(groupMinDepth), + asfloat(groupMaxDepth)); + } + else + { + // min > max marks a tile without geometry. + outputMinMax[groupIndex.xy] = + float2(1.0f, 0.0f); + } + } +} \ No newline at end of file diff --git a/Engine_Master_UPC/DepthMinMaxReduceComputeShader.hlsl b/Engine_Master_UPC/DepthMinMaxReduceComputeShader.hlsl new file mode 100644 index 00000000..dc98451d --- /dev/null +++ b/Engine_Master_UPC/DepthMinMaxReduceComputeShader.hlsl @@ -0,0 +1,75 @@ +Texture2D inputMinMax : register(t0); +RWTexture2D outputMinMax : register(u0); + +cbuffer ReductionParams : register(b0) +{ + uint2 inputSize; + uint2 padding; +}; + +groupshared uint groupMinDepth; +groupshared uint groupMaxDepth; +groupshared uint groupValidCount; + +[numthreads(8, 8, 1)] +void main( + uint3 globalIndex : SV_DispatchThreadID, + uint3 localIndex3D : SV_GroupThreadID, + uint3 groupIndex : SV_GroupID) +{ + uint localIndex = + localIndex3D.y * 8 + localIndex3D.x; + + if (localIndex == 0) + { + groupMinDepth = asuint(1.0f); + groupMaxDepth = asuint(0.0f); + groupValidCount = 0; + } + + GroupMemoryBarrierWithGroupSync(); + + if (globalIndex.x < inputSize.x && + globalIndex.y < inputSize.y) + { + float2 minMaxDepth = + inputMinMax.Load( + int3(globalIndex.xy, 0)); + + const bool validTile = + minMaxDepth.x <= minMaxDepth.y; + + if (validTile) + { + InterlockedMin( + groupMinDepth, + asuint(minMaxDepth.x)); + + InterlockedMax( + groupMaxDepth, + asuint(minMaxDepth.y)); + + InterlockedAdd( + groupValidCount, + 1); + } + } + + GroupMemoryBarrierWithGroupSync(); + + if (localIndex == 0) + { + if (groupValidCount > 0) + { + outputMinMax[groupIndex.xy] = + float2( + asfloat(groupMinDepth), + asfloat(groupMaxDepth)); + } + else + { + outputMinMax[groupIndex.xy] = + float2(1.0f, 0.0f); + } + } +} \ No newline at end of file diff --git a/Engine_Master_UPC/DepthReductionPass.cpp b/Engine_Master_UPC/DepthReductionPass.cpp new file mode 100644 index 00000000..4574ba88 --- /dev/null +++ b/Engine_Master_UPC/DepthReductionPass.cpp @@ -0,0 +1,366 @@ +#include "Globals.h" +#include "DepthReductionPass.h" + +#include "Application.h" +#include "ModuleD3D12.h" +#include "ModuleDescriptors.h" +#include "ModuleResources.h" + +#include "RenderContext.h" +#include "RenderSurface.h" +#include "Texture.h" + +#include +#include + +#include "PlatformHelpers.h" + +DepthReductionPass::DepthReductionPass( + ComPtr device) + : m_device(device) +{ + createRootSignature(); + createPipelineStates(); +} + +uint32_t DepthReductionPass::divideRoundUp( + uint32_t value, + uint32_t divisor) +{ + return (value + divisor - 1u) / divisor; +} + +void DepthReductionPass::createRootSignature() +{ + CD3DX12_DESCRIPTOR_RANGE srvRange; + srvRange.Init( + D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + 1, + 0, + 0); + + CD3DX12_DESCRIPTOR_RANGE uavRange; + uavRange.Init( + D3D12_DESCRIPTOR_RANGE_TYPE_UAV, + 1, + 0, + 0); + + CD3DX12_ROOT_PARAMETER rootParameters[3] = {}; + + rootParameters[0].InitAsDescriptorTable( + 1, + &srvRange, + D3D12_SHADER_VISIBILITY_ALL); + + rootParameters[1].InitAsDescriptorTable( + 1, + &uavRange, + D3D12_SHADER_VISIBILITY_ALL); + + rootParameters[2].InitAsConstants( + sizeof(ReductionConstants) / sizeof(uint32_t), + 0, + 0, + D3D12_SHADER_VISIBILITY_ALL); + + CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; + rootSignatureDesc.Init( + _countof(rootParameters), + rootParameters, + 0, + nullptr, + D3D12_ROOT_SIGNATURE_FLAG_NONE); + + ComPtr signature; + ComPtr error; + + DXCall(D3D12SerializeRootSignature( + &rootSignatureDesc, + D3D_ROOT_SIGNATURE_VERSION_1, + &signature, + &error)); + + DXCall(m_device->CreateRootSignature( + 0, + signature->GetBufferPointer(), + signature->GetBufferSize(), + IID_PPV_ARGS(&m_rootSignature))); +} + +void DepthReductionPass::createPipelineStates() +{ + ComPtr initialShaderBlob; + + ThrowIfFailed(D3DReadFileToBlob( + L"DepthMinMaxInitialComputeShader.cso", + &initialShaderBlob)); + + D3D12_COMPUTE_PIPELINE_STATE_DESC initialPSODesc{}; + initialPSODesc.pRootSignature = m_rootSignature.Get(); + initialPSODesc.CS = + CD3DX12_SHADER_BYTECODE(initialShaderBlob.Get()); + + DXCall(m_device->CreateComputePipelineState( + &initialPSODesc, + IID_PPV_ARGS(&m_initialReductionPipelineState))); + + ComPtr reductionShaderBlob; + + ThrowIfFailed(D3DReadFileToBlob( + L"DepthMinMaxReduceComputeShader.cso", + &reductionShaderBlob)); + + D3D12_COMPUTE_PIPELINE_STATE_DESC reductionPSODesc{}; + reductionPSODesc.pRootSignature = m_rootSignature.Get(); + reductionPSODesc.CS = + CD3DX12_SHADER_BYTECODE(reductionShaderBlob.Get()); + + DXCall(m_device->CreateComputePipelineState( + &reductionPSODesc, + IID_PPV_ARGS(&m_reductionPipelineState))); +} + +void DepthReductionPass::ensureReductionTextures( + uint32_t depthWidth, + uint32_t depthHeight) +{ + const uint32_t requiredWidth = + divideRoundUp(depthWidth, TILE_SIZE); + + const uint32_t requiredHeight = + divideRoundUp(depthHeight, TILE_SIZE); + + if (m_pingTexture != nullptr && + m_pongTexture != nullptr && + requiredWidth == m_reductionTextureWidth && + requiredHeight == m_reductionTextureHeight) + { + return; + } + + m_pingTexture.reset( + app->getModuleResources()->createDepthMinMaxTexture( + requiredWidth, + requiredHeight)); + + m_pongTexture.reset( + app->getModuleResources()->createDepthMinMaxTexture( + requiredWidth, + requiredHeight)); + + if (m_pingTexture != nullptr) + { + m_pingTexture->setName( + L"DepthMinMaxReduction_Ping"); + } + + if (m_pongTexture != nullptr) + { + m_pongTexture->setName( + L"DepthMinMaxReduction_Pong"); + } + + m_reductionTextureWidth = requiredWidth; + m_reductionTextureHeight = requiredHeight; + + m_resultTexture = nullptr; +} + +void DepthReductionPass::prepare(const RenderContext& ctx) +{ + m_inputDepthTexture = + ctx.renderSurface + .getTexture(RenderSurface::DEPTH_STENCIL) + .get(); + + m_resultTexture = nullptr; + + if (m_inputDepthTexture == nullptr) + { + m_depthWidth = 0; + m_depthHeight = 0; + return; + } + + const TextureDesc depthDesc = + m_inputDepthTexture->getDesc(); + + m_depthWidth = depthDesc.width; + m_depthHeight = depthDesc.height; + + if (m_depthWidth == 0 || m_depthHeight == 0) + { + return; + } + + ensureReductionTextures( + m_depthWidth, + m_depthHeight); +} + +void DepthReductionPass::transitionTexture( + ID3D12GraphicsCommandList4* commandList, + Texture& texture, + D3D12_RESOURCE_STATES beforeState, + D3D12_RESOURCE_STATES afterState) +{ + if (beforeState == afterState) + { + return; + } + + CD3DX12_RESOURCE_BARRIER barrier = + CD3DX12_RESOURCE_BARRIER::Transition( + texture.getD3D12Resource().Get(), + beforeState, + afterState); + + commandList->ResourceBarrier(1, &barrier); +} + +void DepthReductionPass::dispatchReductionStage( + ID3D12GraphicsCommandList4* commandList, + ID3D12PipelineState* pipelineState, + Texture& inputTexture, + Texture& outputTexture, + uint32_t inputWidth, + uint32_t inputHeight) +{ + const uint32_t outputWidth = + divideRoundUp(inputWidth, TILE_SIZE); + + const uint32_t outputHeight = + divideRoundUp(inputHeight, TILE_SIZE); + + transitionTexture( + commandList, + outputTexture, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + commandList->SetPipelineState(pipelineState); + + commandList->SetComputeRootDescriptorTable( + 0, + inputTexture.getSRV().gpu); + + commandList->SetComputeRootDescriptorTable( + 1, + outputTexture.getUAV().gpu); + + ReductionConstants constants{}; + constants.inputWidth = inputWidth; + constants.inputHeight = inputHeight; + + commandList->SetComputeRoot32BitConstants( + 2, + sizeof(ReductionConstants) / sizeof(uint32_t), + &constants, + 0); + + commandList->Dispatch( + outputWidth, + outputHeight, + 1); + + CD3DX12_RESOURCE_BARRIER uavBarrier = + CD3DX12_RESOURCE_BARRIER::UAV( + outputTexture.getD3D12Resource().Get()); + + commandList->ResourceBarrier( + 1, + &uavBarrier); + + transitionTexture( + commandList, + outputTexture, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); +} + +void DepthReductionPass::apply( + ID3D12GraphicsCommandList4* commandList) +{ + BEGIN_EVENT(commandList, "DepthReductionPass"); + + if (commandList == nullptr || + m_inputDepthTexture == nullptr || + m_pingTexture == nullptr || + m_pongTexture == nullptr || + m_depthWidth == 0 || + m_depthHeight == 0) + { + END_EVENT(commandList); + return; + } + + ID3D12DescriptorHeap* descriptorHeaps[] = + { + app->getModuleDescriptors() + ->getHeap( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) + .getHeap() + }; + + commandList->SetDescriptorHeaps( + _countof(descriptorHeaps), + descriptorHeaps); + + commandList->SetComputeRootSignature( + m_rootSignature.Get()); + + transitionTexture( + commandList, + *m_inputDepthTexture, + D3D12_RESOURCE_STATE_DEPTH_WRITE, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + dispatchReductionStage( + commandList, + m_initialReductionPipelineState.Get(), + *m_inputDepthTexture, + *m_pingTexture, + m_depthWidth, + m_depthHeight); + + Texture* currentInput = m_pingTexture.get(); + Texture* currentOutput = m_pongTexture.get(); + + uint32_t currentWidth = + divideRoundUp(m_depthWidth, TILE_SIZE); + + uint32_t currentHeight = + divideRoundUp(m_depthHeight, TILE_SIZE); + + while (currentWidth > 1 || currentHeight > 1) + { + dispatchReductionStage( + commandList, + m_reductionPipelineState.Get(), + *currentInput, + *currentOutput, + currentWidth, + currentHeight); + + currentWidth = + divideRoundUp(currentWidth, TILE_SIZE); + + currentHeight = + divideRoundUp(currentHeight, TILE_SIZE); + + Texture* previousInput = currentInput; + currentInput = currentOutput; + currentOutput = previousInput; + } + + m_resultTexture = currentInput; + + transitionTexture( + commandList, + *m_inputDepthTexture, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_DEPTH_WRITE); + + END_EVENT(commandList); +} diff --git a/Engine_Master_UPC/DepthReductionPass.h b/Engine_Master_UPC/DepthReductionPass.h new file mode 100644 index 00000000..891d6893 --- /dev/null +++ b/Engine_Master_UPC/DepthReductionPass.h @@ -0,0 +1,87 @@ +#pragma once + +#include "IRenderPass.h" + +#include +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +class Texture; + +class DepthReductionPass final : public IRenderPass +{ +public: + struct ReductionConstants + { + uint32_t inputWidth = 0; + uint32_t inputHeight = 0; + uint32_t padding0 = 0; + uint32_t padding1 = 0; + }; + +public: + explicit DepthReductionPass(ComPtr device); + ~DepthReductionPass() override = default; + + void prepare(const RenderContext& ctx) override; + void apply(ID3D12GraphicsCommandList4* commandList) override; + + const Texture* getResultTexture() const + { + return m_resultTexture; + } + + Texture* getResultTexture() + { + return m_resultTexture; + } + +private: + static constexpr uint32_t TILE_SIZE = 8; + + static uint32_t divideRoundUp(uint32_t value, uint32_t divisor); + + void createRootSignature(); + void createPipelineStates(); + + void ensureReductionTextures( + uint32_t depthWidth, + uint32_t depthHeight); + + void dispatchReductionStage( + ID3D12GraphicsCommandList4* commandList, + ID3D12PipelineState* pipelineState, + Texture& inputTexture, + Texture& outputTexture, + uint32_t inputWidth, + uint32_t inputHeight); + + void transitionTexture( + ID3D12GraphicsCommandList4* commandList, + Texture& texture, + D3D12_RESOURCE_STATES beforeState, + D3D12_RESOURCE_STATES afterState); + +private: + ComPtr m_device; + + ComPtr m_rootSignature; + ComPtr m_initialReductionPipelineState; + ComPtr m_reductionPipelineState; + + Texture* m_inputDepthTexture = nullptr; + + std::unique_ptr m_pingTexture; + std::unique_ptr m_pongTexture; + + Texture* m_resultTexture = nullptr; + + uint32_t m_depthWidth = 0; + uint32_t m_depthHeight = 0; + + uint32_t m_reductionTextureWidth = 0; + uint32_t m_reductionTextureHeight = 0; +}; diff --git a/Engine_Master_UPC/Engine.vcxproj b/Engine_Master_UPC/Engine.vcxproj index 75bce092..9df3a5c7 100644 --- a/Engine_Master_UPC/Engine.vcxproj +++ b/Engine_Master_UPC/Engine.vcxproj @@ -305,6 +305,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -766,6 +767,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -1009,6 +1011,34 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + + Compute + 5.1 + Compute + 5.1 + Compute + 4.0 + Compute + 4.0 + Compute + 4.0 + Compute + 4.0 + + + Compute + 5.1 + Compute + 5.1 + Compute + 4.0 + Compute + 4.0 + Compute + 4.0 + Compute + 4.0 + 6.0 Vertex diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index f6d7dedf..2686d8e9 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -88,6 +88,7 @@ bool ModuleRender::init() m_renderPasses.push_back(std::make_unique(device)); m_skinningComputePass = std::make_unique(device); + m_depthReductionPass = std::make_unique(device); m_shadowMapPass = std::make_unique(device); m_ssaoGeometryPass = std::make_unique(device); m_ssaoPass = std::make_unique(device); @@ -235,6 +236,7 @@ bool ModuleRender::cleanUp() m_ssaoPass.reset(); m_ssaoGeometryPass.reset(); m_shadowMapPass.reset(); + m_depthReductionPass.reset(); m_skinningComputePass.reset(); m_renderPasses.clear(); diff --git a/Engine_Master_UPC/ModuleRender.h b/Engine_Master_UPC/ModuleRender.h index ed2acbb8..36bdde0e 100644 --- a/Engine_Master_UPC/ModuleRender.h +++ b/Engine_Master_UPC/ModuleRender.h @@ -15,6 +15,7 @@ #include "SSAOGeometryPass.h" #include "SSAOPass.h" #include "SSAOBlurPass.h" +#include "DepthReductionPass.h" using Microsoft::WRL::ComPtr; @@ -87,6 +88,7 @@ class ModuleRender : public Module SkyBoxPass* m_skyBoxPass; std::unique_ptr m_skinningComputePass; + std::unique_ptr m_depthReductionPass; std::unique_ptr m_shadowMapPass; std::unique_ptr m_ssaoGeometryPass; std::unique_ptr m_ssaoPass; From 0a7ef2c20eaa89ae77baa2fea8cae8c4c6dbafff Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 2 Aug 2026 21:51:09 +0200 Subject: [PATCH 03/13] Add GPU shadow frustum matrix generation --- Engine_Master_UPC/Engine.vcxproj | 16 + Engine_Master_UPC/ModuleRender.cpp | 2 + Engine_Master_UPC/ModuleRender.h | 2 + .../ShadowFrustumComputePass.cpp | 325 ++++++++++++++++++ Engine_Master_UPC/ShadowFrustumComputePass.h | 78 +++++ .../ShadowFrustumComputeShader.hlsl | 247 +++++++++++++ 6 files changed, 670 insertions(+) create mode 100644 Engine_Master_UPC/ShadowFrustumComputePass.cpp create mode 100644 Engine_Master_UPC/ShadowFrustumComputePass.h create mode 100644 Engine_Master_UPC/ShadowFrustumComputeShader.hlsl diff --git a/Engine_Master_UPC/Engine.vcxproj b/Engine_Master_UPC/Engine.vcxproj index 9df3a5c7..5d390704 100644 --- a/Engine_Master_UPC/Engine.vcxproj +++ b/Engine_Master_UPC/Engine.vcxproj @@ -319,6 +319,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -820,6 +821,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -1191,6 +1193,20 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; Vertex 6.0 + + Compute + 5.1 + Compute + 5.1 + Compute + 4.0 + Compute + 4.0 + Compute + 4.0 + Compute + 4.0 + Vertex Vertex diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index 2686d8e9..11fba5bc 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -89,6 +89,7 @@ bool ModuleRender::init() m_skinningComputePass = std::make_unique(device); m_depthReductionPass = std::make_unique(device); + m_shadowFrustumComputePass = std::make_unique(device, m_depthReductionPass.get()); m_shadowMapPass = std::make_unique(device); m_ssaoGeometryPass = std::make_unique(device); m_ssaoPass = std::make_unique(device); @@ -236,6 +237,7 @@ bool ModuleRender::cleanUp() m_ssaoPass.reset(); m_ssaoGeometryPass.reset(); m_shadowMapPass.reset(); + m_shadowFrustumComputePass.reset(); m_depthReductionPass.reset(); m_skinningComputePass.reset(); diff --git a/Engine_Master_UPC/ModuleRender.h b/Engine_Master_UPC/ModuleRender.h index 36bdde0e..5314973a 100644 --- a/Engine_Master_UPC/ModuleRender.h +++ b/Engine_Master_UPC/ModuleRender.h @@ -16,6 +16,7 @@ #include "SSAOPass.h" #include "SSAOBlurPass.h" #include "DepthReductionPass.h" +#include "ShadowFrustumComputePass.h" using Microsoft::WRL::ComPtr; @@ -89,6 +90,7 @@ class ModuleRender : public Module std::unique_ptr m_skinningComputePass; std::unique_ptr m_depthReductionPass; + std::unique_ptr m_shadowFrustumComputePass; std::unique_ptr m_shadowMapPass; std::unique_ptr m_ssaoGeometryPass; std::unique_ptr m_ssaoPass; diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp new file mode 100644 index 00000000..5002a209 --- /dev/null +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -0,0 +1,325 @@ +#include "Globals.h" +#include "ShadowFrustumComputePass.h" + +#include "Application.h" +#include "ModuleD3D12.h" +#include "ModuleDescriptors.h" +#include "ModuleResources.h" +#include "ModuleScene.h" + +#include "DepthReductionPass.h" +#include "RenderContext.h" +#include "Texture.h" + +#include "LightComponent.h" +#include "Lights.h" +#include "GameObject.h" +#include "Transform.h" + +#include +#include + +#include "PlatformHelpers.h" + +ShadowFrustumComputePass::ShadowFrustumComputePass( + ComPtr device, + DepthReductionPass* depthReductionPass) + : m_device(device) + , m_depthReductionPass(depthReductionPass) +{ + createRootSignature(); + createPipelineState(); + createOutputBuffer(); +} + +void ShadowFrustumComputePass::createRootSignature() +{ + CD3DX12_DESCRIPTOR_RANGE minMaxRange; + minMaxRange.Init( + D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + 1, + 0, + 0); + + CD3DX12_ROOT_PARAMETER rootParameters[3] = {}; + + // t0: final 1x1 min/max depth texture + rootParameters[0].InitAsDescriptorTable( + 1, + &minMaxRange, + D3D12_SHADER_VISIBILITY_ALL); + + // u0: output lightViewProjection buffer + rootParameters[1].InitAsUnorderedAccessView( + 0, + 0); + + // b0: inverse view, projection, light direction and fitting settings + rootParameters[2].InitAsConstants( + sizeof(FrustumConstants) / sizeof(uint32_t), + 0, + 0, + D3D12_SHADER_VISIBILITY_ALL); + + CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; + rootSignatureDesc.Init( + _countof(rootParameters), + rootParameters, + 0, + nullptr, + D3D12_ROOT_SIGNATURE_FLAG_NONE); + + ComPtr signature; + ComPtr error; + + DXCall(D3D12SerializeRootSignature( + &rootSignatureDesc, + D3D_ROOT_SIGNATURE_VERSION_1, + &signature, + &error)); + + DXCall(m_device->CreateRootSignature( + 0, + signature->GetBufferPointer(), + signature->GetBufferSize(), + IID_PPV_ARGS(&m_rootSignature))); +} + +void ShadowFrustumComputePass::createPipelineState() +{ + ComPtr computeShaderBlob; + + ThrowIfFailed(D3DReadFileToBlob( + L"ShadowFrustumComputeShader.cso", + &computeShaderBlob)); + + D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc{}; + psoDesc.pRootSignature = m_rootSignature.Get(); + psoDesc.CS = CD3DX12_SHADER_BYTECODE(computeShaderBlob.Get()); + + DXCall(m_device->CreateComputePipelineState( + &psoDesc, + IID_PPV_ARGS(&m_pipelineState))); +} + +void ShadowFrustumComputePass::createOutputBuffer() +{ + constexpr size_t BUFFER_SIZE = + D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; + + m_lightViewProjectionBuffer = + app->getModuleResources()->createDefaultBuffer( + BUFFER_SIZE, + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + "ShadowLightViewProjection"); + + m_outputBufferState = + D3D12_RESOURCE_STATE_UNORDERED_ACCESS; +} + +const LightComponent* +ShadowFrustumComputePass::findMainShadowCastingDirectionalLight() const +{ + const std::vector& lights = + app->getModuleScene()->getLightComponents(); + + for (const LightComponent* light : lights) + { + if (light == nullptr || !light->isActive()) + { + continue; + } + + const GameObject* owner = light->getOwner(); + + if (owner == nullptr || + !owner->IsActiveInWindowHierarchy()) + { + continue; + } + + const LightData& data = light->getData(); + + if (data.type != LightType::DIRECTIONAL) + { + continue; + } + + if (!data.shadow.castShadows) + { + continue; + } + + return light; + } + + return nullptr; +} + +void ShadowFrustumComputePass::prepare( + const RenderContext& ctx) +{ + m_enabled = false; + m_constants = {}; + + if (m_depthReductionPass == nullptr) + { + return; + } + + const LightComponent* light = + findMainShadowCastingDirectionalLight(); + + if (light == nullptr) + { + return; + } + + const GameObject* owner = light->getOwner(); + const Transform* transform = + owner != nullptr ? owner->GetTransform() : nullptr; + + if (transform == nullptr) + { + return; + } + + Vector3 lightDirection = transform->getForward(); + + if (lightDirection.LengthSquared() <= 0.000001f) + { + return; + } + + lightDirection.Normalize(); + + /* + * Las matrices se transponen antes de subirlas porque los shaders + * actuales usan mul(rowVector, matrix), igual que el resto del motor. + */ + m_constants.inverseView = + ctx.view.Invert().Transpose(); + + m_constants.projection = + ctx.projection.Transpose(); + + m_constants.lightDirection = lightDirection; + m_constants.sunDistance = + SHADOW_LIGHT_DISTANCE_PADDING; + + m_constants.minOrthoSize = + SHADOW_MIN_ORTHO_SIZE; + + m_constants.padding = Vector3::Zero; + + m_enabled = true; +} + +void ShadowFrustumComputePass::transitionOutputBuffer( + ID3D12GraphicsCommandList4* commandList, + D3D12_RESOURCE_STATES newState) +{ + if (commandList == nullptr || + m_lightViewProjectionBuffer == nullptr || + m_outputBufferState == newState) + { + return; + } + + CD3DX12_RESOURCE_BARRIER barrier = + CD3DX12_RESOURCE_BARRIER::Transition( + m_lightViewProjectionBuffer.Get(), + m_outputBufferState, + newState); + + commandList->ResourceBarrier(1, &barrier); + + m_outputBufferState = newState; +} + +void ShadowFrustumComputePass::apply( + ID3D12GraphicsCommandList4* commandList) +{ + BEGIN_EVENT(commandList, "ShadowFrustumComputePass"); + + if (commandList == nullptr || + !m_enabled || + m_depthReductionPass == nullptr || + m_lightViewProjectionBuffer == nullptr) + { + END_EVENT(commandList); + return; + } + + Texture* minMaxTexture = + m_depthReductionPass->getResultTexture(); + + if (minMaxTexture == nullptr || + !minMaxTexture->hasSRV()) + { + END_EVENT(commandList); + return; + } + + ID3D12DescriptorHeap* descriptorHeaps[] = + { + app->getModuleDescriptors() + ->getHeap( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) + .getHeap() + }; + + commandList->SetDescriptorHeaps( + _countof(descriptorHeaps), + descriptorHeaps); + + transitionOutputBuffer( + commandList, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + commandList->SetPipelineState( + m_pipelineState.Get()); + + commandList->SetComputeRootSignature( + m_rootSignature.Get()); + + commandList->SetComputeRootDescriptorTable( + 0, + minMaxTexture->getSRV().gpu); + + commandList->SetComputeRootUnorderedAccessView( + 1, + m_lightViewProjectionBuffer->GetGPUVirtualAddress()); + + commandList->SetComputeRoot32BitConstants( + 2, + sizeof(FrustumConstants) / sizeof(uint32_t), + &m_constants, + 0); + + commandList->Dispatch(1, 1, 1); + + CD3DX12_RESOURCE_BARRIER uavBarrier = + CD3DX12_RESOURCE_BARRIER::UAV( + m_lightViewProjectionBuffer.Get()); + + commandList->ResourceBarrier(1, &uavBarrier); + + transitionOutputBuffer( + commandList, + D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + + END_EVENT(commandList); +} + +D3D12_GPU_VIRTUAL_ADDRESS +ShadowFrustumComputePass::getLightViewProjectionBufferAddress() const +{ + if (m_lightViewProjectionBuffer == nullptr) + { + return 0; + } + + return m_lightViewProjectionBuffer->GetGPUVirtualAddress(); +} \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.h b/Engine_Master_UPC/ShadowFrustumComputePass.h new file mode 100644 index 00000000..1744a421 --- /dev/null +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -0,0 +1,78 @@ +#pragma once + +#include "IRenderPass.h" +#include "SimpleMath.h" + +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +class DepthReductionPass; +class LightComponent; + +class ShadowFrustumComputePass final : public IRenderPass +{ +public: + struct FrustumConstants + { + Matrix inverseView = Matrix::Identity; + Matrix projection = Matrix::Identity; + + Vector3 lightDirection = Vector3::Zero; + float sunDistance = 20.0f; + + float minOrthoSize = 10.0f; + Vector3 padding = Vector3::Zero; + }; + +public: + ShadowFrustumComputePass( + ComPtr device, + DepthReductionPass* depthReductionPass); + + ~ShadowFrustumComputePass() override = default; + + void prepare(const RenderContext& ctx) override; + void apply(ID3D12GraphicsCommandList4* commandList) override; + + D3D12_GPU_VIRTUAL_ADDRESS getLightViewProjectionBufferAddress() const; + + bool isEnabled() const + { + return m_enabled; + } + +private: + static constexpr float SHADOW_MIN_ORTHO_SIZE = 10.0f; + static constexpr float SHADOW_LIGHT_DISTANCE_PADDING = 20.0f; + +private: + void createRootSignature(); + void createPipelineState(); + void createOutputBuffer(); + + const LightComponent* findMainShadowCastingDirectionalLight() const; + + void transitionOutputBuffer( + ID3D12GraphicsCommandList4* commandList, + D3D12_RESOURCE_STATES newState); + +private: + ComPtr m_device; + + DepthReductionPass* m_depthReductionPass = nullptr; + + ComPtr m_rootSignature; + ComPtr m_pipelineState; + + ComPtr m_lightViewProjectionBuffer; + + D3D12_RESOURCE_STATES m_outputBufferState = + D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + + FrustumConstants m_constants{}; + + bool m_enabled = false; +}; \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl new file mode 100644 index 00000000..cc6aa620 --- /dev/null +++ b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl @@ -0,0 +1,247 @@ +Texture2D inputMinMax : register(t0); + +RWStructuredBuffer + outputLightViewProjection : register(u0); + +cbuffer ShadowFrustumParams : register(b0) +{ + float4x4 inverseView; + float4x4 cameraProjection; + + float3 lightDirection; + float sunDistance; + + float minOrthoSize; + float3 padding; +}; + +float LinearizeViewDepth(float deviceDepth) +{ + /* + * Right-handed projection: + * + * deviceDepth = + * (viewZ * projection._33 + projection._43) / + * (-viewZ) + * + * El resultado devuelto es Z en view space, por tanto negativo + * para geometría situada delante de la cámara. + */ + float denominator = + deviceDepth + cameraProjection._33; + + if (abs(denominator) < 0.000001f) + { + denominator = + denominator < 0.0f + ? -0.000001f + : 0.000001f; + } + + return -cameraProjection._43 / denominator; +} + +void BuildFrustumCorners( + float nearDistance, + float farDistance, + out float3 corners[8]) +{ + const float xScale = cameraProjection._11; + const float yScale = cameraProjection._22; + + uint cornerIndex = 0; + + [unroll] + for (uint depthIndex = 0; depthIndex < 2; ++depthIndex) + { + const float distanceValue = + depthIndex == 0 + ? nearDistance + : farDistance; + + [unroll] + for (uint yIndex = 0; yIndex < 2; ++yIndex) + { + const float ndcY = + yIndex == 0 ? -1.0f : 1.0f; + + [unroll] + for (uint xIndex = 0; xIndex < 2; ++xIndex) + { + const float ndcX = + xIndex == 0 ? -1.0f : 1.0f; + + /* + * Reconstrucción del punto en view space para una + * proyección perspectiva right-handed. + */ + float3 viewPoint; + + viewPoint.x = + ndcX * distanceValue / xScale; + + viewPoint.y = + ndcY * distanceValue / yScale; + + viewPoint.z = -distanceValue; + + float4 worldPoint = + mul(float4(viewPoint, 1.0f), inverseView); + + corners[cornerIndex] = + worldPoint.xyz / worldPoint.w; + + ++cornerIndex; + } + } + } +} + +float4 ComputeBoundingSphere( + float3 corners[8]) +{ + float3 center = 0.0f; + + [unroll] + for (uint i = 0; i < 8; ++i) + { + center += corners[i]; + } + + center /= 8.0f; + + float radius = 0.0f; + + [unroll] + for (uint i = 0; i < 8; ++i) + { + radius = max( + radius, + distance(center, corners[i])); + } + + radius = max( + radius, + minOrthoSize * 0.5f); + + return float4(center, radius); +} + +float4x4 BuildLookAtRH( + float3 eye, + float3 target, + float3 up) +{ + float3 zAxis = normalize(eye - target); + float3 xAxis = normalize(cross(up, zAxis)); + float3 yAxis = cross(zAxis, xAxis); + + return float4x4( + xAxis.x, yAxis.x, zAxis.x, 0.0f, + xAxis.y, yAxis.y, zAxis.y, 0.0f, + xAxis.z, yAxis.z, zAxis.z, 0.0f, + -dot(xAxis, eye), + -dot(yAxis, eye), + -dot(zAxis, eye), + 1.0f); +} + +float4x4 BuildOrthographicRH( + float width, + float height, + float nearPlane, + float farPlane) +{ + const float inverseDepthRange = + 1.0f / (nearPlane - farPlane); + + return float4x4( + 2.0f / width, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f / height, 0.0f, 0.0f, + 0.0f, 0.0f, inverseDepthRange, 0.0f, + 0.0f, 0.0f, + nearPlane * inverseDepthRange, + 1.0f); +} + +[numthreads(1, 1, 1)] +void main() +{ + float2 minMaxDepth = + inputMinMax.Load(int3(0, 0, 0)); + + /* + * min > max es el marcador producido por la reducción cuando + * no se encontró geometría válida. + * + * Todavía no consumiremos este buffer en el ShadowMapPass. + * El fallback definitivo se añadirá en el commit 6. + */ + if (minMaxDepth.x > minMaxDepth.y) + { + outputLightViewProjection[0] = + float4x4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + + return; + } + + float nearViewZ = + LinearizeViewDepth(minMaxDepth.x); + + float farViewZ = + LinearizeViewDepth(minMaxDepth.y); + + float nearDistance = + max(-nearViewZ, 0.0001f); + + float farDistance = + max(-farViewZ, nearDistance + 0.0001f); + + float3 corners[8]; + + BuildFrustumCorners( + nearDistance, + farDistance, + corners); + + float4 sphere = + ComputeBoundingSphere(corners); + + float3 normalizedLightDirection = + normalize(lightDirection); + + float3 eye = + sphere.xyz - + normalizedLightDirection * + (sphere.w + sunDistance); + + float3 up = float3(0.0f, 1.0f, 0.0f); + + if (abs(normalizedLightDirection.y) > 0.95f) + { + up = float3(0.0f, 0.0f, 1.0f); + } + + float4x4 lightView = + BuildLookAtRH( + eye, + sphere.xyz, + up); + + float orthoSize = + max(sphere.w * 2.0f, minOrthoSize); + + float4x4 lightProjection = + BuildOrthographicRH( + orthoSize, + orthoSize, + 0.0f, + sphere.w * 2.0f + sunDistance); + + outputLightViewProjection[0] = + mul(lightView, lightProjection); +} \ No newline at end of file From e57875a5f719f80986ff33c1f17b064b5ed299af Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 2 Aug 2026 23:05:18 +0200 Subject: [PATCH 04/13] Adapt shadow pass to GPU light matrix buffer --- Engine_Master_UPC/ModuleRender.cpp | 2 +- .../ShadowFrustumComputePass.cpp | 5 ++ Engine_Master_UPC/ShadowFrustumComputePass.h | 6 ++ Engine_Master_UPC/ShadowMapPass.cpp | 61 ++++++++++++++++--- Engine_Master_UPC/ShadowMapPass.h | 8 ++- Engine_Master_UPC/ShadowMapVertexShader.hlsl | 14 ++++- 6 files changed, 82 insertions(+), 14 deletions(-) diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index 11fba5bc..5c0e5790 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -90,7 +90,7 @@ bool ModuleRender::init() m_skinningComputePass = std::make_unique(device); m_depthReductionPass = std::make_unique(device); m_shadowFrustumComputePass = std::make_unique(device, m_depthReductionPass.get()); - m_shadowMapPass = std::make_unique(device); + m_shadowMapPass = std::make_unique(device, m_shadowFrustumComputePass.get()); m_ssaoGeometryPass = std::make_unique(device); m_ssaoPass = std::make_unique(device); m_ssaoBlurPass = std::make_unique(device); diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp index 5002a209..8cbea10a 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.cpp +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -161,6 +161,7 @@ void ShadowFrustumComputePass::prepare( const RenderContext& ctx) { m_enabled = false; + m_hasValidResult = false; m_constants = {}; if (m_depthReductionPass == nullptr) @@ -243,6 +244,8 @@ void ShadowFrustumComputePass::apply( { BEGIN_EVENT(commandList, "ShadowFrustumComputePass"); + m_hasValidResult = false; + if (commandList == nullptr || !m_enabled || m_depthReductionPass == nullptr || @@ -310,6 +313,8 @@ void ShadowFrustumComputePass::apply( commandList, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + m_hasValidResult = true; + END_EVENT(commandList); } diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.h b/Engine_Master_UPC/ShadowFrustumComputePass.h index 1744a421..9bbafabc 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.h +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -44,6 +44,11 @@ class ShadowFrustumComputePass final : public IRenderPass return m_enabled; } + bool hasValidResult() const + { + return m_hasValidResult; + } + private: static constexpr float SHADOW_MIN_ORTHO_SIZE = 10.0f; static constexpr float SHADOW_LIGHT_DISTANCE_PADDING = 20.0f; @@ -75,4 +80,5 @@ class ShadowFrustumComputePass final : public IRenderPass FrustumConstants m_constants{}; bool m_enabled = false; + bool m_hasValidResult = false; }; \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowMapPass.cpp b/Engine_Master_UPC/ShadowMapPass.cpp index d60eda4a..721bf1b5 100644 --- a/Engine_Master_UPC/ShadowMapPass.cpp +++ b/Engine_Master_UPC/ShadowMapPass.cpp @@ -18,6 +18,7 @@ #include "VertexBuffer.h" #include "IndexBuffer.h" #include "Skin.h" +#include "ShadowFrustumComputePass.h" #include #include @@ -27,8 +28,8 @@ #include #include -ShadowMapPass::ShadowMapPass(ComPtr device) - : m_device(device) +ShadowMapPass::ShadowMapPass(ComPtr device, ShadowFrustumComputePass* shadowFrustumComputePass) + : m_device(device), m_shadowFrustumComputePass(shadowFrustumComputePass) { createShadowMap(DEFAULT_SHADOW_MAP_SIZE); @@ -88,14 +89,21 @@ void ShadowMapPass::updateShadowViewportAndScissor(uint32_t size) void ShadowMapPass::createRootSignature() { - CD3DX12_ROOT_PARAMETER rootParameters[1] = {}; + CD3DX12_ROOT_PARAMETER rootParameters[2] = {}; + // b0: model matrix, changed for each mesh. rootParameters[0].InitAsConstants( sizeof(ShadowDrawConstants) / sizeof(UINT32), 0, 0, D3D12_SHADER_VISIBILITY_VERTEX); + // b1: light view-projection matrix. + rootParameters[1].InitAsConstantBufferView( + 1, + 0, + D3D12_SHADER_VISIBILITY_VERTEX); + CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; rootSignatureDesc.Init( _countof(rootParameters), @@ -480,6 +488,27 @@ void ShadowMapPass::computeLightMatricesFromBounds( m_frameData.lightView * m_frameData.lightProjection; } +D3D12_GPU_VIRTUAL_ADDRESS +ShadowMapPass::getLightViewProjectionCBAddress() const +{ + if (m_shadowFrustumComputePass != nullptr && + m_shadowFrustumComputePass->hasValidResult()) + { + const D3D12_GPU_VIRTUAL_ADDRESS gpuAddress = + m_shadowFrustumComputePass + ->getLightViewProjectionBufferAddress(); + + if (gpuAddress != 0) + { + return gpuAddress; + } + } + + // current fallback: ShadowDataCB generated in CPU. + // lightViewProjection is the first member of ShadowDataCB. + return m_frameData.shadowCBAddress; +} + void ShadowMapPass::renderCasters(ID3D12GraphicsCommandList4* commandList) { for (MeshRenderer* renderer : m_meshRenderers) @@ -547,14 +576,13 @@ void ShadowMapPass::renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, return; } - Matrix global = transform->getGlobalMatrix(); - - Matrix mvp = useWorldSpaceSkinnedVB - ? m_frameData.lightViewProjection - : global * m_frameData.lightViewProjection; + const Matrix model = + useWorldSpaceSkinnedVB + ? Matrix::Identity + : transform->getGlobalMatrix(); ShadowDrawConstants constants{}; - constants.mvp = mvp.Transpose(); + constants.model = model.Transpose(); commandList->SetGraphicsRoot32BitConstants( 0, @@ -650,6 +678,19 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) return; } + const D3D12_GPU_VIRTUAL_ADDRESS lightViewProjectionAddress = + getLightViewProjectionCBAddress(); + + if (lightViewProjectionAddress == 0) + { + transitionShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + END_EVENT(commandList); + return; + } + transitionShadowMap(commandList, D3D12_RESOURCE_STATE_DEPTH_WRITE); commandList->RSSetViewports(1, &m_viewport); @@ -674,6 +715,8 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) commandList->SetPipelineState(m_pipelineState.Get()); commandList->SetGraphicsRootSignature(m_rootSignature.Get()); + commandList->SetGraphicsRootConstantBufferView(1, lightViewProjectionAddress); + commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); renderCasters(commandList); diff --git a/Engine_Master_UPC/ShadowMapPass.h b/Engine_Master_UPC/ShadowMapPass.h index 7568bc6f..28fc78ad 100644 --- a/Engine_Master_UPC/ShadowMapPass.h +++ b/Engine_Master_UPC/ShadowMapPass.h @@ -14,6 +14,7 @@ using Microsoft::WRL::ComPtr; class LightComponent; class MeshRenderer; +class ShadowFrustumComputePass; class ShadowMapPass : public IRenderPass { @@ -21,11 +22,11 @@ class ShadowMapPass : public IRenderPass struct ShadowDrawConstants { - Matrix mvp = Matrix::Identity; + Matrix model = Matrix::Identity; }; public: - explicit ShadowMapPass(ComPtr device); + explicit ShadowMapPass(ComPtr device, ShadowFrustumComputePass* shadowFrustumComputePass); ~ShadowMapPass() override = default; void prepare(const RenderContext& ctx) override; @@ -51,6 +52,7 @@ class ShadowMapPass : public IRenderPass const Vector3& boundsMin, const Vector3& boundsMax); + D3D12_GPU_VIRTUAL_ADDRESS getLightViewProjectionCBAddress() const; void renderCasters(ID3D12GraphicsCommandList4* commandList); void renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, MeshRenderer& renderer); void transitionShadowMap(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState); @@ -73,6 +75,8 @@ class ShadowMapPass : public IRenderPass private: ComPtr m_device; + ShadowFrustumComputePass* m_shadowFrustumComputePass = nullptr; + std::unique_ptr m_shadowMap; D3D12_RESOURCE_STATES m_shadowMapState = D3D12_RESOURCE_STATE_DEPTH_WRITE; uint32_t m_currentShadowMapSize = DEFAULT_SHADOW_MAP_SIZE; diff --git a/Engine_Master_UPC/ShadowMapVertexShader.hlsl b/Engine_Master_UPC/ShadowMapVertexShader.hlsl index eec46c2f..6c7d10a2 100644 --- a/Engine_Master_UPC/ShadowMapVertexShader.hlsl +++ b/Engine_Master_UPC/ShadowMapVertexShader.hlsl @@ -1,9 +1,19 @@ cbuffer ShadowDrawData : register(b0) { - float4x4 mvp; + float4x4 model; +}; + +cbuffer ShadowViewProjection : register(b1) +{ + float4x4 lightViewProjection; }; float4 main(float3 position : POSITION) : SV_POSITION { - return mul(float4(position, 1.0f), mvp); + float4 worldPosition = + mul(float4(position, 1.0f), model); + + return mul( + worldPosition, + lightViewProjection); } \ No newline at end of file From b15f8ba3848980b613d8a76fbb64f6f2d0edc79a Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 2 Aug 2026 23:40:02 +0200 Subject: [PATCH 05/13] Integrate depth buffer fitting into the shadow pipeline Reorder the render pipeline so the camera depth buffer is generated before shadow rendering. Run the GPU min/max depth reduction and shadow frustum compute passes per viewport, then use the generated shadow data in both the shadow map and deferred lighting passes. --- Engine_Master_UPC/ModuleRender.cpp | 74 ++++++++--- .../ShadowFrustumComputePass.cpp | 48 ++++--- Engine_Master_UPC/ShadowFrustumComputePass.h | 10 ++ .../ShadowFrustumComputeShader.hlsl | 125 +++++++++++++----- Engine_Master_UPC/ShadowMapPass.cpp | 14 ++ 5 files changed, 202 insertions(+), 69 deletions(-) diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index 5c0e5790..ebf4cdb6 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -74,7 +74,7 @@ bool ModuleRender::init() debugDrawPass->registerStatic(app->getModuleNavigation()); debugDrawPass->registerStatic(app->getModuleEditor()->getWindowSceneEditor()); - m_renderPasses.push_back(std::make_unique(device)); + //m_renderPasses.push_back(std::make_unique(device)); m_forwardPrepass = new ForwardPrepass(device); m_renderPasses.push_back(std::unique_ptr(m_forwardPrepass)); @@ -438,19 +438,57 @@ void ModuleRender::renderScene(ID3D12GraphicsCommandList4* commandList, const Re } { - PERF_RENDER("ModuleRender::renderScene::ShadowMapPass"); + PERF_RENDER("ModuleRender::renderScene::Background"); + renderBackground(commandList, outputSurface); + } + + + { + PERF_RENDER("ModuleRender::renderScene::ForwardPrepass"); + + if (m_forwardPrepass != nullptr) + { + m_forwardPrepass->prepare(ctx); + m_forwardPrepass->apply(commandList); + } + } - if (m_shadowMapPass) + { + PERF_RENDER("ModuleRender::renderScene::GeometryPass"); + + if (m_geometryPass != nullptr) + { + m_geometryPass->prepare(ctx); + m_geometryPass->apply(commandList); + } + } + + { + PERF_RENDER("ModuleRender::renderScene::DepthFittedShadowMap"); + + if (m_shadowMapPass != nullptr) { - if (!m_shadowMapRenderedThisFrame) + if (m_shadowFrustumComputePass != nullptr) { - m_shadowMapPass->prepare(ctx); - m_shadowMapPass->apply(commandList); + m_shadowFrustumComputePass->prepare(ctx); + } - m_currentShadowData = &m_shadowMapPass->getFrameData(); - m_shadowMapRenderedThisFrame = true; + if (m_shadowFrustumComputePass != nullptr && + m_shadowFrustumComputePass->isEnabled() && + m_depthReductionPass != nullptr) + { + m_depthReductionPass->prepare(ctx); + m_depthReductionPass->apply(commandList); + + m_shadowFrustumComputePass->apply(commandList); } + m_shadowMapPass->prepare(ctx); + m_shadowMapPass->apply(commandList); + + m_currentShadowData = + &m_shadowMapPass->getFrameData(); + ctx.shadowData = m_currentShadowData; } } @@ -465,6 +503,7 @@ void ModuleRender::renderScene(ID3D12GraphicsCommandList4* commandList, const Re } } + { PERF_RENDER("ModuleRender::renderScene::SSAOPass"); @@ -506,17 +545,16 @@ void ModuleRender::renderScene(ID3D12GraphicsCommandList4* commandList, const Re ctx.ssaoData = &m_currentSSAOData; } - - { - PERF_RENDER("ModuleRender::renderScene::Background"); - renderBackground(commandList, outputSurface); - } - - { PERF_RENDER("ModuleRender::renderScene::PreparePasses"); for (auto& pass : m_renderPasses) { + if (pass.get() == m_forwardPrepass || + pass.get() == m_geometryPass) + { + continue; + } + pass->prepare(ctx); } } @@ -525,6 +563,12 @@ void ModuleRender::renderScene(ID3D12GraphicsCommandList4* commandList, const Re PERF_RENDER("ModuleRender::renderScene::ApplyPasses"); for (auto& pass : m_renderPasses) { + if (pass.get() == m_forwardPrepass || + pass.get() == m_geometryPass) + { + continue; + } + pass->apply(commandList); } } diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp index 8cbea10a..fc9ce53e 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.cpp +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -20,6 +20,7 @@ #include #include "PlatformHelpers.h" +#include ShadowFrustumComputePass::ShadowFrustumComputePass( ComPtr device, @@ -157,8 +158,7 @@ ShadowFrustumComputePass::findMainShadowCastingDirectionalLight() const return nullptr; } -void ShadowFrustumComputePass::prepare( - const RenderContext& ctx) +void ShadowFrustumComputePass::prepare(const RenderContext& ctx) { m_enabled = false; m_hasValidResult = false; @@ -177,6 +177,7 @@ void ShadowFrustumComputePass::prepare( return; } + const LightShadowSettings& shadowSettings = light->getData().shadow; const GameObject* owner = light->getOwner(); const Transform* transform = owner != nullptr ? owner->GetTransform() : nullptr; @@ -195,31 +196,41 @@ void ShadowFrustumComputePass::prepare( lightDirection.Normalize(); - /* - * Las matrices se transponen antes de subirlas porque los shaders - * actuales usan mul(rowVector, matrix), igual que el resto del motor. - */ - m_constants.inverseView = - ctx.view.Invert().Transpose(); + m_constants.inverseView = ctx.view.Invert().Transpose(); - m_constants.projection = - ctx.projection.Transpose(); + m_constants.projection = ctx.projection.Transpose(); m_constants.lightDirection = lightDirection; - m_constants.sunDistance = - SHADOW_LIGHT_DISTANCE_PADDING; + m_constants.sunDistance = SHADOW_LIGHT_DISTANCE_PADDING; - m_constants.minOrthoSize = - SHADOW_MIN_ORTHO_SIZE; + m_constants.minOrthoSize = SHADOW_MIN_ORTHO_SIZE; + + m_constants.shadowBias = shadowSettings.shadowBias; + + m_constants.shadowStrength = shadowSettings.shadowStrength; + + m_constants.shadowsEnabled = 1u; + + m_constants.pcfEnabled = shadowSettings.pcfEnabled ? 1u : 0u; + + m_constants.pcfRadius = shadowSettings.pcfEnabled ? shadowSettings.pcfRadius : 0u; + + const uint32_t shadowMapSize = std::max(1u, shadowSettings.shadowMapSize); + + const float inverseShadowMapSize = 1.0f / static_cast(shadowMapSize); + + m_constants.shadowMapTexelSizeX = inverseShadowMapSize; + + m_constants.shadowMapTexelSizeY = inverseShadowMapSize; + + m_constants.paddingSettings = 0.0f; m_constants.padding = Vector3::Zero; m_enabled = true; } -void ShadowFrustumComputePass::transitionOutputBuffer( - ID3D12GraphicsCommandList4* commandList, - D3D12_RESOURCE_STATES newState) +void ShadowFrustumComputePass::transitionOutputBuffer(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState) { if (commandList == nullptr || m_lightViewProjectionBuffer == nullptr || @@ -228,8 +239,7 @@ void ShadowFrustumComputePass::transitionOutputBuffer( return; } - CD3DX12_RESOURCE_BARRIER barrier = - CD3DX12_RESOURCE_BARRIER::Transition( + CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( m_lightViewProjectionBuffer.Get(), m_outputBufferState, newState); diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.h b/Engine_Master_UPC/ShadowFrustumComputePass.h index 9bbafabc..f90d6134 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.h +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -25,6 +25,16 @@ class ShadowFrustumComputePass final : public IRenderPass float minOrthoSize = 10.0f; Vector3 padding = Vector3::Zero; + + float shadowBias = 0.0005f; + float shadowStrength = 1.0f; + uint32_t shadowsEnabled = 0; + uint32_t pcfEnabled = 0; + + uint32_t pcfRadius = 0; + float shadowMapTexelSizeX = 0.0f; + float shadowMapTexelSizeY = 0.0f; + float paddingSettings = 0.0f; }; public: diff --git a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl index cc6aa620..96c50b53 100644 --- a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl +++ b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl @@ -1,7 +1,21 @@ Texture2D inputMinMax : register(t0); -RWStructuredBuffer - outputLightViewProjection : register(u0); +struct ShadowDataOutput +{ + float4x4 lightViewProjection; + + float shadowBias; + float shadowStrength; + uint shadowsEnabled; + float paddingShadow; + + float2 shadowMapTexelSize; + uint pcfEnabled; + uint pcfRadius; +}; + +RWStructuredBuffer + outputShadowData : register(u0); cbuffer ShadowFrustumParams : register(b0) { @@ -13,20 +27,20 @@ cbuffer ShadowFrustumParams : register(b0) float minOrthoSize; float3 padding; + + float shadowBias; + float shadowStrength; + uint shadowsEnabled; + uint pcfEnabled; + + uint pcfRadius; + float shadowMapTexelSizeX; + float shadowMapTexelSizeY; + float paddingSettings; }; float LinearizeViewDepth(float deviceDepth) { - /* - * Right-handed projection: - * - * deviceDepth = - * (viewZ * projection._33 + projection._43) / - * (-viewZ) - * - * El resultado devuelto es Z en view space, por tanto negativo - * para geometría situada delante de la cámara. - */ float denominator = deviceDepth + cameraProjection._33; @@ -71,10 +85,6 @@ void BuildFrustumCorners( const float ndcX = xIndex == 0 ? -1.0f : 1.0f; - /* - * Reconstrucción del punto en view space para una - * proyección perspectiva right-handed. - */ float3 viewPoint; viewPoint.x = @@ -86,7 +96,9 @@ void BuildFrustumCorners( viewPoint.z = -distanceValue; float4 worldPoint = - mul(float4(viewPoint, 1.0f), inverseView); + mul( + float4(viewPoint, 1.0f), + inverseView); corners[cornerIndex] = worldPoint.xyz / worldPoint.w; @@ -97,8 +109,7 @@ void BuildFrustumCorners( } } -float4 ComputeBoundingSphere( - float3 corners[8]) +float4 ComputeBoundingSphere(float3 corners[8]) { float3 center = 0.0f; @@ -164,27 +175,61 @@ float4x4 BuildOrthographicRH( 1.0f); } +ShadowDataOutput BuildShadowOutput( + float4x4 lightViewProjection, + uint enabled) +{ + ShadowDataOutput output; + + output.lightViewProjection = + lightViewProjection; + + output.shadowBias = + shadowBias; + + output.shadowStrength = + shadowStrength; + + output.shadowsEnabled = + enabled; + + output.paddingShadow = 0.0f; + + output.shadowMapTexelSize = + float2( + shadowMapTexelSizeX, + shadowMapTexelSizeY); + + output.pcfEnabled = + pcfEnabled; + + output.pcfRadius = + pcfRadius; + + return output; +} + [numthreads(1, 1, 1)] void main() { float2 minMaxDepth = inputMinMax.Load(int3(0, 0, 0)); - /* - * min > max es el marcador producido por la reducción cuando - * no se encontró geometría válida. - * - * Todavía no consumiremos este buffer en el ShadowMapPass. - * El fallback definitivo se añadirá en el commit 6. - */ if (minMaxDepth.x > minMaxDepth.y) { - outputLightViewProjection[0] = + float4x4 identityMatrix = float4x4( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + + // No geometry was found in the camera depth buffer. + // Disable shadows instead of using an invalid fitting. + outputShadowData[0] = + BuildShadowOutput( + identityMatrix, + 0u); return; } @@ -199,7 +244,9 @@ void main() max(-nearViewZ, 0.0001f); float farDistance = - max(-farViewZ, nearDistance + 0.0001f); + max( + -farViewZ, + nearDistance + 0.0001f); float3 corners[8]; @@ -219,7 +266,8 @@ void main() normalizedLightDirection * (sphere.w + sunDistance); - float3 up = float3(0.0f, 1.0f, 0.0f); + float3 up = + float3(0.0f, 1.0f, 0.0f); if (abs(normalizedLightDirection.y) > 0.95f) { @@ -233,7 +281,9 @@ void main() up); float orthoSize = - max(sphere.w * 2.0f, minOrthoSize); + max( + sphere.w * 2.0f, + minOrthoSize); float4x4 lightProjection = BuildOrthographicRH( @@ -242,6 +292,11 @@ void main() 0.0f, sphere.w * 2.0f + sunDistance); - outputLightViewProjection[0] = + float4x4 lightViewProjection = mul(lightView, lightProjection); + + outputShadowData[0] = + BuildShadowOutput( + lightViewProjection, + shadowsEnabled); } \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowMapPass.cpp b/Engine_Master_UPC/ShadowMapPass.cpp index 721bf1b5..f5230e64 100644 --- a/Engine_Master_UPC/ShadowMapPass.cpp +++ b/Engine_Master_UPC/ShadowMapPass.cpp @@ -314,6 +314,20 @@ void ShadowMapPass::prepareDirectionalShadowData(const RenderContext& ctx, const sizeof(ShadowDataCB), app->getModuleD3D12()->getCurrentFrame()); } + + if (m_shadowFrustumComputePass != nullptr && + m_shadowFrustumComputePass->hasValidResult()) + { + const D3D12_GPU_VIRTUAL_ADDRESS gpuShadowDataAddress = + m_shadowFrustumComputePass + ->getLightViewProjectionBufferAddress(); + + if (gpuShadowDataAddress != 0) + { + m_frameData.shadowCBAddress = + gpuShadowDataAddress; + } + } } From d7a335753190bd41b981e29ba20229ef4794c6f7 Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Mon, 3 Aug 2026 22:17:33 +0200 Subject: [PATCH 06/13] Final GPU depth buffer shadow fitting Remove the legacy bounds-based shadow fitting path and use the GPU-generated shadow data buffer as the sole source for directional shadow matrices and settings. Simplify shadow frame data, remove frame-global shadow reuse, and keep a safe disabled-shadow fallback when depth fitting is unavailable. --- Engine_Master_UPC/ModuleRender.cpp | 11 +- Engine_Master_UPC/ModuleRender.h | 2 - .../ShadowFrustumComputePass.cpp | 24 +- Engine_Master_UPC/ShadowFrustumComputePass.h | 11 +- Engine_Master_UPC/ShadowMapPass.cpp | 299 ++---------------- Engine_Master_UPC/ShadowMapPass.h | 12 - Engine_Master_UPC/ShadowTypes.h | 4 - 7 files changed, 41 insertions(+), 322 deletions(-) diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index ebf4cdb6..a8315d1a 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -129,9 +129,6 @@ void ModuleRender::preRender() { PERF_RENDER("ModuleRender::preRender"); - m_shadowMapRenderedThisFrame = false; - m_currentShadowData = nullptr; - if (m_pendingStopSimulation) { app->getModuleD3D12()->getCommandQueue()->flush(); @@ -166,9 +163,6 @@ void ModuleRender::preRender() #ifndef GAME_RELEASE { - m_shadowMapRenderedThisFrame = false; - m_currentShadowData = nullptr; - auto* commandList = app->getModuleD3D12()->getCommandList(); PERF_RENDER("ModuleRender::RenderViewports"); @@ -486,10 +480,7 @@ void ModuleRender::renderScene(ID3D12GraphicsCommandList4* commandList, const Re m_shadowMapPass->prepare(ctx); m_shadowMapPass->apply(commandList); - m_currentShadowData = - &m_shadowMapPass->getFrameData(); - - ctx.shadowData = m_currentShadowData; + ctx.shadowData = &m_shadowMapPass->getFrameData(); } } diff --git a/Engine_Master_UPC/ModuleRender.h b/Engine_Master_UPC/ModuleRender.h index 5314973a..ca5f2759 100644 --- a/Engine_Master_UPC/ModuleRender.h +++ b/Engine_Master_UPC/ModuleRender.h @@ -96,8 +96,6 @@ class ModuleRender : public Module std::unique_ptr m_ssaoPass; std::unique_ptr m_ssaoBlurPass; - bool m_shadowMapRenderedThisFrame = false; - const ShadowFrameData* m_currentShadowData = nullptr; SSAOFrameData m_currentSSAOData{}; public: diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp index fc9ce53e..85a346b5 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.cpp +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -108,15 +108,14 @@ void ShadowFrustumComputePass::createOutputBuffer() constexpr size_t BUFFER_SIZE = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; - m_lightViewProjectionBuffer = + m_shadowDataBuffer = app->getModuleResources()->createDefaultBuffer( BUFFER_SIZE, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - "ShadowLightViewProjection"); + "ShadowDataBuffer"); - m_outputBufferState = - D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + m_outputBufferState = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; } const LightComponent* @@ -233,14 +232,14 @@ void ShadowFrustumComputePass::prepare(const RenderContext& ctx) void ShadowFrustumComputePass::transitionOutputBuffer(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState) { if (commandList == nullptr || - m_lightViewProjectionBuffer == nullptr || + m_shadowDataBuffer == nullptr || m_outputBufferState == newState) { return; } CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( - m_lightViewProjectionBuffer.Get(), + m_shadowDataBuffer.Get(), m_outputBufferState, newState); @@ -259,7 +258,7 @@ void ShadowFrustumComputePass::apply( if (commandList == nullptr || !m_enabled || m_depthReductionPass == nullptr || - m_lightViewProjectionBuffer == nullptr) + m_shadowDataBuffer == nullptr) { END_EVENT(commandList); return; @@ -303,7 +302,7 @@ void ShadowFrustumComputePass::apply( commandList->SetComputeRootUnorderedAccessView( 1, - m_lightViewProjectionBuffer->GetGPUVirtualAddress()); + m_shadowDataBuffer->GetGPUVirtualAddress()); commandList->SetComputeRoot32BitConstants( 2, @@ -315,7 +314,7 @@ void ShadowFrustumComputePass::apply( CD3DX12_RESOURCE_BARRIER uavBarrier = CD3DX12_RESOURCE_BARRIER::UAV( - m_lightViewProjectionBuffer.Get()); + m_shadowDataBuffer.Get()); commandList->ResourceBarrier(1, &uavBarrier); @@ -328,13 +327,12 @@ void ShadowFrustumComputePass::apply( END_EVENT(commandList); } -D3D12_GPU_VIRTUAL_ADDRESS -ShadowFrustumComputePass::getLightViewProjectionBufferAddress() const +D3D12_GPU_VIRTUAL_ADDRESS ShadowFrustumComputePass::getShadowDataBufferAddress() const { - if (m_lightViewProjectionBuffer == nullptr) + if (m_shadowDataBuffer == nullptr) { return 0; } - return m_lightViewProjectionBuffer->GetGPUVirtualAddress(); + return m_shadowDataBuffer->GetGPUVirtualAddress(); } \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.h b/Engine_Master_UPC/ShadowFrustumComputePass.h index f90d6134..e58f0141 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.h +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -47,7 +47,7 @@ class ShadowFrustumComputePass final : public IRenderPass void prepare(const RenderContext& ctx) override; void apply(ID3D12GraphicsCommandList4* commandList) override; - D3D12_GPU_VIRTUAL_ADDRESS getLightViewProjectionBufferAddress() const; + D3D12_GPU_VIRTUAL_ADDRESS getShadowDataBufferAddress() const; bool isEnabled() const { @@ -70,9 +70,7 @@ class ShadowFrustumComputePass final : public IRenderPass const LightComponent* findMainShadowCastingDirectionalLight() const; - void transitionOutputBuffer( - ID3D12GraphicsCommandList4* commandList, - D3D12_RESOURCE_STATES newState); + void transitionOutputBuffer(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState); private: ComPtr m_device; @@ -82,10 +80,9 @@ class ShadowFrustumComputePass final : public IRenderPass ComPtr m_rootSignature; ComPtr m_pipelineState; - ComPtr m_lightViewProjectionBuffer; + ComPtr m_shadowDataBuffer; - D3D12_RESOURCE_STATES m_outputBufferState = - D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + D3D12_RESOURCE_STATES m_outputBufferState = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; FrustumConstants m_constants{}; diff --git a/Engine_Master_UPC/ShadowMapPass.cpp b/Engine_Master_UPC/ShadowMapPass.cpp index f5230e64..37da39ee 100644 --- a/Engine_Master_UPC/ShadowMapPass.cpp +++ b/Engine_Master_UPC/ShadowMapPass.cpp @@ -236,291 +236,43 @@ void ShadowMapPass::prepareDisabledShadowData(const RenderContext& ctx) } } -void ShadowMapPass::prepareDirectionalShadowData(const RenderContext& ctx, const LightComponent& light) +void ShadowMapPass::prepareDirectionalShadowData( + const RenderContext& ctx, + const LightComponent& light) { - m_frameData = {}; - m_frameData.enabled = true; - - const LightShadowSettings& shadowSettings = light.getData().shadow; - - resizeShadowMapIfNeeded(shadowSettings.shadowMapSize); - - if (m_shadowMap != nullptr && m_shadowMap->hasSRV()) - { - m_frameData.shadowMapSRV = m_shadowMap->getSRV().gpu; - } + const LightShadowSettings& shadowSettings = + light.getData().shadow; - const GameObject* lightOwner = light.getOwner(); - const Transform* lightTransform = lightOwner != nullptr ? lightOwner->GetTransform() : nullptr; + resizeShadowMapIfNeeded( + shadowSettings.shadowMapSize); - if (lightTransform == nullptr) + if (m_shadowFrustumComputePass == nullptr || + !m_shadowFrustumComputePass->hasValidResult()) { prepareDisabledShadowData(ctx); return; } - Vector3 lightDirection = lightTransform->getForward(); - lightDirection.Normalize(); - - Vector3 boundsMin; - Vector3 boundsMax; - - if (computeVisibleWorldBounds(boundsMin, boundsMax)) - { - computeLightMatricesFromBounds(lightDirection, boundsMin, boundsMax); - } - else - { - const Vector3 target = ctx.cameraPosition; - const Vector3 eye = target - lightDirection * SHADOW_LIGHT_DISTANCE_PADDING; - - Vector3 up = Vector3(0.0f, 1.0f, 0.0f); - - if (std::abs(lightDirection.y) > 0.95f) - { - up = Vector3(0.0f, 0.0f, 1.0f); - } - - m_frameData.lightView = Matrix::CreateLookAt( - eye, - target, - up); - - m_frameData.lightProjection = Matrix::CreateOrthographic( - SHADOW_MIN_ORTHO_SIZE, - SHADOW_MIN_ORTHO_SIZE, - SHADOW_MIN_NEAR_PLANE, - SHADOW_LIGHT_DISTANCE_PADDING * 2.0f); - - m_frameData.lightViewProjection = - m_frameData.lightView * m_frameData.lightProjection; - } - - ShadowDataCB shadowCB{}; - shadowCB.lightViewProjection = m_frameData.lightViewProjection.Transpose(); - shadowCB.shadowBias = shadowSettings.shadowBias; - shadowCB.shadowStrength = shadowSettings.shadowStrength; - shadowCB.shadowsEnabled = 1; - shadowCB.shadowMapTexelSize = Vector2( - 1.0f / static_cast(m_currentShadowMapSize), - 1.0f / static_cast(m_currentShadowMapSize)); - shadowCB.pcfEnabled = shadowSettings.pcfEnabled ? 1u : 0u; - shadowCB.pcfRadius = shadowSettings.pcfEnabled ? shadowSettings.pcfRadius : 0u; - - if (ctx.ringBuffer != nullptr) - { - m_frameData.shadowCBAddress = ctx.ringBuffer->allocate( - &shadowCB, - sizeof(ShadowDataCB), - app->getModuleD3D12()->getCurrentFrame()); - } - - if (m_shadowFrustumComputePass != nullptr && - m_shadowFrustumComputePass->hasValidResult()) - { - const D3D12_GPU_VIRTUAL_ADDRESS gpuShadowDataAddress = - m_shadowFrustumComputePass - ->getLightViewProjectionBufferAddress(); - - if (gpuShadowDataAddress != 0) - { - m_frameData.shadowCBAddress = - gpuShadowDataAddress; - } - } -} - - -bool ShadowMapPass::computeVisibleWorldBounds(Vector3& outMin, Vector3& outMax) const -{ - bool hasBounds = false; - - outMin = Vector3( - std::numeric_limits::max(), - std::numeric_limits::max(), - std::numeric_limits::max()); + const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = + m_shadowFrustumComputePass + ->getShadowDataBufferAddress(); - outMax = Vector3( - std::numeric_limits::lowest(), - std::numeric_limits::lowest(), - std::numeric_limits::lowest()); - - for (MeshRenderer* renderer : m_meshRenderers) + if (shadowDataAddress == 0) { - if (renderer == nullptr) - { - continue; - } - - GameObject* owner = renderer->getOwner(); - - if (owner == nullptr || !owner->IsActiveInWindowHierarchy()) - { - continue; - } - - if (!renderer->isActive()) - { - continue; - } - - if (!renderer->hasMesh()) - { - continue; - } - - Transform* transform = renderer->getTransform(); - - if (transform == nullptr) - { - continue; - } - - Engine::BoundingBox& boundingBox = renderer->getBoundingBox(); - boundingBox.update(transform->getGlobalMatrix()); - - const Vector3* points = boundingBox.getPoints(); - - for (int i = 0; i < 8; ++i) - { - outMin.x = std::min(outMin.x, points[i].x); - outMin.y = std::min(outMin.y, points[i].y); - outMin.z = std::min(outMin.z, points[i].z); - - outMax.x = std::max(outMax.x, points[i].x); - outMax.y = std::max(outMax.y, points[i].y); - outMax.z = std::max(outMax.z, points[i].z); - } - - hasBounds = true; - } - - return hasBounds; -} - -void ShadowMapPass::computeLightMatricesFromBounds( - const Vector3& lightDirection, - const Vector3& boundsMin, - const Vector3& boundsMax) -{ - Vector3 boundsCenter = (boundsMin + boundsMax) * 0.5f; - Vector3 boundsExtents = (boundsMax - boundsMin) * 0.5f; - - float boundsRadius = boundsExtents.Length(); - - if (boundsRadius < SHADOW_MIN_ORTHO_SIZE * 0.5f) - { - boundsRadius = SHADOW_MIN_ORTHO_SIZE * 0.5f; - } - - const float lightDistance = boundsRadius + SHADOW_LIGHT_DISTANCE_PADDING; - - const Vector3 eye = boundsCenter - lightDirection * lightDistance; - const Vector3 target = boundsCenter; - - Vector3 up = Vector3(0.0f, 1.0f, 0.0f); - - if (std::abs(lightDirection.y) > 0.95f) - { - up = Vector3(0.0f, 0.0f, 1.0f); - } - - m_frameData.lightView = Matrix::CreateLookAt( - eye, - target, - up); - - float minX = std::numeric_limits::max(); - float minY = std::numeric_limits::max(); - float minZ = std::numeric_limits::max(); - - float maxX = std::numeric_limits::lowest(); - float maxY = std::numeric_limits::lowest(); - float maxZ = std::numeric_limits::lowest(); - - Vector3 corners[8] = - { - Vector3(boundsMin.x, boundsMin.y, boundsMin.z), - Vector3(boundsMax.x, boundsMin.y, boundsMin.z), - Vector3(boundsMax.x, boundsMax.y, boundsMin.z), - Vector3(boundsMin.x, boundsMax.y, boundsMin.z), - - Vector3(boundsMin.x, boundsMin.y, boundsMax.z), - Vector3(boundsMax.x, boundsMin.y, boundsMax.z), - Vector3(boundsMax.x, boundsMax.y, boundsMax.z), - Vector3(boundsMin.x, boundsMax.y, boundsMax.z), - }; - - for (const Vector3& corner : corners) - { - Vector3 lightSpacePoint = Vector3::Transform(corner, m_frameData.lightView); - - minX = std::min(minX, lightSpacePoint.x); - minY = std::min(minY, lightSpacePoint.y); - - maxX = std::max(maxX, lightSpacePoint.x); - maxY = std::max(maxY, lightSpacePoint.y); - - // In our view convention, points in front of the camera/light are at negative Z. - // Convert view-space Z to a positive distance from the light. - const float depth = -lightSpacePoint.z; - - minZ = std::min(minZ, depth); - maxZ = std::max(maxZ, depth); - + prepareDisabledShadowData(ctx); + return; } - minX -= SHADOW_BOUNDS_PADDING; - minY -= SHADOW_BOUNDS_PADDING; - minZ -= SHADOW_BOUNDS_PADDING; - - maxX += SHADOW_BOUNDS_PADDING; - maxY += SHADOW_BOUNDS_PADDING; - maxZ += SHADOW_BOUNDS_PADDING; - - const float width = std::max(maxX - minX, SHADOW_MIN_ORTHO_SIZE); - const float height = std::max(maxY - minY, SHADOW_MIN_ORTHO_SIZE); - - const float centerX = (minX + maxX) * 0.5f; - const float centerY = (minY + maxY) * 0.5f; - - const float halfWidth = width * 0.5f; - const float halfHeight = height * 0.5f; - - const float nearPlane = std::max(SHADOW_MIN_NEAR_PLANE, minZ); - const float farPlane = std::max(nearPlane + 1.0f, maxZ); - - m_frameData.lightProjection = Matrix::CreateOrthographicOffCenter( - centerX - halfWidth, - centerX + halfWidth, - centerY - halfHeight, - centerY + halfHeight, - nearPlane, - farPlane); - - m_frameData.lightViewProjection = - m_frameData.lightView * m_frameData.lightProjection; -} + m_frameData = {}; + m_frameData.enabled = true; + m_frameData.shadowCBAddress = shadowDataAddress; -D3D12_GPU_VIRTUAL_ADDRESS -ShadowMapPass::getLightViewProjectionCBAddress() const -{ - if (m_shadowFrustumComputePass != nullptr && - m_shadowFrustumComputePass->hasValidResult()) + if (m_shadowMap != nullptr && + m_shadowMap->hasSRV()) { - const D3D12_GPU_VIRTUAL_ADDRESS gpuAddress = - m_shadowFrustumComputePass - ->getLightViewProjectionBufferAddress(); - - if (gpuAddress != 0) - { - return gpuAddress; - } + m_frameData.shadowMapSRV = + m_shadowMap->getSRV().gpu; } - - // current fallback: ShadowDataCB generated in CPU. - // lightViewProjection is the first member of ShadowDataCB. - return m_frameData.shadowCBAddress; } void ShadowMapPass::renderCasters(ID3D12GraphicsCommandList4* commandList) @@ -692,10 +444,9 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) return; } - const D3D12_GPU_VIRTUAL_ADDRESS lightViewProjectionAddress = - getLightViewProjectionCBAddress(); + const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = m_frameData.shadowCBAddress; - if (lightViewProjectionAddress == 0) + if (shadowDataAddress == 0) { transitionShadowMap( commandList, @@ -729,7 +480,7 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) commandList->SetPipelineState(m_pipelineState.Get()); commandList->SetGraphicsRootSignature(m_rootSignature.Get()); - commandList->SetGraphicsRootConstantBufferView(1, lightViewProjectionAddress); + commandList->SetGraphicsRootConstantBufferView(1, shadowDataAddress); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); diff --git a/Engine_Master_UPC/ShadowMapPass.h b/Engine_Master_UPC/ShadowMapPass.h index 28fc78ad..c645e182 100644 --- a/Engine_Master_UPC/ShadowMapPass.h +++ b/Engine_Master_UPC/ShadowMapPass.h @@ -45,14 +45,6 @@ class ShadowMapPass : public IRenderPass void prepareDisabledShadowData(const RenderContext& ctx); void prepareDirectionalShadowData(const RenderContext& ctx, const LightComponent& light); - bool computeVisibleWorldBounds(Vector3& outMin, Vector3& outMax) const; - - void computeLightMatricesFromBounds( - const Vector3& lightDirection, - const Vector3& boundsMin, - const Vector3& boundsMax); - - D3D12_GPU_VIRTUAL_ADDRESS getLightViewProjectionCBAddress() const; void renderCasters(ID3D12GraphicsCommandList4* commandList); void renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, MeshRenderer& renderer); void transitionShadowMap(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState); @@ -64,10 +56,6 @@ class ShadowMapPass : public IRenderPass private: static constexpr uint32_t DEFAULT_SHADOW_MAP_SIZE = 4096; - static constexpr float SHADOW_MIN_ORTHO_SIZE = 10.0f; - static constexpr float SHADOW_BOUNDS_PADDING = 10.0f; - static constexpr float SHADOW_LIGHT_DISTANCE_PADDING = 20.0f; - static constexpr float SHADOW_MIN_NEAR_PLANE = 0.1f; static constexpr float SHADOW_BIAS = 0.0005f; static constexpr float SHADOW_STRENGTH = 1.0f; diff --git a/Engine_Master_UPC/ShadowTypes.h b/Engine_Master_UPC/ShadowTypes.h index 8d3d76ec..a5c9578e 100644 --- a/Engine_Master_UPC/ShadowTypes.h +++ b/Engine_Master_UPC/ShadowTypes.h @@ -26,10 +26,6 @@ struct ShadowFrameData { bool enabled = false; - Matrix lightView = Matrix::Identity; - Matrix lightProjection = Matrix::Identity; - Matrix lightViewProjection = Matrix::Identity; - D3D12_GPU_VIRTUAL_ADDRESS shadowCBAddress = 0; D3D12_GPU_DESCRIPTOR_HANDLE shadowMapSRV{}; }; \ No newline at end of file From b7677d22f0158c3563e7eb6236dc9e188176cf6b Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sat, 8 Aug 2026 21:51:29 +0200 Subject: [PATCH 07/13] Add per-slice DSV support for texture arrays Extend Texture depth-stencil handling to create, access, and release individual DSVs for texture array slices while preserving existing 2D depth texture behaviour --- Engine_Master_UPC/Texture.cpp | 79 +++++++++++++++++++++++++++++++---- Engine_Master_UPC/Texture.h | 5 ++- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/Engine_Master_UPC/Texture.cpp b/Engine_Master_UPC/Texture.cpp index 1f6e7f47..47e213e9 100644 --- a/Engine_Master_UPC/Texture.cpp +++ b/Engine_Master_UPC/Texture.cpp @@ -106,9 +106,18 @@ DescriptorHandle Texture::getRTV(uint32_t mip) const return m_rtv[mip]; } -DescriptorHandle Texture::getDSV() const +DescriptorHandle Texture::getDSV(uint32_t arraySlice) const { assert(hasDSV() && "Texture was not created with TextureView::DSV"); + + if (m_desc.arraySize > 1) + { + assert(arraySlice < m_dsvArray.size() && "DSV array slice out of range"); + return m_dsvArray[arraySlice]; + } + + assert(arraySlice == 0 && "Non-array texture only has DSV slice 0"); + return m_dsv; } @@ -312,13 +321,48 @@ void Texture::createDSV() { D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc{}; dsvDesc.Format = resolvedDSVFormat(); - dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; dsvDesc.Flags = D3D12_DSV_FLAG_NONE; + + DescriptorHeap& dsvHeap = + app->getModuleDescriptors() + ->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_DSV); + + if (m_desc.arraySize > 1) + { + dsvDesc.ViewDimension = + D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + + dsvDesc.Texture2DArray.MipSlice = 0; + dsvDesc.Texture2DArray.ArraySize = 1; + + m_dsvArray.resize(m_desc.arraySize); + + for (uint32_t slice = 0; slice < m_desc.arraySize; ++slice) + { + dsvDesc.Texture2DArray.FirstArraySlice = slice; + + m_dsvArray[slice] = dsvHeap.allocate(); + + m_device.CreateDepthStencilView( + m_Resource.Get(), + &dsvDesc, + m_dsvArray[slice].cpu); + } + + return; + } + + dsvDesc.ViewDimension = + D3D12_DSV_DIMENSION_TEXTURE2D; + dsvDesc.Texture2D.MipSlice = 0; - m_dsv = app->getModuleDescriptors()->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_DSV).allocate(); + m_dsv = dsvHeap.allocate(); - m_device.CreateDepthStencilView(m_Resource.Get(), &dsvDesc, m_dsv.cpu); + m_device.CreateDepthStencilView( + m_Resource.Get(), + &dsvDesc, + m_dsv.cpu); } void Texture::createUAV() @@ -377,11 +421,30 @@ void Texture::releaseViews() } } - if (hasDSV() && m_dsv.IsValid()) + if (hasDSV()) { - //descriptors->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_DSV).free(m_dsv.handle); - app->getModuleDescriptors()->defferDescriptorRelease((Handle)m_dsv.handle, D3D12_DESCRIPTOR_HEAP_TYPE_DSV); - m_dsv = {}; + for (DescriptorHandle& dsv : m_dsvArray) + { + if (dsv.IsValid()) + { + app->getModuleDescriptors()->defferDescriptorRelease( + (Handle)dsv.handle, + D3D12_DESCRIPTOR_HEAP_TYPE_DSV); + + dsv = {}; + } + } + + m_dsvArray.clear(); + + if (m_dsv.IsValid()) + { + app->getModuleDescriptors()->defferDescriptorRelease( + (Handle)m_dsv.handle, + D3D12_DESCRIPTOR_HEAP_TYPE_DSV); + + m_dsv = {}; + } } if (hasUAV()) diff --git a/Engine_Master_UPC/Texture.h b/Engine_Master_UPC/Texture.h index 33796998..d6e14491 100644 --- a/Engine_Master_UPC/Texture.h +++ b/Engine_Master_UPC/Texture.h @@ -1,7 +1,7 @@ #pragma once #include "Resources.h" #include "ICacheable.h" - +#include enum class TextureView : uint8_t { @@ -61,7 +61,7 @@ class Texture : public Resource, public ICacheable DescriptorHandle getSRV() const; DescriptorHandle getRTV(uint32_t mip = 0) const; - DescriptorHandle getDSV() const; + DescriptorHandle getDSV(uint32_t arraySlice = 0) const; DescriptorHandle getUAV(uint32_t mip = 0) const; DescriptorHandle getContiguousRTV(uint32_t index) const; @@ -111,6 +111,7 @@ class Texture : public Resource, public ICacheable DescriptorHandle m_srv{}; DescriptorHandle m_rtv[MAX_MIPS]{}; DescriptorHandle m_dsv{}; + std::vector m_dsvArray; DescriptorHandle m_uav[MAX_MIPS]{}; DescriptorHeapBlock* m_contiguousRTV = nullptr; From 26d7b9782a046b6989c43fe2e80cd6aabe488854 Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sat, 8 Aug 2026 22:22:17 +0200 Subject: [PATCH 08/13] Add cascaded shadow map configuration Add configurable cascade count, split intervals, and Fit to Scene/Fit to Cascade modes to directional shadow settings, including editor controls, validation, and serialization. --- Engine_Master_UPC/LightComponent.cpp | 104 ++++++++++++++++++++++++--- Engine_Master_UPC/Lights.h | 16 +++++ Engine_Master_UPC/ShadowTypes.h | 8 +++ 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/Engine_Master_UPC/LightComponent.cpp b/Engine_Master_UPC/LightComponent.cpp index 128d062b..a9955386 100644 --- a/Engine_Master_UPC/LightComponent.cpp +++ b/Engine_Master_UPC/LightComponent.cpp @@ -51,18 +51,22 @@ namespace static void sanitizeShadowSettings(LightShadowSettings& shadow) { shadow.shadowMapSize = sanitizeShadowMapSize(shadow.shadowMapSize); + shadow.pcfRadius = std::clamp(shadow.pcfRadius, 1u, 2u); + shadow.shadowBias = std::max(0.0f, shadow.shadowBias); + shadow.shadowStrength = std::clamp(shadow.shadowStrength, 0.0f, 1.0f); - shadow.pcfRadius = std::clamp( - shadow.pcfRadius, - 1u, - 2u); + shadow.cascadeCount = std::clamp(shadow.cascadeCount, 1u, MAX_SHADOW_CASCADES); - shadow.shadowBias = std::max(0.0f, shadow.shadowBias); + constexpr float MIN_CASCADE_SPLIT_DELTA = 0.001f; - shadow.shadowStrength = std::clamp( - shadow.shadowStrength, - 0.0f, - 1.0f); + shadow.cascadeSplit0 = std::clamp(shadow.cascadeSplit0, MIN_CASCADE_SPLIT_DELTA, 1.0f - 3.0f * MIN_CASCADE_SPLIT_DELTA); + shadow.cascadeSplit1 = std::clamp(shadow.cascadeSplit1, shadow.cascadeSplit0 + MIN_CASCADE_SPLIT_DELTA, 1.0f - 2.0f * MIN_CASCADE_SPLIT_DELTA); + shadow.cascadeSplit2 = std::clamp(shadow.cascadeSplit2, shadow.cascadeSplit1 + MIN_CASCADE_SPLIT_DELTA, 1.0f - MIN_CASCADE_SPLIT_DELTA); + + if (shadow.cascadeFitMode != ShadowCascadeFitMode::FIT_TO_SCENE && shadow.cascadeFitMode != ShadowCascadeFitMode::FIT_TO_CASCADE) + { + shadow.cascadeFitMode = LightDefaults::DEFAULT_SHADOW_CASCADE_FIT_MODE; + } } } @@ -280,6 +284,64 @@ void LightComponent::drawUi() lightChanged = true; } + if (m_data.type == LightType::DIRECTIONAL) + { + ImGui::Separator(); + ImGui::Text("Cascaded Shadow Maps"); + + int cascadeCount = static_cast(m_data.shadow.cascadeCount); + + if (ImGui::SliderInt("Cascade Count", &cascadeCount, 1, static_cast(MAX_SHADOW_CASCADES))) + { + m_data.shadow.cascadeCount = static_cast(cascadeCount); + lightChanged = true; + } + + static const char* CASCADE_FIT_MODE_NAMES[] = { "Fit to Scene", "Fit to Cascade" }; + int cascadeFitMode = static_cast(m_data.shadow.cascadeFitMode); + + if (ImGui::Combo("Cascade Fit", &cascadeFitMode, CASCADE_FIT_MODE_NAMES, IM_ARRAYSIZE(CASCADE_FIT_MODE_NAMES))) + { + m_data.shadow.cascadeFitMode = static_cast(cascadeFitMode); + lightChanged = true; + } + + if (m_data.shadow.cascadeCount > 1) + { + float splitPercent = m_data.shadow.cascadeSplit0 * 100.0f; + + if (ImGui::DragFloat("Cascade 1 End (%)", &splitPercent, 1.0f, 1.0f, 99.0f, "%.1f")) + { + m_data.shadow.cascadeSplit0 = splitPercent / 100.0f; + lightChanged = true; + } + } + + if (m_data.shadow.cascadeCount > 2) + { + float splitPercent = m_data.shadow.cascadeSplit1 * 100.0f; + + if (ImGui::DragFloat("Cascade 2 End (%)", &splitPercent, 1.0f, 1.0f, 99.0f, "%.1f")) + { + m_data.shadow.cascadeSplit1 = splitPercent / 100.0f; + lightChanged = true; + } + } + + if (m_data.shadow.cascadeCount > 3) + { + float splitPercent = m_data.shadow.cascadeSplit2 * 100.0f; + + if (ImGui::DragFloat("Cascade 3 End (%)", &splitPercent, 1.0f, 1.0f, 99.0f, "%.1f")) + { + m_data.shadow.cascadeSplit2 = splitPercent / 100.0f; + lightChanged = true; + } + } + + ImGui::TextDisabled("Final cascade always ends at 100%%."); + } + if (m_data.type != LightType::DIRECTIONAL) { ImGui::TextDisabled("Shadow rendering for this light type is not implemented yet."); @@ -321,6 +383,30 @@ void LightComponent::serialize(IArchive& archive) archive.serialize(m_data.shadow.shadowBias, "ShadowBias"); archive.serialize(m_data.shadow.shadowStrength, "ShadowStrength"); + uint32_t shadowCascadeCount = m_data.shadow.cascadeCount; + + archive.serialize(shadowCascadeCount, "ShadowCascadeCount"); + + if (archive.mode() == ArchiveMode::Input) + { + m_data.shadow.cascadeCount = shadowCascadeCount; + } + + archive.serialize( m_data.shadow.cascadeSplit0, "ShadowCascadeSplit0"); + + archive.serialize(m_data.shadow.cascadeSplit1, "ShadowCascadeSplit1"); + + archive.serialize(m_data.shadow.cascadeSplit2, "ShadowCascadeSplit2"); + + uint32_t shadowCascadeFitMode = static_cast(m_data.shadow.cascadeFitMode); + + archive.serialize(shadowCascadeFitMode, "ShadowCascadeFitMode"); + + if (archive.mode() == ArchiveMode::Input) + { + m_data.shadow.cascadeFitMode = static_cast(shadowCascadeFitMode); + } + float radius = 0.0f; float innerAngle = 0.0f; float outerAngle = 0.0f; diff --git a/Engine_Master_UPC/Lights.h b/Engine_Master_UPC/Lights.h index 361c0f34..929363b5 100644 --- a/Engine_Master_UPC/Lights.h +++ b/Engine_Master_UPC/Lights.h @@ -3,6 +3,8 @@ #include #include "SimpleMath.h" +#include "ShadowTypes.h" + using DirectX::SimpleMath::Vector3; struct LightDefaults @@ -33,6 +35,14 @@ struct LightDefaults static constexpr float DEFAULT_SHADOW_BIAS = 0.0005f; static constexpr float DEFAULT_SHADOW_STRENGTH = 1.0f; + static constexpr uint32_t DEFAULT_SHADOW_CASCADE_COUNT = 4; + + static constexpr float DEFAULT_SHADOW_CASCADE_SPLIT_0 = 0.10f; + static constexpr float DEFAULT_SHADOW_CASCADE_SPLIT_1 = 0.30f; + static constexpr float DEFAULT_SHADOW_CASCADE_SPLIT_2 = 0.60f; + + static constexpr ShadowCascadeFitMode DEFAULT_SHADOW_CASCADE_FIT_MODE = ShadowCascadeFitMode::FIT_TO_CASCADE; + }; enum class LightType : uint8_t @@ -59,6 +69,12 @@ struct LightShadowSettings float shadowBias = LightDefaults::DEFAULT_SHADOW_BIAS; float shadowStrength = LightDefaults::DEFAULT_SHADOW_STRENGTH; + + uint32_t cascadeCount = LightDefaults::DEFAULT_SHADOW_CASCADE_COUNT; + float cascadeSplit0 = LightDefaults::DEFAULT_SHADOW_CASCADE_SPLIT_0; + float cascadeSplit1 = LightDefaults::DEFAULT_SHADOW_CASCADE_SPLIT_1; + float cascadeSplit2 = LightDefaults::DEFAULT_SHADOW_CASCADE_SPLIT_2; + ShadowCascadeFitMode cascadeFitMode = LightDefaults::DEFAULT_SHADOW_CASCADE_FIT_MODE; }; struct DirectionalLightParameters diff --git a/Engine_Master_UPC/ShadowTypes.h b/Engine_Master_UPC/ShadowTypes.h index a5c9578e..98d82f7d 100644 --- a/Engine_Master_UPC/ShadowTypes.h +++ b/Engine_Master_UPC/ShadowTypes.h @@ -7,6 +7,14 @@ using Matrix = DirectX::SimpleMath::Matrix; using Vector2 = DirectX::SimpleMath::Vector2; +static constexpr uint32_t MAX_SHADOW_CASCADES = 4; + +enum class ShadowCascadeFitMode : uint32_t +{ + FIT_TO_SCENE = 0, + FIT_TO_CASCADE = 1 +}; + struct ShadowDataCB { Matrix lightViewProjection = Matrix::Identity; From 8c8ddd08e294fe60ff31f325a508847f70e440ae Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sat, 8 Aug 2026 23:45:52 +0200 Subject: [PATCH 09/13] Generate cascade light frustums on the GPU Extend GPU shadow frustum fitting to partition the depth-fitted camera range and generate light view-projection matrices for each configured cascade while preserving the existing single-frustum shadow path --- .../ShadowFrustumComputePass.cpp | 17 +- Engine_Master_UPC/ShadowFrustumComputePass.h | 8 + .../ShadowFrustumComputeShader.hlsl | 304 +++++++++++++++--- Engine_Master_UPC/ShadowTypes.h | 25 +- 4 files changed, 301 insertions(+), 53 deletions(-) diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp index 85a346b5..01cd9eeb 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.cpp +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -105,8 +105,9 @@ void ShadowFrustumComputePass::createPipelineState() void ShadowFrustumComputePass::createOutputBuffer() { - constexpr size_t BUFFER_SIZE = - D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; + constexpr size_t ALIGNMENT = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; + constexpr size_t BUFFER_SIZE = ((sizeof(ShadowDataCB) + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; + static_assert(BUFFER_SIZE == 512, "ShadowDataBuffer must be large enough for cascaded shadow data."); m_shadowDataBuffer = app->getModuleResources()->createDefaultBuffer( @@ -226,6 +227,18 @@ void ShadowFrustumComputePass::prepare(const RenderContext& ctx) m_constants.padding = Vector3::Zero; + m_constants.cascadeCount = std::clamp(shadowSettings.cascadeCount, 1u, MAX_SHADOW_CASCADES); + + m_constants.cascadeFitMode = static_cast(shadowSettings.cascadeFitMode); + + m_constants.cascadeSplit0 = shadowSettings.cascadeSplit0; + + m_constants.cascadeSplit1 = shadowSettings.cascadeSplit1; + + m_constants.cascadeSplit2 = shadowSettings.cascadeSplit2; + + m_constants.cascadePadding = Vector3::Zero; + m_enabled = true; } diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.h b/Engine_Master_UPC/ShadowFrustumComputePass.h index e58f0141..5f5118c1 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.h +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -2,6 +2,7 @@ #include "IRenderPass.h" #include "SimpleMath.h" +#include "ShadowTypes.h" #include #include @@ -35,6 +36,13 @@ class ShadowFrustumComputePass final : public IRenderPass float shadowMapTexelSizeX = 0.0f; float shadowMapTexelSizeY = 0.0f; float paddingSettings = 0.0f; + + uint32_t cascadeCount = 1; + uint32_t cascadeFitMode = static_cast(ShadowCascadeFitMode::FIT_TO_CASCADE); + float cascadeSplit0 = 0.10f; + float cascadeSplit1 = 0.30f; + float cascadeSplit2 = 0.60f; + Vector3 cascadePadding = Vector3::Zero; }; public: diff --git a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl index 96c50b53..a3de418b 100644 --- a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl +++ b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl @@ -1,7 +1,12 @@ Texture2D inputMinMax : register(t0); +#define MAX_SHADOW_CASCADES 4 +#define CASCADE_FIT_TO_SCENE 0 +#define CASCADE_FIT_TO_CASCADE 1 + struct ShadowDataOutput { + // Full fitted frustum used by the existing shadow pipeline. float4x4 lightViewProjection; float shadowBias; @@ -12,6 +17,15 @@ struct ShadowDataOutput float2 shadowMapTexelSize; uint pcfEnabled; uint pcfRadius; + + // CSM + uint cascadeCount; + uint cascadeFitMode; + float2 cascadePadding; + + float4 cascadeFarDistances; + + float4x4 cascadeLightViewProjection[MAX_SHADOW_CASCADES]; }; RWStructuredBuffer @@ -37,6 +51,14 @@ cbuffer ShadowFrustumParams : register(b0) float shadowMapTexelSizeX; float shadowMapTexelSizeY; float paddingSettings; + + uint cascadeCount; + uint cascadeFitMode; + float cascadeSplit0; + float cascadeSplit1; + + float cascadeSplit2; + float3 cascadePadding; }; float LinearizeViewDepth(float deviceDepth) @@ -175,6 +197,68 @@ float4x4 BuildOrthographicRH( 1.0f); } +float4x4 BuildIdentityMatrix() +{ + return float4x4( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +float4x4 BuildLightViewProjection( + float nearDistance, + float farDistance) +{ + float3 corners[8]; + + BuildFrustumCorners( + nearDistance, + farDistance, + corners); + + float4 sphere = + ComputeBoundingSphere(corners); + + float3 normalizedLightDirection = + normalize(lightDirection); + + float3 eye = + sphere.xyz - + normalizedLightDirection * + (sphere.w + sunDistance); + + float3 up = + float3(0.0f, 1.0f, 0.0f); + + if (abs(normalizedLightDirection.y) > 0.95f) + { + up = float3(0.0f, 0.0f, 1.0f); + } + + float4x4 lightView = + BuildLookAtRH( + eye, + sphere.xyz, + up); + + float orthoSize = + max( + sphere.w * 2.0f, + minOrthoSize); + + float4x4 lightProjection = + BuildOrthographicRH( + orthoSize, + orthoSize, + 0.0f, + sphere.w * 2.0f + sunDistance); + + return mul( + lightView, + lightProjection); +} + ShadowDataOutput BuildShadowOutput( float4x4 lightViewProjection, uint enabled) @@ -193,7 +277,8 @@ ShadowDataOutput BuildShadowOutput( output.shadowsEnabled = enabled; - output.paddingShadow = 0.0f; + output.paddingShadow = + 0.0f; output.shadowMapTexelSize = float2( @@ -206,6 +291,36 @@ ShadowDataOutput BuildShadowOutput( output.pcfRadius = pcfRadius; + output.cascadeCount = + clamp( + cascadeCount, + 1u, + (uint) MAX_SHADOW_CASCADES); + + output.cascadeFitMode = + cascadeFitMode; + + output.cascadePadding = + float2(0.0f, 0.0f); + + output.cascadeFarDistances = + float4(0.0f, 0.0f, 0.0f, 0.0f); + + float4x4 identityMatrix = + BuildIdentityMatrix(); + + output.cascadeLightViewProjection[0] = + identityMatrix; + + output.cascadeLightViewProjection[1] = + identityMatrix; + + output.cascadeLightViewProjection[2] = + identityMatrix; + + output.cascadeLightViewProjection[3] = + identityMatrix; + return output; } @@ -218,14 +333,8 @@ void main() if (minMaxDepth.x > minMaxDepth.y) { float4x4 identityMatrix = - float4x4( - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); - - // No geometry was found in the camera depth buffer. - // Disable shadows instead of using an invalid fitting. + BuildIdentityMatrix(); + outputShadowData[0] = BuildShadowOutput( identityMatrix, @@ -235,68 +344,163 @@ void main() } float nearViewZ = - LinearizeViewDepth(minMaxDepth.x); + LinearizeViewDepth( + minMaxDepth.x); float farViewZ = - LinearizeViewDepth(minMaxDepth.y); + LinearizeViewDepth( + minMaxDepth.y); float nearDistance = - max(-nearViewZ, 0.0001f); + max( + -nearViewZ, + 0.0001f); float farDistance = max( -farViewZ, nearDistance + 0.0001f); - float3 corners[8]; + // Preserve the current full fitted shadow frustum. + float4x4 fullLightViewProjection = + BuildLightViewProjection( + nearDistance, + farDistance); - BuildFrustumCorners( - nearDistance, - farDistance, - corners); + ShadowDataOutput output = + BuildShadowOutput( + fullLightViewProjection, + shadowsEnabled); - float4 sphere = - ComputeBoundingSphere(corners); + uint activeCascadeCount = + output.cascadeCount; - float3 normalizedLightDirection = - normalize(lightDirection); + float fittedDepthRange = + farDistance - nearDistance; - float3 eye = - sphere.xyz - - normalizedLightDirection * - (sphere.w + sunDistance); - float3 up = - float3(0.0f, 1.0f, 0.0f); + // Cascade 0 + float cascade0FarFraction = + activeCascadeCount > 1 + ? cascadeSplit0 + : 1.0f; - if (abs(normalizedLightDirection.y) > 0.95f) - { - up = float3(0.0f, 0.0f, 1.0f); - } + float cascade0NearDistance = + nearDistance; - float4x4 lightView = - BuildLookAtRH( - eye, - sphere.xyz, - up); + float cascade0FarDistance = + nearDistance + + fittedDepthRange * + cascade0FarFraction; - float orthoSize = + cascade0FarDistance = max( - sphere.w * 2.0f, - minOrthoSize); + cascade0FarDistance, + cascade0NearDistance + 0.0001f); - float4x4 lightProjection = - BuildOrthographicRH( - orthoSize, - orthoSize, - 0.0f, - sphere.w * 2.0f + sunDistance); + output.cascadeFarDistances.x = + cascade0FarDistance; - float4x4 lightViewProjection = - mul(lightView, lightProjection); + output.cascadeLightViewProjection[0] = + BuildLightViewProjection( + cascade0NearDistance, + cascade0FarDistance); + + + // Cascade 1 + if (activeCascadeCount > 1) + { + float cascade1FarFraction = + activeCascadeCount > 2 + ? cascadeSplit1 + : 1.0f; + + float cascade1NearDistance = + output.cascadeFitMode == + CASCADE_FIT_TO_CASCADE + ? cascade0FarDistance + : nearDistance; + + float cascade1FarDistance = + nearDistance + + fittedDepthRange * + cascade1FarFraction; + + cascade1FarDistance = + max( + cascade1FarDistance, + cascade1NearDistance + 0.0001f); + + output.cascadeFarDistances.y = + cascade1FarDistance; + + output.cascadeLightViewProjection[1] = + BuildLightViewProjection( + cascade1NearDistance, + cascade1FarDistance); + + + // Cascade 2 + if (activeCascadeCount > 2) + { + float cascade2FarFraction = + activeCascadeCount > 3 + ? cascadeSplit2 + : 1.0f; + + float cascade2NearDistance = + output.cascadeFitMode == + CASCADE_FIT_TO_CASCADE + ? cascade1FarDistance + : nearDistance; + + float cascade2FarDistance = + nearDistance + + fittedDepthRange * + cascade2FarFraction; + + cascade2FarDistance = + max( + cascade2FarDistance, + cascade2NearDistance + 0.0001f); + + output.cascadeFarDistances.z = + cascade2FarDistance; + + output.cascadeLightViewProjection[2] = + BuildLightViewProjection( + cascade2NearDistance, + cascade2FarDistance); + + + // Cascade 3 + if (activeCascadeCount > 3) + { + float cascade3NearDistance = + output.cascadeFitMode == + CASCADE_FIT_TO_CASCADE + ? cascade2FarDistance + : nearDistance; + + float cascade3FarDistance = + farDistance; + + cascade3FarDistance = + max( + cascade3FarDistance, + cascade3NearDistance + 0.0001f); + + output.cascadeFarDistances.w = + cascade3FarDistance; + + output.cascadeLightViewProjection[3] = + BuildLightViewProjection( + cascade3NearDistance, + cascade3FarDistance); + } + } + } outputShadowData[0] = - BuildShadowOutput( - lightViewProjection, - shadowsEnabled); + output; } \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowTypes.h b/Engine_Master_UPC/ShadowTypes.h index 98d82f7d..13a06049 100644 --- a/Engine_Master_UPC/ShadowTypes.h +++ b/Engine_Master_UPC/ShadowTypes.h @@ -6,6 +6,7 @@ using Matrix = DirectX::SimpleMath::Matrix; using Vector2 = DirectX::SimpleMath::Vector2; +using Vector4 = DirectX::SimpleMath::Vector4; static constexpr uint32_t MAX_SHADOW_CASCADES = 4; @@ -17,6 +18,9 @@ enum class ShadowCascadeFitMode : uint32_t struct ShadowDataCB { + // Legacy/full fitted frustum. + // Kept first so the current single-shadow-map pipeline + // continues working until CSM rendering is enabled. Matrix lightViewProjection = Matrix::Identity; float shadowBias = 0.0005f; @@ -24,12 +28,31 @@ struct ShadowDataCB uint32_t shadowsEnabled = 0; float padding = 0.0f; - //PCF + // PCF Vector2 shadowMapTexelSize = Vector2::Zero; uint32_t pcfEnabled = 0; uint32_t pcfRadius = 1; + + // CSM + uint32_t cascadeCount = 1; + uint32_t cascadeFitMode = static_cast(ShadowCascadeFitMode::FIT_TO_CASCADE); + + Vector2 cascadePadding = Vector2::Zero; + + // View-space far distance of each active cascade. + Vector4 cascadeFarDistances = Vector4::Zero; + + Matrix cascadeLightViewProjection[MAX_SHADOW_CASCADES] = + { + Matrix::Identity, + Matrix::Identity, + Matrix::Identity, + Matrix::Identity + }; }; +static_assert(sizeof(ShadowDataCB) == 384, "ShadowDataCB layout must match the HLSL ShadowDataOutput layout."); + struct ShadowFrameData { bool enabled = false; From 3d607ca1d626715d05e588731559502babce64db Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 9 Aug 2026 02:02:22 +0200 Subject: [PATCH 10/13] Render cascaded shadow maps into a texture array Add a cascaded depth texture array and render each configured directional shadow cascade into its own array slice while preserving the legacy shadow map for validation --- Engine_Master_UPC/ModuleResources.cpp | 3 +- Engine_Master_UPC/ModuleResources.h | 2 +- Engine_Master_UPC/ShadowMapPass.cpp | 243 ++++++++++++++++--- Engine_Master_UPC/ShadowMapPass.h | 15 ++ Engine_Master_UPC/ShadowMapVertexShader.hlsl | 61 ++++- Engine_Master_UPC/ShadowTypes.h | 5 + 6 files changed, 290 insertions(+), 39 deletions(-) diff --git a/Engine_Master_UPC/ModuleResources.cpp b/Engine_Master_UPC/ModuleResources.cpp index 01c8dcd9..d46a2162 100644 --- a/Engine_Master_UPC/ModuleResources.cpp +++ b/Engine_Master_UPC/ModuleResources.cpp @@ -148,7 +148,7 @@ Texture* ModuleResources::createDepthBuffer(float width, float height) return new Texture(GenerateUID(), *m_device.Get(), desc); } -Texture* ModuleResources::createShadowMap(uint32_t size) +Texture* ModuleResources::createShadowMap(uint32_t size, uint32_t arraySize) { TextureDesc desc{}; desc.format = DXGI_FORMAT_R32_TYPELESS; @@ -156,6 +156,7 @@ Texture* ModuleResources::createShadowMap(uint32_t size) desc.srvFormat = DXGI_FORMAT_R32_FLOAT; desc.width = size; desc.height = size; + desc.arraySize = static_cast(std::max(1u, arraySize)); desc.views = TextureView::DSV | TextureView::SRV; desc.initialState = D3D12_RESOURCE_STATE_DEPTH_WRITE; desc.hasClearValue = true; diff --git a/Engine_Master_UPC/ModuleResources.h b/Engine_Master_UPC/ModuleResources.h index eb25386e..4512ee04 100644 --- a/Engine_Master_UPC/ModuleResources.h +++ b/Engine_Master_UPC/ModuleResources.h @@ -56,7 +56,7 @@ class ModuleResources : public Module Texture* createDepthBuffer(float width, float height); - Texture* createShadowMap(uint32_t size); + Texture* createShadowMap(uint32_t size, uint32_t arraySize = 1); Texture* createDepthMinMaxTexture(uint32_t width, uint32_t height); Texture* createRenderTexture(float width, float height); Texture* createHDRRenderTexture(float width, float height); diff --git a/Engine_Master_UPC/ShadowMapPass.cpp b/Engine_Master_UPC/ShadowMapPass.cpp index 37da39ee..899b6879 100644 --- a/Engine_Master_UPC/ShadowMapPass.cpp +++ b/Engine_Master_UPC/ShadowMapPass.cpp @@ -87,9 +87,52 @@ void ShadowMapPass::updateShadowViewportAndScissor(uint32_t size) m_scissorRect.bottom = static_cast(size); } +void ShadowMapPass::createCascadeShadowMap( uint32_t size, uint32_t cascadeCount) +{ + cascadeCount = std::clamp( cascadeCount, 1u, MAX_SHADOW_CASCADES); + + // Texture::createSRV() uses Texture2D when arraySize == 1. + // Keep at least two slices so this resource always has + // a Texture2DArray SRV, even when only one cascade is active. + const uint32_t resourceArraySize = std::max(2u, cascadeCount); + + m_currentCascadeShadowMapSize = size; + m_currentCascadeArraySize = resourceArraySize; + + m_cascadeShadowMap.reset( app->getModuleResources()->createShadowMap( size, resourceArraySize)); + + if (m_cascadeShadowMap != nullptr) + { + m_cascadeShadowMapState = m_cascadeShadowMap->getDesc().initialState; + } + else + { + m_cascadeShadowMapState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + } +} + +void ShadowMapPass::resizeCascadeShadowMapIfNeeded( uint32_t size, uint32_t cascadeCount) +{ + if (size == 0) + { + size = DEFAULT_SHADOW_MAP_SIZE; + } + + cascadeCount = std::clamp( cascadeCount, 1u, MAX_SHADOW_CASCADES); + + const uint32_t requiredArraySize = std::max(2u, cascadeCount); + + if (m_cascadeShadowMap != nullptr && size == m_currentCascadeShadowMapSize && requiredArraySize == m_currentCascadeArraySize) + { + return; + } + + createCascadeShadowMap( size, cascadeCount); +} + void ShadowMapPass::createRootSignature() { - CD3DX12_ROOT_PARAMETER rootParameters[2] = {}; + CD3DX12_ROOT_PARAMETER rootParameters[3] = {}; // b0: model matrix, changed for each mesh. rootParameters[0].InitAsConstants( @@ -104,6 +147,15 @@ void ShadowMapPass::createRootSignature() 0, D3D12_SHADER_VISIBILITY_VERTEX); + // b2: selects the light matrix used by this shadow render. + // 0..MAX_SHADOW_CASCADES-1 = cascade. + // MAX_SHADOW_CASCADES = legacy full fitted frustum. + rootParameters[2].InitAsConstants( + 1, + 2, + 0, + D3D12_SHADER_VISIBILITY_VERTEX); + CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; rootSignatureDesc.Init( _countof(rootParameters), @@ -210,20 +262,24 @@ void ShadowMapPass::prepareDisabledShadowData(const RenderContext& ctx) { m_frameData = {}; m_frameData.enabled = false; + m_activeCascadeCount = 0; if (m_shadowMap != nullptr && m_shadowMap->hasSRV()) { m_frameData.shadowMapSRV = m_shadowMap->getSRV().gpu; } + if (m_cascadeShadowMap != nullptr && m_cascadeShadowMap->hasSRV()) + { + m_frameData.cascadeShadowMapSRV = m_cascadeShadowMap->getSRV().gpu; + } + ShadowDataCB shadowCB{}; shadowCB.lightViewProjection = Matrix::Identity.Transpose(); shadowCB.shadowBias = SHADOW_BIAS; shadowCB.shadowStrength = SHADOW_STRENGTH; shadowCB.shadowsEnabled = 0; - shadowCB.shadowMapTexelSize = Vector2( - 1.0f / static_cast(m_currentShadowMapSize), - 1.0f / static_cast(m_currentShadowMapSize)); + shadowCB.shadowMapTexelSize = Vector2( 1.0f / static_cast(m_currentShadowMapSize), 1.0f / static_cast(m_currentShadowMapSize)); shadowCB.pcfEnabled = 0; shadowCB.pcfRadius = 1; @@ -236,26 +292,23 @@ void ShadowMapPass::prepareDisabledShadowData(const RenderContext& ctx) } } -void ShadowMapPass::prepareDirectionalShadowData( - const RenderContext& ctx, - const LightComponent& light) +void ShadowMapPass::prepareDirectionalShadowData( const RenderContext& ctx, const LightComponent& light) { - const LightShadowSettings& shadowSettings = - light.getData().shadow; + const LightShadowSettings& shadowSettings = light.getData().shadow; - resizeShadowMapIfNeeded( - shadowSettings.shadowMapSize); + resizeShadowMapIfNeeded(shadowSettings.shadowMapSize); - if (m_shadowFrustumComputePass == nullptr || - !m_shadowFrustumComputePass->hasValidResult()) + m_activeCascadeCount = std::clamp(shadowSettings.cascadeCount, 1u, MAX_SHADOW_CASCADES); + + resizeCascadeShadowMapIfNeeded(shadowSettings.shadowMapSize, m_activeCascadeCount); + + if (m_shadowFrustumComputePass == nullptr || !m_shadowFrustumComputePass->hasValidResult()) { prepareDisabledShadowData(ctx); return; } - const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = - m_shadowFrustumComputePass - ->getShadowDataBufferAddress(); + const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = m_shadowFrustumComputePass->getShadowDataBufferAddress(); if (shadowDataAddress == 0) { @@ -267,11 +320,14 @@ void ShadowMapPass::prepareDirectionalShadowData( m_frameData.enabled = true; m_frameData.shadowCBAddress = shadowDataAddress; - if (m_shadowMap != nullptr && - m_shadowMap->hasSRV()) + if (m_shadowMap != nullptr && m_shadowMap->hasSRV()) + { + m_frameData.shadowMapSRV = m_shadowMap->getSRV().gpu; + } + + if (m_cascadeShadowMap != nullptr && m_cascadeShadowMap->hasSRV()) { - m_frameData.shadowMapSRV = - m_shadowMap->getSRV().gpu; + m_frameData.cascadeShadowMapSRV = m_cascadeShadowMap->getSRV().gpu; } } @@ -406,6 +462,33 @@ void ShadowMapPass::transitionShadowMap(ID3D12GraphicsCommandList4* commandList, m_shadowMapState = newState; } +void ShadowMapPass::transitionCascadeShadowMap( ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState) +{ + if (commandList == nullptr || m_cascadeShadowMap == nullptr) + { + return; + } + + if (m_cascadeShadowMapState == newState) + { + return; + } + + ComPtr shadowResource = m_cascadeShadowMap->getD3D12Resource(); + + if (shadowResource == nullptr) + { + return; + } + + CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(shadowResource.Get(), m_cascadeShadowMapState, newState); + + commandList->ResourceBarrier( 1, &barrier); + + m_cascadeShadowMapState = newState; +} + + void ShadowMapPass::prepare(const RenderContext& ctx) { m_meshRenderers = app->getModuleScene()->getMeshRenderers(); @@ -421,7 +504,8 @@ void ShadowMapPass::prepare(const RenderContext& ctx) prepareDirectionalShadowData(ctx, *mainDirectionalLight); } -void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) +void ShadowMapPass::apply( + ID3D12GraphicsCommandList4* commandList) { BEGIN_EVENT(commandList, "ShadowMapPass"); @@ -431,7 +515,8 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) return; } - if (m_shadowMap == nullptr || !m_shadowMap->hasDSV()) + if (m_shadowMap == nullptr || + !m_shadowMap->hasDSV()) { END_EVENT(commandList); return; @@ -439,12 +524,20 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) if (!m_frameData.enabled) { - transitionShadowMap(commandList, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + transitionShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + transitionCascadeShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + END_EVENT(commandList); return; } - const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = m_frameData.shadowCBAddress; + const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = + m_frameData.shadowCBAddress; if (shadowDataAddress == 0) { @@ -452,16 +545,48 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) commandList, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + transitionCascadeShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + END_EVENT(commandList); return; } - transitionShadowMap(commandList, D3D12_RESOURCE_STATE_DEPTH_WRITE); + commandList->RSSetViewports( + 1, + &m_viewport); + + commandList->RSSetScissorRects( + 1, + &m_scissorRect); + + commandList->SetPipelineState( + m_pipelineState.Get()); + + commandList->SetGraphicsRootSignature( + m_rootSignature.Get()); + + commandList->SetGraphicsRootConstantBufferView( + 1, + shadowDataAddress); + + commandList->IASetPrimitiveTopology( + D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + - commandList->RSSetViewports(1, &m_viewport); - commandList->RSSetScissorRects(1, &m_scissorRect); + // ------------------------------------------------- + // Legacy full fitted shadow map. + // Kept temporarily so Deferred remains unchanged + // until Commit 5. + // ------------------------------------------------- - D3D12_CPU_DESCRIPTOR_HANDLE shadowDSV = m_shadowMap->getDSV().cpu; + transitionShadowMap( + commandList, + D3D12_RESOURCE_STATE_DEPTH_WRITE); + + D3D12_CPU_DESCRIPTOR_HANDLE shadowDSV = + m_shadowMap->getDSV().cpu; commandList->OMSetRenderTargets( 0, @@ -477,16 +602,66 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) 0, nullptr); - commandList->SetPipelineState(m_pipelineState.Get()); - commandList->SetGraphicsRootSignature(m_rootSignature.Get()); + commandList->SetGraphicsRoot32BitConstant( + 2, + MAX_SHADOW_CASCADES, + 0); - commandList->SetGraphicsRootConstantBufferView(1, shadowDataAddress); + renderCasters(commandList); - commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + transitionShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); - renderCasters(commandList); - transitionShadowMap(commandList, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + // ------------------------------------------------- + // Cascaded shadow maps. + // Each cascade is rendered into one array slice. + // ------------------------------------------------- + + if (m_cascadeShadowMap != nullptr && + m_cascadeShadowMap->hasDSV() && + m_activeCascadeCount > 0) + { + transitionCascadeShadowMap( + commandList, + D3D12_RESOURCE_STATE_DEPTH_WRITE); + + for (uint32_t cascadeIndex = 0; + cascadeIndex < m_activeCascadeCount; + ++cascadeIndex) + { + D3D12_CPU_DESCRIPTOR_HANDLE cascadeDSV = + m_cascadeShadowMap + ->getDSV(cascadeIndex) + .cpu; + + commandList->OMSetRenderTargets( + 0, + nullptr, + false, + &cascadeDSV); + + commandList->ClearDepthStencilView( + cascadeDSV, + D3D12_CLEAR_FLAG_DEPTH, + 1.0f, + 0, + 0, + nullptr); + + commandList->SetGraphicsRoot32BitConstant( + 2, + cascadeIndex, + 0); + + renderCasters(commandList); + } + + transitionCascadeShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + } END_EVENT(commandList); } \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowMapPass.h b/Engine_Master_UPC/ShadowMapPass.h index c645e182..af1334bf 100644 --- a/Engine_Master_UPC/ShadowMapPass.h +++ b/Engine_Master_UPC/ShadowMapPass.h @@ -35,6 +35,9 @@ class ShadowMapPass : public IRenderPass const Texture* getShadowMap() const { return m_shadowMap.get(); } Texture* getShadowMap() { return m_shadowMap.get(); } + const Texture* getCascadeShadowMap() const { return m_cascadeShadowMap.get(); } + Texture* getCascadeShadowMap() { return m_cascadeShadowMap.get(); } + const ShadowFrameData& getFrameData() const { return m_frameData; } private: @@ -54,6 +57,10 @@ class ShadowMapPass : public IRenderPass void updateShadowViewportAndScissor(uint32_t size); uint32_t getCurrentShadowMapSize() const { return m_currentShadowMapSize; } + void createCascadeShadowMap( uint32_t size, uint32_t cascadeCount); + void resizeCascadeShadowMapIfNeeded( uint32_t size, uint32_t cascadeCount); + void transitionCascadeShadowMap( ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState); + private: static constexpr uint32_t DEFAULT_SHADOW_MAP_SIZE = 4096; @@ -69,6 +76,14 @@ class ShadowMapPass : public IRenderPass D3D12_RESOURCE_STATES m_shadowMapState = D3D12_RESOURCE_STATE_DEPTH_WRITE; uint32_t m_currentShadowMapSize = DEFAULT_SHADOW_MAP_SIZE; + std::unique_ptr m_cascadeShadowMap; + + D3D12_RESOURCE_STATES m_cascadeShadowMapState = D3D12_RESOURCE_STATE_DEPTH_WRITE; + + uint32_t m_currentCascadeShadowMapSize = 0; + uint32_t m_currentCascadeArraySize = 0; + uint32_t m_activeCascadeCount = 0; + ComPtr m_rootSignature; ComPtr m_pipelineState; diff --git a/Engine_Master_UPC/ShadowMapVertexShader.hlsl b/Engine_Master_UPC/ShadowMapVertexShader.hlsl index 6c7d10a2..07caa714 100644 --- a/Engine_Master_UPC/ShadowMapVertexShader.hlsl +++ b/Engine_Master_UPC/ShadowMapVertexShader.hlsl @@ -1,19 +1,74 @@ +#define MAX_SHADOW_CASCADES 4 +#define FULL_FRUSTUM_MATRIX_INDEX 4 + cbuffer ShadowDrawData : register(b0) { float4x4 model; }; -cbuffer ShadowViewProjection : register(b1) +cbuffer ShadowData : register(b1) { float4x4 lightViewProjection; + + float shadowBias; + float shadowStrength; + uint shadowsEnabled; + float paddingShadow; + + float2 shadowMapTexelSize; + uint pcfEnabled; + uint pcfRadius; + + uint cascadeCount; + uint cascadeFitMode; + float2 cascadePadding; + + float4 cascadeFarDistances; + + float4x4 cascadeLightViewProjection[MAX_SHADOW_CASCADES]; +}; + +cbuffer ShadowRenderParams : register(b2) +{ + uint shadowMatrixIndex; }; +float4x4 GetShadowViewProjection() +{ + if (shadowMatrixIndex == 0) + { + return cascadeLightViewProjection[0]; + } + + if (shadowMatrixIndex == 1) + { + return cascadeLightViewProjection[1]; + } + + if (shadowMatrixIndex == 2) + { + return cascadeLightViewProjection[2]; + } + + if (shadowMatrixIndex == 3) + { + return cascadeLightViewProjection[3]; + } + + return lightViewProjection; +} + float4 main(float3 position : POSITION) : SV_POSITION { float4 worldPosition = - mul(float4(position, 1.0f), model); + mul( + float4(position, 1.0f), + model); + + float4x4 shadowViewProjection = + GetShadowViewProjection(); return mul( worldPosition, - lightViewProjection); + shadowViewProjection); } \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowTypes.h b/Engine_Master_UPC/ShadowTypes.h index 13a06049..a26586e6 100644 --- a/Engine_Master_UPC/ShadowTypes.h +++ b/Engine_Master_UPC/ShadowTypes.h @@ -58,5 +58,10 @@ struct ShadowFrameData bool enabled = false; D3D12_GPU_VIRTUAL_ADDRESS shadowCBAddress = 0; + + // Temporary legacy map, still consumed by Deferred D3D12_GPU_DESCRIPTOR_HANDLE shadowMapSRV{}; + + // Cascaded Texture2DArray, consumed starting + D3D12_GPU_DESCRIPTOR_HANDLE cascadeShadowMapSRV{}; }; \ No newline at end of file From dc301c3e0dc74aca8c2651f3feae08bc264f913e Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 9 Aug 2026 18:57:28 +0200 Subject: [PATCH 11/13] Integrate cascaded shadows into deferred lighting Update deferred lighting to select the appropriate shadow cascade per fragment and sample the corresponding Texture2DArray slice while preserving shadow bias, strength, and PCF filtering --- Engine_Master_UPC/DeferredShadingPass.cpp | 8 +-- Engine_Master_UPC/DeferredShadingPass.h | 2 +- Engine_Master_UPC/LightPixelShader.hlsl | 83 ++++++++++++++--------- Engine_Master_UPC/LightingCBuffers.hlsli | 14 +++- Engine_Master_UPC/ShadowMapPass.cpp | 2 +- 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/Engine_Master_UPC/DeferredShadingPass.cpp b/Engine_Master_UPC/DeferredShadingPass.cpp index aa257f21..f95bec71 100644 --- a/Engine_Master_UPC/DeferredShadingPass.cpp +++ b/Engine_Master_UPC/DeferredShadingPass.cpp @@ -155,12 +155,12 @@ void DeferredShadingPass::prepare(const RenderContext& ctx) if (m_hasShadowData) { m_shadowCBAddress = ctx.shadowData->shadowCBAddress; - m_shadowMapSRV = ctx.shadowData->shadowMapSRV; + m_cascadeShadowMapSRV = ctx.shadowData->cascadeShadowMapSRV; } else { m_shadowCBAddress = 0; - m_shadowMapSRV = {}; + m_cascadeShadowMapSRV = {}; } m_renderSurface = &ctx.renderSurface; @@ -234,10 +234,10 @@ void DeferredShadingPass::apply(ID3D12GraphicsCommandList4* commandList) commandList->SetGraphicsRootDescriptorTable(6, app->getModuleDescriptors()->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER).getGPUHandle(ModuleDescriptors::SampleType::LINEAR_WRAP)); - if (m_hasShadowData && m_shadowCBAddress != 0 && m_shadowMapSRV.ptr != 0) + if (m_hasShadowData && m_shadowCBAddress != 0 && m_cascadeShadowMapSRV.ptr != 0) { commandList->SetGraphicsRootConstantBufferView(7, m_shadowCBAddress); - commandList->SetGraphicsRootDescriptorTable(8, m_shadowMapSRV); + commandList->SetGraphicsRootDescriptorTable(8, m_cascadeShadowMapSRV); } if (m_hasSSAOData && m_ssaoSRV.ptr != 0) diff --git a/Engine_Master_UPC/DeferredShadingPass.h b/Engine_Master_UPC/DeferredShadingPass.h index 3b47fef1..e0d540a5 100644 --- a/Engine_Master_UPC/DeferredShadingPass.h +++ b/Engine_Master_UPC/DeferredShadingPass.h @@ -69,7 +69,7 @@ class DeferredShadingPass : public IRenderPass { // ShadowMap D3D12_GPU_VIRTUAL_ADDRESS m_shadowCBAddress = 0; - D3D12_GPU_DESCRIPTOR_HANDLE m_shadowMapSRV{}; + D3D12_GPU_DESCRIPTOR_HANDLE m_cascadeShadowMapSRV{}; bool m_hasShadowData = false; // SSAO diff --git a/Engine_Master_UPC/LightPixelShader.hlsl b/Engine_Master_UPC/LightPixelShader.hlsl index caf510b3..09916767 100644 --- a/Engine_Master_UPC/LightPixelShader.hlsl +++ b/Engine_Master_UPC/LightPixelShader.hlsl @@ -11,7 +11,7 @@ Texture2D emissiveTex : register(t4); TextureCube irradianceTexture : register(t8); TextureCube environmentTexture : register(t9); Texture2D brdfTexture : register(t10); -Texture2D shadowMap : register(t11); +Texture2DArray shadowMap : register(t11); Texture2D ssaoTexture : register(t12); SamplerState linearWrapSample : register(s0); @@ -147,55 +147,51 @@ float3 computeIndirectLighting(in float3 R, in float NdotV, in float3 N, in floa } //--------------------// - - //----------SHADOW MAPPING----------// -float EvaluateShadowSample(float2 shadowUV, float currentDepth) -{ - float closestDepth = shadowMap.Sample(linearClampSample, shadowUV).r; - return currentDepth - shadowBias > closestDepth - ? 1.0f - shadowStrength - : 1.0f; +float4x4 GetCascadeViewProjection(uint cascadeIndex) +{ + if (cascadeIndex == 0) + return cascadeLightViewProjection[0]; + if (cascadeIndex == 1) + return cascadeLightViewProjection[1]; + if (cascadeIndex == 2) + return cascadeLightViewProjection[2]; + return cascadeLightViewProjection[3]; } -float ComputeShadow(float3 worldPos) +bool GetCascadeShadowCoordinates(float3 worldPos, uint cascadeIndex, out float2 shadowUV, out float currentDepth) { - if (shadowsEnabled == 0) - { - return 1.0f; - } + shadowUV = float2(0.0f, 0.0f); + currentDepth = 0.0f; - float4 shadowPos = mul(float4(worldPos, 1.0f), lightViewProjection); + float4 shadowPos = mul(float4(worldPos, 1.0f), GetCascadeViewProjection(cascadeIndex)); if (shadowPos.w == 0.0f) - { - return 1.0f; - } + return false; shadowPos.xyz /= shadowPos.w; - float2 shadowUV; shadowUV.x = shadowPos.x * 0.5f + 0.5f; shadowUV.y = -shadowPos.y * 0.5f + 0.5f; + currentDepth = shadowPos.z; - float currentDepth = shadowPos.z; + return shadowUV.x >= 0.0f && shadowUV.x <= 1.0f && shadowUV.y >= 0.0f && shadowUV.y <= 1.0f && currentDepth >= 0.0f && currentDepth <= 1.0f; +} - if (shadowUV.x < 0.0f || shadowUV.x > 1.0f || - shadowUV.y < 0.0f || shadowUV.y > 1.0f || - currentDepth < 0.0f || currentDepth > 1.0f) - { - return 1.0f; - } +float EvaluateShadowSample(uint cascadeIndex, float2 shadowUV, float currentDepth) +{ + float closestDepth = shadowMap.Sample(linearClampSample, float3(shadowUV, float(cascadeIndex))).r; + return currentDepth - shadowBias > closestDepth ? 1.0f - shadowStrength : 1.0f; +} +float ComputeCascadeShadow(uint cascadeIndex, float2 shadowUV, float currentDepth) +{ if (pcfEnabled == 0 || pcfRadius == 0) - { - return EvaluateShadowSample(shadowUV, currentDepth); - } + return EvaluateShadowSample(cascadeIndex, shadowUV, currentDepth); float shadowSum = 0.0f; float sampleCount = 0.0f; - int radius = int(pcfRadius); for (int y = -radius; y <= radius; ++y) @@ -205,14 +201,13 @@ float ComputeShadow(float3 worldPos) float2 offset = float2(x, y) * shadowMapTexelSize; float2 sampleUV = shadowUV + offset; - if (sampleUV.x < 0.0f || sampleUV.x > 1.0f || - sampleUV.y < 0.0f || sampleUV.y > 1.0f) + if (sampleUV.x < 0.0f || sampleUV.x > 1.0f || sampleUV.y < 0.0f || sampleUV.y > 1.0f) { shadowSum += 1.0f; } else { - shadowSum += EvaluateShadowSample(sampleUV, currentDepth); + shadowSum += EvaluateShadowSample(cascadeIndex, sampleUV, currentDepth); } sampleCount += 1.0f; @@ -221,6 +216,28 @@ float ComputeShadow(float3 worldPos) return shadowSum / sampleCount; } + +float ComputeShadow(float3 worldPos) +{ + if (shadowsEnabled == 0) + return 1.0f; + + uint activeCascadeCount = clamp(cascadeCount, 1u, (uint) MAX_SHADOW_CASCADES); + float2 shadowUV; + float currentDepth; + + if (GetCascadeShadowCoordinates(worldPos, 0u, shadowUV, currentDepth)) + return ComputeCascadeShadow(0u, shadowUV, currentDepth); + if (activeCascadeCount > 1u && GetCascadeShadowCoordinates(worldPos, 1u, shadowUV, currentDepth)) + return ComputeCascadeShadow(1u, shadowUV, currentDepth); + if (activeCascadeCount > 2u && GetCascadeShadowCoordinates(worldPos, 2u, shadowUV, currentDepth)) + return ComputeCascadeShadow(2u, shadowUV, currentDepth); + if (activeCascadeCount > 3u && GetCascadeShadowCoordinates(worldPos, 3u, shadowUV, currentDepth)) + return ComputeCascadeShadow(3u, shadowUV, currentDepth); + + return 1.0f; +} + //--------------------// float SampleSSAO(float4 screenPosition) diff --git a/Engine_Master_UPC/LightingCBuffers.hlsli b/Engine_Master_UPC/LightingCBuffers.hlsli index d0ec0dfb..4bbb7ba8 100644 --- a/Engine_Master_UPC/LightingCBuffers.hlsli +++ b/Engine_Master_UPC/LightingCBuffers.hlsli @@ -15,6 +15,8 @@ cbuffer SceneData : register(b1) #define MAX_POINT_LIGHTS 112 #define MAX_SPOT_LIGHTS 16 +#define MAX_SHADOW_CASCADES 4 + struct DirectionalLight { float3 direction; @@ -65,13 +67,21 @@ cbuffer LightsCB : register(b2) cbuffer ShadowData : register(b3) { float4x4 lightViewProjection; + float shadowBias; float shadowStrength; uint shadowsEnabled; float paddingShadow; - - //PCF + float2 shadowMapTexelSize; uint pcfEnabled; uint pcfRadius; + + uint cascadeCount; + uint cascadeFitMode; + float2 cascadePadding; + + float4 cascadeFarDistances; + + float4x4 cascadeLightViewProjection[MAX_SHADOW_CASCADES]; }; \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowMapPass.cpp b/Engine_Master_UPC/ShadowMapPass.cpp index 899b6879..0d22066a 100644 --- a/Engine_Master_UPC/ShadowMapPass.cpp +++ b/Engine_Master_UPC/ShadowMapPass.cpp @@ -32,7 +32,7 @@ ShadowMapPass::ShadowMapPass(ComPtr device, ShadowFrustumComputeP : m_device(device), m_shadowFrustumComputePass(shadowFrustumComputePass) { createShadowMap(DEFAULT_SHADOW_MAP_SIZE); - + createCascadeShadowMap(1u, 1u); createRootSignature(); createPipelineState(); } From c4fe85da7aaa50ede632b0648088d9aae8020104 Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 9 Aug 2026 22:29:28 +0200 Subject: [PATCH 12/13] Add cascade debug visualization Add an optional per-cascade colour tint to visualise cascade selection during deferred lighting, using the existing shadow data layout. --- Engine_Master_UPC/LightComponent.cpp | 26 +- Engine_Master_UPC/LightComponent.h | 2 +- Engine_Master_UPC/LightPixelShader.hlsl | 129 ++++-- Engine_Master_UPC/LightingCBuffers.hlsli | 2 + Engine_Master_UPC/Lights.h | 2 + .../ShadowFrustumComputePass.cpp | 7 +- Engine_Master_UPC/ShadowFrustumComputePass.h | 3 +- .../ShadowFrustumComputeShader.hlsl | 387 +++++------------- 8 files changed, 208 insertions(+), 350 deletions(-) diff --git a/Engine_Master_UPC/LightComponent.cpp b/Engine_Master_UPC/LightComponent.cpp index a9955386..22b2e91e 100644 --- a/Engine_Master_UPC/LightComponent.cpp +++ b/Engine_Master_UPC/LightComponent.cpp @@ -82,7 +82,7 @@ std::unique_ptr LightComponent::clone(GameObject* newOwner) const newComponent->m_data = m_data; - return newComponent; + return newComponent; } void LightComponent::setTypeDirectional() @@ -140,15 +140,15 @@ void LightComponent::drawUi() { const LightType newType = static_cast(typeIndex); - if (newType == LightType::DIRECTIONAL) + if (newType == LightType::DIRECTIONAL) { setTypeDirectional(); } - else if (newType == LightType::POINT) + else if (newType == LightType::POINT) { setTypePoint(m_data.parameters.point.radius); } - else if (newType == LightType::SPOT) + else if (newType == LightType::SPOT) { setTypeSpot(m_data.parameters.spot.radius, m_data.parameters.spot.innerAngleDegrees, m_data.parameters.spot.outerAngleDegrees); } @@ -194,12 +194,12 @@ void LightComponent::drawUi() lightChanged = true; } - if (ImGui::DragFloat("Inner Angle##Spot", &m_data.parameters.spot.innerAngleDegrees, 0.1f, 0.0f, 179.0f)) + if (ImGui::DragFloat("Inner Angle##Spot", &m_data.parameters.spot.innerAngleDegrees, 0.1f, 0.0f, 179.0f)) { lightChanged = true; } - if (ImGui::DragFloat("Outer Angle##Spot", &m_data.parameters.spot.outerAngleDegrees, 0.1f, 0.0f, 179.0f)) + if (ImGui::DragFloat("Outer Angle##Spot", &m_data.parameters.spot.outerAngleDegrees, 0.1f, 0.0f, 179.0f)) { lightChanged = true; } @@ -306,6 +306,11 @@ void LightComponent::drawUi() lightChanged = true; } + if (ImGui::Checkbox("Debug Cascade Tint", &m_data.shadow.cascadeDebugEnabled)) + { + lightChanged = true; + } + if (m_data.shadow.cascadeCount > 1) { float splitPercent = m_data.shadow.cascadeSplit0 * 100.0f; @@ -348,7 +353,7 @@ void LightComponent::drawUi() } } - if (lightChanged) + if (lightChanged) { sanitize(); } @@ -392,7 +397,7 @@ void LightComponent::serialize(IArchive& archive) m_data.shadow.cascadeCount = shadowCascadeCount; } - archive.serialize( m_data.shadow.cascadeSplit0, "ShadowCascadeSplit0"); + archive.serialize(m_data.shadow.cascadeSplit0, "ShadowCascadeSplit0"); archive.serialize(m_data.shadow.cascadeSplit1, "ShadowCascadeSplit1"); @@ -407,6 +412,8 @@ void LightComponent::serialize(IArchive& archive) m_data.shadow.cascadeFitMode = static_cast(shadowCascadeFitMode); } + archive.serialize(m_data.shadow.cascadeDebugEnabled, "ShadowCascadeDebugEnabled"); + float radius = 0.0f; float innerAngle = 0.0f; float outerAngle = 0.0f; @@ -456,7 +463,7 @@ void LightComponent::serialize(IArchive& archive) } void LightComponent::debugDraw() { - if ( !isActive() || !m_owner->GetActive()) + if (!isActive() || !m_owner->GetActive()) { return; } @@ -530,4 +537,3 @@ void LightComponent::debugDraw() break; } } - diff --git a/Engine_Master_UPC/LightComponent.h b/Engine_Master_UPC/LightComponent.h index ae7e05eb..16993614 100644 --- a/Engine_Master_UPC/LightComponent.h +++ b/Engine_Master_UPC/LightComponent.h @@ -2,7 +2,7 @@ #include "Component.h" #include "Lights.h" #include "IDebugDrawable.h" - + class LightComponent final : public Component { public: diff --git a/Engine_Master_UPC/LightPixelShader.hlsl b/Engine_Master_UPC/LightPixelShader.hlsl index 09916767..062653b5 100644 --- a/Engine_Master_UPC/LightPixelShader.hlsl +++ b/Engine_Master_UPC/LightPixelShader.hlsl @@ -22,18 +22,19 @@ SamplerState pointClampSample : register(s3); //----------DIRECT LIGHTING----------// + float3 LightCalculation(float3 lightDirection, float3 viewDirection, float3 normalVector, float NdotV, float alphaRoughness, float3 diffuseColor, float3 lightColor, float3 F0) { float3 halfVector = normalize(lightDirection + viewDirection); - + float NdotL = clamp(-dot(normalVector, lightDirection), 0.001, 1.0); float NdotH = saturate(dot(normalVector, halfVector)); float VdotH = saturate(dot(viewDirection, halfVector)); - + float3 fresnel = SchlickFresnel(F0, NdotH); float smithVisibility = SmithVisibilityFunction(NdotL, NdotV, alphaRoughness); float normalDistribution = NormalDistributionFunction(NdotH, alphaRoughness); - + return (diffuseColor + (0.25 * fresnel * smithVisibility * normalDistribution)) * lightColor * NdotL; } @@ -41,7 +42,7 @@ float3 ComputeDirectionalLight(uint lightIndex, float3 viewDirection, float3 nor { float3 lightDirection = normalize(directionalLights[lightIndex].direction); float3 lightColor = directionalLights[lightIndex].color * directionalLights[lightIndex].intensity; - + return LightCalculation(lightDirection, viewDirection, normalVector, NdotV, alphaRoughness, diffuseColor, lightColor, F0); } @@ -58,28 +59,31 @@ float EpicAttenuation(float distanceValue, float radiusValue) numerator *= numerator; float denominator = distanceValue * distanceValue + 1.0f; + return numerator / denominator; } float3 ComputePointLight(uint lightIndex, float3 worldPos, float3 viewDirection, float3 normalVector, float NdotV, float alphaRoughness, float3 F0, float3 diffuseColor) { float3 toSurface = worldPos - pointLights[lightIndex].position; - + float distanceToSurface = length(toSurface); + if (distanceToSurface <= EPS) return 0.0f; float attenuation = EpicAttenuation(distanceToSurface, pointLights[lightIndex].radius); - + float3 lightDirection = toSurface / distanceToSurface; float3 lightColor = pointLights[lightIndex].color * pointLights[lightIndex].intensity * attenuation; - + return LightCalculation(lightDirection, viewDirection, normalVector, NdotV, alphaRoughness, diffuseColor, lightColor, F0); } float SpotConeAttenuation(float cosineAngle, float cosineInner, float cosineOuter) { float denominator = max(cosineInner - cosineOuter, EPS); + return saturate((cosineAngle - cosineOuter) / denominator); } @@ -87,26 +91,29 @@ float3 ComputeSpotLight(uint lightIndex, float3 worldPos, float3 viewDirection, { float3 spotDirection = normalize(spotLights[lightIndex].direction); float3 toSurface = worldPos - spotLights[lightIndex].position; - + float distanceProjected = dot(toSurface, spotDirection); + if (distanceProjected <= 0.0f) return 0.0f; float3 lightDirection = normalize(toSurface); - + float attenuation = EpicAttenuation(distanceProjected, spotLights[lightIndex].radius); float cosineAngle = dot(lightDirection, spotDirection); float coneAttenuation = SpotConeAttenuation(cosineAngle, spotLights[lightIndex].cosineInnerAngle, spotLights[lightIndex].cosineOuterAngle); float3 lightColor = spotLights[lightIndex].color * spotLights[lightIndex].intensity * attenuation * coneAttenuation; - + return LightCalculation(lightDirection, viewDirection, normalVector, NdotV, alphaRoughness, diffuseColor, lightColor, F0); } + //--------------------// //----------INDIRECT LIGHTING----------// + float computeSpecularAO(float NdotV, float diffuseAO, float roughness) { return saturate(pow(NdotV + diffuseAO, exp2(-16.0 * roughness - 1.0)) - 1.0 + diffuseAO); @@ -122,9 +129,9 @@ float3 getDiffuseAmbientLight(in float3 normal, in float3 baseColour) void getSpecularAmbientLightNoFresnel(in float3 R, float NdotV, float roughness, in uint numLevels, out float3 firstTerm, out float3 secondTerm) { float3 radiance = environmentTexture.SampleLevel(linearWrapSample, R, roughness * (numLevels - 1)).rgb; - + float2 fab = brdfTexture.Sample(linearClampSample, float2(NdotV, roughness)).rg; - + firstTerm = radiance * fab.x; secondTerm = radiance * fab.y; } @@ -135,6 +142,7 @@ float3 computeIndirectLighting(in float3 R, in float NdotV, in float3 N, in floa diffuse *= ao; float3 firstTerm, secondTerm; + getSpecularAmbientLightNoFresnel(R, NdotV, roughness, roughnessLevels, firstTerm, secondTerm); float3 metalSpecular = baseColour * firstTerm + secondTerm; @@ -145,18 +153,24 @@ float3 computeIndirectLighting(in float3 R, in float NdotV, in float3 N, in floa return lerp(diffuse + dielectricSpecular, metalSpecular, metallic); } + //--------------------// + + //----------SHADOW MAPPING----------// float4x4 GetCascadeViewProjection(uint cascadeIndex) { if (cascadeIndex == 0) return cascadeLightViewProjection[0]; + if (cascadeIndex == 1) return cascadeLightViewProjection[1]; + if (cascadeIndex == 2) return cascadeLightViewProjection[2]; + return cascadeLightViewProjection[3]; } @@ -182,6 +196,7 @@ bool GetCascadeShadowCoordinates(float3 worldPos, uint cascadeIndex, out float2 float EvaluateShadowSample(uint cascadeIndex, float2 shadowUV, float currentDepth) { float closestDepth = shadowMap.Sample(linearClampSample, float3(shadowUV, float(cascadeIndex))).r; + return currentDepth - shadowBias > closestDepth ? 1.0f - shadowStrength : 1.0f; } @@ -217,29 +232,63 @@ float ComputeCascadeShadow(uint cascadeIndex, float2 shadowUV, float currentDept return shadowSum / sampleCount; } -float ComputeShadow(float3 worldPos) +float ComputeShadow(float3 worldPos, out uint selectedCascadeIndex) { + selectedCascadeIndex = MAX_SHADOW_CASCADES; + if (shadowsEnabled == 0) return 1.0f; uint activeCascadeCount = clamp(cascadeCount, 1u, (uint) MAX_SHADOW_CASCADES); + float2 shadowUV; float currentDepth; if (GetCascadeShadowCoordinates(worldPos, 0u, shadowUV, currentDepth)) + { + selectedCascadeIndex = 0u; return ComputeCascadeShadow(0u, shadowUV, currentDepth); + } + if (activeCascadeCount > 1u && GetCascadeShadowCoordinates(worldPos, 1u, shadowUV, currentDepth)) + { + selectedCascadeIndex = 1u; return ComputeCascadeShadow(1u, shadowUV, currentDepth); + } + if (activeCascadeCount > 2u && GetCascadeShadowCoordinates(worldPos, 2u, shadowUV, currentDepth)) + { + selectedCascadeIndex = 2u; return ComputeCascadeShadow(2u, shadowUV, currentDepth); + } + if (activeCascadeCount > 3u && GetCascadeShadowCoordinates(worldPos, 3u, shadowUV, currentDepth)) + { + selectedCascadeIndex = 3u; return ComputeCascadeShadow(3u, shadowUV, currentDepth); + } return 1.0f; } +float3 GetCascadeDebugColor(uint cascadeIndex) +{ + if (cascadeIndex == 0u) + return float3(1.0f, 0.2f, 0.2f); + + if (cascadeIndex == 1u) + return float3(0.2f, 1.0f, 0.2f); + + if (cascadeIndex == 2u) + return float3(0.2f, 0.4f, 1.0f); + + return float3(1.0f, 0.8f, 0.2f); +} + //--------------------// + + float SampleSSAO(float4 screenPosition) { if (renderFlags.x < 0.5f) @@ -248,6 +297,7 @@ float SampleSSAO(float4 screenPosition) } float2 ssaoUV = screenPosition.xy * invScreenSize; + return ssaoTexture.Sample(pointClampSample, ssaoUV).r; } @@ -255,47 +305,43 @@ float4 main(float4 position : SV_Position, float2 coord : TEXCOORD0) : SV_TARGET { //Initialize material values float3 worldPos = positionTex.Sample(linearWrapSample, coord); - - + + //Read base color float3 albedo = baseColorTex.Sample(linearWrapSample, coord); - - + + //Read metalic roughness AO float3 metallicRoughnessAOSample = metallicRoughnessTex.Sample(linearWrapSample, coord).rgb; float metallic = metallicRoughnessAOSample.b; float alphaRoughness = metallicRoughnessAOSample.g; float ao = metallicRoughnessAOSample.r; - - - + + //Read emissive float3 emissive = emissiveTex.Sample(linearWrapSample, coord); //float3 emissive = 0; - - - + + //Read normal float3 finalWorldNormal = normalTex.Sample(linearWrapSample, coord).rgb; - - + //Prepare data for render equation float3 F0Metallic = albedo; float3 F0NonMetallic = 0.04; - + float3 diffuseColorMetallic = 0; float3 diffuseColorNonMetallic = albedo / PI; - + float3 viewDirection = normalize(viewPos - worldPos); float3 reflection = normalize(reflect(-viewDirection, finalWorldNormal)); float NdotV = abs(dot(finalWorldNormal, viewDirection)) + 0.001; float horizon = min(1.0 + dot(reflection, finalWorldNormal), 1.0); - + alphaRoughness = alphaRoughness * alphaRoughness; - - - + + //Calculate directional direct lighting float3 directionalMetallic = 0.0f; float3 directionalNonMetallic = 0.0f; @@ -306,8 +352,7 @@ float4 main(float4 position : SV_Position, float2 coord : TEXCOORD0) : SV_TARGET directionalNonMetallic += ComputeDirectionalLight(i, viewDirection, finalWorldNormal, NdotV, alphaRoughness, F0NonMetallic, diffuseColorNonMetallic); } - - + //Calculate point and spot direct lighting float3 otherMetallic = 0.0f; float3 otherNonMetallic = 0.0f; @@ -324,10 +369,10 @@ float4 main(float4 position : SV_Position, float2 coord : TEXCOORD0) : SV_TARGET otherNonMetallic += ComputeSpotLight(i, worldPos, viewDirection, finalWorldNormal, NdotV, alphaRoughness, F0NonMetallic, diffuseColorNonMetallic); } - - + //Apply shadow only to directional direct lighting - float shadow = ComputeShadow(worldPos); + uint selectedCascadeIndex; + float shadow = ComputeShadow(worldPos, selectedCascadeIndex); float3 directionalLighting = lerp(directionalNonMetallic, directionalMetallic, metallic); float3 otherLighting = lerp(otherNonMetallic, otherMetallic, metallic); @@ -335,7 +380,6 @@ float4 main(float4 position : SV_Position, float2 coord : TEXCOORD0) : SV_TARGET float3 directLighting = directionalLighting * shadow + otherLighting; - //Calculate indirect lighting float ssao = SampleSSAO(position); @@ -350,14 +394,17 @@ float4 main(float4 position : SV_Position, float2 coord : TEXCOORD0) : SV_TARGET specularAO *= horizon; float3 indirectLighting = computeIndirectLighting(reflection, NdotV, finalWorldNormal, F0Metallic, alphaRoughness, 11, metallic, diffuseAO, specularAO); - - + + //Calculate final color // Output linear HDR colour. Exposure, tone mapping and gamma correction are // applied later by the post-process pass. float3 finalColor = directLighting + indirectLighting + emissive; - - + if (cascadePadding.x > 0.5f && selectedCascadeIndex < MAX_SHADOW_CASCADES) + { + finalColor = lerp(finalColor, GetCascadeDebugColor(selectedCascadeIndex), 0.35f); + } + return float4(finalColor, 1.0f); } \ No newline at end of file diff --git a/Engine_Master_UPC/LightingCBuffers.hlsli b/Engine_Master_UPC/LightingCBuffers.hlsli index 4bbb7ba8..2a1f9276 100644 --- a/Engine_Master_UPC/LightingCBuffers.hlsli +++ b/Engine_Master_UPC/LightingCBuffers.hlsli @@ -21,6 +21,7 @@ struct DirectionalLight { float3 direction; float pad0; + float3 color; float intensity; }; @@ -29,6 +30,7 @@ struct PointLight { float3 position; float radius; + float3 color; float intensity; }; diff --git a/Engine_Master_UPC/Lights.h b/Engine_Master_UPC/Lights.h index 929363b5..3075d364 100644 --- a/Engine_Master_UPC/Lights.h +++ b/Engine_Master_UPC/Lights.h @@ -42,6 +42,7 @@ struct LightDefaults static constexpr float DEFAULT_SHADOW_CASCADE_SPLIT_2 = 0.60f; static constexpr ShadowCascadeFitMode DEFAULT_SHADOW_CASCADE_FIT_MODE = ShadowCascadeFitMode::FIT_TO_CASCADE; + static constexpr bool DEFAULT_SHADOW_CASCADE_DEBUG_ENABLED = false; }; @@ -75,6 +76,7 @@ struct LightShadowSettings float cascadeSplit1 = LightDefaults::DEFAULT_SHADOW_CASCADE_SPLIT_1; float cascadeSplit2 = LightDefaults::DEFAULT_SHADOW_CASCADE_SPLIT_2; ShadowCascadeFitMode cascadeFitMode = LightDefaults::DEFAULT_SHADOW_CASCADE_FIT_MODE; + bool cascadeDebugEnabled = LightDefaults::DEFAULT_SHADOW_CASCADE_DEBUG_ENABLED; }; struct DirectionalLightParameters diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp index 01cd9eeb..b0682167 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.cpp +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -237,7 +237,8 @@ void ShadowFrustumComputePass::prepare(const RenderContext& ctx) m_constants.cascadeSplit2 = shadowSettings.cascadeSplit2; - m_constants.cascadePadding = Vector3::Zero; + m_constants.cascadeDebugEnabled = shadowSettings.cascadeDebugEnabled ? 1u : 0u; + m_constants.cascadePadding = Vector2::Zero; m_enabled = true; } @@ -253,8 +254,8 @@ void ShadowFrustumComputePass::transitionOutputBuffer(ID3D12GraphicsCommandList4 CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( m_shadowDataBuffer.Get(), - m_outputBufferState, - newState); + m_outputBufferState, + newState); commandList->ResourceBarrier(1, &barrier); diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.h b/Engine_Master_UPC/ShadowFrustumComputePass.h index 5f5118c1..e81851bf 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.h +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -42,7 +42,8 @@ class ShadowFrustumComputePass final : public IRenderPass float cascadeSplit0 = 0.10f; float cascadeSplit1 = 0.30f; float cascadeSplit2 = 0.60f; - Vector3 cascadePadding = Vector3::Zero; + uint32_t cascadeDebugEnabled = 0; + Vector2 cascadePadding = Vector2::Zero; }; public: diff --git a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl index a3de418b..120abe1a 100644 --- a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl +++ b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl @@ -28,8 +28,7 @@ struct ShadowDataOutput float4x4 cascadeLightViewProjection[MAX_SHADOW_CASCADES]; }; -RWStructuredBuffer - outputShadowData : register(u0); +RWStructuredBuffer outputShadowData : register(u0); cbuffer ShadowFrustumParams : register(b0) { @@ -51,36 +50,30 @@ cbuffer ShadowFrustumParams : register(b0) float shadowMapTexelSizeX; float shadowMapTexelSizeY; float paddingSettings; - + uint cascadeCount; uint cascadeFitMode; float cascadeSplit0; float cascadeSplit1; float cascadeSplit2; - float3 cascadePadding; + uint cascadeDebugEnabled; + float2 cascadePadding; }; float LinearizeViewDepth(float deviceDepth) { - float denominator = - deviceDepth + cameraProjection._33; + float denominator = deviceDepth + cameraProjection._33; if (abs(denominator) < 0.000001f) { - denominator = - denominator < 0.0f - ? -0.000001f - : 0.000001f; + denominator = denominator < 0.0f ? -0.000001f : 0.000001f; } return -cameraProjection._43 / denominator; } -void BuildFrustumCorners( - float nearDistance, - float farDistance, - out float3 corners[8]) +void BuildFrustumCorners(float nearDistance, float farDistance, out float3 corners[8]) { const float xScale = cameraProjection._11; const float yScale = cameraProjection._22; @@ -90,41 +83,26 @@ void BuildFrustumCorners( [unroll] for (uint depthIndex = 0; depthIndex < 2; ++depthIndex) { - const float distanceValue = - depthIndex == 0 - ? nearDistance - : farDistance; + const float distanceValue = depthIndex == 0 ? nearDistance : farDistance; [unroll] for (uint yIndex = 0; yIndex < 2; ++yIndex) { - const float ndcY = - yIndex == 0 ? -1.0f : 1.0f; + const float ndcY = yIndex == 0 ? -1.0f : 1.0f; [unroll] for (uint xIndex = 0; xIndex < 2; ++xIndex) { - const float ndcX = - xIndex == 0 ? -1.0f : 1.0f; + const float ndcX = xIndex == 0 ? -1.0f : 1.0f; float3 viewPoint; - - viewPoint.x = - ndcX * distanceValue / xScale; - - viewPoint.y = - ndcY * distanceValue / yScale; - + viewPoint.x = ndcX * distanceValue / xScale; + viewPoint.y = ndcY * distanceValue / yScale; viewPoint.z = -distanceValue; - float4 worldPoint = - mul( - float4(viewPoint, 1.0f), - inverseView); - - corners[cornerIndex] = - worldPoint.xyz / worldPoint.w; + float4 worldPoint = mul(float4(viewPoint, 1.0f), inverseView); + corners[cornerIndex] = worldPoint.xyz / worldPoint.w; ++cornerIndex; } } @@ -148,22 +126,15 @@ float4 ComputeBoundingSphere(float3 corners[8]) [unroll] for (uint i = 0; i < 8; ++i) { - radius = max( - radius, - distance(center, corners[i])); + radius = max(radius, distance(center, corners[i])); } - radius = max( - radius, - minOrthoSize * 0.5f); + radius = max(radius, minOrthoSize * 0.5f); return float4(center, radius); } -float4x4 BuildLookAtRH( - float3 eye, - float3 target, - float3 up) +float4x4 BuildLookAtRH(float3 eye, float3 target, float3 up) { float3 zAxis = normalize(eye - target); float3 xAxis = normalize(cross(up, zAxis)); @@ -173,28 +144,20 @@ float4x4 BuildLookAtRH( xAxis.x, yAxis.x, zAxis.x, 0.0f, xAxis.y, yAxis.y, zAxis.y, 0.0f, xAxis.z, yAxis.z, zAxis.z, 0.0f, - -dot(xAxis, eye), - -dot(yAxis, eye), - -dot(zAxis, eye), - 1.0f); + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1.0f + ); } -float4x4 BuildOrthographicRH( - float width, - float height, - float nearPlane, - float farPlane) +float4x4 BuildOrthographicRH(float width, float height, float nearPlane, float farPlane) { - const float inverseDepthRange = - 1.0f / (nearPlane - farPlane); + const float inverseDepthRange = 1.0f / (nearPlane - farPlane); return float4x4( 2.0f / width, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f / height, 0.0f, 0.0f, 0.0f, 0.0f, inverseDepthRange, 0.0f, - 0.0f, 0.0f, - nearPlane * inverseDepthRange, - 1.0f); + 0.0f, 0.0f, nearPlane * inverseDepthRange, 1.0f + ); } float4x4 BuildIdentityMatrix() @@ -203,123 +166,63 @@ float4x4 BuildIdentityMatrix() 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); + 0.0f, 0.0f, 0.0f, 1.0f + ); } -float4x4 BuildLightViewProjection( - float nearDistance, - float farDistance) +float4x4 BuildLightViewProjection(float nearDistance, float farDistance) { float3 corners[8]; - BuildFrustumCorners( - nearDistance, - farDistance, - corners); - - float4 sphere = - ComputeBoundingSphere(corners); - - float3 normalizedLightDirection = - normalize(lightDirection); + BuildFrustumCorners(nearDistance, farDistance, corners); - float3 eye = - sphere.xyz - - normalizedLightDirection * - (sphere.w + sunDistance); - - float3 up = - float3(0.0f, 1.0f, 0.0f); + float4 sphere = ComputeBoundingSphere(corners); + float3 normalizedLightDirection = normalize(lightDirection); + float3 eye = sphere.xyz - normalizedLightDirection * (sphere.w + sunDistance); + float3 up = float3(0.0f, 1.0f, 0.0f); if (abs(normalizedLightDirection.y) > 0.95f) { up = float3(0.0f, 0.0f, 1.0f); } - float4x4 lightView = - BuildLookAtRH( - eye, - sphere.xyz, - up); - - float orthoSize = - max( - sphere.w * 2.0f, - minOrthoSize); - - float4x4 lightProjection = - BuildOrthographicRH( - orthoSize, - orthoSize, - 0.0f, - sphere.w * 2.0f + sunDistance); - - return mul( - lightView, - lightProjection); + float4x4 lightView = BuildLookAtRH(eye, sphere.xyz, up); + float orthoSize = max(sphere.w * 2.0f, minOrthoSize); + float4x4 lightProjection = BuildOrthographicRH(orthoSize, orthoSize, 0.0f, sphere.w * 2.0f + sunDistance); + + return mul(lightView, lightProjection); } -ShadowDataOutput BuildShadowOutput( - float4x4 lightViewProjection, - uint enabled) +ShadowDataOutput BuildShadowOutput(float4x4 lightViewProjection, uint enabled) { ShadowDataOutput output; - output.lightViewProjection = - lightViewProjection; + output.lightViewProjection = lightViewProjection; - output.shadowBias = - shadowBias; + output.shadowBias = shadowBias; + output.shadowStrength = shadowStrength; + output.shadowsEnabled = enabled; + output.paddingShadow = 0.0f; - output.shadowStrength = - shadowStrength; + output.shadowMapTexelSize = float2(shadowMapTexelSizeX, shadowMapTexelSizeY); + output.pcfEnabled = pcfEnabled; + output.pcfRadius = pcfRadius; - output.shadowsEnabled = - enabled; + output.cascadeCount = clamp(cascadeCount, 1u, (uint) MAX_SHADOW_CASCADES); + output.cascadeFitMode = cascadeFitMode; - output.paddingShadow = - 0.0f; + // x = debug cascade tint enabled + // y = unused + output.cascadePadding = float2(cascadeDebugEnabled != 0u ? 1.0f : 0.0f, 0.0f); - output.shadowMapTexelSize = - float2( - shadowMapTexelSizeX, - shadowMapTexelSizeY); + output.cascadeFarDistances = float4(0.0f, 0.0f, 0.0f, 0.0f); - output.pcfEnabled = - pcfEnabled; + float4x4 identityMatrix = BuildIdentityMatrix(); - output.pcfRadius = - pcfRadius; - - output.cascadeCount = - clamp( - cascadeCount, - 1u, - (uint) MAX_SHADOW_CASCADES); - - output.cascadeFitMode = - cascadeFitMode; - - output.cascadePadding = - float2(0.0f, 0.0f); - - output.cascadeFarDistances = - float4(0.0f, 0.0f, 0.0f, 0.0f); - - float4x4 identityMatrix = - BuildIdentityMatrix(); - - output.cascadeLightViewProjection[0] = - identityMatrix; - - output.cascadeLightViewProjection[1] = - identityMatrix; - - output.cascadeLightViewProjection[2] = - identityMatrix; - - output.cascadeLightViewProjection[3] = - identityMatrix; + output.cascadeLightViewProjection[0] = identityMatrix; + output.cascadeLightViewProjection[1] = identityMatrix; + output.cascadeLightViewProjection[2] = identityMatrix; + output.cascadeLightViewProjection[3] = identityMatrix; return output; } @@ -327,180 +230,76 @@ ShadowDataOutput BuildShadowOutput( [numthreads(1, 1, 1)] void main() { - float2 minMaxDepth = - inputMinMax.Load(int3(0, 0, 0)); + float2 minMaxDepth = inputMinMax.Load(int3(0, 0, 0)); if (minMaxDepth.x > minMaxDepth.y) { - float4x4 identityMatrix = - BuildIdentityMatrix(); - - outputShadowData[0] = - BuildShadowOutput( - identityMatrix, - 0u); - + float4x4 identityMatrix = BuildIdentityMatrix(); + outputShadowData[0] = BuildShadowOutput(identityMatrix, 0u); return; } - float nearViewZ = - LinearizeViewDepth( - minMaxDepth.x); - - float farViewZ = - LinearizeViewDepth( - minMaxDepth.y); - - float nearDistance = - max( - -nearViewZ, - 0.0001f); + float nearViewZ = LinearizeViewDepth(minMaxDepth.x); + float farViewZ = LinearizeViewDepth(minMaxDepth.y); - float farDistance = - max( - -farViewZ, - nearDistance + 0.0001f); + float nearDistance = max(-nearViewZ, 0.0001f); + float farDistance = max(-farViewZ, nearDistance + 0.0001f); // Preserve the current full fitted shadow frustum. - float4x4 fullLightViewProjection = - BuildLightViewProjection( - nearDistance, - farDistance); - - ShadowDataOutput output = - BuildShadowOutput( - fullLightViewProjection, - shadowsEnabled); - - uint activeCascadeCount = - output.cascadeCount; + float4x4 fullLightViewProjection = BuildLightViewProjection(nearDistance, farDistance); - float fittedDepthRange = - farDistance - nearDistance; + ShadowDataOutput output = BuildShadowOutput(fullLightViewProjection, shadowsEnabled); + uint activeCascadeCount = output.cascadeCount; + float fittedDepthRange = farDistance - nearDistance; // Cascade 0 - float cascade0FarFraction = - activeCascadeCount > 1 - ? cascadeSplit0 - : 1.0f; + float cascade0FarFraction = activeCascadeCount > 1 ? cascadeSplit0 : 1.0f; + float cascade0NearDistance = nearDistance; + float cascade0FarDistance = nearDistance + fittedDepthRange * cascade0FarFraction; - float cascade0NearDistance = - nearDistance; - - float cascade0FarDistance = - nearDistance + - fittedDepthRange * - cascade0FarFraction; - - cascade0FarDistance = - max( - cascade0FarDistance, - cascade0NearDistance + 0.0001f); - - output.cascadeFarDistances.x = - cascade0FarDistance; - - output.cascadeLightViewProjection[0] = - BuildLightViewProjection( - cascade0NearDistance, - cascade0FarDistance); + cascade0FarDistance = max(cascade0FarDistance, cascade0NearDistance + 0.0001f); + output.cascadeFarDistances.x = cascade0FarDistance; + output.cascadeLightViewProjection[0] = BuildLightViewProjection(cascade0NearDistance, cascade0FarDistance); // Cascade 1 if (activeCascadeCount > 1) { - float cascade1FarFraction = - activeCascadeCount > 2 - ? cascadeSplit1 - : 1.0f; - - float cascade1NearDistance = - output.cascadeFitMode == - CASCADE_FIT_TO_CASCADE - ? cascade0FarDistance - : nearDistance; + float cascade1FarFraction = activeCascadeCount > 2 ? cascadeSplit1 : 1.0f; + float cascade1NearDistance = output.cascadeFitMode == CASCADE_FIT_TO_CASCADE ? cascade0FarDistance : nearDistance; + float cascade1FarDistance = nearDistance + fittedDepthRange * cascade1FarFraction; - float cascade1FarDistance = - nearDistance + - fittedDepthRange * - cascade1FarFraction; - - cascade1FarDistance = - max( - cascade1FarDistance, - cascade1NearDistance + 0.0001f); - - output.cascadeFarDistances.y = - cascade1FarDistance; - - output.cascadeLightViewProjection[1] = - BuildLightViewProjection( - cascade1NearDistance, - cascade1FarDistance); + cascade1FarDistance = max(cascade1FarDistance, cascade1NearDistance + 0.0001f); + output.cascadeFarDistances.y = cascade1FarDistance; + output.cascadeLightViewProjection[1] = BuildLightViewProjection(cascade1NearDistance, cascade1FarDistance); // Cascade 2 if (activeCascadeCount > 2) { - float cascade2FarFraction = - activeCascadeCount > 3 - ? cascadeSplit2 - : 1.0f; - - float cascade2NearDistance = - output.cascadeFitMode == - CASCADE_FIT_TO_CASCADE - ? cascade1FarDistance - : nearDistance; - - float cascade2FarDistance = - nearDistance + - fittedDepthRange * - cascade2FarFraction; + float cascade2FarFraction = activeCascadeCount > 3 ? cascadeSplit2 : 1.0f; + float cascade2NearDistance = output.cascadeFitMode == CASCADE_FIT_TO_CASCADE ? cascade1FarDistance : nearDistance; + float cascade2FarDistance = nearDistance + fittedDepthRange * cascade2FarFraction; - cascade2FarDistance = - max( - cascade2FarDistance, - cascade2NearDistance + 0.0001f); - - output.cascadeFarDistances.z = - cascade2FarDistance; - - output.cascadeLightViewProjection[2] = - BuildLightViewProjection( - cascade2NearDistance, - cascade2FarDistance); + cascade2FarDistance = max(cascade2FarDistance, cascade2NearDistance + 0.0001f); + output.cascadeFarDistances.z = cascade2FarDistance; + output.cascadeLightViewProjection[2] = BuildLightViewProjection(cascade2NearDistance, cascade2FarDistance); // Cascade 3 if (activeCascadeCount > 3) { - float cascade3NearDistance = - output.cascadeFitMode == - CASCADE_FIT_TO_CASCADE - ? cascade2FarDistance - : nearDistance; - - float cascade3FarDistance = - farDistance; - - cascade3FarDistance = - max( - cascade3FarDistance, - cascade3NearDistance + 0.0001f); - - output.cascadeFarDistances.w = - cascade3FarDistance; - - output.cascadeLightViewProjection[3] = - BuildLightViewProjection( - cascade3NearDistance, - cascade3FarDistance); + float cascade3NearDistance = output.cascadeFitMode == CASCADE_FIT_TO_CASCADE ? cascade2FarDistance : nearDistance; + float cascade3FarDistance = farDistance; + + cascade3FarDistance = max(cascade3FarDistance, cascade3NearDistance + 0.0001f); + + output.cascadeFarDistances.w = cascade3FarDistance; + output.cascadeLightViewProjection[3] = BuildLightViewProjection(cascade3NearDistance, cascade3FarDistance); } } } - outputShadowData[0] = - output; + outputShadowData[0] = output; } \ No newline at end of file From 8c39de66913d3a7746907aa1169d4470a071f907 Mon Sep 17 00:00:00 2001 From: DaniBellido Date: Sun, 16 Aug 2026 23:55:36 +0200 Subject: [PATCH 13/13] Add cascade frustum debug visualization Add debug visualization for the depth-fitted camera frustum and individual CSM cascade sub-frustum. Tint colour only visible in Game View. --- Engine_Master_UPC/ModuleRender.cpp | 1 + .../ShadowFrustumComputePass.cpp | 576 ++++++++++++++---- Engine_Master_UPC/ShadowFrustumComputePass.h | 40 +- .../ShadowFrustumComputeShader.hlsl | 4 + 4 files changed, 501 insertions(+), 120 deletions(-) diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index a8315d1a..6278d232 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -90,6 +90,7 @@ bool ModuleRender::init() m_skinningComputePass = std::make_unique(device); m_depthReductionPass = std::make_unique(device); m_shadowFrustumComputePass = std::make_unique(device, m_depthReductionPass.get()); + m_debugDrawPass->registerStatic(m_shadowFrustumComputePass.get()); m_shadowMapPass = std::make_unique(device, m_shadowFrustumComputePass.get()); m_ssaoGeometryPass = std::make_unique(device); m_ssaoPass = std::make_unique(device); diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp index b0682167..2551777f 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.cpp +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -15,115 +15,350 @@ #include "Lights.h" #include "GameObject.h" #include "Transform.h" +#include "Scene.h" +#include "CameraComponent.h" #include #include #include "PlatformHelpers.h" + #include +#include +#include + +namespace +{ + constexpr bool SHADOW_DEBUG_DEPTH_ENABLED = false; + + bool buildClipVolumeCorners(const Matrix& viewProjection, Vector3 corners[8]) + { + const Matrix inverseViewProjection = viewProjection.Invert(); + + const Vector4 clipCorners[8] = + { + Vector4(-1.0f, 1.0f, 0.0f, 1.0f), + Vector4(1.0f, 1.0f, 0.0f, 1.0f), + Vector4(1.0f, -1.0f, 0.0f, 1.0f), + Vector4(-1.0f, -1.0f, 0.0f, 1.0f), + + Vector4(-1.0f, 1.0f, 1.0f, 1.0f), + Vector4(1.0f, 1.0f, 1.0f, 1.0f), + Vector4(1.0f, -1.0f, 1.0f, 1.0f), + Vector4(-1.0f, -1.0f, 1.0f, 1.0f) + }; + + for (uint32_t i = 0; i < 8; ++i) + { + Vector4 worldPoint = Vector4::Transform(clipCorners[i], inverseViewProjection); + + if (std::abs(worldPoint.w) <= 0.000001f) + { + return false; + } + + worldPoint /= worldPoint.w; + corners[i] = Vector3(worldPoint.x, worldPoint.y, worldPoint.z); + } + + return true; + } + + void drawWireBox(const Vector3 corners[8], const float color[3]) + { + dd::line(&corners[0].x, &corners[1].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[1].x, &corners[2].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[2].x, &corners[3].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[3].x, &corners[0].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + + dd::line(&corners[4].x, &corners[5].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[5].x, &corners[6].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[6].x, &corners[7].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[7].x, &corners[4].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + + dd::line(&corners[0].x, &corners[4].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[1].x, &corners[5].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[2].x, &corners[6].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + dd::line(&corners[3].x, &corners[7].x, color, 0, SHADOW_DEBUG_DEPTH_ENABLED); + } + + bool buildCameraSubFrustumCorners(const Matrix& view, const Matrix& projection, float nearDistance, float farDistance, Vector3 corners[8]) + { + if (nearDistance <= 0.0f || farDistance <= nearDistance) + { + return false; + } + + const float xScale = projection._11; + const float yScale = projection._22; + + if (std::abs(xScale) <= 0.000001f || std::abs(yScale) <= 0.000001f) + { + return false; + } + + const Matrix inverseView = view.Invert(); + + const float ndcX[4] = { -1.0f, 1.0f, 1.0f, -1.0f }; + const float ndcY[4] = { 1.0f, 1.0f, -1.0f, -1.0f }; + + for (uint32_t depthIndex = 0; depthIndex < 2; ++depthIndex) + { + const float distance = depthIndex == 0 ? nearDistance : farDistance; + + for (uint32_t cornerIndex = 0; cornerIndex < 4; ++cornerIndex) + { + Vector3 viewPoint; + viewPoint.x = ndcX[cornerIndex] * distance / xScale; + viewPoint.y = ndcY[cornerIndex] * distance / yScale; + viewPoint.z = -distance; -ShadowFrustumComputePass::ShadowFrustumComputePass( - ComPtr device, - DepthReductionPass* depthReductionPass) + corners[depthIndex * 4 + cornerIndex] = Vector3::Transform(viewPoint, inverseView); + } + } + + return true; + } + + bool getCameraDepthRange(const Matrix& projection, float& nearDistance, float& farDistance) + { + const float nearDenominator = projection._33; + const float farDenominator = 1.0f + projection._33; + + if (std::abs(nearDenominator) <= 0.000001f || std::abs(farDenominator) <= 0.000001f) + { + return false; + } + + nearDistance = projection._43 / nearDenominator; + farDistance = projection._43 / farDenominator; + + nearDistance = std::abs(nearDistance); + farDistance = std::abs(farDistance); + + if (farDistance < nearDistance) + { + std::swap(nearDistance, farDistance); + } + + return nearDistance > 0.0f && farDistance > nearDistance; + } + +} + +ShadowFrustumComputePass::ShadowFrustumComputePass(ComPtr device, DepthReductionPass* depthReductionPass) : m_device(device) , m_depthReductionPass(depthReductionPass) { createRootSignature(); createPipelineState(); createOutputBuffer(); + createDebugReadbackBuffers(); } void ShadowFrustumComputePass::createRootSignature() { CD3DX12_DESCRIPTOR_RANGE minMaxRange; - minMaxRange.Init( - D3D12_DESCRIPTOR_RANGE_TYPE_SRV, - 1, - 0, - 0); + minMaxRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0); CD3DX12_ROOT_PARAMETER rootParameters[3] = {}; // t0: final 1x1 min/max depth texture - rootParameters[0].InitAsDescriptorTable( - 1, - &minMaxRange, - D3D12_SHADER_VISIBILITY_ALL); + rootParameters[0].InitAsDescriptorTable(1, &minMaxRange, D3D12_SHADER_VISIBILITY_ALL); - // u0: output lightViewProjection buffer - rootParameters[1].InitAsUnorderedAccessView( - 0, - 0); + // u0: output shadow data buffer + rootParameters[1].InitAsUnorderedAccessView(0, 0); // b0: inverse view, projection, light direction and fitting settings - rootParameters[2].InitAsConstants( - sizeof(FrustumConstants) / sizeof(uint32_t), - 0, - 0, - D3D12_SHADER_VISIBILITY_ALL); + rootParameters[2].InitAsConstants(sizeof(FrustumConstants) / sizeof(uint32_t), 0, 0, D3D12_SHADER_VISIBILITY_ALL); CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; - rootSignatureDesc.Init( - _countof(rootParameters), - rootParameters, - 0, - nullptr, - D3D12_ROOT_SIGNATURE_FLAG_NONE); + rootSignatureDesc.Init(_countof(rootParameters), rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_NONE); ComPtr signature; ComPtr error; - DXCall(D3D12SerializeRootSignature( - &rootSignatureDesc, - D3D_ROOT_SIGNATURE_VERSION_1, - &signature, - &error)); + DXCall(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); - DXCall(m_device->CreateRootSignature( - 0, - signature->GetBufferPointer(), - signature->GetBufferSize(), - IID_PPV_ARGS(&m_rootSignature))); + DXCall(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature))); } void ShadowFrustumComputePass::createPipelineState() { ComPtr computeShaderBlob; - ThrowIfFailed(D3DReadFileToBlob( - L"ShadowFrustumComputeShader.cso", - &computeShaderBlob)); + ThrowIfFailed(D3DReadFileToBlob(L"ShadowFrustumComputeShader.cso", &computeShaderBlob)); D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc{}; psoDesc.pRootSignature = m_rootSignature.Get(); psoDesc.CS = CD3DX12_SHADER_BYTECODE(computeShaderBlob.Get()); - DXCall(m_device->CreateComputePipelineState( - &psoDesc, - IID_PPV_ARGS(&m_pipelineState))); + DXCall(m_device->CreateComputePipelineState(&psoDesc, IID_PPV_ARGS(&m_pipelineState))); } void ShadowFrustumComputePass::createOutputBuffer() { constexpr size_t ALIGNMENT = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; constexpr size_t BUFFER_SIZE = ((sizeof(ShadowDataCB) + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; + static_assert(BUFFER_SIZE == 512, "ShadowDataBuffer must be large enough for cascaded shadow data."); - m_shadowDataBuffer = - app->getModuleResources()->createDefaultBuffer( - BUFFER_SIZE, - D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, - D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - "ShadowDataBuffer"); + m_shadowDataBuffer = app->getModuleResources()->createDefaultBuffer( + BUFFER_SIZE, + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + "ShadowDataBuffer"); m_outputBufferState = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; } -const LightComponent* -ShadowFrustumComputePass::findMainShadowCastingDirectionalLight() const +void ShadowFrustumComputePass::createDebugReadbackBuffers() +{ + m_debugReadbackBuffers.resize(FRAMES_IN_FLIGHT); + m_debugReadbackPending.assign(FRAMES_IN_FLIGHT, false); + m_debugCaptureMetadata.resize(FRAMES_IN_FLIGHT); + + const CD3DX12_HEAP_PROPERTIES heapProperties(D3D12_HEAP_TYPE_READBACK); + const CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(sizeof(ShadowDataCB)); + + for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) + { + DXCall(m_device->CreateCommittedResource( + &heapProperties, + D3D12_HEAP_FLAG_NONE, + &bufferDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(&m_debugReadbackBuffers[i]))); + + m_debugReadbackBuffers[i]->SetName(L"ShadowCascadeDebugReadback"); + } +} + +void ShadowFrustumComputePass::refreshDebugReadbackForCurrentFrame() +{ + ModuleD3D12* d3d12 = app->getModuleD3D12(); + + if (d3d12 == nullptr || d3d12->getCommandQueue() == nullptr) + { + return; + } + + const uint32_t frameIndex = d3d12->getCurrentFrameIndex(); + + if (frameIndex >= m_debugReadbackBuffers.size()) + { + return; + } + + const uint64_t frameFenceValue = d3d12->getCurrentFrame(); + + // prepare() can be called more than once per engine frame because Editor + // and Game use the same render pipeline. Only inspect this frame slot once. + if (m_hasObservedDebugFrame && + m_observedDebugFrameIndex == frameIndex && + m_observedDebugFrameFenceValue == frameFenceValue) + { + return; + } + + m_observedDebugFrameIndex = frameIndex; + m_observedDebugFrameFenceValue = frameFenceValue; + m_hasObservedDebugFrame = true; + + if (!m_debugReadbackPending[frameIndex]) + { + return; + } + + if (!d3d12->getCommandQueue()->isFenceComplete(frameFenceValue)) + { + return; + } + + ComPtr& readbackBuffer = m_debugReadbackBuffers[frameIndex]; + + if (readbackBuffer == nullptr) + { + return; + } + + void* mappedData = nullptr; + + D3D12_RANGE readRange{}; + readRange.Begin = 0; + readRange.End = sizeof(ShadowDataCB); + + const HRESULT mapResult = readbackBuffer->Map(0, &readRange, &mappedData); + + if (FAILED(mapResult) || mappedData == nullptr) + { + return; + } + + std::memcpy(&m_debugShadowData, mappedData, sizeof(ShadowDataCB)); + + D3D12_RANGE writeRange{}; + writeRange.Begin = 0; + writeRange.End = 0; + + readbackBuffer->Unmap(0, &writeRange); + + const DebugCaptureMetadata& metadata = m_debugCaptureMetadata[frameIndex]; + + if (metadata.valid) + { + m_debugCameraView = metadata.view; + m_debugCameraProjection = metadata.projection; + m_hasDebugShadowData = m_debugShadowData.shadowsEnabled != 0; + } + else + { + m_hasDebugShadowData = false; + } + + m_debugReadbackPending[frameIndex] = false; + m_debugCaptureMetadata[frameIndex] = {}; +} + +void ShadowFrustumComputePass::recordDebugReadback(ID3D12GraphicsCommandList4* commandList) +{ + if (commandList == nullptr || !m_captureDebugReadback || m_shadowDataBuffer == nullptr) + { + return; + } + + ModuleD3D12* d3d12 = app->getModuleD3D12(); + + if (d3d12 == nullptr) + { + return; + } + + const uint32_t frameIndex = d3d12->getCurrentFrameIndex(); + + if (frameIndex >= m_debugReadbackBuffers.size() || + m_debugReadbackBuffers[frameIndex] == nullptr || + m_debugReadbackPending[frameIndex]) + { + return; + } + + transitionOutputBuffer(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE); + + commandList->CopyBufferRegion( + m_debugReadbackBuffers[frameIndex].Get(), + 0, + m_shadowDataBuffer.Get(), + 0, + sizeof(ShadowDataCB)); + + m_debugReadbackPending[frameIndex] = true; +} + +const LightComponent* ShadowFrustumComputePass::findMainShadowCastingDirectionalLight() const { - const std::vector& lights = - app->getModuleScene()->getLightComponents(); + const std::vector& lights = app->getModuleScene()->getLightComponents(); for (const LightComponent* light : lights) { @@ -134,8 +369,7 @@ ShadowFrustumComputePass::findMainShadowCastingDirectionalLight() const const GameObject* owner = light->getOwner(); - if (owner == nullptr || - !owner->IsActiveInWindowHierarchy()) + if (owner == nullptr || !owner->IsActiveInWindowHierarchy()) { continue; } @@ -160,8 +394,12 @@ ShadowFrustumComputePass::findMainShadowCastingDirectionalLight() const void ShadowFrustumComputePass::prepare(const RenderContext& ctx) { + refreshDebugReadbackForCurrentFrame(); + m_enabled = false; m_hasValidResult = false; + m_captureDebugReadback = false; + m_drawDebugForCurrentView = false; m_constants = {}; if (m_depthReductionPass == nullptr) @@ -169,8 +407,7 @@ void ShadowFrustumComputePass::prepare(const RenderContext& ctx) return; } - const LightComponent* light = - findMainShadowCastingDirectionalLight(); + const LightComponent* light = findMainShadowCastingDirectionalLight(); if (light == nullptr) { @@ -178,9 +415,35 @@ void ShadowFrustumComputePass::prepare(const RenderContext& ctx) } const LightShadowSettings& shadowSettings = light->getData().shadow; + + m_drawDebugForCurrentView = + ctx.renderDebug && + ctx.viewType == RenderViewType::Editor && + shadowSettings.cascadeDebugEnabled; + + m_captureDebugReadback = + ctx.viewType == RenderViewType::Game && + shadowSettings.cascadeDebugEnabled; + + if (m_captureDebugReadback) + { + ModuleD3D12* d3d12 = app->getModuleD3D12(); + + if (d3d12 != nullptr) + { + const uint32_t frameIndex = d3d12->getCurrentFrameIndex(); + + if (frameIndex < m_debugCaptureMetadata.size() && !m_debugReadbackPending[frameIndex]) + { + m_debugCaptureMetadata[frameIndex].view = ctx.view; + m_debugCaptureMetadata[frameIndex].projection = ctx.projection; + m_debugCaptureMetadata[frameIndex].valid = true; + } + } + } + const GameObject* owner = light->getOwner(); - const Transform* transform = - owner != nullptr ? owner->GetTransform() : nullptr; + const Transform* transform = owner != nullptr ? owner->GetTransform() : nullptr; if (transform == nullptr) { @@ -197,47 +460,36 @@ void ShadowFrustumComputePass::prepare(const RenderContext& ctx) lightDirection.Normalize(); m_constants.inverseView = ctx.view.Invert().Transpose(); - m_constants.projection = ctx.projection.Transpose(); m_constants.lightDirection = lightDirection; m_constants.sunDistance = SHADOW_LIGHT_DISTANCE_PADDING; m_constants.minOrthoSize = SHADOW_MIN_ORTHO_SIZE; + m_constants.padding = Vector3::Zero; m_constants.shadowBias = shadowSettings.shadowBias; - m_constants.shadowStrength = shadowSettings.shadowStrength; - m_constants.shadowsEnabled = 1u; m_constants.pcfEnabled = shadowSettings.pcfEnabled ? 1u : 0u; - m_constants.pcfRadius = shadowSettings.pcfEnabled ? shadowSettings.pcfRadius : 0u; const uint32_t shadowMapSize = std::max(1u, shadowSettings.shadowMapSize); - const float inverseShadowMapSize = 1.0f / static_cast(shadowMapSize); m_constants.shadowMapTexelSizeX = inverseShadowMapSize; - m_constants.shadowMapTexelSizeY = inverseShadowMapSize; - m_constants.paddingSettings = 0.0f; - m_constants.padding = Vector3::Zero; - m_constants.cascadeCount = std::clamp(shadowSettings.cascadeCount, 1u, MAX_SHADOW_CASCADES); - m_constants.cascadeFitMode = static_cast(shadowSettings.cascadeFitMode); m_constants.cascadeSplit0 = shadowSettings.cascadeSplit0; - m_constants.cascadeSplit1 = shadowSettings.cascadeSplit1; - m_constants.cascadeSplit2 = shadowSettings.cascadeSplit2; - m_constants.cascadeDebugEnabled = shadowSettings.cascadeDebugEnabled ? 1u : 0u; + m_constants.cascadeDebugEnabled = ctx.viewType == RenderViewType::Game && shadowSettings.cascadeDebugEnabled ? 1u : 0u; m_constants.cascadePadding = Vector2::Zero; m_enabled = true; @@ -245,9 +497,7 @@ void ShadowFrustumComputePass::prepare(const RenderContext& ctx) void ShadowFrustumComputePass::transitionOutputBuffer(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState) { - if (commandList == nullptr || - m_shadowDataBuffer == nullptr || - m_outputBufferState == newState) + if (commandList == nullptr || m_shadowDataBuffer == nullptr || m_outputBufferState == newState) { return; } @@ -262,8 +512,7 @@ void ShadowFrustumComputePass::transitionOutputBuffer(ID3D12GraphicsCommandList4 m_outputBufferState = newState; } -void ShadowFrustumComputePass::apply( - ID3D12GraphicsCommandList4* commandList) +void ShadowFrustumComputePass::apply(ID3D12GraphicsCommandList4* commandList) { BEGIN_EVENT(commandList, "ShadowFrustumComputePass"); @@ -278,11 +527,9 @@ void ShadowFrustumComputePass::apply( return; } - Texture* minMaxTexture = - m_depthReductionPass->getResultTexture(); + Texture* minMaxTexture = m_depthReductionPass->getResultTexture(); - if (minMaxTexture == nullptr || - !minMaxTexture->hasSRV()) + if (minMaxTexture == nullptr || !minMaxTexture->hasSRV()) { END_EVENT(commandList); return; @@ -290,57 +537,154 @@ void ShadowFrustumComputePass::apply( ID3D12DescriptorHeap* descriptorHeaps[] = { - app->getModuleDescriptors() - ->getHeap( - D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) - .getHeap() + app->getModuleDescriptors()->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV).getHeap() }; - commandList->SetDescriptorHeaps( - _countof(descriptorHeaps), - descriptorHeaps); + commandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps); - transitionOutputBuffer( - commandList, - D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + transitionOutputBuffer(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); - commandList->SetPipelineState( - m_pipelineState.Get()); + commandList->SetPipelineState(m_pipelineState.Get()); + commandList->SetComputeRootSignature(m_rootSignature.Get()); - commandList->SetComputeRootSignature( - m_rootSignature.Get()); - - commandList->SetComputeRootDescriptorTable( - 0, - minMaxTexture->getSRV().gpu); - - commandList->SetComputeRootUnorderedAccessView( - 1, - m_shadowDataBuffer->GetGPUVirtualAddress()); - - commandList->SetComputeRoot32BitConstants( - 2, - sizeof(FrustumConstants) / sizeof(uint32_t), - &m_constants, - 0); + commandList->SetComputeRootDescriptorTable(0, minMaxTexture->getSRV().gpu); + commandList->SetComputeRootUnorderedAccessView(1, m_shadowDataBuffer->GetGPUVirtualAddress()); + commandList->SetComputeRoot32BitConstants(2, sizeof(FrustumConstants) / sizeof(uint32_t), &m_constants, 0); commandList->Dispatch(1, 1, 1); - CD3DX12_RESOURCE_BARRIER uavBarrier = - CD3DX12_RESOURCE_BARRIER::UAV( - m_shadowDataBuffer.Get()); - + CD3DX12_RESOURCE_BARRIER uavBarrier = CD3DX12_RESOURCE_BARRIER::UAV(m_shadowDataBuffer.Get()); commandList->ResourceBarrier(1, &uavBarrier); - transitionOutputBuffer( - commandList, - D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + recordDebugReadback(commandList); + + transitionOutputBuffer(commandList, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); m_hasValidResult = true; END_EVENT(commandList); } +void ShadowFrustumComputePass::debugDraw() +{ + if (!m_drawDebugForCurrentView) + { + return; + } + + Scene* scene = app->getModuleScene()->getScene(); + + if (scene == nullptr) + { + return; + } + + const CameraComponent* gameCamera = scene->getDefaultCamera(); + + if (gameCamera == nullptr) + { + return; + } + + const LightComponent* light = findMainShadowCastingDirectionalLight(); + + if (light == nullptr) + { + return; + } + + const LightShadowSettings& shadowSettings = light->getData().shadow; + + const Matrix cameraView = gameCamera->getViewMatrix(); + const Matrix cameraProjection = gameCamera->getProjectionMatrix(); + + float fittedNearDistance = 0.0f; + float fittedFarDistance = 0.0f; + + if (m_hasDebugShadowData) + { + fittedNearDistance = m_debugShadowData.cascadePadding.y; + + fittedFarDistance = std::max( + std::max(m_debugShadowData.cascadeFarDistances.x, m_debugShadowData.cascadeFarDistances.y), + std::max(m_debugShadowData.cascadeFarDistances.z, m_debugShadowData.cascadeFarDistances.w)); + } + else if (!getCameraDepthRange(cameraProjection, fittedNearDistance, fittedFarDistance)) + { + return; + } + + if (fittedNearDistance <= 0.0f || fittedFarDistance <= fittedNearDistance) + { + return; + } + + const uint32_t activeCascadeCount = std::clamp(shadowSettings.cascadeCount, 1u, MAX_SHADOW_CASCADES); + const float fittedDepthRange = fittedFarDistance - fittedNearDistance; + + float cascadeFarDistances[MAX_SHADOW_CASCADES] = + { + fittedFarDistance, + fittedFarDistance, + fittedFarDistance, + fittedFarDistance + }; + + cascadeFarDistances[0] = fittedNearDistance + fittedDepthRange * (activeCascadeCount > 1 ? shadowSettings.cascadeSplit0 : 1.0f); + + if (activeCascadeCount > 1) + { + cascadeFarDistances[1] = fittedNearDistance + fittedDepthRange * (activeCascadeCount > 2 ? shadowSettings.cascadeSplit1 : 1.0f); + } + + if (activeCascadeCount > 2) + { + cascadeFarDistances[2] = fittedNearDistance + fittedDepthRange * (activeCascadeCount > 3 ? shadowSettings.cascadeSplit2 : 1.0f); + } + + if (activeCascadeCount > 3) + { + cascadeFarDistances[3] = fittedFarDistance; + } + + const float cascadeColors[MAX_SHADOW_CASCADES][3] = + { + { 1.0f, 0.2f, 0.2f }, + { 0.2f, 1.0f, 0.2f }, + { 0.2f, 0.4f, 1.0f }, + { 1.0f, 0.8f, 0.2f } + }; + + Vector3 cameraFrustumCorners[8]; + + if (buildCameraSubFrustumCorners(cameraView, cameraProjection, fittedNearDistance, fittedFarDistance, cameraFrustumCorners)) + { + const float cameraColor[3] = { 1.0f, 1.0f, 1.0f }; + drawWireBox(cameraFrustumCorners, cameraColor); + } + + for (uint32_t cascadeIndex = 0; cascadeIndex < activeCascadeCount; ++cascadeIndex) + { + float cascadeNearDistance = fittedNearDistance; + + if (shadowSettings.cascadeFitMode == ShadowCascadeFitMode::FIT_TO_CASCADE && cascadeIndex > 0) + { + cascadeNearDistance = cascadeFarDistances[cascadeIndex - 1]; + } + + const float cascadeFarDistance = cascadeFarDistances[cascadeIndex]; + + Vector3 cascadeCorners[8]; + + if (!buildCameraSubFrustumCorners(cameraView, cameraProjection, cascadeNearDistance, cascadeFarDistance, cascadeCorners)) + { + continue; + } + + drawWireBox(cascadeCorners, cascadeColors[cascadeIndex]); + } +} + D3D12_GPU_VIRTUAL_ADDRESS ShadowFrustumComputePass::getShadowDataBufferAddress() const { if (m_shadowDataBuffer == nullptr) diff --git a/Engine_Master_UPC/ShadowFrustumComputePass.h b/Engine_Master_UPC/ShadowFrustumComputePass.h index e81851bf..cbcff925 100644 --- a/Engine_Master_UPC/ShadowFrustumComputePass.h +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -1,11 +1,13 @@ #pragma once #include "IRenderPass.h" +#include "IDebugDrawable.h" #include "SimpleMath.h" #include "ShadowTypes.h" #include #include +#include #include using Microsoft::WRL::ComPtr; @@ -13,7 +15,7 @@ using Microsoft::WRL::ComPtr; class DepthReductionPass; class LightComponent; -class ShadowFrustumComputePass final : public IRenderPass +class ShadowFrustumComputePass final : public IRenderPass, public IDebugDrawable { public: struct FrustumConstants @@ -41,21 +43,30 @@ class ShadowFrustumComputePass final : public IRenderPass uint32_t cascadeFitMode = static_cast(ShadowCascadeFitMode::FIT_TO_CASCADE); float cascadeSplit0 = 0.10f; float cascadeSplit1 = 0.30f; + float cascadeSplit2 = 0.60f; uint32_t cascadeDebugEnabled = 0; Vector2 cascadePadding = Vector2::Zero; }; +private: + struct DebugCaptureMetadata + { + Matrix view = Matrix::Identity; + Matrix projection = Matrix::Identity; + bool valid = false; + }; + public: - ShadowFrustumComputePass( - ComPtr device, - DepthReductionPass* depthReductionPass); + ShadowFrustumComputePass(ComPtr device, DepthReductionPass* depthReductionPass); ~ShadowFrustumComputePass() override = default; void prepare(const RenderContext& ctx) override; void apply(ID3D12GraphicsCommandList4* commandList) override; + void debugDraw() override; + D3D12_GPU_VIRTUAL_ADDRESS getShadowDataBufferAddress() const; bool isEnabled() const @@ -77,6 +88,10 @@ class ShadowFrustumComputePass final : public IRenderPass void createPipelineState(); void createOutputBuffer(); + void createDebugReadbackBuffers(); + void refreshDebugReadbackForCurrentFrame(); + void recordDebugReadback(ID3D12GraphicsCommandList4* commandList); + const LightComponent* findMainShadowCastingDirectionalLight() const; void transitionOutputBuffer(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState); @@ -97,4 +112,21 @@ class ShadowFrustumComputePass final : public IRenderPass bool m_enabled = false; bool m_hasValidResult = false; + + std::vector> m_debugReadbackBuffers; + std::vector m_debugReadbackPending; + std::vector m_debugCaptureMetadata; + + ShadowDataCB m_debugShadowData{}; + + Matrix m_debugCameraView = Matrix::Identity; + Matrix m_debugCameraProjection = Matrix::Identity; + + bool m_hasDebugShadowData = false; + bool m_captureDebugReadback = false; + bool m_drawDebugForCurrentView = false; + + uint32_t m_observedDebugFrameIndex = 0; + uint64_t m_observedDebugFrameFenceValue = 0; + bool m_hasObservedDebugFrame = false; }; \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl index 120abe1a..791b5c0b 100644 --- a/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl +++ b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl @@ -249,6 +249,10 @@ void main() float4x4 fullLightViewProjection = BuildLightViewProjection(nearDistance, farDistance); ShadowDataOutput output = BuildShadowOutput(fullLightViewProjection, shadowsEnabled); + + // cascadePadding.x = debug enabled + // cascadePadding.y = depth-fitted camera near distance + output.cascadePadding.y = nearDistance; uint activeCascadeCount = output.cascadeCount; float fittedDepthRange = farDistance - nearDistance;