[FEATURE] Add support for shading of terrain

This adds new options for user to choose how the terrain should be rendered:
- shading disabled - color of terrain is determined only from map texture
- shading enabled - color of terrain is determined using Phong's shading model,
  taking into account map texture, terrain normal vector, scene light(s) and
  terrain material's ambient+specular colors and shininess

(configuration of lights is not there yet - now we always have a single point
light with white color at the middle of the scene at 1km above the zero elevation)
This commit is contained in:
Martin Dobias 2018-11-11 16:10:58 +01:00
parent cf24e654b8
commit d3e81f6d18
13 changed files with 151 additions and 18 deletions

View File

@ -73,6 +73,8 @@ Reads settings from a DOM element
Writes settings to a DOM element
%End
bool operator==( const QgsPhongMaterialSettings &other ) const;
};

View File

@ -92,6 +92,7 @@ Qgs3DMapScene::Qgs3DMapScene( const Qgs3DMapSettings &map, QgsAbstract3DEngine *
connect( &map, &Qgs3DMapSettings::mapTileResolutionChanged, this, &Qgs3DMapScene::createTerrain );
connect( &map, &Qgs3DMapSettings::maxTerrainScreenErrorChanged, this, &Qgs3DMapScene::createTerrain );
connect( &map, &Qgs3DMapSettings::maxTerrainGroundErrorChanged, this, &Qgs3DMapScene::createTerrain );
connect( &map, &Qgs3DMapSettings::terrainShadingChanged, this, &Qgs3DMapScene::createTerrain );
// create entities of renderers

View File

@ -37,6 +37,8 @@ Qgs3DMapSettings::Qgs3DMapSettings( const Qgs3DMapSettings &other )
, mMapTileResolution( other.mMapTileResolution )
, mMaxTerrainScreenError( other.mMaxTerrainScreenError )
, mMaxTerrainGroundError( other.mMaxTerrainGroundError )
, mTerrainShadingEnabled( other.mTerrainShadingEnabled )
, mTerrainShadingMaterial( other.mTerrainShadingMaterial )
, mShowTerrainBoundingBoxes( other.mShowTerrainBoundingBoxes )
, mShowTerrainTileInfo( other.mShowTerrainTileInfo )
, mShowCameraViewCenter( other.mShowCameraViewCenter )
@ -79,6 +81,10 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
mMapTileResolution = elemTerrain.attribute( QStringLiteral( "texture-size" ), QStringLiteral( "512" ) ).toInt();
mMaxTerrainScreenError = elemTerrain.attribute( QStringLiteral( "max-terrain-error" ), QStringLiteral( "3" ) ).toFloat();
mMaxTerrainGroundError = elemTerrain.attribute( QStringLiteral( "max-ground-error" ), QStringLiteral( "1" ) ).toFloat();
mTerrainShadingEnabled = elemTerrain.attribute( QStringLiteral( "shading-enabled" ), QStringLiteral( "0" ) ).toInt();
QDomElement elemTerrainShadingMaterial = elemTerrain.firstChildElement( QStringLiteral( "shading-material" ) );
if ( !elemTerrainShadingMaterial.isNull() )
mTerrainShadingMaterial.readXml( elemTerrainShadingMaterial );
mShowLabels = elemTerrain.attribute( QStringLiteral( "show-labels" ), QStringLiteral( "0" ) ).toInt();
QDomElement elemMapLayers = elemTerrain.firstChildElement( QStringLiteral( "layers" ) );
QDomElement elemMapLayer = elemMapLayers.firstChildElement( QStringLiteral( "layer" ) );
@ -169,6 +175,10 @@ QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteCon
elemTerrain.setAttribute( QStringLiteral( "texture-size" ), mMapTileResolution );
elemTerrain.setAttribute( QStringLiteral( "max-terrain-error" ), QString::number( mMaxTerrainScreenError ) );
elemTerrain.setAttribute( QStringLiteral( "max-ground-error" ), QString::number( mMaxTerrainGroundError ) );
elemTerrain.setAttribute( QStringLiteral( "shading-enabled" ), mTerrainShadingEnabled ? 1 : 0 );
QDomElement elemTerrainShadingMaterial = doc.createElement( QStringLiteral( "shading-material" ) );
mTerrainShadingMaterial.writeXml( elemTerrainShadingMaterial );
elemTerrain.appendChild( elemTerrainShadingMaterial );
elemTerrain.setAttribute( QStringLiteral( "show-labels" ), mShowLabels ? 1 : 0 );
QDomElement elemMapLayers = doc.createElement( QStringLiteral( "layers" ) );
Q_FOREACH ( const QgsMapLayerRef &layerRef, mLayers )
@ -370,6 +380,24 @@ void Qgs3DMapSettings::setTerrainGenerator( QgsTerrainGenerator *gen )
emit terrainGeneratorChanged();
}
void Qgs3DMapSettings::setTerrainShadingEnabled( bool enabled )
{
if ( mTerrainShadingEnabled == enabled )
return;
mTerrainShadingEnabled = enabled;
emit terrainShadingChanged();
}
void Qgs3DMapSettings::setTerrainShadingMaterial( const QgsPhongMaterialSettings &material )
{
if ( mTerrainShadingMaterial == material )
return;
mTerrainShadingMaterial = material;
emit terrainShadingChanged();
}
void Qgs3DMapSettings::setRenderers( const QList<QgsAbstract3DRenderer *> &renderers )
{
mRenderers = renderers;

View File

@ -24,6 +24,7 @@
#include "qgscoordinatereferencesystem.h"
#include "qgsmaplayerref.h"
#include "qgsphongmaterialsettings.h"
#include "qgsterraingenerator.h"
#include "qgsvector3d.h"
@ -200,6 +201,35 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject
//! Returns terrain generator. It takes care of producing terrain tiles from the input data.
QgsTerrainGenerator *terrainGenerator() const { return mTerrainGenerator.get(); }
/**
* Sets whether terrain shading is enabled.
* \sa isTerrainShadingEnabled()
* \since QGIS 3.6
*/
void setTerrainShadingEnabled( bool enabled );
/**
* Returns whether terrain shading is enabled. When enabled, in addition to the terrain texture
* generated from the map, the terrain rendering will take into account position of the lights,
* terrain normals and terrain shading material (ambient and specular colors, shininess).
* \since QGIS 3.6
*/
bool isTerrainShadingEnabled() const { return mTerrainShadingEnabled; }
/**
* Sets terrain shading material.
* \sa terrainShadingMaterial()
* \since QGIS 3.6
*/
void setTerrainShadingMaterial( const QgsPhongMaterialSettings &material );
/**
* Returns terrain shading material. Diffuse color component is ignored since the diffuse component
* is provided by 2D rendered map texture. Only used when isTerrainShadingEnabled() is true.
* \since QGIS 3.6
*/
QgsPhongMaterialSettings terrainShadingMaterial() const { return mTerrainShadingMaterial; }
//! Sets list of extra 3D renderers to use in the scene. Takes ownership of the objects.
void setRenderers( const QList<QgsAbstract3DRenderer *> &renderers SIP_TRANSFER );
//! Returns list of extra 3D renderers
@ -261,6 +291,12 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject
void maxTerrainScreenErrorChanged();
//! Emitted when the maximum terrain ground error has changed
void maxTerrainGroundErrorChanged();
/**
* Emitted when terrain shading enabled flag or terrain shading material has changed
* \since QGIS 3.6
*/
void terrainShadingChanged();
//! Emitted when the flag whether terrain's bounding boxes are shown has changed
void showTerrainBoundingBoxesChanged();
//! Emitted when the flag whether terrain's tile info is shown has changed
@ -285,6 +321,8 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject
int mMapTileResolution = 512; //!< Size of map textures of tiles in pixels (width/height)
float mMaxTerrainScreenError = 3.f; //!< Maximum allowed terrain error in pixels (determines when tiles are switched to more detailed ones)
float mMaxTerrainGroundError = 1.f; //!< Maximum allowed horizontal map error in map units (determines how many zoom levels will be used)
bool mTerrainShadingEnabled = false; //!< Whether terrain should be shaded taking lights into account
QgsPhongMaterialSettings mTerrainShadingMaterial; //!< Material to use for the terrain (if shading is enabled). Diffuse color is ignored.
bool mShowTerrainBoundingBoxes = false; //!< Whether to show bounding boxes of entities - useful for debugging
bool mShowTerrainTileInfo = false; //!< Whether to draw extra information about terrain tiles to the textures - useful for debugging
bool mShowCameraViewCenter = false; //!< Whether to show camera view center as a sphere - useful for debugging

View File

@ -65,6 +65,14 @@ class _3D_EXPORT QgsPhongMaterialSettings
//! Writes settings to a DOM element
void writeXml( QDomElement &elem ) const;
bool operator==( const QgsPhongMaterialSettings &other ) const
{
return mAmbient == other.mAmbient &&
mDiffuse == other.mDiffuse &&
mSpecular == other.mSpecular &&
mShininess == other.mShininess;
}
private:
QColor mAmbient;
QColor mDiffuse;

View File

@ -94,7 +94,7 @@ Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *par
// create material
createTextureComponent( entity );
createTextureComponent( entity, map.isTerrainShadingEnabled(), map.terrainShadingMaterial() );
// create transform

View File

@ -52,7 +52,8 @@ Qt3DCore::QEntity *FlatTerrainChunkLoader::createEntity( Qt3DCore::QEntity *pare
// create material
createTextureComponent( entity );
const Qgs3DMapSettings &map = terrain()->map3D();
createTextureComponent( entity, map.isTerrainShadingEnabled(), map.terrainShadingMaterial() );
// create transform

View File

@ -25,11 +25,8 @@
#include <Qt3DRender/QTexture>
#if QT_VERSION >= 0x050900
#include <Qt3DExtras/QTextureMaterial>
#else
#include <Qt3DExtras/QDiffuseMapMaterial>
#endif
#include "quantizedmeshterraingenerator.h"
@ -67,23 +64,32 @@ void QgsTerrainTileLoader::loadTexture()
mTextureJobId = mTerrain->textureGenerator()->render( mExtentMapCrs, mTileDebugText );
}
void QgsTerrainTileLoader::createTextureComponent( QgsTerrainTileEntity *entity )
void QgsTerrainTileLoader::createTextureComponent( QgsTerrainTileEntity *entity, bool isShadingEnabled, const QgsPhongMaterialSettings &shadingMaterial )
{
Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D( entity );
QgsTerrainTextureImage *textureImage = new QgsTerrainTextureImage( mTextureImage, mExtentMapCrs, mTileDebugText );
texture->addTextureImage( textureImage );
texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
Qt3DExtras::QTextureMaterial *material = nullptr;
#if QT_VERSION >= 0x050900
material = new Qt3DExtras::QTextureMaterial;
material->setTexture( texture );
#else
material = new Qt3DExtras::QDiffuseMapMaterial;
material->setDiffuse( texture );
material->setShininess( 1 );
material->setAmbient( Qt::white );
#endif
Qt3DRender::QMaterial *material = nullptr;
if ( isShadingEnabled )
{
Qt3DExtras::QDiffuseMapMaterial *diffuseMapMaterial;
diffuseMapMaterial = new Qt3DExtras::QDiffuseMapMaterial;
diffuseMapMaterial->setDiffuse( texture );
diffuseMapMaterial->setAmbient( shadingMaterial.ambient() );
diffuseMapMaterial->setSpecular( shadingMaterial.specular() );
diffuseMapMaterial->setShininess( shadingMaterial.shininess() );
material = diffuseMapMaterial;
}
else
{
Qt3DExtras::QTextureMaterial *textureMaterial = new Qt3DExtras::QTextureMaterial;
textureMaterial->setTexture( texture );
material = textureMaterial;
}
entity->setTextureImage( textureImage );
entity->addComponent( material ); // takes ownership if the component has no parent
}

View File

@ -32,6 +32,7 @@
#include <QImage>
#include "qgsrectangle.h"
class QgsPhongMaterialSettings;
class QgsTerrainEntity;
class QgsTerrainTileEntity;
@ -54,7 +55,7 @@ class QgsTerrainTileLoader : public QgsChunkLoader
//! Starts asynchronous rendering of map texture
void loadTexture();
//! Creates material component for the entity with the rendered map as a texture
void createTextureComponent( QgsTerrainTileEntity *entity );
void createTextureComponent( QgsTerrainTileEntity *entity, bool isShadingEnabled, const QgsPhongMaterialSettings &shadingMaterial );
//! Gives access to the terain entity
QgsTerrainEntity *terrain() { return mTerrain; }

View File

@ -69,6 +69,10 @@ Qgs3DMapConfigWidget::Qgs3DMapConfigWidget( Qgs3DMapSettings *map, QgsMapCanvas
chkShowBoundingBoxes->setChecked( mMap->showTerrainBoundingBoxes() );
chkShowCameraViewCenter->setChecked( mMap->showCameraViewCenter() );
groupTerrainShading->setChecked( mMap->isTerrainShadingEnabled() );
widgetTerrainMaterial->setDiffuseVisible( false );
widgetTerrainMaterial->setMaterial( mMap->terrainShadingMaterial() );
connect( cboTerrainLayer, static_cast<void ( QComboBox::* )( int )>( &QgsMapLayerComboBox::currentIndexChanged ), this, &Qgs3DMapConfigWidget::onTerrainLayerChanged );
connect( spinMapResolution, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ), this, &Qgs3DMapConfigWidget::updateMaxZoomLevel );
connect( spinGroundError, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &Qgs3DMapConfigWidget::updateMaxZoomLevel );
@ -134,6 +138,9 @@ void Qgs3DMapConfigWidget::apply()
mMap->setShowTerrainTilesInfo( chkShowTileInfo->isChecked() );
mMap->setShowTerrainBoundingBoxes( chkShowBoundingBoxes->isChecked() );
mMap->setShowCameraViewCenter( chkShowCameraViewCenter->isChecked() );
mMap->setTerrainShadingEnabled( groupTerrainShading->isChecked() );
mMap->setTerrainShadingMaterial( widgetTerrainMaterial->material() );
}
void Qgs3DMapConfigWidget::onTerrainLayerChanged()

View File

@ -31,6 +31,17 @@ QgsPhongMaterialWidget::QgsPhongMaterialWidget( QWidget *parent )
connect( spinShininess, static_cast<void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsPhongMaterialWidget::changed );
}
void QgsPhongMaterialWidget::setDiffuseVisible( bool visible )
{
label->setVisible( visible );
btnDiffuse->setVisible( visible );
}
bool QgsPhongMaterialWidget::isDiffuseVisible() const
{
return btnDiffuse->isVisible();
}
void QgsPhongMaterialWidget::setMaterial( const QgsPhongMaterialSettings &material )
{
btnDiffuse->setColor( material.diffuse() );

View File

@ -30,6 +30,9 @@ class QgsPhongMaterialWidget : public QWidget, private Ui::PhongMaterialWidget
public:
explicit QgsPhongMaterialWidget( QWidget *parent = nullptr );
void setDiffuseVisible( bool visible );
bool isDiffuseVisible() const;
void setMaterial( const QgsPhongMaterialSettings &material );
QgsPhongMaterialSettings material() const;

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>691</width>
<height>830</height>
<height>1135</height>
</rect>
</property>
<property name="windowTitle">
@ -87,6 +87,21 @@
</layout>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBox" name="groupTerrainShading">
<property name="title">
<string>Terrain shading</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QgsPhongMaterialWidget" name="widgetTerrainMaterial" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0">
@ -224,6 +239,18 @@
<extends>QSpinBox</extends>
<header>qgsspinbox.h</header>
</customwidget>
<customwidget>
<class>QgsPhongMaterialWidget</class>
<extends>QWidget</extends>
<header>qgsphongmaterialwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>cboTerrainLayer</tabstop>