382 lines
16 KiB
C++
382 lines
16 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/LightComponent.h>
|
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
|
#include <XCEngine/Core/Asset/IResource.h>
|
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
|
#include <XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
|
|
using namespace XCEngine::Components;
|
|
using namespace XCEngine::Core;
|
|
using namespace XCEngine::Resources;
|
|
using namespace XCEngine::Rendering;
|
|
|
|
namespace {
|
|
|
|
RenderContext CreateValidContext() {
|
|
RenderContext context;
|
|
context.device = reinterpret_cast<XCEngine::RHI::RHIDevice*>(1);
|
|
context.commandList = reinterpret_cast<XCEngine::RHI::RHICommandList*>(1);
|
|
context.commandQueue = reinterpret_cast<XCEngine::RHI::RHICommandQueue*>(1);
|
|
return context;
|
|
}
|
|
|
|
Mesh* CreatePlannerTestMesh(const char* path, const XCEngine::Math::Bounds& bounds) {
|
|
auto* mesh = new Mesh();
|
|
IResource::ConstructParams params = {};
|
|
params.name = "PlannerTestMesh";
|
|
params.path = path;
|
|
params.guid = ResourceGUID::Generate(path);
|
|
mesh->Initialize(params);
|
|
mesh->SetBounds(bounds);
|
|
return mesh;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(SceneRenderRequestPlanner_Test, FallsBackToSceneCamerasWhenOverrideCameraIsNotUsable) {
|
|
Scene scene("SceneRenderRequestPlannerOverrideFallback");
|
|
|
|
GameObject* overrideObject = scene.CreateGameObject("Override");
|
|
auto* overrideCamera = overrideObject->AddComponent<CameraComponent>();
|
|
overrideCamera->SetDepth(-10.0f);
|
|
overrideCamera->SetEnabled(false);
|
|
|
|
GameObject* sceneCameraObject = scene.CreateGameObject("SceneCamera");
|
|
auto* sceneCamera = sceneCameraObject->AddComponent<CameraComponent>();
|
|
sceneCamera->SetDepth(3.0f);
|
|
|
|
SceneRenderRequestPlanner planner;
|
|
const std::vector<CameraComponent*> cameras = planner.CollectCameras(scene, overrideCamera);
|
|
|
|
ASSERT_EQ(cameras.size(), 1u);
|
|
EXPECT_EQ(cameras[0], sceneCamera);
|
|
}
|
|
|
|
TEST(SceneRenderRequestPlanner_Test, UsesOverrideCameraExclusivelyWhenItIsUsable) {
|
|
Scene scene("SceneRenderRequestPlannerOverrideOnly");
|
|
|
|
GameObject* overrideObject = scene.CreateGameObject("Override");
|
|
auto* overrideCamera = overrideObject->AddComponent<CameraComponent>();
|
|
overrideCamera->SetDepth(9.0f);
|
|
|
|
GameObject* sceneCameraObject = scene.CreateGameObject("SceneCamera");
|
|
auto* sceneCamera = sceneCameraObject->AddComponent<CameraComponent>();
|
|
sceneCamera->SetDepth(-5.0f);
|
|
|
|
SceneRenderRequestPlanner planner;
|
|
const std::vector<CameraComponent*> cameras = planner.CollectCameras(scene, overrideCamera);
|
|
|
|
ASSERT_EQ(cameras.size(), 1u);
|
|
EXPECT_EQ(cameras[0], overrideCamera);
|
|
}
|
|
|
|
TEST(SceneRenderRequestPlanner_Test, CollectsOnlyUsableSceneCamerasInStableRenderOrder) {
|
|
Scene scene("SceneRenderRequestPlannerCollectScene");
|
|
|
|
GameObject* inactiveObject = scene.CreateGameObject("Inactive");
|
|
auto* inactiveCamera = inactiveObject->AddComponent<CameraComponent>();
|
|
inactiveObject->SetActive(false);
|
|
|
|
GameObject* highBaseObject = scene.CreateGameObject("HighBase");
|
|
auto* highBase = highBaseObject->AddComponent<CameraComponent>();
|
|
highBase->SetDepth(10.0f);
|
|
highBase->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* firstTieObject = scene.CreateGameObject("FirstTie");
|
|
auto* firstTie = firstTieObject->AddComponent<CameraComponent>();
|
|
firstTie->SetDepth(2.0f);
|
|
firstTie->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* secondTieObject = scene.CreateGameObject("SecondTie");
|
|
auto* secondTie = secondTieObject->AddComponent<CameraComponent>();
|
|
secondTie->SetDepth(2.0f);
|
|
secondTie->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* overlayObject = scene.CreateGameObject("Overlay");
|
|
auto* overlay = overlayObject->AddComponent<CameraComponent>();
|
|
overlay->SetDepth(-10.0f);
|
|
overlay->SetStackType(CameraStackType::Overlay);
|
|
|
|
SceneRenderRequestPlanner planner;
|
|
const std::vector<CameraComponent*> cameras = planner.CollectCameras(scene, nullptr);
|
|
|
|
(void)inactiveCamera;
|
|
ASSERT_EQ(cameras.size(), 4u);
|
|
EXPECT_EQ(cameras[0], firstTie);
|
|
EXPECT_EQ(cameras[1], secondTie);
|
|
EXPECT_EQ(cameras[2], highBase);
|
|
EXPECT_EQ(cameras[3], overlay);
|
|
}
|
|
|
|
TEST(SceneRenderRequestPlanner_Test, BuildsRequestsAndDropsZeroSizedViewportsWithoutBreakingClearFlow) {
|
|
Scene scene("SceneRenderRequestPlannerBuildRequests");
|
|
|
|
GameObject* firstBaseObject = scene.CreateGameObject("FirstBase");
|
|
auto* firstBase = firstBaseObject->AddComponent<CameraComponent>();
|
|
firstBase->SetStackType(CameraStackType::Base);
|
|
firstBase->SetDepth(1.0f);
|
|
|
|
GameObject* skippedBaseObject = scene.CreateGameObject("SkippedBase");
|
|
auto* skippedBase = skippedBaseObject->AddComponent<CameraComponent>();
|
|
skippedBase->SetStackType(CameraStackType::Base);
|
|
skippedBase->SetDepth(2.0f);
|
|
skippedBase->SetViewportRect(XCEngine::Math::Rect(0.5f, 0.5f, 0.0f, 1.0f));
|
|
|
|
GameObject* overlayObject = scene.CreateGameObject("Overlay");
|
|
auto* overlay = overlayObject->AddComponent<CameraComponent>();
|
|
overlay->SetStackType(CameraStackType::Overlay);
|
|
overlay->SetDepth(3.0f);
|
|
|
|
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, firstBase);
|
|
EXPECT_EQ(requests[0].clearFlags, RenderClearFlags::All);
|
|
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, 2048u);
|
|
EXPECT_EQ(request.directionalShadow.mapHeight, 2048u);
|
|
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, 2048u);
|
|
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u);
|
|
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);
|
|
}
|
|
|
|
TEST(SceneRenderRequestPlanner_Test, AppliesConfiguredDirectionalShadowPlanningSettings) {
|
|
Scene scene("SceneRenderRequestPlannerCustomDirectionalShadow");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetStackType(CameraStackType::Base);
|
|
camera->SetDepth(1.0f);
|
|
camera->SetProjectionType(CameraProjectionType::Perspective);
|
|
camera->SetNearClipPlane(0.3f);
|
|
camera->SetFarClipPlane(100.0f);
|
|
|
|
GameObject* shadowLightObject = scene.CreateGameObject("ShadowLight");
|
|
auto* shadowLight = shadowLightObject->AddComponent<LightComponent>();
|
|
shadowLight->SetLightType(LightType::Directional);
|
|
shadowLight->SetCastsShadows(true);
|
|
|
|
SceneRenderRequestPlanner planner;
|
|
DirectionalShadowPlanningSettings settings = {};
|
|
settings.mapDimension = 2048u;
|
|
settings.minFocusDistance = 12.0f;
|
|
settings.maxFocusDistance = 12.0f;
|
|
settings.perspectiveFocusFactor = 0.01f;
|
|
settings.minDepthRange = 80.0f;
|
|
settings.sampling.receiverDepthBias = 0.0025f;
|
|
settings.sampling.normalBiasScale = 2.0f;
|
|
settings.sampling.shadowStrength = 0.75f;
|
|
settings.casterBias.depthBiasFactor = 2.5f;
|
|
settings.casterBias.depthBiasUnits = 4;
|
|
planner.SetDirectionalShadowPlanningSettings(settings);
|
|
|
|
const std::vector<CameraRenderRequest> requests = planner.BuildRequests(
|
|
scene,
|
|
nullptr,
|
|
CreateValidContext(),
|
|
RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const CameraRenderRequest& request = requests[0];
|
|
ASSERT_TRUE(request.directionalShadow.IsValid());
|
|
EXPECT_EQ(request.directionalShadow.mapWidth, 2048u);
|
|
EXPECT_EQ(request.directionalShadow.mapHeight, 2048u);
|
|
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportWidth, 2048u);
|
|
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u);
|
|
EXPECT_GE(request.directionalShadow.focusPoint.z, 6.0f);
|
|
EXPECT_LE(request.directionalShadow.focusPoint.z, 6.3f);
|
|
EXPECT_FLOAT_EQ(request.directionalShadow.sampling.receiverDepthBias, 0.0025f);
|
|
EXPECT_FLOAT_EQ(request.directionalShadow.sampling.normalBiasScale, 2.0f);
|
|
EXPECT_FLOAT_EQ(request.directionalShadow.sampling.shadowStrength, 0.75f);
|
|
EXPECT_FLOAT_EQ(request.directionalShadow.casterBias.depthBiasFactor, 2.5f);
|
|
EXPECT_EQ(request.directionalShadow.casterBias.depthBiasUnits, 4);
|
|
EXPECT_GE(
|
|
request.directionalShadow.farClipPlane - request.directionalShadow.nearClipPlane,
|
|
80.0f);
|
|
}
|
|
|
|
TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSettings) {
|
|
SceneRenderRequestPlanner planner;
|
|
|
|
DirectionalShadowPlanningSettings invalidSettings = {};
|
|
invalidSettings.mapDimension = 0u;
|
|
invalidSettings.minFocusDistance = -1.0f;
|
|
invalidSettings.maxFocusDistance = 0.0f;
|
|
invalidSettings.perspectiveFocusFactor = 0.0f;
|
|
invalidSettings.orthographicFocusFactor = -2.0f;
|
|
invalidSettings.minDepthRange = 0.0f;
|
|
invalidSettings.boundsPadding = -1.0f;
|
|
invalidSettings.minDepthPadding = -3.0f;
|
|
invalidSettings.sampling.receiverDepthBias = -0.5f;
|
|
invalidSettings.sampling.normalBiasScale = -2.0f;
|
|
invalidSettings.sampling.shadowStrength = 3.0f;
|
|
invalidSettings.casterBias.depthBiasFactor = -1.0f;
|
|
invalidSettings.casterBias.depthBiasUnits = -6;
|
|
planner.SetDirectionalShadowPlanningSettings(invalidSettings);
|
|
|
|
const DirectionalShadowPlanningSettings& settings =
|
|
planner.GetDirectionalShadowPlanningSettings();
|
|
EXPECT_EQ(settings.mapDimension, 2048u);
|
|
EXPECT_FLOAT_EQ(settings.minFocusDistance, 5.0f);
|
|
EXPECT_FLOAT_EQ(settings.maxFocusDistance, 32.0f);
|
|
EXPECT_FLOAT_EQ(settings.perspectiveFocusFactor, 1.0f);
|
|
EXPECT_FLOAT_EQ(settings.orthographicFocusFactor, 2.0f);
|
|
EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f);
|
|
EXPECT_FLOAT_EQ(settings.boundsPadding, 1.0f);
|
|
EXPECT_FLOAT_EQ(settings.minDepthPadding, 2.0f);
|
|
EXPECT_FLOAT_EQ(settings.sampling.receiverDepthBias, 0.0010f);
|
|
EXPECT_FLOAT_EQ(settings.sampling.normalBiasScale, 2.0f);
|
|
EXPECT_FLOAT_EQ(settings.sampling.shadowStrength, 1.0f);
|
|
EXPECT_FLOAT_EQ(settings.casterBias.depthBiasFactor, 2.5f);
|
|
EXPECT_EQ(settings.casterBias.depthBiasUnits, 4);
|
|
}
|
|
|
|
TEST(SceneRenderRequestPlanner_Test, IgnoresReceiveOnlyMeshesWhenFittingDirectionalShadowBounds) {
|
|
Scene scene("SceneRenderRequestPlannerReceiveOnlyShadowBounds");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetStackType(CameraStackType::Base);
|
|
camera->SetDepth(1.0f);
|
|
camera->SetProjectionType(CameraProjectionType::Perspective);
|
|
camera->SetNearClipPlane(0.3f);
|
|
camera->SetFarClipPlane(100.0f);
|
|
|
|
GameObject* shadowLightObject = scene.CreateGameObject("ShadowLight");
|
|
auto* shadowLight = shadowLightObject->AddComponent<LightComponent>();
|
|
shadowLight->SetLightType(LightType::Directional);
|
|
shadowLight->SetCastsShadows(true);
|
|
|
|
GameObject* casterObject = scene.CreateGameObject("Caster");
|
|
casterObject->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(0.0f, 0.0f, 6.0f));
|
|
auto* casterFilter = casterObject->AddComponent<MeshFilterComponent>();
|
|
auto* casterRenderer = casterObject->AddComponent<MeshRendererComponent>();
|
|
casterRenderer->SetCastShadows(true);
|
|
casterRenderer->SetReceiveShadows(true);
|
|
Mesh* casterMesh = CreatePlannerTestMesh(
|
|
"Meshes/planner_caster.mesh",
|
|
XCEngine::Math::Bounds(
|
|
XCEngine::Math::Vector3(-0.5f, -0.5f, -0.5f),
|
|
XCEngine::Math::Vector3(1.0f, 1.0f, 1.0f)));
|
|
casterFilter->SetMesh(casterMesh);
|
|
|
|
SceneRenderRequestPlanner planner;
|
|
const std::vector<CameraRenderRequest> baselineRequests = planner.BuildRequests(
|
|
scene,
|
|
nullptr,
|
|
CreateValidContext(),
|
|
RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(baselineRequests.size(), 1u);
|
|
ASSERT_TRUE(baselineRequests[0].directionalShadow.IsValid());
|
|
const float baselineHalfExtent = baselineRequests[0].directionalShadow.orthographicHalfExtent;
|
|
const float baselineTexelWorldSize = baselineRequests[0].directionalShadow.texelWorldSize;
|
|
|
|
GameObject* receiverObject = scene.CreateGameObject("ReceiverOnly");
|
|
receiverObject->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(0.0f, 0.0f, 16.0f));
|
|
auto* receiverFilter = receiverObject->AddComponent<MeshFilterComponent>();
|
|
auto* receiverRenderer = receiverObject->AddComponent<MeshRendererComponent>();
|
|
receiverRenderer->SetCastShadows(false);
|
|
receiverRenderer->SetReceiveShadows(true);
|
|
Mesh* receiverMesh = CreatePlannerTestMesh(
|
|
"Meshes/planner_receiver_only.mesh",
|
|
XCEngine::Math::Bounds(
|
|
XCEngine::Math::Vector3(-20.0f, -20.0f, -0.5f),
|
|
XCEngine::Math::Vector3(40.0f, 40.0f, 1.0f)));
|
|
receiverFilter->SetMesh(receiverMesh);
|
|
|
|
const std::vector<CameraRenderRequest> updatedRequests = planner.BuildRequests(
|
|
scene,
|
|
nullptr,
|
|
CreateValidContext(),
|
|
RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(updatedRequests.size(), 1u);
|
|
ASSERT_TRUE(updatedRequests[0].directionalShadow.IsValid());
|
|
EXPECT_FLOAT_EQ(updatedRequests[0].directionalShadow.orthographicHalfExtent, baselineHalfExtent);
|
|
EXPECT_FLOAT_EQ(updatedRequests[0].directionalShadow.texelWorldSize, baselineTexelWorldSize);
|
|
|
|
casterFilter->ClearMesh();
|
|
receiverFilter->ClearMesh();
|
|
delete casterMesh;
|
|
delete receiverMesh;
|
|
}
|