Add directional shadow request planning

This commit is contained in:
2026-04-04 20:03:23 +08:00
parent 781c3b9a78
commit a548e0d0a9
6 changed files with 251 additions and 1 deletions

View File

@@ -38,6 +38,26 @@ struct ScenePassRenderRequest {
using DepthOnlyRenderRequest = ScenePassRenderRequest; using DepthOnlyRenderRequest = ScenePassRenderRequest;
using ShadowCasterRenderRequest = ScenePassRenderRequest; using ShadowCasterRenderRequest = ScenePassRenderRequest;
struct DirectionalShadowRenderPlan {
bool enabled = false;
Math::Vector3 lightDirection = Math::Vector3::Back();
Math::Vector3 focusPoint = Math::Vector3::Zero();
float orthographicHalfExtent = 0.0f;
float nearClipPlane = 0.1f;
float farClipPlane = 0.0f;
uint32_t mapWidth = 0;
uint32_t mapHeight = 0;
RenderCameraData cameraData = {};
bool IsValid() const {
return enabled &&
mapWidth > 0 &&
mapHeight > 0 &&
cameraData.viewportWidth == mapWidth &&
cameraData.viewportHeight == mapHeight;
}
};
struct ObjectIdRenderRequest { struct ObjectIdRenderRequest {
RenderSurface surface; RenderSurface surface;
@@ -62,6 +82,7 @@ struct CameraRenderRequest {
RenderSurface surface; RenderSurface surface;
DepthOnlyRenderRequest depthOnly; DepthOnlyRenderRequest depthOnly;
ShadowCasterRenderRequest shadowCaster; ShadowCasterRenderRequest shadowCaster;
DirectionalShadowRenderPlan directionalShadow;
ObjectIdRenderRequest objectId; ObjectIdRenderRequest objectId;
float cameraDepth = 0.0f; float cameraDepth = 0.0f;
uint8_t cameraStackOrder = 0; uint8_t cameraStackOrder = 0;

View File

@@ -18,6 +18,7 @@ namespace Rendering {
struct RenderDirectionalLightData { struct RenderDirectionalLightData {
bool enabled = false; bool enabled = false;
bool castsShadows = false;
Math::Vector3 direction = Math::Vector3::Back(); Math::Vector3 direction = Math::Vector3::Back();
float intensity = 1.0f; float intensity = 1.0f;
Math::Color color = Math::Color::White(); Math::Color color = Math::Color::White();

View File

@@ -168,6 +168,7 @@ void RenderSceneExtractor::ExtractLighting(
RenderDirectionalLightData lightData; RenderDirectionalLightData lightData;
lightData.enabled = true; lightData.enabled = true;
lightData.castsShadows = mainDirectionalLight->GetCastsShadows();
lightData.intensity = mainDirectionalLight->GetIntensity(); lightData.intensity = mainDirectionalLight->GetIntensity();
lightData.color = mainDirectionalLight->GetColor(); lightData.color = mainDirectionalLight->GetColor();

View File

@@ -1,5 +1,7 @@
#include "Rendering/SceneRenderRequestPlanner.h" #include "Rendering/SceneRenderRequestPlanner.h"
#include "Components/LightComponent.h"
#include "Core/Math/Matrix4.h"
#include "Rendering/SceneRenderRequestUtils.h" #include "Rendering/SceneRenderRequestUtils.h"
#include "Scene/Scene.h" #include "Scene/Scene.h"
@@ -9,6 +11,139 @@
namespace XCEngine { namespace XCEngine {
namespace Rendering { namespace Rendering {
namespace {
constexpr uint32_t kDirectionalShadowMapDimension = 1024;
constexpr float kMinShadowFocusDistance = 5.0f;
constexpr float kMaxShadowFocusDistance = 25.0f;
constexpr float kPerspectiveShadowFocusFactor = 0.25f;
constexpr float kOrthographicShadowFocusFactor = 2.0f;
constexpr float kMinShadowHalfExtent = 10.0f;
constexpr float kMaxShadowHalfExtent = 40.0f;
constexpr float kPerspectiveShadowCoverageFactor = 0.20f;
constexpr float kOrthographicShadowCoverageFactor = 2.0f;
constexpr float kShadowNearClipPlane = 0.1f;
constexpr float kMinShadowDepthRange = 20.0f;
bool IsUsableDirectionalLight(const Components::LightComponent* light) {
return light != nullptr &&
light->IsEnabled() &&
light->GetGameObject() != nullptr &&
light->GetGameObject()->IsActiveInHierarchy() &&
light->GetLightType() == Components::LightType::Directional;
}
Components::LightComponent* FindMainDirectionalLight(const Components::Scene& scene) {
const std::vector<Components::LightComponent*> lights =
scene.FindObjectsOfType<Components::LightComponent>();
Components::LightComponent* mainDirectionalLight = nullptr;
for (Components::LightComponent* light : lights) {
if (!IsUsableDirectionalLight(light)) {
continue;
}
if (mainDirectionalLight == nullptr ||
light->GetIntensity() > mainDirectionalLight->GetIntensity()) {
mainDirectionalLight = light;
}
}
return mainDirectionalLight;
}
bool ShouldPlanDirectionalShadowForCamera(
const Components::CameraComponent& camera,
size_t renderedBaseCameraCount,
size_t renderedRequestCount) {
if (camera.GetStackType() == Components::CameraStackType::Overlay) {
return renderedBaseCameraCount == 0u &&
renderedRequestCount == 0u;
}
return true;
}
DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
const Components::CameraComponent& camera,
const Components::LightComponent& light) {
DirectionalShadowRenderPlan plan = {};
if (!light.GetCastsShadows()) {
return plan;
}
Math::Vector3 lightDirection = light.transform().GetForward() * -1.0f;
if (lightDirection.SqrMagnitude() <= Math::EPSILON) {
lightDirection = Math::Vector3::Back();
} else {
lightDirection = lightDirection.Normalized();
}
const Math::Vector3 viewForward = camera.transform().GetForward().SqrMagnitude() <= Math::EPSILON
? Math::Vector3::Forward()
: camera.transform().GetForward().Normalized();
const Math::Vector3 cameraPosition = camera.transform().GetPosition();
const float focusDistance = camera.GetProjectionType() == Components::CameraProjectionType::Perspective
? std::clamp(
camera.GetFarClipPlane() * kPerspectiveShadowFocusFactor,
kMinShadowFocusDistance,
kMaxShadowFocusDistance)
: std::clamp(
camera.GetOrthographicSize() * kOrthographicShadowFocusFactor,
kMinShadowFocusDistance,
kMaxShadowFocusDistance);
const Math::Vector3 focusPoint = cameraPosition + viewForward * focusDistance;
const float shadowHalfExtent = camera.GetProjectionType() == Components::CameraProjectionType::Perspective
? std::clamp(
camera.GetFarClipPlane() * kPerspectiveShadowCoverageFactor,
kMinShadowHalfExtent,
kMaxShadowHalfExtent)
: std::clamp(
camera.GetOrthographicSize() * kOrthographicShadowCoverageFactor,
kMinShadowHalfExtent,
kMaxShadowHalfExtent);
const float shadowDepthRange = std::max(shadowHalfExtent * 4.0f, kMinShadowDepthRange);
const Math::Vector3 shadowWorldPosition =
focusPoint - lightDirection * (shadowDepthRange * 0.5f);
Math::Vector3 shadowUp = Math::Vector3::Up();
if (std::abs(Math::Vector3::Dot(lightDirection, shadowUp)) > 0.98f) {
shadowUp = Math::Vector3::Forward();
}
const Math::Matrix4x4 view =
Math::Matrix4x4::LookAt(shadowWorldPosition, focusPoint, shadowUp);
const Math::Matrix4x4 projection = Math::Matrix4x4::Orthographic(
-shadowHalfExtent,
shadowHalfExtent,
-shadowHalfExtent,
shadowHalfExtent,
kShadowNearClipPlane,
shadowDepthRange);
plan.enabled = true;
plan.lightDirection = lightDirection;
plan.focusPoint = focusPoint;
plan.orthographicHalfExtent = shadowHalfExtent;
plan.nearClipPlane = kShadowNearClipPlane;
plan.farClipPlane = shadowDepthRange;
plan.mapWidth = kDirectionalShadowMapDimension;
plan.mapHeight = kDirectionalShadowMapDimension;
plan.cameraData.view = view.Transpose();
plan.cameraData.projection = projection.Transpose();
plan.cameraData.viewProjection = (projection * view).Transpose();
plan.cameraData.worldPosition = shadowWorldPosition;
plan.cameraData.clearColor = Math::Color::Black();
plan.cameraData.clearFlags = RenderClearFlags::Depth;
plan.cameraData.viewportWidth = plan.mapWidth;
plan.cameraData.viewportHeight = plan.mapHeight;
return plan;
}
} // namespace
std::vector<Components::CameraComponent*> SceneRenderRequestPlanner::CollectCameras( std::vector<Components::CameraComponent*> SceneRenderRequestPlanner::CollectCameras(
const Components::Scene& scene, const Components::Scene& scene,
Components::CameraComponent* overrideCamera) const { Components::CameraComponent* overrideCamera) const {
@@ -56,6 +191,23 @@ std::vector<CameraRenderRequest> SceneRenderRequestPlanner::BuildRequests(
continue; continue;
} }
if (ShouldPlanDirectionalShadowForCamera(
*camera,
renderedBaseCameraCount,
requests.size())) {
if (Components::LightComponent* mainDirectionalLight = FindMainDirectionalLight(scene);
mainDirectionalLight != nullptr &&
mainDirectionalLight->GetCastsShadows()) {
request.directionalShadow =
BuildDirectionalShadowRenderPlan(*camera, *mainDirectionalLight);
if (request.directionalShadow.IsValid()) {
request.shadowCaster.clearFlags = RenderClearFlags::Depth;
request.shadowCaster.hasCameraDataOverride = true;
request.shadowCaster.cameraDataOverride = request.directionalShadow.cameraData;
}
}
}
requests.push_back(request); requests.push_back(request);
if (camera->GetStackType() == Components::CameraStackType::Base) { if (camera->GetStackType() == Components::CameraStackType::Base) {
++renderedBaseCameraCount; ++renderedBaseCameraCount;

View File

@@ -165,6 +165,7 @@ TEST(RenderSceneExtractor_Test, ExtractsBrightestDirectionalLightAsMainLight) {
mainLight->SetLightType(LightType::Directional); mainLight->SetLightType(LightType::Directional);
mainLight->SetColor(Color(1.0f, 0.8f, 0.6f, 1.0f)); mainLight->SetColor(Color(1.0f, 0.8f, 0.6f, 1.0f));
mainLight->SetIntensity(2.5f); mainLight->SetIntensity(2.5f);
mainLight->SetCastsShadows(true);
mainLightObject->GetTransform()->SetLocalRotation( mainLightObject->GetTransform()->SetLocalRotation(
Quaternion::LookRotation(Vector3(-0.3f, -1.0f, -0.2f).Normalized())); Quaternion::LookRotation(Vector3(-0.3f, -1.0f, -0.2f).Normalized()));
@@ -177,6 +178,7 @@ TEST(RenderSceneExtractor_Test, ExtractsBrightestDirectionalLightAsMainLight) {
EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.r, 1.0f); EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.r, 1.0f);
EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.g, 0.8f); EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.g, 0.8f);
EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.b, 0.6f); EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.b, 0.6f);
EXPECT_TRUE(sceneData.lighting.mainDirectionalLight.castsShadows);
EXPECT_EQ( EXPECT_EQ(
sceneData.lighting.mainDirectionalLight.direction, sceneData.lighting.mainDirectionalLight.direction,
mainLightObject->GetTransform()->GetForward().Normalized() * -1.0f); mainLightObject->GetTransform()->GetForward().Normalized() * -1.0f);
@@ -357,7 +359,6 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
EXPECT_EQ(ResolveMaterial(sceneData.visibleItems[0]), embeddedMaterial); EXPECT_EQ(ResolveMaterial(sceneData.visibleItems[0]), embeddedMaterial);
meshFilter->ClearMesh(); meshFilter->ClearMesh();
delete embeddedMaterial;
delete mesh; delete mesh;
} }

View File

@@ -1,6 +1,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <XCEngine/Components/CameraComponent.h> #include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/LightComponent.h>
#include <XCEngine/Rendering/SceneRenderRequestPlanner.h> #include <XCEngine/Rendering/SceneRenderRequestPlanner.h>
#include <XCEngine/Scene/Scene.h> #include <XCEngine/Scene/Scene.h>
@@ -126,3 +127,76 @@ TEST(SceneRenderRequestPlanner_Test, BuildsRequestsAndDropsZeroSizedViewportsWit
EXPECT_EQ(requests[1].camera, overlay); EXPECT_EQ(requests[1].camera, overlay);
EXPECT_EQ(requests[1].clearFlags, RenderClearFlags::Depth); EXPECT_EQ(requests[1].clearFlags, RenderClearFlags::Depth);
} }
TEST(SceneRenderRequestPlanner_Test, BuildsDirectionalShadowPlanForBaseCameraWhenMainDirectionalLightCastsShadows) {
Scene scene("SceneRenderRequestPlannerDirectionalShadow");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetStackType(CameraStackType::Base);
camera->SetDepth(1.0f);
GameObject* shadowLightObject = scene.CreateGameObject("ShadowLight");
auto* shadowLight = shadowLightObject->AddComponent<LightComponent>();
shadowLight->SetLightType(LightType::Directional);
shadowLight->SetIntensity(2.5f);
shadowLight->SetCastsShadows(true);
SceneRenderRequestPlanner planner;
const std::vector<CameraRenderRequest> requests = planner.BuildRequests(
scene,
nullptr,
CreateValidContext(),
RenderSurface(640, 360));
ASSERT_EQ(requests.size(), 1u);
const CameraRenderRequest& request = requests[0];
EXPECT_EQ(request.camera, camera);
ASSERT_TRUE(request.directionalShadow.IsValid());
EXPECT_TRUE(request.directionalShadow.enabled);
EXPECT_EQ(request.directionalShadow.mapWidth, 1024u);
EXPECT_EQ(request.directionalShadow.mapHeight, 1024u);
EXPECT_EQ(request.directionalShadow.lightDirection, XCEngine::Math::Vector3::Back());
EXPECT_GT(request.directionalShadow.focusPoint.z, 0.0f);
EXPECT_TRUE(request.shadowCaster.hasCameraDataOverride);
EXPECT_EQ(request.shadowCaster.clearFlags, RenderClearFlags::Depth);
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportWidth, 1024u);
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 1024u);
EXPECT_EQ(request.shadowCaster.cameraDataOverride.clearFlags, RenderClearFlags::Depth);
}
TEST(SceneRenderRequestPlanner_Test, SkipsDirectionalShadowPlanForOverlayCameraWhenBaseCameraExists) {
Scene scene("SceneRenderRequestPlannerOverlayShadow");
GameObject* baseCameraObject = scene.CreateGameObject("BaseCamera");
auto* baseCamera = baseCameraObject->AddComponent<CameraComponent>();
baseCamera->SetStackType(CameraStackType::Base);
baseCamera->SetDepth(1.0f);
GameObject* overlayCameraObject = scene.CreateGameObject("OverlayCamera");
auto* overlayCamera = overlayCameraObject->AddComponent<CameraComponent>();
overlayCamera->SetStackType(CameraStackType::Overlay);
overlayCamera->SetDepth(2.0f);
GameObject* shadowLightObject = scene.CreateGameObject("ShadowLight");
auto* shadowLight = shadowLightObject->AddComponent<LightComponent>();
shadowLight->SetLightType(LightType::Directional);
shadowLight->SetIntensity(3.0f);
shadowLight->SetCastsShadows(true);
SceneRenderRequestPlanner planner;
const std::vector<CameraRenderRequest> requests = planner.BuildRequests(
scene,
nullptr,
CreateValidContext(),
RenderSurface(640, 360));
ASSERT_EQ(requests.size(), 2u);
EXPECT_EQ(requests[0].camera, baseCamera);
EXPECT_TRUE(requests[0].directionalShadow.enabled);
EXPECT_TRUE(requests[0].shadowCaster.hasCameraDataOverride);
EXPECT_EQ(requests[1].camera, overlayCamera);
EXPECT_FALSE(requests[1].directionalShadow.enabled);
EXPECT_FALSE(requests[1].shadowCaster.hasCameraDataOverride);
}