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;
};
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
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <XCEngine/Core/Math/Vector2.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/Rendering/FrameData/RenderCameraData.h>
@@ -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 {

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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<const RenderPipelineAsset> CreateDefaultPipelineAsset() {
static const std::shared_ptr<const RenderPipelineAsset> s_defaultPipelineAsset =
std::make_shared<Pipelines::BuiltinForwardPipelineAsset>();
@@ -386,16 +390,14 @@ RenderDirectionalShadowData BuildDirectionalShadowData(
: (plan.orthographicHalfExtent > Math::EPSILON && plan.mapWidth > 0u
? (plan.orthographicHalfExtent * 2.0f) / static_cast<float>(plan.mapWidth)
: 0.0f);
shadowData.shadowParams = Math::Vector4(
0.0015f,
shadowData.mapMetrics.inverseMapSize = Math::Vector2(
1.0f / static_cast<float>(plan.mapWidth),
1.0f / static_cast<float>(plan.mapHeight),
0.85f);
shadowData.shadowOptions = Math::Vector4(
1.0f,
texelWorldSize,
1.5f,
0.0f);
1.0f / static_cast<float>(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;
}

View File

@@ -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);

View File

@@ -12,7 +12,6 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdio>
#include <limits>
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<Math::Vector3, 8> frustumCorners = {};
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
@@ -216,8 +209,6 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
focusPoint += corner;
}
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());
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<float>::max();
float maxX = std::numeric_limits<float>::lowest();
@@ -251,14 +240,10 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
float minZ = std::numeric_limits<float>::max();
float maxZ = std::numeric_limits<float>::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<Components::MeshFilterComponent*> meshFilters =
scene.FindObjectsOfType<Components::MeshFilterComponent>();
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;
}