Add directional shadow request planning
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user