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 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 {
RenderSurface surface;
@@ -62,6 +82,7 @@ struct CameraRenderRequest {
RenderSurface surface;
DepthOnlyRenderRequest depthOnly;
ShadowCasterRenderRequest shadowCaster;
DirectionalShadowRenderPlan directionalShadow;
ObjectIdRenderRequest objectId;
float cameraDepth = 0.0f;
uint8_t cameraStackOrder = 0;

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
#include "Rendering/SceneRenderRequestPlanner.h"
#include "Components/LightComponent.h"
#include "Core/Math/Matrix4.h"
#include "Rendering/SceneRenderRequestUtils.h"
#include "Scene/Scene.h"
@@ -9,6 +11,139 @@
namespace XCEngine {
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(
const Components::Scene& scene,
Components::CameraComponent* overrideCamera) const {
@@ -56,6 +191,23 @@ std::vector<CameraRenderRequest> SceneRenderRequestPlanner::BuildRequests(
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);
if (camera->GetStackType() == Components::CameraStackType::Base) {
++renderedBaseCameraCount;