rendering: formalize main light shadow params

This commit is contained in:
2026-04-13 01:06:09 +08:00
parent 64212a53c7
commit 2ee74e7761
8 changed files with 94 additions and 61 deletions

View File

@@ -25,6 +25,21 @@ Shader "Builtin Forward Lit"
float4 spotAnglesAndFlags; float4 spotAnglesAndFlags;
}; };
struct ShadowMapMetrics
{
float2 inverseMapSize;
float worldTexelSize;
float padding;
};
struct ShadowSamplingData
{
float enabled;
float receiverDepthBias;
float normalBiasScale;
float shadowStrength;
};
cbuffer LightingConstants cbuffer LightingConstants
{ {
float4 gMainLightDirectionAndIntensity; float4 gMainLightDirectionAndIntensity;
@@ -42,8 +57,8 @@ Shader "Builtin Forward Lit"
cbuffer ShadowReceiverConstants cbuffer ShadowReceiverConstants
{ {
float4x4 gWorldToShadowMatrix; float4x4 gWorldToShadowMatrix;
float4 gShadowBiasAndTexelSize; ShadowMapMetrics gShadowMapMetrics;
float4 gShadowOptions; ShadowSamplingData gShadowSampling;
}; };
Texture2D BaseColorTexture; Texture2D BaseColorTexture;
@@ -83,12 +98,13 @@ Shader "Builtin Forward Lit"
#ifndef XC_MAIN_LIGHT_SHADOWS #ifndef XC_MAIN_LIGHT_SHADOWS
return 1.0f; return 1.0f;
#else #else
if (gShadowOptions.x < 0.5f) { if (gShadowSampling.enabled < 0.5f) {
return 1.0f; return 1.0f;
} }
const float nDotL = saturate(dot(normalize(normalWS), normalize(lightDirectionWS))); 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 float3 shadowPositionWS = positionWS + normalize(normalWS) * normalBiasWorld;
const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f)); const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f));
if (shadowClip.w <= 0.0f) { if (shadowClip.w <= 0.0f) {
@@ -109,7 +125,7 @@ Shader "Builtin Forward Lit"
return 1.0f; 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 #else
if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || if (shadowUv.x < 0.0f || shadowUv.x > 1.0f ||
shadowUv.y < 0.0f || shadowUv.y > 1.0f || shadowUv.y < 0.0f || shadowUv.y > 1.0f ||
@@ -117,9 +133,9 @@ Shader "Builtin Forward Lit"
return 1.0f; return 1.0f;
} }
const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x; const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias;
#endif #endif
const float2 shadowTexelSize = gShadowBiasAndTexelSize.yz; const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize;
float visibility = 0.0f; float visibility = 0.0f;
[unroll] [unroll]
for (int offsetY = -1; offsetY <= 1; ++offsetY) { for (int offsetY = -1; offsetY <= 1; ++offsetY) {
@@ -139,7 +155,7 @@ Shader "Builtin Forward Lit"
} }
visibility *= (1.0f / 9.0f); 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); return lerp(1.0f - shadowStrength, 1.0f, visibility);
#endif #endif
} }

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <XCEngine/Core/Math/Vector2.h>
#include <XCEngine/Core/Math/Vector3.h> #include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h> #include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/Rendering/FrameData/RenderCameraData.h> #include <XCEngine/Rendering/FrameData/RenderCameraData.h>
@@ -50,11 +51,32 @@ struct RenderAdditionalLightData {
float spotAngle = 0.0f; 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 { struct RenderDirectionalShadowData {
bool enabled = false; bool enabled = false;
Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity(); Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity();
Math::Vector4 shadowParams = Math::Vector4::Zero(); RenderDirectionalShadowMapMetrics mapMetrics = {};
Math::Vector4 shadowOptions = Math::Vector4::Zero(); RenderDirectionalShadowSamplingData sampling = {};
RHI::RHIResourceView* shadowMap = nullptr; RHI::RHIResourceView* shadowMap = nullptr;
bool IsValid() const { bool IsValid() const {

View File

@@ -91,8 +91,8 @@ private:
struct ShadowReceiverConstants { struct ShadowReceiverConstants {
Math::Matrix4x4 worldToShadow = Math::Matrix4x4::Identity(); Math::Matrix4x4 worldToShadow = Math::Matrix4x4::Identity();
Math::Vector4 shadowBiasAndTexelSize = Math::Vector4::Zero(); RenderDirectionalShadowMapMetrics shadowMapMetrics = {};
Math::Vector4 shadowOptions = Math::Vector4::Zero(); RenderDirectionalShadowSamplingData shadowSampling = {};
}; };
struct SkyboxConstants { struct SkyboxConstants {

View File

@@ -12,6 +12,9 @@ class Scene;
namespace Rendering { 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 { struct DirectionalShadowPlanningSettings {
uint32_t mapDimension = 1024u; uint32_t mapDimension = 1024u;
float minFocusDistance = 5.0f; float minFocusDistance = 5.0f;

View File

@@ -19,6 +19,10 @@ namespace Rendering {
namespace { namespace {
constexpr float kDirectionalShadowReceiverDepthBias = 0.0015f;
constexpr float kDirectionalShadowNormalBiasScale = 1.5f;
constexpr float kDirectionalShadowStrength = 0.85f;
std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineAsset() { std::shared_ptr<const RenderPipelineAsset> CreateDefaultPipelineAsset() {
static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset = static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset =
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>(); std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
@@ -386,16 +390,14 @@ RenderDirectionalShadowData BuildDirectionalShadowData(
: (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u : (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u
? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth) ? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth)
: 0.0f); : 0.0f);
shadowData.shadowParams = Math::Vector4( shadowData.mapMetrics.inverseMapSize = Math::Vector2(
0.0015f,
1.0f / static_cast<float>(plan.mapWidth), 1.0f / static_cast<float>(plan.mapWidth),
1.0f / static_cast<float>(plan.mapHeight), 1.0f / static_cast<float>(plan.mapHeight));
0.85f); shadowData.mapMetrics.worldTexelSize = texelWorldSize;
shadowData.shadowOptions = Math::Vector4( shadowData.sampling.enabled = 1.0f;
1.0f, shadowData.sampling.receiverDepthBias = kDirectionalShadowReceiverDepthBias;
texelWorldSize, shadowData.sampling.normalBiasScale = kDirectionalShadowNormalBiasScale;
1.5f, shadowData.sampling.shadowStrength = kDirectionalShadowStrength;
0.0f);
return shadowData; return shadowData;
} }

View File

@@ -714,17 +714,12 @@ bool BuiltinForwardPipeline::DrawVisibleItem(
visibleItem.localToWorld.Inverse() visibleItem.localToWorld.Inverse()
}; };
const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting); const LightingConstants lightingConstants = BuildLightingConstants(sceneData.lighting);
const ShadowReceiverConstants shadowReceiverConstants = { ShadowReceiverConstants shadowReceiverConstants = {};
sceneData.lighting.HasMainDirectionalShadow() if (sceneData.lighting.HasMainDirectionalShadow()) {
? sceneData.lighting.mainDirectionalShadow.viewProjection shadowReceiverConstants.worldToShadow = sceneData.lighting.mainDirectionalShadow.viewProjection;
: Math::Matrix4x4::Identity(), shadowReceiverConstants.shadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics;
sceneData.lighting.HasMainDirectionalShadow() shadowReceiverConstants.shadowSampling = sceneData.lighting.mainDirectionalShadow.sampling;
? sceneData.lighting.mainDirectionalShadow.shadowParams }
: Math::Vector4::Zero(),
sceneData.lighting.HasMainDirectionalShadow()
? sceneData.lighting.mainDirectionalShadow.shadowOptions
: Math::Vector4::Zero()
};
const Resources::Material* material = ResolveMaterial(visibleItem); const Resources::Material* material = ResolveMaterial(visibleItem);
const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material); const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(sceneData, material);

View File

@@ -12,7 +12,6 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstdio>
#include <limits> #include <limits>
namespace XCEngine { namespace XCEngine {
@@ -134,12 +133,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
const Components::LightComponent& light, const Components::LightComponent& light,
const DirectionalShadowPlanningSettings& shadowSettings, const DirectionalShadowPlanningSettings& shadowSettings,
float viewportAspect) { float viewportAspect) {
std::fprintf(stderr, "[shadow-debug] enter BuildDirectionalShadowRenderPlan\n");
std::fflush(stderr);
DirectionalShadowRenderPlan plan = {}; DirectionalShadowRenderPlan plan = {};
if (!light.GetCastsShadows()) { if (!light.GetCastsShadows()) {
std::fprintf(stderr, "[shadow-debug] early out: light shadows disabled\n");
std::fflush(stderr);
return plan; return plan;
} }
@@ -173,8 +168,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
shadowSettings.maxFocusDistance); shadowSettings.maxFocusDistance);
const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f); const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f);
const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance); const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance);
std::fprintf(stderr, "[shadow-debug] built slice distances\n");
std::fflush(stderr);
std::array<Math::Vector3, 8> frustumCorners = {}; std::array<Math::Vector3, 8> frustumCorners = {};
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) { if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
@@ -216,8 +209,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
focusPoint += corner; focusPoint += corner;
} }
focusPoint /= static_cast<float>(frustumCorners.size()); focusPoint /= static_cast<float>(frustumCorners.size());
std::fprintf(stderr, "[shadow-debug] built frustum corners\n");
std::fflush(stderr);
Math::Bounds frustumWorldBounds(frustumCorners[0], Math::Vector3::Zero()); Math::Bounds frustumWorldBounds(frustumCorners[0], Math::Vector3::Zero());
for (size_t index = 1; index < frustumCorners.size(); ++index) { for (size_t index = 1; index < frustumCorners.size(); ++index) {
@@ -241,8 +232,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
shadowWorldPosition, shadowWorldPosition,
shadowRotation, shadowRotation,
Math::Vector3::One()).Inverse(); Math::Vector3::One()).Inverse();
std::fprintf(stderr, "[shadow-debug] built light view matrix\n");
std::fflush(stderr);
float minX = std::numeric_limits<float>::max(); float minX = std::numeric_limits<float>::max();
float maxX = std::numeric_limits<float>::lowest(); float maxX = std::numeric_limits<float>::lowest();
@@ -251,14 +240,10 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
float minZ = std::numeric_limits<float>::max(); float minZ = std::numeric_limits<float>::max();
float maxZ = std::numeric_limits<float>::lowest(); float maxZ = std::numeric_limits<float>::lowest();
ExpandLightSpaceBounds(view, frustumCorners, minX, maxX, minY, maxY, minZ, maxZ); 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 uint32_t cullingMask = camera.GetCullingMask();
const std::vector<Components::MeshFilterComponent*> meshFilters = const std::vector<Components::MeshFilterComponent*> meshFilters =
scene.FindObjectsOfType<Components::MeshFilterComponent>(); scene.FindObjectsOfType<Components::MeshFilterComponent>();
std::fprintf(stderr, "[shadow-debug] mesh filter count=%zu\n", meshFilters.size());
std::fflush(stderr);
for (Components::MeshFilterComponent* meshFilter : meshFilters) { for (Components::MeshFilterComponent* meshFilter : meshFilters) {
if (meshFilter == nullptr || if (meshFilter == nullptr ||
!meshFilter->IsEnabled() || !meshFilter->IsEnabled() ||
@@ -300,8 +285,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
ExpandLightSpaceBounds(view, worldCorners, minX, maxX, minY, maxY, minZ, maxZ); 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; minX -= shadowSettings.boundsPadding;
maxX += shadowSettings.boundsPadding; maxX += shadowSettings.boundsPadding;
@@ -349,8 +332,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
maxY, maxY,
minZ, minZ,
maxZ); maxZ);
std::fprintf(stderr, "[shadow-debug] built orthographic projection\n");
std::fflush(stderr);
plan.enabled = true; plan.enabled = true;
plan.lightDirection = lightDirection; plan.lightDirection = lightDirection;
@@ -369,8 +350,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
plan.cameraData.clearFlags = RenderClearFlags::Depth; plan.cameraData.clearFlags = RenderClearFlags::Depth;
plan.cameraData.viewportWidth = plan.mapWidth; plan.cameraData.viewportWidth = plan.mapWidth;
plan.cameraData.viewportHeight = plan.mapHeight; plan.cameraData.viewportHeight = plan.mapHeight;
std::fprintf(stderr, "[shadow-debug] leave BuildDirectionalShadowRenderPlan\n");
std::fflush(stderr);
return plan; return plan;
} }

View File

@@ -65,6 +65,21 @@ Shader "XC Character Toon"
float4 spotAnglesAndFlags; float4 spotAnglesAndFlags;
}; };
struct ShadowMapMetrics
{
float2 inverseMapSize;
float worldTexelSize;
float padding;
};
struct ShadowSamplingData
{
float enabled;
float receiverDepthBias;
float normalBiasScale;
float shadowStrength;
};
cbuffer LightingConstants cbuffer LightingConstants
{ {
float4 gMainLightDirectionAndIntensity; float4 gMainLightDirectionAndIntensity;
@@ -116,8 +131,8 @@ Shader "XC Character Toon"
cbuffer ShadowReceiverConstants cbuffer ShadowReceiverConstants
{ {
float4x4 gWorldToShadowMatrix; float4x4 gWorldToShadowMatrix;
float4 gShadowBiasAndTexelSize; ShadowMapMetrics gShadowMapMetrics;
float4 gShadowOptions; ShadowSamplingData gShadowSampling;
}; };
Texture2D BaseColorTexture; Texture2D BaseColorTexture;
@@ -250,7 +265,7 @@ Shader "XC Character Toon"
#ifndef XC_MAIN_LIGHT_SHADOWS #ifndef XC_MAIN_LIGHT_SHADOWS
return 1.0f; return 1.0f;
#else #else
if (gShadowOptions.x < 0.5f) { if (gShadowSampling.enabled < 0.5f) {
return 1.0f; return 1.0f;
} }
@@ -258,7 +273,8 @@ Shader "XC Character Toon"
const float3 resolvedLightDirectionWS = const float3 resolvedLightDirectionWS =
NormalizeSafe(lightDirectionWS, float3(0.0f, -1.0f, 0.0f)); NormalizeSafe(lightDirectionWS, float3(0.0f, -1.0f, 0.0f));
const float nDotL = saturate(dot(resolvedNormalWS, resolvedLightDirectionWS)); 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 float3 shadowPositionWS = positionWS + resolvedNormalWS * normalBiasWorld;
const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f)); const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f));
if (shadowClip.w <= 0.0f) { if (shadowClip.w <= 0.0f) {
@@ -279,7 +295,7 @@ Shader "XC Character Toon"
return 1.0f; 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 #else
if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || if (shadowUv.x < 0.0f || shadowUv.x > 1.0f ||
shadowUv.y < 0.0f || shadowUv.y > 1.0f || shadowUv.y < 0.0f || shadowUv.y > 1.0f ||
@@ -287,9 +303,9 @@ Shader "XC Character Toon"
return 1.0f; return 1.0f;
} }
const float receiverDepth = shadowNdc.z - gShadowBiasAndTexelSize.x; const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias;
#endif #endif
const float2 shadowTexelSize = gShadowBiasAndTexelSize.yz; const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize;
float visibility = 0.0f; float visibility = 0.0f;
[unroll] [unroll]
for (int offsetY = -1; offsetY <= 1; ++offsetY) { for (int offsetY = -1; offsetY <= 1; ++offsetY) {
@@ -309,7 +325,7 @@ Shader "XC Character Toon"
} }
visibility *= (1.0f / 9.0f); 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); return lerp(1.0f - shadowStrength, 1.0f, visibility);
#endif #endif
} }