Graphics: Improve directional light stabilization and split

This commit is contained in:
SirLynix 2023-09-08 09:04:49 +02:00 committed by Jérôme Leclercq
parent 413dd2ce71
commit b3a43eb5ed
1 changed files with 59 additions and 37 deletions

View File

@ -11,6 +11,7 @@
#include <Nazara/Math/Quaternion.hpp>
#include <Nazara/Math/Sphere.hpp>
#include <NazaraUtils/Algorithm.hpp>
#include <NazaraUtils/StackArray.hpp>
#include <NazaraUtils/StackVector.hpp>
#include <Nazara/Graphics/Debug.hpp>
@ -44,24 +45,53 @@ namespace Nz
assert(viewer);
PerViewerData& viewerData = *Retrieve(m_viewerData, viewer);
// Extract frustum from main viewer
const Vector3f& eyePosition = viewer->GetViewerInstance().GetEyePosition();
const Matrix4f& viewProjMatrix = viewer->GetViewerInstance().GetViewProjMatrix();
const ViewerInstance& viewerInstance = viewer->GetViewerInstance();
//std::array planePct = { 0.1f, 0.3f, 0.6f }; // TODO: Generate the separations based on other settings
std::array planePct = { 0.03f, 0.1f, 0.2f, 0.5f }; // TODO: Generate the separations based on other settings
assert(m_cascadeCount <= planePct.size() + 1);
// Extract frustum from main viewer
const Vector3f& eyePosition = viewerInstance.GetEyePosition();
const Matrix4f& viewProjMatrix = viewerInstance.GetViewProjMatrix();
float nearPlane = viewerInstance.GetNearPlane();
float farPlane = viewerInstance.GetFarPlane();
// Calculate split depths based on view camera frustum
// Based on method presented in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html
constexpr float lambda = 0.95f;
float ratio = farPlane / nearPlane;
float clipRange = farPlane - nearPlane;
StackArray<float> cascadeSplits = NazaraStackArrayNoInit(float, m_cascadeCount - 1);
for (uint32_t i = 0; i < m_cascadeCount - 1; i++)
{
float p = float(i + 1) / float(m_cascadeCount);
float log = nearPlane * std::pow(ratio, p);
float uniform = nearPlane + clipRange * p;
float d = lambda * (log - uniform) + uniform;
cascadeSplits[i] = (d - nearPlane) / clipRange;
}
StackVector<Frustumf> frustums = NazaraStackVector(Frustumf, m_cascadeCount);
StackVector<float> frustumDists = NazaraStackVector(float, m_cascadeCount);
Frustumf frustum = Frustumf::Extract(viewProjMatrix);
frustum.Split(planePct.data(), m_cascadeCount - 1, [&](float zNearPct, float zFarPct)
frustum.Split(cascadeSplits.data(), m_cascadeCount - 1, [&](float zNearPct, float zFarPct)
{
frustums.push_back(frustum.Reduce(zNearPct, zFarPct));
frustumDists.push_back(frustums.back().GetPlane(FrustumPlane::Far).SignedDistance(eyePosition));
});
constexpr std::array cascadeColors = {
Color::Green(),
Color::Yellow(),
Color::Red(),
Color::Blue(),
Color::Cyan(),
Color::Magenta(),
Color::Orange(),
Color::Gray()
};
for (std::size_t cascadeIndex = 0; cascadeIndex < m_cascadeCount; ++cascadeIndex)
{
CascadeData& cascade = viewerData.cascades[cascadeIndex];
@ -75,13 +105,15 @@ namespace Nz
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f);
ViewerInstance& viewerInstance = cascade.viewer.GetViewerInstance();
cascade.viewProjMatrix = viewerInstance.GetViewProjMatrix() * biasMatrix;
ViewerInstance& cascadeViewerInstance = cascade.viewer.GetViewerInstance();
cascade.viewProjMatrix = cascadeViewerInstance.GetViewProjMatrix() * biasMatrix;
m_pipeline.QueueTransfer(&viewerInstance);
m_pipeline.QueueTransfer(&cascadeViewerInstance);
// Prepare depth pass
Frustumf lightFrustum = Frustumf::Extract(viewerInstance.GetViewProjMatrix());
Frustumf lightFrustum = Frustumf::Extract(cascadeViewerInstance.GetViewProjMatrix());
//m_pipeline.GetDebugDrawer().DrawFrustum(lightFrustum, cascadeColors[cascadeIndex]);
std::size_t visibilityHash = 5U;
const auto& visibleRenderables = m_pipeline.FrustumCull(lightFrustum, 0xFFFFFFFF, visibilityHash);
@ -92,7 +124,7 @@ namespace Nz
void DirectionalLightShadowData::ComputeLightView(CascadeData& cascade, const Frustumf& cascadeFrustum, float cascadeDist)
{
ViewerInstance& viewerInstance = cascade.viewer.GetViewerInstance();
ViewerInstance& shadowViewer = cascade.viewer.GetViewerInstance();
EnumArray<BoxCorner, Vector3f> frustumCorners = cascadeFrustum.ComputeCorners();
@ -113,7 +145,10 @@ namespace Nz
Matrix4f lightView = Matrix4f::TransformInverse(frustumCenter, m_light.GetRotation());
// Compute light projection matrix
Boxf aabb = Boxf::FromExtends(frustumCenter - Vector3f(radius), frustumCenter + Vector3f(radius));
Vector3f maxExtent = frustumCenter + Vector3f(radius);
Vector3f minExtent = frustumCenter - Vector3f(radius);
Boxf aabb = Boxf::FromExtents(minExtent, maxExtent);
float left = std::numeric_limits<float>::infinity();
float right = -std::numeric_limits<float>::infinity();
@ -133,44 +168,31 @@ namespace Nz
zFar = std::max(zFar, viewCorner.z);
}
// Tune this parameter according to the scene
constexpr float zMult = 2.0f;
if (zNear < 0)
zNear *= zMult;
else
zNear /= zMult;
if (zFar < 0)
zFar /= zMult;
else
zFar *= zMult;
cascade.distance = cascadeDist;
Matrix4f lightProj = Matrix4f::Ortho(left, right, top, bottom, zNear, zFar);
viewerInstance.UpdateProjViewMatrices(lightProj, lightView);
viewerInstance.UpdateEyePosition(frustumCenter);
viewerInstance.UpdateNearFarPlanes(zNear, zFar);
shadowViewer.UpdateProjViewMatrices(lightProj, lightView);
shadowViewer.UpdateEyePosition(frustumCenter);
shadowViewer.UpdateNearFarPlanes(zNear, zFar);
}
void DirectionalLightShadowData::StabilizeShadows(CascadeData& cascade)
{
ViewerInstance& viewerInstance = cascade.viewer.GetViewerInstance();
ViewerInstance& shadowViewer = cascade.viewer.GetViewerInstance();
// Stabilize cascade shadows by keeping the center to a texel boundary of the previous frame
// https://www.junkship.net/News/2020/11/22/shadow-of-a-doubt-part-2
Vector4f shadowOrigin(0.0f, 0.0f, 0.0f, 1.0f);
shadowOrigin = viewerInstance.GetViewProjMatrix() * shadowOrigin;
shadowOrigin *= m_texelScale;
// Stabilize cascade shadows by keeping the center to a texel boundary
// see Michal Valient's article "Stable Cascaded Shadow Maps"
Vector4f shadowOrigin = shadowViewer.GetViewProjMatrix() * Vector4f(0.f, 0.f, 0.f, 1.f);
shadowOrigin *= m_invTexelScale;
Vector2f roundedOrigin = { std::round(shadowOrigin.x), std::round(shadowOrigin.y) };
Vector2f roundOffset = roundedOrigin - Vector2f(shadowOrigin);
roundOffset *= m_invTexelScale;
roundOffset *= m_texelScale;
Matrix4f lightProj = viewerInstance.GetProjectionMatrix();
Matrix4f lightProj = shadowViewer.GetProjectionMatrix();
lightProj.ApplyTranslation(Vector3f(roundOffset.x, roundOffset.y, 0.f));
viewerInstance.UpdateProjectionMatrix(lightProj);
shadowViewer.UpdateProjectionMatrix(lightProj);
}
void DirectionalLightShadowData::RegisterMaterialInstance(const MaterialInstance& matInstance)