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