rendering: stabilize single-map directional shadow fitting
This commit is contained in:
@@ -16,9 +16,11 @@ namespace Rendering {
|
|||||||
// fitting and the default sampling / caster bias values emitted into the
|
// fitting and the default sampling / caster bias values emitted into the
|
||||||
// runtime shadow contract.
|
// runtime shadow contract.
|
||||||
struct DirectionalShadowPlanningSettings {
|
struct DirectionalShadowPlanningSettings {
|
||||||
uint32_t mapDimension = 1024u;
|
// Keep the single-map MainLight shadow at a higher baseline so the current
|
||||||
|
// non-cascaded implementation has enough texel density to be usable.
|
||||||
|
uint32_t mapDimension = 2048u;
|
||||||
float minFocusDistance = 5.0f;
|
float minFocusDistance = 5.0f;
|
||||||
float maxFocusDistance = 64.0f;
|
float maxFocusDistance = 32.0f;
|
||||||
float perspectiveFocusFactor = 1.0f;
|
float perspectiveFocusFactor = 1.0f;
|
||||||
float orthographicFocusFactor = 2.0f;
|
float orthographicFocusFactor = 2.0f;
|
||||||
float minDepthRange = 20.0f;
|
float minDepthRange = 20.0f;
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan(
|
|||||||
gameObject->GetComponent<Components::MeshRendererComponent>();
|
gameObject->GetComponent<Components::MeshRendererComponent>();
|
||||||
if (meshRenderer == nullptr ||
|
if (meshRenderer == nullptr ||
|
||||||
!meshRenderer->IsEnabled() ||
|
!meshRenderer->IsEnabled() ||
|
||||||
(!meshRenderer->GetCastShadows() && !meshRenderer->GetReceiveShadows())) {
|
!meshRenderer->GetCastShadows()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,16 @@
|
|||||||
|
|
||||||
#include <XCEngine/Components/CameraComponent.h>
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
#include <XCEngine/Components/LightComponent.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/Rendering/Planning/SceneRenderRequestPlanner.h>
|
||||||
#include <XCEngine/Scene/Scene.h>
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
|
||||||
using namespace XCEngine::Components;
|
using namespace XCEngine::Components;
|
||||||
|
using namespace XCEngine::Core;
|
||||||
|
using namespace XCEngine::Resources;
|
||||||
using namespace XCEngine::Rendering;
|
using namespace XCEngine::Rendering;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -18,6 +24,17 @@ RenderContext CreateValidContext() {
|
|||||||
return context;
|
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
|
} // namespace
|
||||||
|
|
||||||
TEST(SceneRenderRequestPlanner_Test, FallsBackToSceneCamerasWhenOverrideCameraIsNotUsable) {
|
TEST(SceneRenderRequestPlanner_Test, FallsBackToSceneCamerasWhenOverrideCameraIsNotUsable) {
|
||||||
@@ -154,14 +171,14 @@ TEST(SceneRenderRequestPlanner_Test, BuildsDirectionalShadowPlanForBaseCameraWhe
|
|||||||
EXPECT_EQ(request.camera, camera);
|
EXPECT_EQ(request.camera, camera);
|
||||||
ASSERT_TRUE(request.directionalShadow.IsValid());
|
ASSERT_TRUE(request.directionalShadow.IsValid());
|
||||||
EXPECT_TRUE(request.directionalShadow.enabled);
|
EXPECT_TRUE(request.directionalShadow.enabled);
|
||||||
EXPECT_EQ(request.directionalShadow.mapWidth, 1024u);
|
EXPECT_EQ(request.directionalShadow.mapWidth, 2048u);
|
||||||
EXPECT_EQ(request.directionalShadow.mapHeight, 1024u);
|
EXPECT_EQ(request.directionalShadow.mapHeight, 2048u);
|
||||||
EXPECT_EQ(request.directionalShadow.lightDirection, XCEngine::Math::Vector3::Back());
|
EXPECT_EQ(request.directionalShadow.lightDirection, XCEngine::Math::Vector3::Back());
|
||||||
EXPECT_GT(request.directionalShadow.focusPoint.z, 0.0f);
|
EXPECT_GT(request.directionalShadow.focusPoint.z, 0.0f);
|
||||||
EXPECT_TRUE(request.shadowCaster.hasCameraDataOverride);
|
EXPECT_TRUE(request.shadowCaster.hasCameraDataOverride);
|
||||||
EXPECT_EQ(request.shadowCaster.clearFlags, RenderClearFlags::Depth);
|
EXPECT_EQ(request.shadowCaster.clearFlags, RenderClearFlags::Depth);
|
||||||
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportWidth, 1024u);
|
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportWidth, 2048u);
|
||||||
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 1024u);
|
EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u);
|
||||||
EXPECT_EQ(request.shadowCaster.cameraDataOverride.clearFlags, RenderClearFlags::Depth);
|
EXPECT_EQ(request.shadowCaster.cameraDataOverride.clearFlags, RenderClearFlags::Depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,9 +294,9 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe
|
|||||||
|
|
||||||
const DirectionalShadowPlanningSettings& settings =
|
const DirectionalShadowPlanningSettings& settings =
|
||||||
planner.GetDirectionalShadowPlanningSettings();
|
planner.GetDirectionalShadowPlanningSettings();
|
||||||
EXPECT_EQ(settings.mapDimension, 1024u);
|
EXPECT_EQ(settings.mapDimension, 2048u);
|
||||||
EXPECT_FLOAT_EQ(settings.minFocusDistance, 5.0f);
|
EXPECT_FLOAT_EQ(settings.minFocusDistance, 5.0f);
|
||||||
EXPECT_FLOAT_EQ(settings.maxFocusDistance, 64.0f);
|
EXPECT_FLOAT_EQ(settings.maxFocusDistance, 32.0f);
|
||||||
EXPECT_FLOAT_EQ(settings.perspectiveFocusFactor, 1.0f);
|
EXPECT_FLOAT_EQ(settings.perspectiveFocusFactor, 1.0f);
|
||||||
EXPECT_FLOAT_EQ(settings.orthographicFocusFactor, 2.0f);
|
EXPECT_FLOAT_EQ(settings.orthographicFocusFactor, 2.0f);
|
||||||
EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f);
|
EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f);
|
||||||
@@ -291,3 +308,74 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe
|
|||||||
EXPECT_FLOAT_EQ(settings.casterBias.depthBiasFactor, 2.5f);
|
EXPECT_FLOAT_EQ(settings.casterBias.depthBiasFactor, 2.5f);
|
||||||
EXPECT_EQ(settings.casterBias.depthBiasUnits, 4);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user