Skip to content

Commit

Permalink
Merge pull request #8458 from wonder-sk/terrain-shading
Browse files Browse the repository at this point in the history
[3d] Terrain shading
  • Loading branch information
wonder-sk committed Nov 12, 2018
2 parents 9b04a29 + bcf8b0d commit 1275708
Show file tree
Hide file tree
Showing 15 changed files with 222 additions and 44 deletions.
2 changes: 2 additions & 0 deletions python/3d/auto_generated/qgsphongmaterialsettings.sip.in
Expand Up @@ -73,6 +73,8 @@ Reads settings from a DOM element
Writes settings to a DOM element
%End

bool operator==( const QgsPhongMaterialSettings &other ) const;

};


Expand Down
1 change: 1 addition & 0 deletions src/3d/qgs3dmapscene.cpp
Expand Up @@ -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

Expand Down
28 changes: 28 additions & 0 deletions src/3d/qgs3dmapsettings.cpp
Expand Up @@ -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 )
Expand Down Expand Up @@ -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" ) );
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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;
Expand Down
44 changes: 41 additions & 3 deletions src/3d/qgs3dmapsettings.h
Expand Up @@ -24,6 +24,7 @@

#include "qgscoordinatereferencesystem.h"
#include "qgsmaplayerref.h"
#include "qgsphongmaterialsettings.h"
#include "qgsterraingenerator.h"
#include "qgsvector3d.h"

Expand Down Expand Up @@ -153,7 +154,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject

/**
* Sets resolution (in pixels) of the texture of a terrain tile
* \sa mapTileResolution()
* \see mapTileResolution()
*/
void setMapTileResolution( int res );

Expand All @@ -165,7 +166,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject

/**
* Sets maximum allowed screen error of terrain tiles in pixels.
* \sa maxTerrainScreenError()
* \see maxTerrainScreenError()
*/
void setMaxTerrainScreenError( float error );

Expand All @@ -180,7 +181,7 @@ class _3D_EXPORT Qgs3DMapSettings : public QObject

/**
* Returns maximum ground error of terrain tiles in world units.
* \sa maxTerrainGroundError()
* \see maxTerrainGroundError()
*/
void setMaxTerrainGroundError( float error );

Expand All @@ -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.
* \see 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.
* \see 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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/3d/qgsphongmaterialsettings.h
Expand Up @@ -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;
Expand Down
68 changes: 55 additions & 13 deletions src/3d/terrain/qgsdemterraintilegeometry_p.cpp
Expand Up @@ -27,7 +27,7 @@
using namespace Qt3DRender;


static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByteArray &heights )
static QByteArray createPlaneVertexData( int res, float side, float vertScale, float skirtHeight, const QByteArray &heights )
{
Q_ASSERT( res >= 2 );
Q_ASSERT( heights.count() == res * res * static_cast<int>( sizeof( float ) ) );
Expand Down Expand Up @@ -58,17 +58,20 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte
// as we do not create valid triangles that would use such vertices
const float noDataHeight = 0;

const int iMax = resolution.width() - 1;
const int jMax = resolution.height() - 1;

// Iterate over z
for ( int j = -1; j <= resolution.height(); ++j )
{
int jBound = qBound( 0, j, resolution.height() - 1 );
int jBound = qBound( 0, j, jMax );
const float z = z0 + static_cast<float>( jBound ) * dz;
const float v = static_cast<float>( jBound ) * dv;

// Iterate over x
for ( int i = -1; i <= resolution.width(); ++i )
{
int iBound = qBound( 0, i, resolution.width() - 1 );
int iBound = qBound( 0, i, iMax );
const float x = x0 + static_cast<float>( iBound ) * dx;
const float u = static_cast<float>( iBound ) * du;

Expand All @@ -83,18 +86,49 @@ static QByteArray createPlaneVertexData( int res, float skirtHeight, const QByte

// position
*fptr++ = x;
*fptr++ = height;
*fptr++ = height / side * vertScale;
*fptr++ = z;

// texture coordinates
*fptr++ = u;
*fptr++ = v;

// TODO: compute correct normals based on neighboring pixels
// normal
*fptr++ = 0.0f;
*fptr++ = 1.0f;
*fptr++ = 0.0f;
// calculate normal coordinates
#define zAt( ii, jj ) zData[ jj * resolution.width() + ii ] * vertScale
float zi0 = zAt( qBound( 0, i - 1, iMax ), jBound );
float zi1 = zAt( qBound( 0, i + 1, iMax ), jBound );
float zj0 = zAt( iBound, qBound( 0, j - 1, jMax ) );
float zj1 = zAt( iBound, qBound( 0, j + 1, jMax ) );

QVector3D n;
if ( std::isnan( zi0 ) || std::isnan( zi1 ) || std::isnan( zj0 ) || std::isnan( zj1 ) )
n = QVector3D( 0, 1, 0 );
else
{
float di, dj;
float zij = height * vertScale;

if ( i == 0 )
di = 2 * ( zij - zi1 );
else if ( i == iMax )
di = 2 * ( zi0 - zij );
else
di = zi0 - zi1;

if ( j == 0 )
dj = 2 * ( zij - zj1 );
else if ( j == jMax )
dj = 2 * ( zj0 - zij );
else
dj = zj0 - zj1;

n = QVector3D( di, 2 * side / res, dj );
n.normalize();
}

*fptr++ = n.x();
*fptr++ = n.y();
*fptr++ = n.z();
}
}

Expand Down Expand Up @@ -176,22 +210,26 @@ static QByteArray createPlaneIndexData( int res, const QByteArray &heightMap )
class PlaneVertexBufferFunctor : public QBufferDataGenerator
{
public:
explicit PlaneVertexBufferFunctor( int resolution, float skirtHeight, const QByteArray &heightMap )
explicit PlaneVertexBufferFunctor( int resolution, float side, float vertScale, float skirtHeight, const QByteArray &heightMap )
: mResolution( resolution )
, mSide( side )
, mVertScale( vertScale )
, mSkirtHeight( skirtHeight )
, mHeightMap( heightMap )
{}

QByteArray operator()() final
{
return createPlaneVertexData( mResolution, mSkirtHeight, mHeightMap );
return createPlaneVertexData( mResolution, mSide, mVertScale, mSkirtHeight, mHeightMap );
}

bool operator ==( const QBufferDataGenerator &other ) const final
{
const PlaneVertexBufferFunctor *otherFunctor = functor_cast<PlaneVertexBufferFunctor>( &other );
if ( otherFunctor != nullptr )
return ( otherFunctor->mResolution == mResolution &&
otherFunctor->mSide == mSide &&
otherFunctor->mVertScale == mVertScale &&
otherFunctor->mSkirtHeight == mSkirtHeight &&
otherFunctor->mHeightMap == mHeightMap );
return false;
Expand All @@ -201,6 +239,8 @@ class PlaneVertexBufferFunctor : public QBufferDataGenerator

private:
int mResolution;
float mSide;
float mVertScale;
float mSkirtHeight;
QByteArray mHeightMap;
};
Expand Down Expand Up @@ -239,9 +279,11 @@ class PlaneIndexBufferFunctor : public QBufferDataGenerator
// ------------


DemTerrainTileGeometry::DemTerrainTileGeometry( int resolution, float skirtHeight, const QByteArray &heightMap, DemTerrainTileGeometry::QNode *parent )
DemTerrainTileGeometry::DemTerrainTileGeometry( int resolution, float side, float vertScale, float skirtHeight, const QByteArray &heightMap, DemTerrainTileGeometry::QNode *parent )
: QGeometry( parent )
, mResolution( resolution )
, mSide( side )
, mVertScale( vertScale )
, mSkirtHeight( skirtHeight )
, mHeightMap( heightMap )
{
Expand Down Expand Up @@ -357,7 +399,7 @@ void DemTerrainTileGeometry::init()

// switched to setting data instead of just setting data generators because we also need the buffers
// available for ray-mesh intersections and we can't access the private copy of data in Qt (if there is any)
mVertexBuffer->setData( PlaneVertexBufferFunctor( mResolution, mSkirtHeight, mHeightMap )() );
mVertexBuffer->setData( PlaneVertexBufferFunctor( mResolution, mSide, mVertScale, mSkirtHeight, mHeightMap )() );
mIndexBuffer->setData( PlaneIndexBufferFunctor( mResolution, mHeightMap )() );

addAttribute( mPositionAttribute );
Expand Down
4 changes: 3 additions & 1 deletion src/3d/terrain/qgsdemterraintilegeometry_p.h
Expand Up @@ -59,14 +59,16 @@ class DemTerrainTileGeometry : public Qt3DRender::QGeometry
* Constructs a terrain tile geometry. Resolution is the number of vertices on one side of the tile,
* heightMap is array of float values with one height value for each vertex
*/
explicit DemTerrainTileGeometry( int resolution, float skirtHeight, const QByteArray &heightMap, QNode *parent = nullptr );
explicit DemTerrainTileGeometry( int resolution, float side, float vertScale, float skirtHeight, const QByteArray &heightMap, QNode *parent = nullptr );

bool rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QMatrix4x4 &worldTransform, QVector3D &intersectionPoint );

private:
void init();

int mResolution;
float mSide;
float mVertScale;
float mSkirtHeight;
QByteArray mHeightMap;
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
Expand Down
21 changes: 11 additions & 10 deletions src/3d/terrain/qgsdemterraintileloader_p.cpp
Expand Up @@ -76,32 +76,33 @@ Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *par
return nullptr;
}

const Qgs3DMapSettings &map = terrain()->map3D();
QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( mNode->tileX(), mNode->tileY(), mNode->tileZ() ); //node->extent;
double x0 = extent.xMinimum() - map.origin().x();
double y0 = extent.yMinimum() - map.origin().y();
double side = extent.width();
double half = side / 2;


QgsTerrainTileEntity *entity = new QgsTerrainTileEntity;

// create geometry renderer

Qt3DRender::QGeometryRenderer *mesh = new Qt3DRender::QGeometryRenderer;
mesh->setGeometry( new DemTerrainTileGeometry( mResolution, mSkirtHeight, mHeightMap, mesh ) );
mesh->setGeometry( new DemTerrainTileGeometry( mResolution, side, map.terrainVerticalScale(), mSkirtHeight, mHeightMap, mesh ) );
entity->addComponent( mesh ); // takes ownership if the component has no parent

// create material

createTextureComponent( entity );
createTextureComponent( entity, map.isTerrainShadingEnabled(), map.terrainShadingMaterial() );

// create transform

Qt3DCore::QTransform *transform = nullptr;
transform = new Qt3DCore::QTransform();
entity->addComponent( transform );

const Qgs3DMapSettings &map = terrain()->map3D();
QgsRectangle extent = map.terrainGenerator()->tilingScheme().tileToExtent( mNode->tileX(), mNode->tileY(), mNode->tileZ() ); //node->extent;
double x0 = extent.xMinimum() - map.origin().x();
double y0 = extent.yMinimum() - map.origin().y();
double side = extent.width();
double half = side / 2;

transform->setScale3D( QVector3D( side, map.terrainVerticalScale(), side ) );
transform->setScale( side );
transform->setTranslation( QVector3D( x0 + half, 0, - ( y0 + half ) ) );

mNode->setExactBbox( QgsAABB( x0, zMin * map.terrainVerticalScale(), -y0, x0 + side, zMax * map.terrainVerticalScale(), -( y0 + side ) ) );
Expand Down

0 comments on commit 1275708

Please sign in to comment.