QGIS API Documentation 4.1.0-Master (ca2ac17535b)
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 mExposureParameter = new Qt3DRender::QParameter( u"exposureAdjustment"_s, 0.0f );
123 mMaterial->addParameter( mExposureParameter );
124 mToneMappingParameter = new Qt3DRender::QParameter( u"toneMapping"_s, 1 );
125 mMaterial->addParameter( mToneMappingParameter );
126
127 const QString vertexShaderPath = u"qrc:/shaders/postprocess.vert"_s;
128 const QString fragmentShaderPath = u"qrc:/shaders/postprocess.frag"_s;
129
130 mShader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( vertexShaderPath ) ) );
131
132 const QByteArray fragmentShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( fragmentShaderPath ) );
133 const QByteArray finalFragmentShaderCode = Qgs3DUtils::addDefinesToShaderCode( fragmentShaderCode, QStringList( { "ENABLE_EFFECTS" } ) );
134 mShader->setFragmentShaderCode( finalFragmentShaderCode );
135}
136
137void QgsPostprocessingEntity::updateShadowSettings( const QgsVector3D &lightDir, float maximumShadowRenderingDistance )
138{
139 // We are using "Cascading Shadow Maps" technique.
140 // Reading/watching which was useful during development:
141 // https://learnopengl.com/Guest-Articles/2021/CSM
142 // https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf
143 // https://www.youtube.com/watch?v=Jhopq2lkzMQ
144 // https://www.youtube.com/watch?v=qbDrqARX07o
145 // https://web.archive.org/web/20170710150304/https://cesiumjs.org/presentations/ShadowsAndCesiumImplementation.pdf
146
147 const QVector3D lightDirection = lightDir.toVector3D().normalized();
148 const QVector3D up = Qgs3DUtils::calculateDirectionalLightUpVector( lightDirection );
149
150 const float mainCameraNearPlane = mMainCamera->nearPlane();
151 // cap the far plane to the shadow rendering distance so we don't waste shadow resolution
152 const float mainCameraFarPlane = std::min( mMainCamera->farPlane(), maximumShadowRenderingDistance );
153
154 // "Practical Split Scheme" for cascading shadow maps.
155 // using a quite large lambda to account for typical near/far plane distances seen in
156 // QGIS 3d maps (0.5 - ~2500)
157 // We match Cesium's lambda -- see https://web.archive.org/web/20170710150304/https://cesiumjs.org/presentations/ShadowsAndCesiumImplementation.pdf (slide 38)
158 constexpr float PRACTICAL_SPLIT_SCHEME_LAMBDA = 0.9f;
159 const std::vector<float> cascadeSplits = Qgs3DUtils::calculateCascadeSplits( Qgs3D::NUM_SHADOW_CASCADES, mainCameraNearPlane, mainCameraFarPlane, PRACTICAL_SPLIT_SCHEME_LAMBDA );
160
161 const QMatrix4x4 invertedCameraView = mMainCamera->viewMatrix().inverted();
162 const float cameraFov = mMainCamera->fieldOfView();
163 const float cameraAspect = mMainCamera->aspectRatio();
164
165 const float shadowMapResolution = static_cast< float >( mShadowMapResolution );
166
167 // we are building two matrix lists, one containing the exact bounds of each
168 // cascade, and the other which is an exact match for the actual camera used
169 // for each cascade's texture. These differ, as we pull back the camera's
170 // near plane for reasons described below...
171 QVariantList csmMatrices( Qgs3D::NUM_SHADOW_CASCADES, QVariant() );
172 QVariantList csmBoundsMatrices( Qgs3D::NUM_SHADOW_CASCADES, QVariant() );
173
174 // here we are calculating the cascades using bounding spheres, in order to stabilise the
175 // matrices and avoid shadow shimmer when the camera is moved or rotated
176 // see eg https://media.gdcvault.com/gdc09/slides/100_Handout%203.pdf from slide 21
177 for ( int i = 0; i < Qgs3D::NUM_SHADOW_CASCADES; ++i )
178 {
179 const float zNear = cascadeSplits[i];
180 const float zFar = cascadeSplits[i + 1];
181
182 // calculate the 8 corners of the camera frustum slice in world space
183 QVector3D worldFrustumCorners[8];
184 QVector3D worldFrustrumCenter;
185 Qgs3DUtils::calculateFrustumSliceCorners( zNear, zFar, cameraFov, cameraAspect, invertedCameraView, worldFrustumCorners, worldFrustrumCenter );
186
187 // calculate the bounding sphere radius around the frustum center
188 float rawRadius = 0.0f;
189 for ( int j = 0; j < 8; ++j )
190 {
191 rawRadius = std::max( rawRadius, ( worldFrustumCorners[j] - worldFrustrumCenter ).length() );
192 }
193
194 // round up slightly to stabilize against floating point inaccuracies
195 // use dynamic step size based on the raw radius so we round larger radius to coarser increments
196 const float stepSize = std::max( std::pow( 2.0f, std::floor( std::log2( rawRadius ) ) - 4.0f ), 0.01f );
197 const float radius = std::ceil( rawRadius / stepSize ) * stepSize;
198
199 // project the actual world frustum center into this rotation-aligned light space
200 QMatrix4x4 lightRotation;
201 lightRotation.lookAt( QVector3D( 0, 0, 0 ), lightDirection, up );
202 QVector3D centerLightSpace = lightRotation * worldFrustrumCenter;
203
204 // snap to texels
205 // calculate how many world units are represented by a single texel
206 const float worldUnitsPerTexel = ( 2.0f * radius ) / shadowMapResolution;
207 // snap the light center to the nearest texel
208 centerLightSpace.setX( std::floor( centerLightSpace.x() / worldUnitsPerTexel ) * worldUnitsPerTexel );
209 centerLightSpace.setY( std::floor( centerLightSpace.y() / worldUnitsPerTexel ) * worldUnitsPerTexel );
210 const QVector3D snappedWorldCenter = lightRotation.inverted() * centerLightSpace;
211
212 // create the light view matrix
213 QMatrix4x4 lightView;
214 const QVector3D lightPos = snappedWorldCenter - ( lightDirection * radius );
215 lightView.lookAt( lightPos, snappedWorldCenter, up );
216
217 // apply to the specific light camera
218 mLightCameras[i]->setPosition( lightPos );
219 mLightCameras[i]->setViewCenter( snappedWorldCenter );
220 mLightCameras[i]->setUpVector( up );
221
222 float lightCameraLeft = -radius;
223 float lightCameraRight = radius;
224 float lightCameraBottom = -radius;
225 float lightCameraTop = radius;
226 // the Z-bounds must cover the entire bounding sphere
227 float lightCameraNearPlane = -radius;
228 float lightCameraFarPlane = radius * 2.0f;
229
230 QMatrix4x4 orthoBoundsMatrix;
231 orthoBoundsMatrix.ortho( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
232 csmBoundsMatrices[i] = QVariant::fromValue( orthoBoundsMatrix * lightView );
233
234 // Pull the near plane way back to catch shadows from behind the camera
235 // If we don't do this, then we'll lose the tops of shadows which should be cast by objects
236 // which sit behind this cascade slice's frustrum
237 constexpr float NEAR_PLANE_RETREAT = 5000.0f;
238 lightCameraNearPlane -= NEAR_PLANE_RETREAT;
239
240 // apply the corresponding Orthographic projection to the camera
241 mLightCameras[i]->lens()->setOrthographicProjection( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
242
243 // calculate combined light space matrix for the shader
244 QMatrix4x4 orthoMatrix;
245 orthoMatrix.ortho( lightCameraLeft, lightCameraRight, lightCameraBottom, lightCameraTop, lightCameraNearPlane, lightCameraFarPlane );
246 csmMatrices[i] = QVariant::fromValue( orthoMatrix * lightView );
247 }
248
249 mCsmMatricesParameter->setValue( csmMatrices );
250 mCsmBoundsMatricesParameter->setValue( csmBoundsMatrices );
251 mMaxShadowDistanceParameter->setValue( mainCameraFarPlane );
252}
253
255{
256 mRenderShadowsParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
257}
258
260{
261 // yes, this is very hacky! It only works once, when first enabling display
262 // of the cascading shadow splits. That's ok, it's only here so that we can visualize them
263 // in tests...
264 if ( enabled )
265 {
266 const QString fragmentShaderPath = u"qrc:/shaders/postprocess.frag"_s;
267 const QByteArray fragmentShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( fragmentShaderPath ) );
268 QStringList defines { "ENABLE_EFFECTS", "TINT_CASCADES" };
269 const QByteArray finalFragmentShaderCode = Qgs3DUtils::addDefinesToShaderCode( fragmentShaderCode, defines );
270 mShader->setFragmentShaderCode( finalFragmentShaderCode );
271 }
272}
273
275{
276 mShadowLightIndexParameter->setValue( QVariant::fromValue( index ) );
277}
278
280{
281 mShadowBiasParameter->setValue( QVariant::fromValue( shadowBias ) );
282}
283
285{
286 mShadowMapResolution = resolution;
287}
288
290{
291 mEyeDomeLightingEnabledParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
292}
293
295{
296 mEyeDomeLightingStrengthParameter->setValue( QVariant::fromValue( strength ) );
297}
298
300{
301 mEyeDomeLightingDistanceParameter->setValue( QVariant::fromValue( distance ) );
302}
303
305{
306 mAmbientOcclusionEnabledParameter->setValue( enabled );
307}
308
310{
311 mBloomEnabledParameter->setValue( QVariant::fromValue( enabled ? 1 : 0 ) );
312}
313
315{
316 mBloomFactorParameter->setValue( factor );
317}
318
320{
321 mExposureParameter->setValue( static_cast< float >( settings.exposureAdjustment() ) );
322 mToneMappingParameter->setValue( static_cast< int >( settings.toneMapping() ) );
323}
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 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 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 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.
void updateColorGradingSettings(const QgsColorGradingSettings &settings)
Updates settings for color grading.
void updateShadowSettings(const QgsVector3D &lightDirection, float maximumShadowRenderingDistance)
Sets shadow rendering to use a directional light.
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.
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.