Add directional shadow request planning
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -168,6 +168,7 @@ void RenderSceneExtractor::ExtractLighting(
|
||||
|
||||
RenderDirectionalLightData lightData;
|
||||
lightData.enabled = true;
|
||||
lightData.castsShadows = mainDirectionalLight->GetCastsShadows();
|
||||
lightData.intensity = mainDirectionalLight->GetIntensity();
|
||||
lightData.color = mainDirectionalLight->GetColor();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -165,6 +165,7 @@ TEST(RenderSceneExtractor_Test, ExtractsBrightestDirectionalLightAsMainLight) {
|
||||
mainLight->SetLightType(LightType::Directional);
|
||||
mainLight->SetColor(Color(1.0f, 0.8f, 0.6f, 1.0f));
|
||||
mainLight->SetIntensity(2.5f);
|
||||
mainLight->SetCastsShadows(true);
|
||||
mainLightObject->GetTransform()->SetLocalRotation(
|
||||
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.g, 0.8f);
|
||||
EXPECT_EQ(sceneData.lighting.mainDirectionalLight.color.b, 0.6f);
|
||||
EXPECT_TRUE(sceneData.lighting.mainDirectionalLight.castsShadows);
|
||||
EXPECT_EQ(
|
||||
sceneData.lighting.mainDirectionalLight.direction,
|
||||
mainLightObject->GetTransform()->GetForward().Normalized() * -1.0f);
|
||||
@@ -357,7 +359,6 @@ TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasN
|
||||
EXPECT_EQ(ResolveMaterial(sceneData.visibleItems[0]), embeddedMaterial);
|
||||
|
||||
meshFilter->ClearMesh();
|
||||
delete embeddedMaterial;
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include <XCEngine/Rendering/SceneRenderRequestPlanner.h>
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
@@ -126,3 +127,76 @@ TEST(SceneRenderRequestPlanner_Test, BuildsRequestsAndDropsZeroSizedViewportsWit
|
||||
EXPECT_EQ(requests[1].camera, overlay);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user