QGIS API Documentation 4.1.0-Master (26185ffb827)
Loading...
Searching...
No Matches
qgspostprocessingentity.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspostprocessingentity.cpp
3 --------------------------------------
4 Date : August 2020
5 Copyright : (C) 2020 by Belgacem Nedjima
6 Email : gb underscore nedjima at esi dot dz
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgs3dutils.h"
20#include "qgsbloomrenderview.h"
23#include "qgsframegraph.h"
24#include "qgsshadowrenderview.h"
25
26#include <QString>
27#include <QUrl>
28#include <Qt3DCore/QAttribute>
29#include <Qt3DCore/QBuffer>
30#include <Qt3DCore/QGeometry>
31#include <Qt3DRender/QDepthTest>
32#include <Qt3DRender/QGeometryRenderer>
33#include <Qt3DRender/QGraphicsApiFilter>
34#include <Qt3DRender/QMaterial>
35#include <Qt3DRender/QParameter>
36#include <Qt3DRender/QTechnique>
37
38#include "moc_qgspostprocessingentity.cpp"
39
40using namespace Qt::StringLiterals;
41
42QgsPostprocessingEntity::QgsPostprocessingEntity( QgsFrameGraph *frameGraph, Qt3DRender::QLayer *layer, QNode *parent )
43 : QgsRenderPassQuad( layer, parent )
44{
45 QgsShadowRenderView &shadowRenderView = frameGraph->shadowRenderView();
46 QgsForwardRenderView &forwardRenderView = frameGraph->forwardRenderView();
48 QgsBloomRenderView &bloomRenderView = frameGraph->bloomRenderView();
49
50 mColorTextureParameter = new Qt3DRender::QParameter( u"colorTexture"_s, forwardRenderView.colorTexture() );
51 mDepthTextureParameter = new Qt3DRender::QParameter( u"depthTexture"_s, forwardRenderView.depthTexture() );
52 mAmbientOcclusionTextureParameter = new Qt3DRender::QParameter( u"ssaoTexture"_s, aoRenderView.blurredFactorMapTexture() );
53
54 mMaterial->addParameter( mColorTextureParameter );
55 mMaterial->addParameter( mDepthTextureParameter );
56 mMaterial->addParameter( mAmbientOcclusionTextureParameter );
57
58 QList<Qt3DRender::QParameter *> globalShadowParams;
59 mShadowMapParameter = new Qt3DRender::QParameter( u"shadowTexture"_s, shadowRenderView.mapTextureArray() );
60 globalShadowParams << mShadowMapParameter;
61
62 mMainCamera = frameGraph->mainCamera();
63
64 for ( int i = 0; i < Qgs3D::NUM_SHADOW_CASCADES; ++i )
65 {
66 mLightCameras[i] = shadowRenderView.lightCamera( i );
67 }
68
69 // a [0] suffix for a QParameter name maps the parameter to a GLSL array.
70 // We must take care that the parameter value is always a variant list of equal length!
71 const QVariantList csmMatrices = QVariantList( Qgs3D::NUM_SHADOW_CASCADES, QVariant::fromValue( QMatrix4x4() ) );
72 mCsmMatricesParameter = new Qt3DRender::QParameter( QString( "csmMatrices[0]" ), csmMatrices );
73 globalShadowParams << mCsmMatricesParameter;
74 mCsmBoundsMatricesParameter = new Qt3DRender::QParameter( QString( "csmBoundsMatrices[0]" ), csmMatrices );
75 globalShadowParams << mCsmBoundsMatricesParameter;
76 mMaxShadowDistanceParameter = new Qt3DRender::QParameter( u"maxShadowDistance"_s, QVariant::fromValue( 0.0f ) );
77 globalShadowParams << mMaxShadowDistanceParameter;
78
79 mFarPlaneParameter = new Qt3DRender::QParameter( u"farPlane"_s, mMainCamera->farPlane() );
80 mMaterial->addParameter( mFarPlaneParameter );
81 connect( mMainCamera, &Qt3DRender::QCamera::farPlaneChanged, mFarPlaneParameter, [&]( float farPlane ) { mFarPlaneParameter->setValue( farPlane ); } );
82 mNearPlaneParameter = new Qt3DRender::QParameter( u"nearPlane"_s, mMainCamera->nearPlane() );
83 mMaterial->addParameter( mNearPlaneParameter );
84 connect( mMainCamera, &Qt3DRender::QCamera::nearPlaneChanged, mNearPlaneParameter, [&]( float nearPlane ) { mNearPlaneParameter->setValue( nearPlane ); } );
85
86 mMainCameraInvViewMatrixParameter = new Qt3DRender::QParameter( u"invertedCameraView"_s, mMainCamera->viewMatrix().inverted() );
87 mMaterial->addParameter( mMainCameraInvViewMatrixParameter );
88 mMainCameraInvProjMatrixParameter = new Qt3DRender::QParameter( u"invertedCameraProj"_s, mMainCamera->projectionMatrix().inverted() );
89 mMaterial->addParameter( mMainCameraInvProjMatrixParameter );
90 connect( mMainCamera, &Qt3DRender::QCamera::projectionMatrixChanged, mMainCameraInvProjMatrixParameter, [&]( const QMatrix4x4 &projectionMatrix ) {
91 mMainCameraInvProjMatrixParameter->setValue( projectionMatrix.inverted() );
92 } );
93 connect( mMainCamera, &Qt3DRender::QCamera::viewMatrixChanged, mMainCameraInvViewMatrixParameter, [&]() { mMainCameraInvViewMatrixParameter->setValue( mMainCamera->viewMatrix().inverted() ); } );
94
95 mRenderShadowsParameter = new Qt3DRender::QParameter( u"renderShadows"_s, QVariant::fromValue( 0 ) );
96 globalShadowParams << mRenderShadowsParameter;
97 mShadowLightIndexParameter = new Qt3DRender::QParameter( u"shadowLightIndex"_s, QVariant::fromValue( 0 ) );
98 globalShadowParams << mShadowLightIndexParameter;
99 mShadowBiasParameter = new Qt3DRender::QParameter( u"shadowBias"_s, QVariant::fromValue( 0.00001f ) );
100 globalShadowParams << mShadowBiasParameter;
101
102 frameGraph->addGlobalParameters( globalShadowParams );
103
104 mEyeDomeLightingEnabledParameter = new Qt3DRender::QParameter( u"edlEnabled"_s, QVariant::fromValue( 0 ) );
105 mEyeDomeLightingStrengthParameter = new Qt3DRender::QParameter( u"edlStrength"_s, QVariant::fromValue( 1000.0f ) );
106 mEyeDomeLightingDistanceParameter = new Qt3DRender::QParameter( u"edlDistance"_s, QVariant::fromValue( 2.0f ) );
107 mMaterial->addParameter( mEyeDomeLightingEnabledParameter );
108 mMaterial->addParameter( mEyeDomeLightingStrengthParameter );
109 mMaterial->addParameter( mEyeDomeLightingDistanceParameter );
110
111 mAmbientOcclusionEnabledParameter = new Qt3DRender::QParameter( u"ssaoEnabled"_s, QVariant::fromValue( 0 ) );
112 mMaterial->addParameter( mAmbientOcclusionEnabledParameter );
113
114 mBloomTextureParameter = new Qt3DRender::QParameter( u"bloomTexture"_s, bloomRenderView.bloomTexture() );
115 mMaterial->addParameter( mBloomTextureParameter );
116
117 mBloomEnabledParameter = new Qt3DRender::QParameter( u"bloomEnabled"_s, QVariant::fromValue( 1 ) );
118 mMaterial->addParameter( mBloomEnabledParameter );
119
120 mBloomFactorParameter = new Qt3DRender::QParameter( u"bloomFactor"_s, 0.05 );
121 mMaterial->addParameter( mBloomFactorParameter );
122
123 mExposureParameter = new Qt3DRender::QParameter( u"exposureAdjustment"_s, 0.0f );
124 mMaterial->addParameter( mExposureParameter );
125 mToneMappingParameter = new Qt3DRender::QParameter( u"toneMapping"_s, 1 );
126 mMaterial->addParameter( mToneMappingParameter );
127
128 const QString vertexShaderPath = u"qrc:/shaders/postprocess.vert"_s;
129 const QString fragmentShaderPath = u"qrc:/shaders/postprocess.frag"_s;
130
131 mShader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( vertexShaderPath ) ) );
132
133 const QByteArray fragmentShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( fragmentShaderPath ) );
134 const QByteArray finalFragmentShaderCode = Qgs3DUtils::addDefinesToShaderCode( fragmentShaderCode, QStringList( { "ENABLE_EFFECTS" } ) );
135 mShader->setFragmentShaderCode( finalFragmentShaderCode );
136}
137
139{
140 setBloomFactor( static_cast< float >( settings.intensity() ) );
141}
142
143void QgsPostprocessingEntity::updateShadowSettings( const QgsShadowSettings &shadowSettings, const QgsVector3D &lightDir, int size, int globalLightIndex )
144{
145 // We are using "Cascading Shadow Maps" technique.
146 // Reading/watching which was useful during development:
147 // https://learnopengl.com/Guest-Articles/2021/CSM
148 // https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf
149 // https://www.youtube.com/watch?v=Jhopq2lkzMQ
150 // https://www.youtube.com/watch?v=qbDrqARX07o
151 // https://web.archive.org/web/20170710150304/https://cesiumjs.org/presentations/ShadowsAndCesiumImplementation.pdf
152
153 mShadowMapResolution = size;
154 setShadowLightIndex( globalLightIndex );
155 setShadowBias( static_cast<float>( shadowSettings.shadowBias() ) );
156 float maximumShadowRenderingDistance = static_cast<float>( shadowSettings.maximumShadowRenderingDistance() );
157
158 const QVector3D lightDirection = lightDir.toVector3D().normalized();
159 const QVector3D up = Qgs3DUtils::calculateDirectionalLightUpVector( lightDirection );
160
161 const float mainCameraNearPlane = mMainCamera->nearPlane();
162 // cap the far plane to the shadow rendering distance so we don't waste shadow resolution
163 const float mainCameraFarPlane = std::min( mMainCamera->farPlane(), maximumShadowRenderingDistance );
164
165 // "Practical Split Scheme" for cascading shadow maps.
166 // using a quite large lambda to account for typical near/far plane distances seen in
167 // QGIS 3d maps (0.5 - ~2500)
168 // We match Cesium's lambda -- see https://web.archive.org/web/20170710150304/https://cesiumjs.org/presentations/ShadowsAndCesiumImplementation.pdf (slide 38)
169 constexpr float PRACTICAL_SPLIT_SCHEME_LAMBDA = 0.9f;
170 const std::vector<float> cascadeSplits = Qgs3DUtils::calculateCascadeSplits( Qgs3D::NUM_SHADOW_CASCADES, mainCameraNearPlane, mainCameraFarPlane, PRACTICAL_SPLIT_SCHEME_LAMBDA );
171
172 const QMatrix4x4 invertedCameraView = mMainCamera->viewMatrix().inverted();
173 const float cameraFov = mMainCamera->fieldOfView();
174 const float cameraAspect = mMainCamera->aspectRatio();
175
176 const float shadowMapResolution = static_cast< float >( mShadowMapResolution );
177
178 // we are building two matrix lists, one containing the exact bounds of each
179 // cascade, and the other which is an exact match for the actual camera used
180 // for each cascade's texture. These differ, as we pull back the camera's
181 // near plane for reasons described below...
182 QVariantList csmMatrices( Qgs3D::NUM_SHADOW_CASCADES, QVariant() );
183 QVariantList csmBoundsMatrices( Qgs3D::NUM_SHADOW_CASCADES, QVariant() );
184
185 // here we are calculating the cascades using bounding spheres, in order to stabilise the
186 // matrices and avoid shadow shimmer when the camera is moved or rotated
187 // see eg https://media.gdcvault.com/gdc09/slides/100_Handout%203.pdf from slide 21
188 for ( int i = 0; i < Qgs3D::NUM_SHADOW_CASCADES; ++i )
189 {
190 const float zNear = cascadeSplits[i];
191 const float zFar = cascadeSplits[i + 1];
192
193 // calculate the 8 corners of the camera frustum slice in world space
194 QVector3D worldFrustumCorners[8];
195 QVector3D worldFrustrumCenter;
196 Qgs3DUtils::calculateFrustumSliceCorners( zNear, zFar, cameraFov, cameraAspect, invertedCameraView, worldFrustumCorners, worldFrustrumCenter );
197
198 // calculate the bounding sphere radius around the frustum center
199 float rawRadius = 0.0f;
200 for ( int j = 0; j < 8; ++j )
201 {
202 rawRadius = std::max( rawRadius, ( worldFrustumCorners[j] - worldFrustrumCenter ).length() );
203 }
204
205 // round up slightly to stabilize against floating point inaccuracies
206 // use dynamic step size based on the raw radius so we round larger radius to coarser increments
207 const float stepSize = std::max( std::pow( 2.0f, std::floor( std::log2( rawRadius ) ) - 4.0f ), 0.01f );
208 const float radius = std::ceil( rawRadius / stepSize ) * stepSize;
209
210 // project the actual world frustum center into this rotation-aligned light space
211 QMatrix4x4 lightRotation;
212 lightRotation.lookAt( QVector3D( 0, 0, 0 ), lightDirection, up );
213 QVector3D centerLightSpace = lightRotation * worldFrustrumCenter;
214
215 // snap to texels
216 // calculate how many world units are represented by a single texel
217 const float worldUnitsPerTexel = ( 2.0f * radius ) / shadowMapResolution;
218 // snap the light center to the nearest texel
219 centerLightSpace.setX( std::floor( centerLightSpace.x() / worldUnitsPerTexel ) * worldUnitsPerTexel );
220 centerLightSpace.setY( std::floor( centerLightSpace.y() / worldUnitsPerTexel ) * worldUnitsPerTexel );
221 const QVector3D snappedWorldCenter = lightRotation.inverted() * centerLightSpace;
222
223 // create the light view matrix
224 QMatrix4x4 lightView;
225 const QVector3D lightPos = snappedWorldCenter - ( lightDirection * radius );
226 lightView.lookAt( lightPos, snappedWorldCenter, up );
227
228 // apply to the specific light camera
229 mLightCameras[i]->setPosition( lightPos );
230 mLightCameras[i]->setViewCenter( snappedWorldCenter );
231 mLightCameras[i]->setUpVector( up );
232
233 float lightCameraLeft = -radius;
234 float lightCameraRight = radius;
235 float lightCameraBottom = -radius;
236 float lightCameraTop = radius;
237 // the Z-bounds must cover the entire bounding sphere
238 float lightCameraNearPlane = -radius;
239 float lightCameraFarPlane = radius * 2.0f;
240
241 QMatrix4x4 orthoBoundsMatrix;
242 orthoBoundsMatrix.ortho( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
243 csmBoundsMatrices[i] = QVariant::fromValue( orthoBoundsMatrix * lightView );
244
245 // Pull the near plane way back to catch shadows from behind the camera
246 // If we don't do this, then we'll lose the tops of shadows which should be cast by objects
247 // which sit behind this cascade slice's frustrum
248 constexpr float NEAR_PLANE_RETREAT = 5000.0f;
249 lightCameraNearPlane -= NEAR_PLANE_RETREAT;
250
251 // apply the corresponding Orthographic projection to the camera
252 mLightCameras[i]->lens()->setOrthographicProjection( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
253
254 // calculate combined light space matrix for the shader
255 QMatrix4x4 orthoMatrix;
256 orthoMatrix.ortho( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
257 csmMatrices[i] = QVariant::fromValue( orthoMatrix * lightView );
258 }
259
260 mCsmMatricesParameter->setValue( csmMatrices );
261 mCsmBoundsMatricesParameter->setValue( csmBoundsMatrices );
262 mMaxShadowDistanceParameter->setValue( mainCameraFarPlane );
263
265}
266
268{
269 mRenderShadowsParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
270}
271
273{
274 // yes, this is very hacky! It only works once, when first enabling display
275 // of the cascading shadow splits. That's ok, it's only here so that we can visualize them
276 // in tests...
277 if ( enabled )
278 {
279 const QString fragmentShaderPath = u"qrc:/shaders/postprocess.frag"_s;
280 const QByteArray fragmentShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( fragmentShaderPath ) );
281 QStringList defines { "ENABLE_EFFECTS", "TINT_CASCADES" };
282 const QByteArray finalFragmentShaderCode = Qgs3DUtils::addDefinesToShaderCode( fragmentShaderCode, defines );
283 mShader->setFragmentShaderCode( finalFragmentShaderCode );
284 }
285}
286
288{
289 mShadowLightIndexParameter->setValue( QVariant::fromValue( index ) );
290}
291
293{
294 mShadowBiasParameter->setValue( QVariant::fromValue( shadowBias ) );
295}
296
302
304{
305 mEyeDomeLightingEnabledParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
306}
307
309{
310 mEyeDomeLightingStrengthParameter->setValue( QVariant::fromValue( strength ) );
311}
312
314{
315 mEyeDomeLightingDistanceParameter->setValue( QVariant::fromValue( distance ) );
316}
317
319{
320 mAmbientOcclusionEnabledParameter->setValue( enabled );
321}
322
324{
325 mBloomEnabledParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
326}
327
329{
330 mBloomFactorParameter->setValue( factor );
331}
332
334{
335 mExposureParameter->setValue( static_cast< float >( settings.exposureAdjustment() ) );
336 mToneMappingParameter->setValue( static_cast< int >( settings.toneMapping() ) );
337}
Definition of the world.
double eyeDomeLightingStrength() const
Returns the eye dome lighting strength value.
int eyeDomeLightingDistance() const
Returns the eye dome lighting distance value (contributes to the contrast of the image).
static QVector3D calculateDirectionalLightUpVector(const QVector3D &lightDirection)
Calculates an appropriate up vector for a directional light.
static QByteArray addDefinesToShaderCode(const QByteArray &shaderCode, const QStringList &defines)
Inserts some define macros into a shader source code.
static std::vector< float > calculateCascadeSplits(int numberCascades, float nearPlane, float farPlane, float lambda=0.9f)
Calculates the split distances for cascading shadow maps using the "Practical Split Scheme".
static void calculateFrustumSliceCorners(float zNear, float zFar, float fov, float aspectRatio, const QMatrix4x4 &invertedCameraView, QVector3D(&corners)[8], QVector3D &center)
Calculates the 8 corners of a camera frustum slice in world space and its center point.
static constexpr int NUM_SHADOW_CASCADES
Number of shadow map cascades.
Definition qgs3d.h:104
Container class that holds different objects related to ambient occlusion rendering.
Qt3DRender::QTexture2D * blurredFactorMapTexture() const
Returns blur pass texture.
Container class that holds different objects related to bloom rendering.
Qt3DRender::QTexture2D * bloomTexture() const
Returns the texture containing the final bloom effect.
Contains the configuration of the lighting "bloom" effect.
double intensity() const
Returns the intensity of the bloom effect.
Contains the configuration of the scene's color grading settings, such as exposure and tone mapping.
Qgis::ToneMappingMethod toneMapping() const
Returns the tone mapping method applied to the scene.
double exposureAdjustment() const
Returns the exposure adjustment value.
Container class that holds different objects related to forward rendering.
Qt3DRender::QTexture2D * colorTexture() const
Returns forward color texture.
Qt3DRender::QTexture2D * depthTexture() const
Returns forward depth texture.
Container class that holds different objects related to frame graphs of 3D scenes.
void addGlobalParameters(const QList< Qt3DRender::QParameter * > &parameters)
Adds additional global parameters to the graph.
QgsAmbientOcclusionRenderView & ambientOcclusionRenderView()
Returns ambient occlusion renderview.
QgsForwardRenderView & forwardRenderView()
Returns forward renderview.
QgsShadowRenderView & shadowRenderView()
Returns shadow renderview.
QgsBloomRenderView & bloomRenderView()
Returns the bloom render view.
Qt3DRender::QCamera * mainCamera()
Returns the main camera.
void updateShadowSettings(const QgsShadowSettings &shadowSettings, const QgsVector3D &lightDir, int size, int globalLightIndex)
Sets shadow rendering to use a directional light.
void setShowCascadingShadowSplits(bool enabled)
Sets whether the splits between cascading shadow map boundaries should be shown.
void setAmbientOcclusionEnabled(bool enabled)
Sets whether screen space ambient occlusion is enabled.
void setShadowRenderingEnabled(bool enabled)
Sets whether shadow rendering is enabled.
void setShadowLightIndex(int index)
Sets the index of the directional light that is casting shadows.
void setBloomEnabled(bool enabled)
Sets whether physically based bloom is enabled.
void setEyeDomeLightingDistance(int distance)
Sets the eye dome lighting distance (contributes to the contrast of the image).
void setShadowBias(float shadowBias)
Sets the shadow bias value.
void setEyeDomeLightingStrength(double strength)
Sets the eye dome lighting strength.
void updateEyeDomeSettings(const Qgs3DMapSettings &settings)
Updates eye dome lighting settings from settings.
void setBloomFactor(float factor)
Sets the bloom factor, which controls the strength of the bloom effect.
void updateBloomSettings(const QgsBloomSettings &settings)
Sets bloom rendering to use a directional light.
void setEyeDomeLightingEnabled(bool enabled)
Sets whether eye dome lighting is enabled.
void updateColorGradingSettings(const QgsColorGradingSettings &settings)
Updates settings for color grading.
QgsPostprocessingEntity(QgsFrameGraph *frameGraph, Qt3DRender::QLayer *layer, QNode *parent=nullptr)
Constructor.
QgsRenderPassQuad(Qt3DRender::QLayer *layer, QNode *parent=nullptr)
Constructor.
Qt3DRender::QShaderProgram * mShader
Qt3DRender::QMaterial * mMaterial
Container class that holds different objects related to shadow rendering.
Qt3DRender::QCamera * lightCamera(int index)
Returns the light camera with the specified index.
Qt3DRender::QTexture2DArray * mapTextureArray() const
Returns the shadow depth texture array.
Contains configuration for rendering shadows.
double maximumShadowRenderingDistance() const
Returns the maximum shadow rendering distance accounted for when rendering shadows Objects further aw...
double shadowBias() const
Returns the shadow bias used to correct the numerical imprecision of shadows (for the depth test) Thi...
bool showCascadeSplits() const
Returns true if the cascading shadow splits should be tinted in the view.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
QVector3D toVector3D() const
Converts the current object to QVector3D.