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/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..5d390704 100644 --- a/Engine_Master_UPC/Engine.vcxproj +++ b/Engine_Master_UPC/Engine.vcxproj @@ -305,6 +305,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -318,6 +319,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -766,6 +768,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -818,6 +821,7 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + @@ -1009,6 +1013,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 @@ -1161,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/LightComponent.cpp b/Engine_Master_UPC/LightComponent.cpp index 128d062b..22b2e91e 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; + } } } @@ -78,7 +82,7 @@ std::unique_ptr LightComponent::clone(GameObject* newOwner) const newComponent->m_data = m_data; - return newComponent; + return newComponent; } void LightComponent::setTypeDirectional() @@ -136,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); } @@ -190,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; } @@ -280,13 +284,76 @@ 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 (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; + + 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."); } } - if (lightChanged) + if (lightChanged) { sanitize(); } @@ -321,6 +388,32 @@ 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); + } + + archive.serialize(m_data.shadow.cascadeDebugEnabled, "ShadowCascadeDebugEnabled"); + float radius = 0.0f; float innerAngle = 0.0f; float outerAngle = 0.0f; @@ -370,7 +463,7 @@ void LightComponent::serialize(IArchive& archive) } void LightComponent::debugDraw() { - if ( !isActive() || !m_owner->GetActive()) + if (!isActive() || !m_owner->GetActive()) { return; } @@ -444,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 caf510b3..062653b5 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); @@ -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,57 +153,60 @@ float3 computeIndirectLighting(in float3 R, in float NdotV, in float3 N, in floa return lerp(diffuse + dielectricSpecular, metalSpecular, metallic); } + //--------------------// //----------SHADOW MAPPING----------// -float EvaluateShadowSample(float2 shadowUV, float currentDepth) + +float4x4 GetCascadeViewProjection(uint cascadeIndex) { - float closestDepth = shadowMap.Sample(linearClampSample, shadowUV).r; + if (cascadeIndex == 0) + return cascadeLightViewProjection[0]; + + if (cascadeIndex == 1) + return cascadeLightViewProjection[1]; + + if (cascadeIndex == 2) + return cascadeLightViewProjection[2]; - return currentDepth - shadowBias > closestDepth - ? 1.0f - shadowStrength - : 1.0f; + 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 +216,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,8 +231,64 @@ float ComputeShadow(float3 worldPos) return shadowSum / sampleCount; } + +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) @@ -231,6 +297,7 @@ float SampleSSAO(float4 screenPosition) } float2 ssaoUV = screenPosition.xy * invScreenSize; + return ssaoTexture.Sample(pointClampSample, ssaoUV).r; } @@ -238,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; @@ -289,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; @@ -307,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); @@ -318,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); @@ -333,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 d0ec0dfb..2a1f9276 100644 --- a/Engine_Master_UPC/LightingCBuffers.hlsli +++ b/Engine_Master_UPC/LightingCBuffers.hlsli @@ -15,10 +15,13 @@ cbuffer SceneData : register(b1) #define MAX_POINT_LIGHTS 112 #define MAX_SPOT_LIGHTS 16 +#define MAX_SHADOW_CASCADES 4 + struct DirectionalLight { float3 direction; float pad0; + float3 color; float intensity; }; @@ -27,6 +30,7 @@ struct PointLight { float3 position; float radius; + float3 color; float intensity; }; @@ -65,13 +69,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/Lights.h b/Engine_Master_UPC/Lights.h index 361c0f34..3075d364 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,15 @@ 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; + static constexpr bool DEFAULT_SHADOW_CASCADE_DEBUG_ENABLED = false; + }; enum class LightType : uint8_t @@ -59,6 +70,13 @@ 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; + bool cascadeDebugEnabled = LightDefaults::DEFAULT_SHADOW_CASCADE_DEBUG_ENABLED; }; struct DirectionalLightParameters diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index f6d7dedf..6278d232 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)); @@ -88,7 +88,10 @@ bool ModuleRender::init() m_renderPasses.push_back(std::make_unique(device)); m_skinningComputePass = std::make_unique(device); - m_shadowMapPass = 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); m_ssaoBlurPass = std::make_unique(device); @@ -127,9 +130,6 @@ void ModuleRender::preRender() { PERF_RENDER("ModuleRender::preRender"); - m_shadowMapRenderedThisFrame = false; - m_currentShadowData = nullptr; - if (m_pendingStopSimulation) { app->getModuleD3D12()->getCommandQueue()->flush(); @@ -164,9 +164,6 @@ void ModuleRender::preRender() #ifndef GAME_RELEASE { - m_shadowMapRenderedThisFrame = false; - m_currentShadowData = nullptr; - auto* commandList = app->getModuleD3D12()->getCommandList(); PERF_RENDER("ModuleRender::RenderViewports"); @@ -235,6 +232,8 @@ bool ModuleRender::cleanUp() m_ssaoPass.reset(); m_ssaoGeometryPass.reset(); m_shadowMapPass.reset(); + m_shadowFrustumComputePass.reset(); + m_depthReductionPass.reset(); m_skinningComputePass.reset(); m_renderPasses.clear(); @@ -434,20 +433,55 @@ 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); + } + } + + { + 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) + if (m_shadowMapPass != nullptr) { - if (!m_shadowMapRenderedThisFrame) + if (m_shadowFrustumComputePass != nullptr) { - m_shadowMapPass->prepare(ctx); - m_shadowMapPass->apply(commandList); + m_shadowFrustumComputePass->prepare(ctx); + } + + if (m_shadowFrustumComputePass != nullptr && + m_shadowFrustumComputePass->isEnabled() && + m_depthReductionPass != nullptr) + { + m_depthReductionPass->prepare(ctx); + m_depthReductionPass->apply(commandList); - m_currentShadowData = &m_shadowMapPass->getFrameData(); - m_shadowMapRenderedThisFrame = true; + m_shadowFrustumComputePass->apply(commandList); } - ctx.shadowData = m_currentShadowData; + m_shadowMapPass->prepare(ctx); + m_shadowMapPass->apply(commandList); + + ctx.shadowData = &m_shadowMapPass->getFrameData(); } } @@ -461,6 +495,7 @@ void ModuleRender::renderScene(ID3D12GraphicsCommandList4* commandList, const Re } } + { PERF_RENDER("ModuleRender::renderScene::SSAOPass"); @@ -502,17 +537,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); } } @@ -521,6 +555,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/ModuleRender.h b/Engine_Master_UPC/ModuleRender.h index ed2acbb8..ca5f2759 100644 --- a/Engine_Master_UPC/ModuleRender.h +++ b/Engine_Master_UPC/ModuleRender.h @@ -15,6 +15,8 @@ #include "SSAOGeometryPass.h" #include "SSAOPass.h" #include "SSAOBlurPass.h" +#include "DepthReductionPass.h" +#include "ShadowFrustumComputePass.h" using Microsoft::WRL::ComPtr; @@ -87,13 +89,13 @@ class ModuleRender : public Module SkyBoxPass* m_skyBoxPass; 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; std::unique_ptr m_ssaoBlurPass; - bool m_shadowMapRenderedThisFrame = false; - const ShadowFrameData* m_currentShadowData = nullptr; SSAOFrameData m_currentSSAOData{}; public: diff --git a/Engine_Master_UPC/ModuleResources.cpp b/Engine_Master_UPC/ModuleResources.cpp index 68a11e32..d46a2162 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) { @@ -146,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; @@ -154,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; @@ -166,6 +169,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..4512ee04 100644 --- a/Engine_Master_UPC/ModuleResources.h +++ b/Engine_Master_UPC/ModuleResources.h @@ -56,7 +56,8 @@ 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/ShadowFrustumComputePass.cpp b/Engine_Master_UPC/ShadowFrustumComputePass.cpp new file mode 100644 index 00000000..2551777f --- /dev/null +++ b/Engine_Master_UPC/ShadowFrustumComputePass.cpp @@ -0,0 +1,696 @@ +#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 "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; + + 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); + + CD3DX12_ROOT_PARAMETER rootParameters[3] = {}; + + // t0: final 1x1 min/max depth texture + rootParameters[0].InitAsDescriptorTable(1, &minMaxRange, D3D12_SHADER_VISIBILITY_ALL); + + // 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); + + 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 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_outputBufferState = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; +} + +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(); + + 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) +{ + refreshDebugReadbackForCurrentFrame(); + + m_enabled = false; + m_hasValidResult = false; + m_captureDebugReadback = false; + m_drawDebugForCurrentView = false; + m_constants = {}; + + if (m_depthReductionPass == nullptr) + { + return; + } + + const LightComponent* light = findMainShadowCastingDirectionalLight(); + + if (light == nullptr) + { + return; + } + + 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; + + if (transform == nullptr) + { + return; + } + + Vector3 lightDirection = transform->getForward(); + + if (lightDirection.LengthSquared() <= 0.000001f) + { + return; + } + + 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.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 = ctx.viewType == RenderViewType::Game && shadowSettings.cascadeDebugEnabled ? 1u : 0u; + m_constants.cascadePadding = Vector2::Zero; + + m_enabled = true; +} + +void ShadowFrustumComputePass::transitionOutputBuffer(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState) +{ + if (commandList == nullptr || m_shadowDataBuffer == nullptr || m_outputBufferState == newState) + { + return; + } + + CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition( + m_shadowDataBuffer.Get(), + m_outputBufferState, + newState); + + commandList->ResourceBarrier(1, &barrier); + + m_outputBufferState = newState; +} + +void ShadowFrustumComputePass::apply(ID3D12GraphicsCommandList4* commandList) +{ + BEGIN_EVENT(commandList, "ShadowFrustumComputePass"); + + m_hasValidResult = false; + + if (commandList == nullptr || + !m_enabled || + m_depthReductionPass == nullptr || + m_shadowDataBuffer == 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_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()); + commandList->ResourceBarrier(1, &uavBarrier); + + 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) + { + return 0; + } + + return m_shadowDataBuffer->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..cbcff925 --- /dev/null +++ b/Engine_Master_UPC/ShadowFrustumComputePass.h @@ -0,0 +1,132 @@ +#pragma once + +#include "IRenderPass.h" +#include "IDebugDrawable.h" +#include "SimpleMath.h" +#include "ShadowTypes.h" + +#include +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +class DepthReductionPass; +class LightComponent; + +class ShadowFrustumComputePass final : public IRenderPass, public IDebugDrawable +{ +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; + + 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; + + 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; + 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() 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 + { + 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; + +private: + void createRootSignature(); + void createPipelineState(); + void createOutputBuffer(); + + void createDebugReadbackBuffers(); + void refreshDebugReadbackForCurrentFrame(); + void recordDebugReadback(ID3D12GraphicsCommandList4* commandList); + + 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_shadowDataBuffer; + + D3D12_RESOURCE_STATES m_outputBufferState = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + + FrustumConstants m_constants{}; + + 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 new file mode 100644 index 00000000..791b5c0b --- /dev/null +++ b/Engine_Master_UPC/ShadowFrustumComputeShader.hlsl @@ -0,0 +1,309 @@ +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; + float shadowStrength; + uint shadowsEnabled; + float paddingShadow; + + float2 shadowMapTexelSize; + uint pcfEnabled; + uint pcfRadius; + + // CSM + uint cascadeCount; + uint cascadeFitMode; + float2 cascadePadding; + + float4 cascadeFarDistances; + + float4x4 cascadeLightViewProjection[MAX_SHADOW_CASCADES]; +}; + +RWStructuredBuffer outputShadowData : register(u0); + +cbuffer ShadowFrustumParams : register(b0) +{ + float4x4 inverseView; + float4x4 cameraProjection; + + float3 lightDirection; + float sunDistance; + + float minOrthoSize; + float3 padding; + + float shadowBias; + float shadowStrength; + uint shadowsEnabled; + uint pcfEnabled; + + uint pcfRadius; + float shadowMapTexelSizeX; + float shadowMapTexelSizeY; + float paddingSettings; + + uint cascadeCount; + uint cascadeFitMode; + float cascadeSplit0; + float cascadeSplit1; + + float cascadeSplit2; + uint cascadeDebugEnabled; + float2 cascadePadding; +}; + +float LinearizeViewDepth(float deviceDepth) +{ + 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; + + 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 + ); +} + +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) +{ + 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; + + output.cascadeCount = clamp(cascadeCount, 1u, (uint) MAX_SHADOW_CASCADES); + output.cascadeFitMode = cascadeFitMode; + + // x = debug cascade tint enabled + // y = unused + output.cascadePadding = float2(cascadeDebugEnabled != 0u ? 1.0f : 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; +} + +[numthreads(1, 1, 1)] +void main() +{ + float2 minMaxDepth = inputMinMax.Load(int3(0, 0, 0)); + + if (minMaxDepth.x > minMaxDepth.y) + { + 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 farDistance = max(-farViewZ, nearDistance + 0.0001f); + + // Preserve the current full fitted shadow frustum. + 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; + + // Cascade 0 + float cascade0FarFraction = activeCascadeCount > 1 ? cascadeSplit0 : 1.0f; + float cascade0NearDistance = nearDistance; + float cascade0FarDistance = nearDistance + fittedDepthRange * cascade0FarFraction; + + 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 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] = output; +} \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowMapPass.cpp b/Engine_Master_UPC/ShadowMapPass.cpp index d60eda4a..0d22066a 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,11 +28,11 @@ #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); - + createCascadeShadowMap(1u, 1u); createRootSignature(); createPipelineState(); } @@ -86,16 +87,75 @@ 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[1] = {}; + CD3DX12_ROOT_PARAMETER rootParameters[3] = {}; + // 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); + + // 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), @@ -202,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; @@ -228,256 +292,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; - } + m_activeCascadeCount = std::clamp(shadowSettings.cascadeCount, 1u, MAX_SHADOW_CASCADES); - const GameObject* lightOwner = light.getOwner(); - const Transform* lightTransform = lightOwner != nullptr ? lightOwner->GetTransform() : nullptr; + resizeCascadeShadowMapIfNeeded(shadowSettings.shadowMapSize, m_activeCascadeCount); - 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()); - } -} - - -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()); - - outMax = Vector3( - std::numeric_limits::lowest(), - std::numeric_limits::lowest(), - std::numeric_limits::lowest()); - - for (MeshRenderer* renderer : m_meshRenderers) - { - 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(); + const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = m_shadowFrustumComputePass->getShadowDataBufferAddress(); - if (boundsRadius < SHADOW_MIN_ORTHO_SIZE * 0.5f) + if (shadowDataAddress == 0) { - boundsRadius = SHADOW_MIN_ORTHO_SIZE * 0.5f; + prepareDisabledShadowData(ctx); + return; } - 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); + m_frameData = {}; + m_frameData.enabled = true; + m_frameData.shadowCBAddress = shadowDataAddress; - if (std::abs(lightDirection.y) > 0.95f) + if (m_shadowMap != nullptr && m_shadowMap->hasSRV()) { - up = Vector3(0.0f, 0.0f, 1.0f); + m_frameData.shadowMapSRV = m_shadowMap->getSRV().gpu; } - 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) + if (m_cascadeShadowMap != nullptr && m_cascadeShadowMap->hasSRV()) { - 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); - + m_frameData.cascadeShadowMapSRV = m_cascadeShadowMap->getSRV().gpu; } - - 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; } void ShadowMapPass::renderCasters(ID3D12GraphicsCommandList4* commandList) @@ -547,14 +398,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, @@ -612,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(); @@ -627,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"); @@ -637,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; @@ -645,17 +524,69 @@ 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; } - transitionShadowMap(commandList, D3D12_RESOURCE_STATE_DEPTH_WRITE); + const D3D12_GPU_VIRTUAL_ADDRESS shadowDataAddress = + m_frameData.shadowCBAddress; - commandList->RSSetViewports(1, &m_viewport); - commandList->RSSetScissorRects(1, &m_scissorRect); + if (shadowDataAddress == 0) + { + transitionShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); - D3D12_CPU_DESCRIPTOR_HANDLE shadowDSV = m_shadowMap->getDSV().cpu; + transitionCascadeShadowMap( + commandList, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + END_EVENT(commandList); + return; + } + + 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); + + + // ------------------------------------------------- + // Legacy full fitted shadow map. + // Kept temporarily so Deferred remains unchanged + // until Commit 5. + // ------------------------------------------------- + + transitionShadowMap( + commandList, + D3D12_RESOURCE_STATE_DEPTH_WRITE); + + D3D12_CPU_DESCRIPTOR_HANDLE shadowDSV = + m_shadowMap->getDSV().cpu; commandList->OMSetRenderTargets( 0, @@ -671,14 +602,66 @@ void ShadowMapPass::apply(ID3D12GraphicsCommandList4* commandList) 0, nullptr); - commandList->SetPipelineState(m_pipelineState.Get()); - commandList->SetGraphicsRootSignature(m_rootSignature.Get()); - - commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + commandList->SetGraphicsRoot32BitConstant( + 2, + MAX_SHADOW_CASCADES, + 0); renderCasters(commandList); - transitionShadowMap(commandList, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + 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 7568bc6f..af1334bf 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; @@ -34,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: @@ -44,13 +48,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); - void renderCasters(ID3D12GraphicsCommandList4* commandList); void renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, MeshRenderer& renderer); void transitionShadowMap(ID3D12GraphicsCommandList4* commandList, D3D12_RESOURCE_STATES newState); @@ -60,12 +57,12 @@ 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; - 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; @@ -73,10 +70,20 @@ 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; + 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 eec46c2f..07caa714 100644 --- a/Engine_Master_UPC/ShadowMapVertexShader.hlsl +++ b/Engine_Master_UPC/ShadowMapVertexShader.hlsl @@ -1,9 +1,74 @@ +#define MAX_SHADOW_CASCADES 4 +#define FULL_FRUSTUM_MATRIX_INDEX 4 + cbuffer ShadowDrawData : register(b0) { - float4x4 mvp; + float4x4 model; +}; + +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 { - return mul(float4(position, 1.0f), mvp); + float4 worldPosition = + mul( + float4(position, 1.0f), + model); + + float4x4 shadowViewProjection = + GetShadowViewProjection(); + + return mul( + worldPosition, + shadowViewProjection); } \ No newline at end of file diff --git a/Engine_Master_UPC/ShadowTypes.h b/Engine_Master_UPC/ShadowTypes.h index 8d3d76ec..a26586e6 100644 --- a/Engine_Master_UPC/ShadowTypes.h +++ b/Engine_Master_UPC/ShadowTypes.h @@ -6,9 +6,21 @@ using Matrix = DirectX::SimpleMath::Matrix; using Vector2 = DirectX::SimpleMath::Vector2; +using Vector4 = DirectX::SimpleMath::Vector4; + +static constexpr uint32_t MAX_SHADOW_CASCADES = 4; + +enum class ShadowCascadeFitMode : uint32_t +{ + FIT_TO_SCENE = 0, + FIT_TO_CASCADE = 1 +}; 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; @@ -16,20 +28,40 @@ 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; - Matrix lightView = Matrix::Identity; - Matrix lightProjection = Matrix::Identity; - Matrix lightViewProjection = Matrix::Identity; - 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 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;