From e737a7d0d64d0d56a65ab8fc7a9ad9feef7dd482 Mon Sep 17 00:00:00 2001
From: Jere Tuliniemi <jere.tuliniemi@qt.io>
Date: Thu, 16 Apr 2020 13:01:24 +0300
Subject: [PATCH] Limit directional shadow map frustum to scene bounds
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

If using scene bounds creates a smaller shadow map camera frustum, use
that.

Task-number: QTBUG-83362
Change-Id: I77b261cfe07aca9a238b907ece842d6f5266a395
Reviewed-by: Antti Määttä <antti.maatta@qt.io>
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
---
 .../qssgrendererimpllayerrenderdata.cpp       | 101 ++++++++++++------
 1 file changed, 70 insertions(+), 31 deletions(-)

diff --git a/src/runtimerender/rendererimpl/qssgrendererimpllayerrenderdata.cpp b/src/runtimerender/rendererimpl/qssgrendererimpllayerrenderdata.cpp
index 98b22899f..6f0437229 100644
--- a/src/runtimerender/rendererimpl/qssgrendererimpllayerrenderdata.cpp
+++ b/src/runtimerender/rendererimpl/qssgrendererimpllayerrenderdata.cpp
@@ -354,12 +354,43 @@ void computeFrustumBounds(const QSSGRenderCamera &inCamera, const QRectF &inView
     ctrBound *= 0.125f;
 }
 
+QSSGBounds3 calculateShadowCameraBoundingBox(const QVector3D *points, const QVector3D &forward,
+                                             const QVector3D &up, const QVector3D &right)
+{
+    float minDistanceZ = std::numeric_limits<float>::max();
+    float maxDistanceZ = -std::numeric_limits<float>::max();
+    float minDistanceY = std::numeric_limits<float>::max();
+    float maxDistanceY = -std::numeric_limits<float>::max();
+    float minDistanceX = std::numeric_limits<float>::max();
+    float maxDistanceX = -std::numeric_limits<float>::max();
+    for (int i = 0; i < 8; ++i) {
+        float distanceZ = QVector3D::dotProduct(points[i], forward);
+        if (distanceZ < minDistanceZ)
+            minDistanceZ = distanceZ;
+        if (distanceZ > maxDistanceZ)
+            maxDistanceZ = distanceZ;
+        float distanceY = QVector3D::dotProduct(points[i], up);
+        if (distanceY < minDistanceY)
+            minDistanceY = distanceY;
+        if (distanceY > maxDistanceY)
+            maxDistanceY = distanceY;
+        float distanceX = QVector3D::dotProduct(points[i], right);
+        if (distanceX < minDistanceX)
+            minDistanceX = distanceX;
+        if (distanceX > maxDistanceX)
+            maxDistanceX = distanceX;
+    }
+    return QSSGBounds3(QVector3D(minDistanceX, minDistanceY, minDistanceZ),
+                       QVector3D(maxDistanceX, maxDistanceY, maxDistanceZ));
+}
+
 void setupCameraForShadowMap(const QVector2D &/*inCameraVec*/,
                              QSSGRenderContext & /*inContext*/,
                              const QRectF &inViewport,
                              const QSSGRenderCamera &inCamera,
                              const QSSGRenderLight *inLight,
-                             QSSGRenderCamera &theCamera)
+                             QSSGRenderCamera &theCamera,
+                             QVector3D *scenePoints = nullptr)
 {
     // setup light matrix
     quint32 mapRes = 1 << inLight->m_shadowMapRes;
@@ -374,8 +405,15 @@ void setupCameraForShadowMap(const QVector2D &/*inCameraVec*/,
     theCamera.fov = qDegreesToRadians(90.f);
 
     if (inLight->m_lightType == QSSGRenderLight::Type::Directional) {
-        QVector3D frustBounds[8], boundCtr;
-        computeFrustumBounds(inCamera, inViewport, boundCtr, frustBounds);
+        QVector3D frustumPoints[8], boundCtr, sceneCtr;
+        computeFrustumBounds(inCamera, inViewport, boundCtr, frustumPoints);
+
+        if (scenePoints) {
+            sceneCtr = QVector3D(0, 0, 0);
+            for (int i = 0; i < 8; ++i)
+                sceneCtr += scenePoints[i];
+            sceneCtr *= 0.125f;
+        }
 
         QVector3D forward = inLightDir;
         forward.normalize();
@@ -389,37 +427,24 @@ void setupCameraForShadowMap(const QVector2D &/*inCameraVec*/,
         up.normalize();
 
         // Calculate bounding box of the scene camera frustum
-        float minDistanceZ = std::numeric_limits<float>::max();
-        float maxDistanceZ = -std::numeric_limits<float>::max();
-        float minDistanceY = std::numeric_limits<float>::max();
-        float maxDistanceY = -std::numeric_limits<float>::max();
-        float minDistanceX = std::numeric_limits<float>::max();
-        float maxDistanceX = -std::numeric_limits<float>::max();
-        for (int i = 0; i < 8; ++i) {
-            float distanceZ = QVector3D::dotProduct(frustBounds[i], forward);
-            if (distanceZ < minDistanceZ)
-                minDistanceZ = distanceZ;
-            if (distanceZ > maxDistanceZ)
-                maxDistanceZ = distanceZ;
-            float distanceY = QVector3D::dotProduct(frustBounds[i], up);
-            if (distanceY < minDistanceY)
-                minDistanceY = distanceY;
-            if (distanceY > maxDistanceY)
-                maxDistanceY = distanceY;
-            float distanceX = QVector3D::dotProduct(frustBounds[i], right);
-            if (distanceX < minDistanceX)
-                minDistanceX = distanceX;
-            if (distanceX > maxDistanceX)
-                maxDistanceX = distanceX;
+        QSSGBounds3 bounds = calculateShadowCameraBoundingBox(frustumPoints, forward, up, right);
+        inLightPos = boundCtr;
+        if (scenePoints) {
+            QSSGBounds3 sceneBounds = calculateShadowCameraBoundingBox(scenePoints, forward, up,
+                                                                       right);
+            if (sceneBounds.extents().x() * sceneBounds.extents().y() * sceneBounds.extents().z()
+                    < bounds.extents().x() * bounds.extents().y() * bounds.extents().z()) {
+                bounds = sceneBounds;
+                inLightPos = sceneCtr;
+            }
         }
 
         // Apply bounding box parameters to shadow map camera projection matrix
         // so that the whole scene is fit inside the shadow map
-        inLightPos = boundCtr;
-        theViewport.setHeight(std::abs(maxDistanceY - minDistanceY));
-        theViewport.setWidth(std::abs(maxDistanceX - minDistanceX));
-        theCamera.clipNear = -std::abs(maxDistanceZ - minDistanceZ);
-        theCamera.clipFar = std::abs(maxDistanceZ - minDistanceZ);
+        theViewport.setHeight(bounds.extents().y() * 2);
+        theViewport.setWidth(bounds.extents().x() * 2);
+        theCamera.clipNear = -bounds.extents().z() * 2;
+        theCamera.clipFar = bounds.extents().z() * 2;
     }
 
     theCamera.flags.setFlag(QSSGRenderCamera::Flag::Orthographic, inLight->m_lightType == QSSGRenderLight::Type::Directional);
@@ -693,16 +718,30 @@ void QSSGLayerRenderData::renderShadowMapPass(QSSGResourceFrameBuffer *theFB)
     QSSGRenderClearFlags clearFlags(QSSGRenderClearValues::Depth | QSSGRenderClearValues::Stencil
                                       | QSSGRenderClearValues::Color);
 
+    auto bounds = camera->parent->getBounds(renderer->contextInterface()->bufferManager());
+
+    QVector3D scenePoints[8];
+    scenePoints[0] = bounds.minimum;
+    scenePoints[1] = QVector3D(bounds.maximum.x(), bounds.minimum.y(), bounds.minimum.z());
+    scenePoints[2] = QVector3D(bounds.minimum.x(), bounds.maximum.y(), bounds.minimum.z());
+    scenePoints[3] = QVector3D(bounds.maximum.x(), bounds.maximum.y(), bounds.minimum.z());
+    scenePoints[4] = QVector3D(bounds.minimum.x(), bounds.minimum.y(), bounds.maximum.z());
+    scenePoints[5] = QVector3D(bounds.maximum.x(), bounds.minimum.y(), bounds.maximum.z());
+    scenePoints[6] = QVector3D(bounds.minimum.x(), bounds.maximum.y(), bounds.maximum.z());
+    scenePoints[7] = bounds.maximum;
+
     for (int i = 0; i < globalLights.size(); i++) {
         // don't render shadows when not casting
         if (!globalLights[i]->m_castShadow)
             continue;
+
         QSSGShadowMapEntry *pEntry = shadowMapManager->getShadowMapEntry(i);
         if (pEntry && pEntry->m_depthMap && pEntry->m_depthCopy && pEntry->m_depthRender) {
             QSSGRenderCamera theCamera;
 
             QVector2D theCameraProps = QVector2D(camera->clipNear, camera->clipFar);
-            setupCameraForShadowMap(theCameraProps, *renderer->context(), __viewport.m_initialValue, *camera, globalLights[i], theCamera);
+            setupCameraForShadowMap(theCameraProps, *renderer->context(), __viewport.m_initialValue,
+                                    *camera, globalLights[i], theCamera, scenePoints);
             // we need this matrix for the final rendering
             theCamera.calculateViewProjectionMatrix(pEntry->m_lightVP);
             pEntry->m_lightView = theCamera.globalTransform.inverted();
-- 
GitLab