diff --git a/engine/assets/builtin/shaders/forward-lit.shader b/engine/assets/builtin/shaders/forward-lit.shader index fbb4746f..0375418c 100644 --- a/engine/assets/builtin/shaders/forward-lit.shader +++ b/engine/assets/builtin/shaders/forward-lit.shader @@ -25,6 +25,21 @@ Shader "Builtin Forward Lit" float4 spotAnglesAndFlags; }; + struct ShadowMapMetrics + { + float2 inverseMapSize; + float worldTexelSize; + float padding; + }; + + struct ShadowSamplingData + { + float enabled; + float receiverDepthBias; + float normalBiasScale; + float shadowStrength; + }; + cbuffer LightingConstants { float4 gMainLightDirectionAndIntensity; @@ -42,8 +57,8 @@ Shader "Builtin Forward Lit" cbuffer ShadowReceiverConstants { float4x4 gWorldToShadowMatrix; - float4 gShadowBiasAndTexelSize; - float4 gShadowOptions; + ShadowMapMetrics gShadowMapMetrics; + ShadowSamplingData gShadowSampling; }; Texture2D BaseColorTexture; @@ -83,12 +98,13 @@ Shader "Builtin Forward Lit" #ifndef XC_MAIN_LIGHT_SHADOWS return 1.0f; #else - if (gShadowOptions.x < 0.5f) { + if (gShadowSampling.enabled < 0.5f) { return 1.0f; } const float nDotL = saturate(dot(normalize(normalWS), normalize(lightDirectionWS))); - const float normalBiasWorld = gShadowOptions.y * gShadowOptions.z * (1.0f - nDotL); + const float normalBiasWorld = + gShadowMapMetrics.worldTexelSize * gShadowSampling.normalBiasScale * (1.0f - nDotL); const float3 shadowPositionWS = positionWS + normalize(normalWS) * normalBiasWorld; const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f)); if (shadowClip.w <= 0.0f) { @@ -109,7 +125,7 @@ Shader "Builtin Forward Lit" return 1.0f; } - const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowBiasAndTexelSize.x; + const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowSampling.receiverDepthBias; #else if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || shadowUv.y < 0.0f || shadowUv.y > 1.0f || @@ -117,9 +133,9 @@ Shader "Builtin Forward Lit" return 1.0f; } - const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x; + const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias; #endif - const float2 shadowTexelSize = gShadowBiasAndTexelSize.yz; + const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize; float visibility = 0.0f; [unroll] for (int offsetY = -1; offsetY <= 1; ++offsetY) { @@ -139,7 +155,7 @@ Shader "Builtin Forward Lit" } visibility *= (1.0f / 9.0f); - const float shadowStrength = saturate(gShadowBiasAndTexelSize.w); + const float shadowStrength = saturate(gShadowSampling.shadowStrength); return lerp(1.0f - shadowStrength, 1.0f, visibility); #endif } diff --git a/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h b/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h index d613f5be..097d6978 100644 --- a/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h +++ b/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -50,11 +51,32 @@ struct RenderAdditionalLightData { float spotAngle = 0.0f; }; +struct RenderDirectionalShadowMapMetrics { + Math::Vector2 inverseMapSize = Math::Vector2::Zero(); + float worldTexelSize = 0.0f; + float padding = 0.0f; +}; + +static_assert( + sizeof(RenderDirectionalShadowMapMetrics) == sizeof(float) * 4u, + "RenderDirectionalShadowMapMetrics must stay float4-sized for GPU constant layout"); + +struct RenderDirectionalShadowSamplingData { + float enabled = 0.0f; + float receiverDepthBias = 0.0f; + float normalBiasScale = 0.0f; + float shadowStrength = 0.0f; +}; + +static_assert( + sizeof(RenderDirectionalShadowSamplingData) == sizeof(float) * 4u, + "RenderDirectionalShadowSamplingData must stay float4-sized for GPU constant layout"); + struct RenderDirectionalShadowData { bool enabled = false; Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity(); - Math::Vector4 shadowParams = Math::Vector4::Zero(); - Math::Vector4 shadowOptions = Math::Vector4::Zero(); + RenderDirectionalShadowMapMetrics mapMetrics = {}; + RenderDirectionalShadowSamplingData sampling = {}; RHI::RHIResourceView* shadowMap = nullptr; bool IsValid() const { diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 8bd6152a..5fb6d673 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -91,8 +91,8 @@ private: struct ShadowReceiverConstants { Math::Matrix4x4 worldToShadow = Math::Matrix4x4::Identity(); - Math::Vector4 shadowBiasAndTexelSize = Math::Vector4::Zero(); - Math::Vector4 shadowOptions = Math::Vector4::Zero(); + RenderDirectionalShadowMapMetrics shadowMapMetrics = {}; + RenderDirectionalShadowSamplingData shadowSampling = {}; }; struct SkyboxConstants { diff --git a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h index 6ad89878..33679e80 100644 --- a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h +++ b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h @@ -12,6 +12,9 @@ class Scene; namespace Rendering { +// Planning-only knobs for fitting the single main-light shadow camera to the +// current view. Receiver-side bias/filtering parameters live in runtime shadow +// data and are not configured here. struct DirectionalShadowPlanningSettings { uint32_t mapDimension = 1024u; float minFocusDistance = 5.0f; diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 824ee3d9..4d9952d9 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -19,6 +19,10 @@ namespace Rendering { namespace { +constexpr float kDirectionalShadowReceiverDepthBias = 0.0015f; +constexpr float kDirectionalShadowNormalBiasScale = 1.5f; +constexpr float kDirectionalShadowStrength = 0.85f; + std::shared_ptr CreateDefaultPipelineAsset() { static const std::shared_ptr s_defaultPipelineAsset = std::make_shared(); @@ -386,16 +390,14 @@ RenderDirectionalShadowData BuildDirectionalShadowData( : (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u ? (plan.orthographicHalfExtent * 2.0f) / static_cast(plan.mapWidth) : 0.0f); - shadowData.shadowParams = Math::Vector4( - 0.0015f, + shadowData.mapMetrics.inverseMapSize = Math::Vector2( 1.0f / static_cast(plan.mapWidth), - 1.0f / static_cast(plan.mapHeight), - 0.85f); - shadowData.shadowOptions = Math::Vector4( - 1.0f, - texelWorldSize, - 1.5f, - 0.0f); + 1.0f / static_cast(plan.mapHeight)); + shadowData.mapMetrics.worldTexelSize = texelWorldSize; + shadowData.sampling.enabled = 1.0f; + shadowData.sampling.receiverDepthBias = kDirectionalShadowReceiverDepthBias; + shadowData.sampling.normalBiasScale = kDirectionalShadowNormalBiasScale; + shadowData.sampling.shadowStrength = kDirectionalShadowStrength; return shadowData; } diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp index ba56ff7c..b00548cf 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp @@ -714,17 +714,12 @@ bool BuiltinForwardPipeline::DrawVisibleItem( visibleItem.localToWorld.Inverse() }; const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting); - const ShadowReceiverConstants shadowReceiverConstants = { - sceneData.lighting.HasMainDirectionalShadow() - ? sceneData.lighting.mainDirectionalShadow.viewProjection - : Math::Matrix4x4::Identity(), - sceneData.lighting.HasMainDirectionalShadow() - ? sceneData.lighting.mainDirectionalShadow.shadowParams - : Math::Vector4::Zero(), - sceneData.lighting.HasMainDirectionalShadow() - ? sceneData.lighting.mainDirectionalShadow.shadowOptions - : Math::Vector4::Zero() - }; + ShadowReceiverConstants shadowReceiverConstants = {}; + if (sceneData.lighting.HasMainDirectionalShadow()) { + shadowReceiverConstants.worldToShadow = sceneData.lighting.mainDirectionalShadow.viewProjection; + shadowReceiverConstants.shadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics; + shadowReceiverConstants.shadowSampling = sceneData.lighting.mainDirectionalShadow.sampling; + } const Resources::Material* material = ResolveMaterial(visibleItem); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material); diff --git a/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp b/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp index 4dee7897..1cbece7a 100644 --- a/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp +++ b/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include namespace XCEngine { @@ -134,12 +133,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( const Components::LightComponent& light, const DirectionalShadowPlanningSettings& shadowSettings, float viewportAspect) { - std::fprintf(stderr, "[shadow-debug] enter BuildDirectionalShadowRenderPlan\n"); - std::fflush(stderr); DirectionalShadowRenderPlan plan = {}; if (!light.GetCastsShadows()) { - std::fprintf(stderr, "[shadow-debug] early out: light shadows disabled\n"); - std::fflush(stderr); return plan; } @@ -173,8 +168,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( shadowSettings.maxFocusDistance); const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f); const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance); - std::fprintf(stderr, "[shadow-debug] built slice distances\n"); - std::fflush(stderr); std::array frustumCorners = {}; if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) { @@ -216,8 +209,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( focusPoint += corner; } focusPoint /= static_cast(frustumCorners.size()); - std::fprintf(stderr, "[shadow-debug] built frustum corners\n"); - std::fflush(stderr); Math::Bounds frustumWorldBounds(frustumCorners[0], Math::Vector3::Zero()); for (size_t index = 1; index < frustumCorners.size(); ++index) { @@ -241,8 +232,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( shadowWorldPosition, shadowRotation, Math::Vector3::One()).Inverse(); - std::fprintf(stderr, "[shadow-debug] built light view matrix\n"); - std::fflush(stderr); float minX = std::numeric_limits::max(); float maxX = std::numeric_limits::lowest(); @@ -251,14 +240,10 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( float minZ = std::numeric_limits::max(); float maxZ = std::numeric_limits::lowest(); ExpandLightSpaceBounds(view, frustumCorners, minX, maxX, minY, maxY, minZ, maxZ); - std::fprintf(stderr, "[shadow-debug] expanded frustum bounds\n"); - std::fflush(stderr); const uint32_t cullingMask = camera.GetCullingMask(); const std::vector meshFilters = scene.FindObjectsOfType(); - std::fprintf(stderr, "[shadow-debug] mesh filter count=%zu\n", meshFilters.size()); - std::fflush(stderr); for (Components::MeshFilterComponent* meshFilter : meshFilters) { if (meshFilter == nullptr || !meshFilter->IsEnabled() || @@ -300,8 +285,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( ExpandLightSpaceBounds(view, worldCorners, minX, maxX, minY, maxY, minZ, maxZ); } - std::fprintf(stderr, "[shadow-debug] finished mesh bounds expansion\n"); - std::fflush(stderr); minX -= shadowSettings.boundsPadding; maxX += shadowSettings.boundsPadding; @@ -349,8 +332,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( maxY, minZ, maxZ); - std::fprintf(stderr, "[shadow-debug] built orthographic projection\n"); - std::fflush(stderr); plan.enabled = true; plan.lightDirection = lightDirection; @@ -369,8 +350,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( plan.cameraData.clearFlags = RenderClearFlags::Depth; plan.cameraData.viewportWidth = plan.mapWidth; plan.cameraData.viewportHeight = plan.mapHeight; - std::fprintf(stderr, "[shadow-debug] leave BuildDirectionalShadowRenderPlan\n"); - std::fflush(stderr); return plan; } diff --git a/project/Assets/Shaders/XCCharacterToon.shader b/project/Assets/Shaders/XCCharacterToon.shader index 482daeeb..de0d4d36 100644 --- a/project/Assets/Shaders/XCCharacterToon.shader +++ b/project/Assets/Shaders/XCCharacterToon.shader @@ -65,6 +65,21 @@ Shader "XC Character Toon" float4 spotAnglesAndFlags; }; + struct ShadowMapMetrics + { + float2 inverseMapSize; + float worldTexelSize; + float padding; + }; + + struct ShadowSamplingData + { + float enabled; + float receiverDepthBias; + float normalBiasScale; + float shadowStrength; + }; + cbuffer LightingConstants { float4 gMainLightDirectionAndIntensity; @@ -116,8 +131,8 @@ Shader "XC Character Toon" cbuffer ShadowReceiverConstants { float4x4 gWorldToShadowMatrix; - float4 gShadowBiasAndTexelSize; - float4 gShadowOptions; + ShadowMapMetrics gShadowMapMetrics; + ShadowSamplingData gShadowSampling; }; Texture2D BaseColorTexture; @@ -250,7 +265,7 @@ Shader "XC Character Toon" #ifndef XC_MAIN_LIGHT_SHADOWS return 1.0f; #else - if (gShadowOptions.x < 0.5f) { + if (gShadowSampling.enabled < 0.5f) { return 1.0f; } @@ -258,7 +273,8 @@ Shader "XC Character Toon" const float3 resolvedLightDirectionWS = NormalizeSafe(lightDirectionWS, float3(0.0f, -1.0f, 0.0f)); const float nDotL = saturate(dot(resolvedNormalWS, resolvedLightDirectionWS)); - const float normalBiasWorld = gShadowOptions.y * gShadowOptions.z * (1.0f - nDotL); + const float normalBiasWorld = + gShadowMapMetrics.worldTexelSize * gShadowSampling.normalBiasScale * (1.0f - nDotL); const float3 shadowPositionWS = positionWS + resolvedNormalWS * normalBiasWorld; const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f)); if (shadowClip.w <= 0.0f) { @@ -279,7 +295,7 @@ Shader "XC Character Toon" return 1.0f; } - const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowBiasAndTexelSize.x; + const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowSampling.receiverDepthBias; #else if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || shadowUv.y < 0.0f || shadowUv.y > 1.0f || @@ -287,9 +303,9 @@ Shader "XC Character Toon" return 1.0f; } - const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x; + const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias; #endif - const float2 shadowTexelSize = gShadowBiasAndTexelSize.yz; + const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize; float visibility = 0.0f; [unroll] for (int offsetY = -1; offsetY <= 1; ++offsetY) { @@ -309,7 +325,7 @@ Shader "XC Character Toon" } visibility *= (1.0f / 9.0f); - const float shadowStrength = saturate(gShadowBiasAndTexelSize.w); + const float shadowStrength = saturate(gShadowSampling.shadowStrength); return lerp(1.0f - shadowStrength, 1.0f, visibility); #endif }