QGIS API Documentation 4.1.0-Master (31622b25bb0)
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 mMaterial->addParameter( mColorTextureParameter );
54 mMaterial->addParameter( mDepthTextureParameter );
55 mMaterial->addParameter( mAmbientOcclusionTextureParameter );
56
57 QList<Qt3DRender::QParameter *> globalShadowParams;
58 mShadowMapParameter = new Qt3DRender::QParameter( u"shadowTexture"_s, shadowRenderView.mapTextureArray() );
59 globalShadowParams << mShadowMapParameter;
60
61 mMainCamera = frameGraph->mainCamera();
62
63 for ( int i = 0; i < Qgs3D::NUM_SHADOW_CASCADES; ++i )
64 {
65 mLightCameras[i] = shadowRenderView.lightCamera( i );
66 }
67
68 // a [0] suffix for a QParameter name maps the parameter to a GLSL array.
69 // We must take care that the parameter value is always a variant list of equal length!
70 const QVariantList csmMatrices = QVariantList( Qgs3D::NUM_SHADOW_CASCADES, QVariant::fromValue( QMatrix4x4() ) );
71 mCsmMatricesParameter = new Qt3DRender::QParameter( QString( "csmMatrices[0]" ), csmMatrices );
72 globalShadowParams << mCsmMatricesParameter;
73 mCsmBoundsMatricesParameter = new Qt3DRender::QParameter( QString( "csmBoundsMatrices[0]" ), csmMatrices );
74 globalShadowParams << mCsmBoundsMatricesParameter;
75 mMaxShadowDistanceParameter = new Qt3DRender::QParameter( u"maxShadowDistance"_s, QVariant::fromValue( 0.0f ) );
76 globalShadowParams << mMaxShadowDistanceParameter;
77
78 mFarPlaneParameter = new Qt3DRender::QParameter( u"farPlane"_s, mMainCamera->farPlane() );
79 mMaterial->addParameter( mFarPlaneParameter );
80 connect( mMainCamera, &Qt3DRender::QCamera::farPlaneChanged, mFarPlaneParameter, [&]( float farPlane ) { mFarPlaneParameter->setValue( farPlane ); } );
81 mNearPlaneParameter = new Qt3DRender::QParameter( u"nearPlane"_s, mMainCamera->nearPlane() );
82 mMaterial->addParameter( mNearPlaneParameter );
83 connect( mMainCamera, &Qt3DRender::QCamera::nearPlaneChanged, mNearPlaneParameter, [&]( float nearPlane ) { mNearPlaneParameter->setValue( nearPlane ); } );
84
85 mMainCameraInvViewMatrixParameter = new Qt3DRender::QParameter( u"invertedCameraView"_s, mMainCamera->viewMatrix().inverted() );
86 mMaterial->addParameter( mMainCameraInvViewMatrixParameter );
87 mMainCameraInvProjMatrixParameter = new Qt3DRender::QParameter( u"invertedCameraProj"_s, mMainCamera->projectionMatrix().inverted() );
88 mMaterial->addParameter( mMainCameraInvProjMatrixParameter );
89 connect( mMainCamera, &Qt3DRender::QCamera::projectionMatrixChanged, mMainCameraInvProjMatrixParameter, [&]( const QMatrix4x4 &projectionMatrix ) {
90 mMainCameraInvProjMatrixParameter->setValue( projectionMatrix.inverted() );
91 } );
92 connect( mMainCamera, &Qt3DRender::QCamera::viewMatrixChanged, mMainCameraInvViewMatrixParameter, [&]() { mMainCameraInvViewMatrixParameter->setValue( mMainCamera->viewMatrix().inverted() ); } );
93
94 mRenderShadowsParameter = new Qt3DRender::QParameter( u"renderShadows"_s, QVariant::fromValue( 0 ) );
95 globalShadowParams << mRenderShadowsParameter;
96 mShadowLightIndexParameter = new Qt3DRender::QParameter( u"shadowLightIndex"_s, QVariant::fromValue( 0 ) );
97 globalShadowParams << mShadowLightIndexParameter;
98 mShadowBiasParameter = new Qt3DRender::QParameter( u"shadowBias"_s, QVariant::fromValue( 0.00001f ) );
99 globalShadowParams << mShadowBiasParameter;
100
101 frameGraph->addGlobalParameters( globalShadowParams );
102
103 mEyeDomeLightingEnabledParameter = new Qt3DRender::QParameter( u"edlEnabled"_s, QVariant::fromValue( 0 ) );
104 mEyeDomeLightingStrengthParameter = new Qt3DRender::QParameter( u"edlStrength"_s, QVariant::fromValue( 1000.0f ) );
105 mEyeDomeLightingDistanceParameter = new Qt3DRender::QParameter( u"edlDistance"_s, QVariant::fromValue( 2.0f ) );
106 mMaterial->addParameter( mEyeDomeLightingEnabledParameter );
107 mMaterial->addParameter( mEyeDomeLightingStrengthParameter );
108 mMaterial->addParameter( mEyeDomeLightingDistanceParameter );
109
110 mAmbientOcclusionEnabledParameter = new Qt3DRender::QParameter( u"ssaoEnabled"_s, QVariant::fromValue( 0 ) );
111 mMaterial->addParameter( mAmbientOcclusionEnabledParameter );
112
113 mBloomTextureParameter = new Qt3DRender::QParameter( u"bloomTexture"_s, bloomRenderView.bloomTexture() );
114 mMaterial->addParameter( mBloomTextureParameter );
115
116 mBloomEnabledParameter = new Qt3DRender::QParameter( u"bloomEnabled"_s, QVariant::fromValue( 1 ) );
117 mMaterial->addParameter( mBloomEnabledParameter );
118
119 mBloomFactorParameter = new Qt3DRender::QParameter( u"bloomFactor"_s, 0.05 );
120 mMaterial->addParameter( mBloomFactorParameter );
121
122 const QString vertexShaderPath = u"qrc:/shaders/postprocess.vert"_s;
123 const QString fragmentShaderPath = u"qrc:/shaders/postprocess.frag"_s;
124
125 mShader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( vertexShaderPath ) ) );
126
127 const QByteArray fragmentShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( fragmentShaderPath ) );
128 const QByteArray finalFragmentShaderCode = Qgs3DUtils::addDefinesToShaderCode( fragmentShaderCode, QStringList( { "ENABLE_EFFECTS" } ) );
129 mShader->setFragmentShaderCode( finalFragmentShaderCode );
130}
131
132void QgsPostprocessingEntity::updateShadowSettings( const QgsDirectionalLightSettings &light, float maximumShadowRenderingDistance )
133{
134 // We are using "Cascading Shadow Maps" technique.
135 // Reading/watching which was useful during development:
136 // https://learnopengl.com/Guest-Articles/2021/CSM
137 // https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf
138 // https://www.youtube.com/watch?v=Jhopq2lkzMQ
139 // https://www.youtube.com/watch?v=qbDrqARX07o
140 // https://web.archive.org/web/20170710150304/https://cesiumjs.org/presentations/ShadowsAndCesiumImplementation.pdf
141
142 const QVector3D lightDirection = light.direction().toVector3D().normalized();
143 const QVector3D up = Qgs3DUtils::calculateDirectionalLightUpVector( lightDirection );
144
145 const float mainCameraNearPlane = mMainCamera->nearPlane();
146 // cap the far plane to the shadow rendering distance so we don't waste shadow resolution
147 const float mainCameraFarPlane = std::min( mMainCamera->farPlane(), maximumShadowRenderingDistance );
148
149 // "Practical Split Scheme" for cascading shadow maps.
150 // using a quite large lambda to account for typical near/far plane distances seen in
151 // QGIS 3d maps (0.5 - ~2500)
152 // We match Cesium's lambda -- see https://web.archive.org/web/20170710150304/https://cesiumjs.org/presentations/ShadowsAndCesiumImplementation.pdf (slide 38)
153 constexpr float PRACTICAL_SPLIT_SCHEME_LAMBDA = 0.9f;
154 const std::vector<float> cascadeSplits = Qgs3DUtils::calculateCascadeSplits( Qgs3D::NUM_SHADOW_CASCADES, mainCameraNearPlane, mainCameraFarPlane, PRACTICAL_SPLIT_SCHEME_LAMBDA );
155
156 const QMatrix4x4 invertedCameraView = mMainCamera->viewMatrix().inverted();
157 const float cameraFov = mMainCamera->fieldOfView();
158 const float cameraAspect = mMainCamera->aspectRatio();
159
160 const float shadowMapResolution = static_cast< float >( mShadowMapResolution );
161
162 // we are building two matrix lists, one containing the exact bounds of each
163 // cascade, and the other which is an exact match for the actual camera used
164 // for each cascade's texture. These differ, as we pull back the camera's
165 // near plane for reasons described below...
166 QVariantList csmMatrices( Qgs3D::NUM_SHADOW_CASCADES, QVariant() );
167 QVariantList csmBoundsMatrices( Qgs3D::NUM_SHADOW_CASCADES, QVariant() );
168
169 // here we are calculating the cascades using bounding spheres, in order to stabilise the
170 // matrices and avoid shadow shimmer when the camera is moved or rotated
171 // see eg https://media.gdcvault.com/gdc09/slides/100_Handout%203.pdf from slide 21
172 for ( int i = 0; i < Qgs3D::NUM_SHADOW_CASCADES; ++i )
173 {
174 const float zNear = cascadeSplits[i];
175 const float zFar = cascadeSplits[i + 1];
176
177 // calculate the 8 corners of the camera frustum slice in world space
178 QVector3D worldFrustumCorners[8];
179 QVector3D worldFrustrumCenter;
180 Qgs3DUtils::calculateFrustumSliceCorners( zNear, zFar, cameraFov, cameraAspect, invertedCameraView, worldFrustumCorners, worldFrustrumCenter );
181
182 // calculate the bounding sphere radius around the frustum center
183 float rawRadius = 0.0f;
184 for ( int j = 0; j < 8; ++j )
185 {
186 rawRadius = std::max( rawRadius, ( worldFrustumCorners[j] - worldFrustrumCenter ).length() );
187 }
188
189 // round up slightly to stabilize against floating point inaccuracies
190 // use dynamic step size based on the raw radius so we round larger radius to coarser increments
191 const float stepSize = std::max( std::pow( 2.0f, std::floor( std::log2( rawRadius ) ) - 4.0f ), 0.01f );
192 const float radius = std::ceil( rawRadius / stepSize ) * stepSize;
193
194 // project the actual world frustum center into this rotation-aligned light space
195 QMatrix4x4 lightRotation;
196 lightRotation.lookAt( QVector3D( 0, 0, 0 ), lightDirection, up );
197 QVector3D centerLightSpace = lightRotation * worldFrustrumCenter;
198
199 // snap to texels
200 // calculate how many world units are represented by a single texel
201 const float worldUnitsPerTexel = ( 2.0f * radius ) / shadowMapResolution;
202 // snap the light center to the nearest texel
203 centerLightSpace.setX( std::floor( centerLightSpace.x() / worldUnitsPerTexel ) * worldUnitsPerTexel );
204 centerLightSpace.setY( std::floor( centerLightSpace.y() / worldUnitsPerTexel ) * worldUnitsPerTexel );
205 const QVector3D snappedWorldCenter = lightRotation.inverted() * centerLightSpace;
206
207 // create the light view matrix
208 QMatrix4x4 lightView;
209 const QVector3D lightPos = snappedWorldCenter - ( lightDirection * radius );
210 lightView.lookAt( lightPos, snappedWorldCenter, up );
211
212 // apply to the specific light camera
213 mLightCameras[i]->setPosition( lightPos );
214 mLightCameras[i]->setViewCenter( snappedWorldCenter );
215 mLightCameras[i]->setUpVector( up );
216
217 float lightCameraLeft = -radius;
218 float lightCameraRight = radius;
219 float lightCameraBottom = -radius;
220 float lightCameraTop = radius;
221 // the Z-bounds must cover the entire bounding sphere
222 float lightCameraNearPlane = -radius;
223 float lightCameraFarPlane = radius * 2.0f;
224
225 QMatrix4x4 orthoBoundsMatrix;
226 orthoBoundsMatrix.ortho( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
227 csmBoundsMatrices[i] = QVariant::fromValue( orthoBoundsMatrix * lightView );
228
229 // Pull the near plane way back to catch shadows from behind the camera
230 // If we don't do this, then we'll lose the tops of shadows which should be cast by objects
231 // which sit behind this cascade slice's frustrum
232 constexpr float NEAR_PLANE_RETREAT = 5000.0f;
233 lightCameraNearPlane -= NEAR_PLANE_RETREAT;
234
235 // apply the corresponding Orthographic projection to the camera
236 mLightCameras[i]->lens()->setOrthographicProjection( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
237
238 // calculate combined light space matrix for the shader
239 QMatrix4x4 orthoMatrix;
240 orthoMatrix.ortho( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
241 csmMatrices[i] = QVariant::fromValue( orthoMatrix * lightView );
242 }
243
244 mCsmMatricesParameter->setValue( csmMatrices );
245 mCsmBoundsMatricesParameter->setValue( csmBoundsMatrices );
246 mMaxShadowDistanceParameter->setValue( mainCameraFarPlane );
247}
248
250{
251 mRenderShadowsParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
252}
253
255{
256 // yes, this is very hacky! It only works once, when first enabling display
257 // of the cascading shadow splits. That's ok, it's only here so that we can visualize them
258 // in tests...
259 if ( enabled )
260 {
261 const QString fragmentShaderPath = u"qrc:/shaders/postprocess.frag"_s;
262 const QByteArray fragmentShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( fragmentShaderPath ) );
263 QStringList defines { "ENABLE_EFFECTS", "TINT_CASCADES" };
264 const QByteArray finalFragmentShaderCode = Qgs3DUtils::addDefinesToShaderCode( fragmentShaderCode, defines );
265 mShader->setFragmentShaderCode( finalFragmentShaderCode );
266 }
267}
268
270{
271 mShadowLightIndexParameter->setValue( QVariant::fromValue( index ) );
272}
273
275{
276 mShadowBiasParameter->setValue( QVariant::fromValue( shadowBias ) );
277}
278
280{
281 mShadowMapResolution = resolution;
282}
283
285{
286 mEyeDomeLightingEnabledParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
287}
288
290{
291 mEyeDomeLightingStrengthParameter->setValue( QVariant::fromValue( strength ) );
292}
293
295{
296 mEyeDomeLightingDistanceParameter->setValue( QVariant::fromValue( distance ) );
297}
298
300{
301 mAmbientOcclusionEnabledParameter->setValue( enabled );
302}
303
305{
306 mBloomEnabledParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
307}
308
310{
311 mBloomFactorParameter->setValue( factor );
312}
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.
Definition of a directional light in a 3D map scene.
QgsVector3D direction() const
Returns the direction of the light in degrees.
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 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 updateShadowSettings(const QgsDirectionalLightSettings &light, float maximumShadowRenderingDistance)
Sets shadow rendering to use a directional light.
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 setShadowMapResolution(int resolution)
Sets the shadow texture map resolution.
void setBloomFactor(float factor)
Sets the bloom factor, which controls the strength of the bloom effect.
void setEyeDomeLightingEnabled(bool enabled)
Sets whether eye dome lighting is enabled.
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.
QVector3D toVector3D() const
Converts the current object to QVector3D.