From da2ac7399f3b35026ec23a9f20e18d212733e0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 13 Feb 2025 10:34:18 +0100 Subject: [PATCH 1/4] Decouple 3D scene update from near/far planes --- src/3d/qgs3dmapscene.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index baa60913ef0c..f4729acbef18 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -310,15 +310,7 @@ void Qgs3DMapScene::onCameraChanged() } updateScene( true ); - bool changedCameraPlanes = updateCameraNearFarPlanes(); - - if ( changedCameraPlanes ) - { - // repeat update of entities - because we have updated camera's near/far planes, - // the active nodes may have changed as well - updateScene( true ); - updateCameraNearFarPlanes(); - } + updateCameraNearFarPlanes(); onShadowSettingsChanged(); @@ -342,7 +334,15 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) sceneContext.cameraPos = camera->position(); const QSize size = mEngine->size(); sceneContext.screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct? - sceneContext.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix(); + + // Make our own projection matrix so that frustum culling done by the + // entities isn't dependent on the current near/far planes, which would then + // require multiple steps to stabilize. + QMatrix4x4 projMatrix; + projMatrix.setToIdentity(); + // Just use some high values for the far plane so we don't have to custom-make the matrix. + projMatrix.perspective( camera->fieldOfView(), camera->aspectRatio(), 1, 1'000'000'000'000.0 ); + sceneContext.viewProjectionMatrix = projMatrix * camera->viewMatrix(); for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) From 7be4280313587b5d504f3451c017b8c68d746139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 20 Feb 2025 00:11:03 +0100 Subject: [PATCH 2/4] Speed up QgsVirtualPointCloudEntity::handleSceneUpdate Done by adding frustum culling early in the loop, results in a ~10x speedup in one project. --- src/3d/qgsvirtualpointcloudentity_p.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/3d/qgsvirtualpointcloudentity_p.cpp b/src/3d/qgsvirtualpointcloudentity_p.cpp index f2afd4b24e48..852ffcb05a55 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.cpp +++ b/src/3d/qgsvirtualpointcloudentity_p.cpp @@ -130,9 +130,18 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon const QVector subIndexes = provider()->subIndexes(); for ( int i = 0; i < subIndexes.size(); ++i ) { + // If the chunked entity needs an update, do it even if it's occluded, + // since otherwise we'd return needsUpdate() == true until it comes into + // view again. + bool needsUpdate = mChunkedEntitiesMap.contains( i ) && mChunkedEntitiesMap[i]->needsUpdate(); + const QgsBox3D &box3D = mBboxes.at( i ); - if ( box3D.isEmpty() ) + if ( !needsUpdate && box3D.isEmpty() ) + continue; + + QgsAABB aabb = QgsAABB::fromBox3D( box3D, mBboxesEntity->vertexDataOrigin() ); + if ( !needsUpdate && Qgs3DUtils::isCullable( aabb, sceneContext.viewProjectionMatrix ) ) continue; // magic number 256 is the common span value for a COPC root node From e30a17b060455fa0748625ab262c2f60f987d6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Tue, 4 Mar 2025 21:05:28 +0100 Subject: [PATCH 3/4] Apply suggestions per review --- src/3d/qgs3dmapscene.cpp | 15 +++++++++++---- src/3d/qgsvirtualpointcloudentity_p.cpp | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index f4729acbef18..6ae769af1d9e 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -338,10 +338,17 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) // Make our own projection matrix so that frustum culling done by the // entities isn't dependent on the current near/far planes, which would then // require multiple steps to stabilize. - QMatrix4x4 projMatrix; - projMatrix.setToIdentity(); - // Just use some high values for the far plane so we don't have to custom-make the matrix. - projMatrix.perspective( camera->fieldOfView(), camera->aspectRatio(), 1, 1'000'000'000'000.0 ); + // The matrix is constructed just like in QMatrix4x4::perspective(), but for + // all elements involving the near and far plane, the limit of the expression + // with the far plane going to infinity is taken. + float fovRadians = ( camera->fieldOfView() / 2.0f ) * static_cast( M_PI ) / 180.0f; + float fovCotan = std::cos( fovRadians ) / std::sin( fovRadians ); + QMatrix4x4 projMatrix( + fovCotan / camera->fieldOfView(), 0, 0, 0, + 0, fovCotan, 0, 0, + 0, 0, -1, -2, + 0, 0, -1, 0 + ); sceneContext.viewProjectionMatrix = projMatrix * camera->viewMatrix(); diff --git a/src/3d/qgsvirtualpointcloudentity_p.cpp b/src/3d/qgsvirtualpointcloudentity_p.cpp index 852ffcb05a55..0c9dea1b7a25 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.cpp +++ b/src/3d/qgsvirtualpointcloudentity_p.cpp @@ -140,7 +140,7 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon if ( !needsUpdate && box3D.isEmpty() ) continue; - QgsAABB aabb = QgsAABB::fromBox3D( box3D, mBboxesEntity->vertexDataOrigin() ); + QgsAABB aabb = QgsAABB::fromBox3D( box3D, mMapSettings->origin() ); if ( !needsUpdate && Qgs3DUtils::isCullable( aabb, sceneContext.viewProjectionMatrix ) ) continue; @@ -190,7 +190,7 @@ QgsRange QgsVirtualPointCloudEntity::getNearFarPlaneRange( const QMatrix4 { for ( const QgsBox3D &box : mBboxes ) { - QgsAABB aabb = QgsAABB::fromBox3D( box, mBboxesEntity->vertexDataOrigin() ); + QgsAABB aabb = QgsAABB::fromBox3D( box, mMapSettings->origin() ); float bboxfnear; float bboxffar; Qgs3DUtils::computeBoundingBoxNearFarPlanes( aabb, viewMatrix, bboxfnear, bboxffar ); From 4f233c8cce505363e341fc301687bd26fbdf81c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Tue, 4 Mar 2025 23:00:50 +0100 Subject: [PATCH 4/4] Fix projection matrix formula --- src/3d/qgs3dmapscene.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 6ae769af1d9e..41690e9d6161 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -344,7 +344,7 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) float fovRadians = ( camera->fieldOfView() / 2.0f ) * static_cast( M_PI ) / 180.0f; float fovCotan = std::cos( fovRadians ) / std::sin( fovRadians ); QMatrix4x4 projMatrix( - fovCotan / camera->fieldOfView(), 0, 0, 0, + fovCotan / camera->aspectRatio(), 0, 0, 0, 0, fovCotan, 0, 0, 0, 0, -1, -2, 0, 0, -1, 0