Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3D] Use QGIS material for the terrain #58134

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

ptitjano
Copy link
Contributor

Description

This is factored out from #57899

It allows to use QGIS material for the terrain instead of native qt3D ones. It will make it possible to customize those shaders. There are 3 different cases to handle:

  • no texture: there is a drop-in replacement with QgsPhongMaterialSettings
  • texture, no shading: QgsPhongTexturedMaterialSettings is extended to handle both textures and textures from a path
  • texture, shading. A new material is introduced: QgsTextureMaterial

This PR needs #58114 to be merged first and thus it includes it.

@github-actions github-actions bot added this to the 3.40.0 milestone Jul 16, 2024
@ptitjano ptitjano self-assigned this Jul 16, 2024
@ptitjano ptitjano added the 3D Relates to QGIS' 3D engine or rendering label Jul 16, 2024
Copy link

github-actions bot commented Jul 16, 2024

🪟 Windows builds ready!

Windows builds of this PR are available for testing here. Debug symbols for this build are available here.

(Built from commit b5b6c70)

Copy link

github-actions bot commented Jul 16, 2024

Tests failed for Qt 5

One or more tests failed using the build from commit 6494e5d

polygon3d_extrusion_textured_phong (testExtrudedPolygonsTexturedPhong)

polygon3d_extrusion_textured_phong

Test failed at testExtrudedPolygonsTexturedPhong at tests/src/3d/testqgs3drendering.cpp:532

Rendered image did not match tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/expected_polygon3d_extrusion_textured_phong.png (found 40353 pixels different)

The full test report (included comparison of rendered vs expected images) can be found here.

Further documentation on the QGIS test infrastructure can be found in the Developer's Guide.

Copy link

github-actions bot commented Jul 16, 2024

Tests failed for Qt 6

One or more tests failed using the build from commit 6494e5d

polygon3d_extrusion_textured_phong (testExtrudedPolygonsTexturedPhong)

polygon3d_extrusion_textured_phong

Test failed at testExtrudedPolygonsTexturedPhong at tests/src/3d/testqgs3drendering.cpp:532

Rendered image did not match tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/expected_polygon3d_extrusion_textured_phong.png (found 4813 pixels different)

The full test report (included comparison of rendered vs expected images) can be found here.

Further documentation on the QGIS test infrastructure can be found in the Developer's Guide.

@ptitjano ptitjano force-pushed the terrain-qgis-shaders branch 3 times, most recently from 6494e5d to 9664e26 Compare July 19, 2024 13:19
There is no functional change. With this change, the terrain uses a
material defined by QGIS instead of a default Qt3D. This will make it
easier to customize the shader in the future.
This is already waht QgsPhongTexturedMaterialSettings does. The reason
for this change is to avoid the user-set values changing for them when
saving/restoring projects.
This is already waht QgsPhongTexturedMaterialSettings does. The reason
for this change is to avoid the user-set values changing for them when
saving/restoring projects.
@ptitjano ptitjano force-pushed the terrain-qgis-shaders branch 2 times, most recently from 14446d6 to f192260 Compare July 19, 2024 13:30
@ptitjano ptitjano force-pushed the terrain-qgis-shaders branch 3 times, most recently from 5f4552d to 4655e3a Compare July 20, 2024 09:53
if ( texture )
{
mDiffuseTexturePath.clear();
mDiffuseTexture.reset( texture );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't get how this can be stable. QTexture2D is a QObject subclass, so we'll have both thread safety issues (if a QgsPhongTexturedMaterialSettings is copied across threads) + the risk of a parent object for the QTexture2D instance causing object deletion outside of the shared_ptr .

@wonder-sk i'd love your insights here too, given that you're the resident expert in Qt3D internals 😆

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to what Nyall said... passing QTexture2D around looks quite scary (and just like it was pointed out - QObject-based class in shared_ptr is not safe).

Looking at the QgsPhongTexturedMaterialSettings::toMaterial() implementation, we end up extracting QImage from it, so why not have QgsPhongTexturedMaterialSettings::setDiffuseTexture( const QImage &diffuse ) instead? It would simplify things, not requiring two variants (either a path or a texture). Another argument against using QTexture2D is that PyQGIS users would not be able to use that (since we do not have PyQt3D linked).

Please double check also the functionality of terrain texture updates (e.g. when a map layer in project participating in the terrain's texture images gets enabled/disabled) - in such case, we do not recreate all the entities and materials again, instead we just update the image in QgsTerrainTextureImage. I believe this functionality is lost in the current PR, because the material now does not use the QgsTerrainTextureImage object (which gets new image), so this link seems to be lost. We may need some other mechanism to make sure that terrain's texture can be updated without a complete rebuild of the material...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to what Nyall said... passing QTexture2D around looks quite scary (and just like it was pointed out - QObject-based class in shared_ptr is not safe).

This was never my intention to use a smart pointer, that was @nyalldawson's idea 😆 !

Looking at the QgsPhongTexturedMaterialSettings::toMaterial() implementation, we end up extracting QImage from it, so why not have QgsPhongTexturedMaterialSettings::setDiffuseTexture( const QImage &diffuse ) instead? It would simplify things, not requiring two variants (either a path or a texture). Another argument against using QTexture2D is that PyQGIS users would not be able to use that (since we do not have PyQt3D linked).

Indeed. And that's the case now.
Thanks for your comment. I came to the same conclusion. At first, I was trying to copy/paste Qt3D behavior but It creates a Material on the fly while QGIS stores settings and then creates a Material with toMaterial(). When I realized that the QImage from the stored QTexture2Dneeds to be copied to create a newQTexture2D, I thought that it would be much simpler to directly use a QImage`.

Please double check also the functionality of terrain texture updates (e.g. when a map layer in project participating in the terrain's texture images gets enabled/disabled) - in such case, we do not recreate all the entities and materials again, instead we just update the image in QgsTerrainTextureImage. I believe this functionality is lost in the current PR, because the material now does not use the QgsTerrainTextureImage object (which gets new image), so this link seems to be lost. We may need some other mechanism to make sure that terrain's texture can be updated without a complete rebuild of the material...

I don't understand. On current master, on terrain texture updates, QgsTerrainTileLoader::createTextureComponent gets called and thus the material is recreated. I don't see how only the image is updated. Based on my understanding, this is is not the case on current master.
Anyway, the terrain texture updates works on this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates!

I don't understand. On current master, on terrain texture updates, QgsTerrainTileLoader::createTextureComponent gets called and thus the material is recreated. I don't see how only the image is updated. Based on my understanding, this is is not the case on current master.
Anyway, the terrain texture updates works on this PR.

Please try this use case:

  1. open a 3D view
  2. enable or disable a map layer (one that does not have a 3D renderer so that the terrain texture gets updated) - I expect this still works because QgsTextureMaterial + createTexture() are used
  3. now in 3D view settings, enable terrain shading
  4. enable or disable a map layer - here I am expecting that the terrain texture will not get updated anymore

The mechanism how the terrain texture is updated works like this (verified on qgis master):

  • when the list of map layers for terrain texture gets modified, QgsTerrainEntity::invalidateMapImages() gets called
  • this will create tile update jobs (TerrainMapUpdateJob) from TerrainMapUpdateJobFactory
  • when an updated texture is ready, TerrainMapUpdateJob::onTileReady is called and it uses QgsTerrainTileEntity::textureImage() to get QgsTerrainTextureImage and update the image

As far as I can tell, when terrain shading is enabled, QgsTerrainTileLoader::createTextureComponent() now does not use createTexture() which is the key bit here - it wraps texture's QImage into QgsTerrainTextureImage (which allows updates of the image without full re-creation of terrain's tile entity).

For terrain tile load, probably you may want to have an approach similar to QgsTextureMaterial::setTexture()... the QgsPhongTexturedMaterialSettings object would be created without any texture image, and in a second step you would pass the texture image wrapped into QgsTerrainTextureImage...

QgsPhongTexturedMaterial allows to use a texture based on the path of
an image. With this change, it is now possible to also directly use an
image.
This allows to create a phong textured material settings from a phong
settings and a texture.
There is no functional change. With this change, the terrain uses a
material defined by QGIS instead of a default Qt3D. This will make it
easier to customize the shader in the future.
This is the same material as the qt3d one:
`Qt3DExtras::QTextureMaterial`. It will also be used in the following
commit by the terrain.
There is no functional change. With this change, the terrain uses a
material defined by QGIS instead of a default Qt3D. This will make it
easier to customize the shader in the future.
const QImage textureSourceImage = QgsApplication::imageCache()->pathAsImage( mDiffuseTexturePath, QSize(), true, 1.0, fitsInCache );
( void )fitsInCache;
Qt3DRender::QMaterial *material = nullptr;
QImage textureSourceImage = mDiffuseTextureImage.copy();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we doing explicit copy of the QImage here, and in QgsPhongTexturedMaterialSettings::setDiffuseTexture() ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3D Relates to QGIS' 3D engine or rendering
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants