QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsmetalroughmaterial.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmetalroughmaterial.cpp
3 --------------------------------------
4 Date : December 2023
5 Copyright : (C) 2023 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
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#include <Qt3DRender/QParameter>
18#include <Qt3DRender/QRenderPass>
19#include <Qt3DRender/QTechnique>
20#include <Qt3DRender/QTexture>
21#include <Qt3DRender/QAbstractTexture>
22#include <Qt3DRender/QEffect>
23#include <Qt3DRender/QShaderProgramBuilder>
24#include <Qt3DRender/QGraphicsApiFilter>
25
27QgsMetalRoughMaterial::QgsMetalRoughMaterial( QNode *parent )
28 : QMaterial( parent )
29 , mBaseColorParameter( new Qt3DRender::QParameter( QStringLiteral( "baseColor" ), QColor( "grey" ), this ) )
30 , mMetalnessParameter( new Qt3DRender::QParameter( QStringLiteral( "metalness" ), 0.0f, this ) )
31 , mRoughnessParameter( new Qt3DRender::QParameter( QStringLiteral( "roughness" ), 0.0f, this ) )
32 , mBaseColorMapParameter( new Qt3DRender::QParameter( QStringLiteral( "baseColorMap" ), QVariant(), this ) )
33 , mMetalnessMapParameter( new Qt3DRender::QParameter( QStringLiteral( "metalnessMap" ), QVariant(), this ) )
34 , mRoughnessMapParameter( new Qt3DRender::QParameter( QStringLiteral( "roughnessMap" ), QVariant(), this ) )
35 , mAmbientOcclusionMapParameter( new Qt3DRender::QParameter( QStringLiteral( "ambientOcclusionMap" ), QVariant(), this ) )
36 , mNormalMapParameter( new Qt3DRender::QParameter( QStringLiteral( "normalMap" ), QVariant(), this ) )
37 , mTextureScaleParameter( new Qt3DRender::QParameter( QStringLiteral( "texCoordScale" ), 1.0f, this ) )
38 , mMetalRoughEffect( new Qt3DRender::QEffect( this ) )
39 , mMetalRoughGL3Technique( new Qt3DRender::QTechnique( this ) )
40 , mMetalRoughGL3RenderPass( new Qt3DRender::QRenderPass( this ) )
41 , mMetalRoughGL3Shader( new Qt3DRender::QShaderProgram( this ) )
42 , mFilterKey( new Qt3DRender::QFilterKey( this ) )
43{
44 init();
45}
46
47QgsMetalRoughMaterial::~QgsMetalRoughMaterial() = default;
48
49QVariant QgsMetalRoughMaterial::baseColor() const
50{
51 return mBaseColorParameter->value();
52}
53
54QVariant QgsMetalRoughMaterial::metalness() const
55{
56 return mMetalnessParameter->value();
57}
58
59QVariant QgsMetalRoughMaterial::roughness() const
60{
61 return mRoughnessParameter->value();
62}
63
64QVariant QgsMetalRoughMaterial::ambientOcclusion() const
65{
66 return mAmbientOcclusionMapParameter->value();
67}
68
69QVariant QgsMetalRoughMaterial::normal() const
70{
71 return mNormalMapParameter->value();
72}
73
74float QgsMetalRoughMaterial::textureScale() const
75{
76 return mTextureScaleParameter->value().toFloat();
77}
78
79void QgsMetalRoughMaterial::setBaseColor( const QVariant &baseColor )
80{
81 mBaseColorParameter->setValue( baseColor );
82 mBaseColorMapParameter->setValue( baseColor );
83 bool oldUsingBaseColorMap = mUsingBaseColorMap;
84
85 if ( baseColor.value<Qt3DRender::QAbstractTexture *>() )
86 {
87 mUsingBaseColorMap = true;
88 mMetalRoughEffect->addParameter( mBaseColorMapParameter );
89 if ( mMetalRoughEffect->parameters().contains( mBaseColorParameter ) )
90 mMetalRoughEffect->removeParameter( mBaseColorParameter );
91 }
92 else
93 {
94 mUsingBaseColorMap = false;
95 if ( mMetalRoughEffect->parameters().contains( mBaseColorMapParameter ) )
96 mMetalRoughEffect->removeParameter( mBaseColorMapParameter );
97 mMetalRoughEffect->addParameter( mBaseColorParameter );
98 }
99
100 if ( oldUsingBaseColorMap != mUsingBaseColorMap )
101 updateFragmentShader();
102}
103
104void QgsMetalRoughMaterial::setMetalness( const QVariant &metalness )
105{
106 mMetalnessParameter->setValue( metalness );
107 mMetalnessMapParameter->setValue( metalness );
108 bool oldUsingMetalnessMap = mUsingMetalnessMap;
109
110 if ( metalness.value<Qt3DRender::QAbstractTexture *>() )
111 {
112 mUsingMetalnessMap = true;
113 mMetalRoughEffect->addParameter( mMetalnessMapParameter );
114 if ( mMetalRoughEffect->parameters().contains( mMetalnessParameter ) )
115 mMetalRoughEffect->removeParameter( mMetalnessParameter );
116 }
117 else
118 {
119 mUsingMetalnessMap = false;
120 if ( mMetalRoughEffect->parameters().contains( mMetalnessMapParameter ) )
121 mMetalRoughEffect->removeParameter( mMetalnessMapParameter );
122 mMetalRoughEffect->addParameter( mMetalnessParameter );
123 }
124
125 if ( oldUsingMetalnessMap != mUsingMetalnessMap )
126 updateFragmentShader();
127}
128
129void QgsMetalRoughMaterial::setRoughness( const QVariant &roughness )
130{
131 mRoughnessParameter->setValue( roughness );
132 mRoughnessMapParameter->setValue( roughness );
133 bool oldUsingRoughnessMap = mUsingRoughnessMap;
134
135 if ( roughness.value<Qt3DRender::QAbstractTexture *>() )
136 {
137 mUsingRoughnessMap = true;
138 mMetalRoughEffect->addParameter( mRoughnessMapParameter );
139 if ( mMetalRoughEffect->parameters().contains( mRoughnessParameter ) )
140 mMetalRoughEffect->removeParameter( mRoughnessParameter );
141 }
142 else
143 {
144 mUsingRoughnessMap = false;
145 if ( mMetalRoughEffect->parameters().contains( mRoughnessMapParameter ) )
146 mMetalRoughEffect->removeParameter( mRoughnessMapParameter );
147 mMetalRoughEffect->addParameter( mRoughnessParameter );
148 }
149
150 if ( oldUsingRoughnessMap != mUsingRoughnessMap )
151 updateFragmentShader();
152}
153
154void QgsMetalRoughMaterial::setAmbientOcclusion( const QVariant &ambientOcclusion )
155{
156 mAmbientOcclusionMapParameter->setValue( ambientOcclusion );
157 bool oldUsingAmbientOcclusionMap = mUsingAmbientOcclusionMap;
158
159 if ( ambientOcclusion.value<Qt3DRender::QAbstractTexture *>() )
160 {
161 mUsingAmbientOcclusionMap = true;
162 mMetalRoughEffect->addParameter( mAmbientOcclusionMapParameter );
163 }
164 else
165 {
166 mUsingAmbientOcclusionMap = false;
167 if ( mMetalRoughEffect->parameters().contains( mAmbientOcclusionMapParameter ) )
168 mMetalRoughEffect->removeParameter( mAmbientOcclusionMapParameter );
169 }
170
171 if ( oldUsingAmbientOcclusionMap != mUsingAmbientOcclusionMap )
172 updateFragmentShader();
173}
174
175void QgsMetalRoughMaterial::setNormal( const QVariant &normal )
176{
177 mNormalMapParameter->setValue( normal );
178 bool oldUsingNormalMap = mUsingNormalMap;
179
180 if ( normal.value<Qt3DRender::QAbstractTexture *>() )
181 {
182 mUsingNormalMap = true;
183 mMetalRoughEffect->addParameter( mNormalMapParameter );
184 }
185 else
186 {
187 mUsingNormalMap = false;
188 if ( mMetalRoughEffect->parameters().contains( mNormalMapParameter ) )
189 mMetalRoughEffect->removeParameter( mNormalMapParameter );
190 }
191
192 if ( oldUsingNormalMap != mUsingNormalMap )
193 updateFragmentShader();
194}
195
196void QgsMetalRoughMaterial::setTextureScale( float textureScale )
197{
198 mTextureScaleParameter->setValue( textureScale );
199}
200
201QByteArray addDefinesToShaderCode( const QByteArray &shaderCode, const QStringList &defines )
202{
203 // There is one caveat to take care of - GLSL source code needs to start with #version as
204 // a first directive, otherwise we get the old GLSL 100 version. So we can't just prepend the
205 // shader source code, but insert our defines at the right place.
206
207 QStringList defineLines;
208 for ( const QString &define : defines )
209 defineLines += "#define " + define + "\n";
210
211 QString definesText = defineLines.join( QString() );
212
213 QByteArray newShaderCode = shaderCode;
214 int versionIndex = shaderCode.indexOf( "#version " );
215 int insertionIndex = versionIndex == -1 ? 0 : shaderCode.indexOf( '\n', versionIndex + 1 ) + 1;
216 newShaderCode.insert( insertionIndex, definesText.toLatin1() );
217 return newShaderCode;
218}
219
220void QgsMetalRoughMaterial::init()
221{
222 QObject::connect( mBaseColorParameter, &Qt3DRender::QParameter::valueChanged,
223 this, &QgsMetalRoughMaterial::baseColorChanged );
224 QObject::connect( mMetalnessParameter, &Qt3DRender::QParameter::valueChanged,
225 this, &QgsMetalRoughMaterial::metalnessChanged );
226 QObject::connect( mRoughnessParameter, &Qt3DRender::QParameter::valueChanged,
227 this, &QgsMetalRoughMaterial::roughnessChanged );
228 QObject::connect( mAmbientOcclusionMapParameter, &Qt3DRender::QParameter::valueChanged,
229 this, &QgsMetalRoughMaterial::ambientOcclusionChanged );
230 QObject::connect( mNormalMapParameter, &Qt3DRender::QParameter::valueChanged,
231 this, &QgsMetalRoughMaterial::normalChanged );
232 connect( mTextureScaleParameter, &Qt3DRender::QParameter::valueChanged,
233 this, &QgsMetalRoughMaterial::handleTextureScaleChanged );
234
235 mMetalRoughGL3Shader->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/default.vert" ) ) ) );
236
237 updateFragmentShader();
238
239 mMetalRoughGL3Technique->graphicsApiFilter()->setApi( Qt3DRender::QGraphicsApiFilter::OpenGL );
240 mMetalRoughGL3Technique->graphicsApiFilter()->setMajorVersion( 3 );
241 mMetalRoughGL3Technique->graphicsApiFilter()->setMinorVersion( 1 );
242 mMetalRoughGL3Technique->graphicsApiFilter()->setProfile( Qt3DRender::QGraphicsApiFilter::CoreProfile );
243
244 mFilterKey->setParent( this );
245 mFilterKey->setName( QStringLiteral( "renderingStyle" ) );
246 mFilterKey->setValue( QStringLiteral( "forward" ) );
247
248 mMetalRoughGL3Technique->addFilterKey( mFilterKey );
249 mMetalRoughGL3RenderPass->setShaderProgram( mMetalRoughGL3Shader );
250 mMetalRoughGL3Technique->addRenderPass( mMetalRoughGL3RenderPass );
251 mMetalRoughEffect->addTechnique( mMetalRoughGL3Technique );
252
253 // Given parameters a parent
254 mBaseColorMapParameter->setParent( mMetalRoughEffect );
255 mMetalnessMapParameter->setParent( mMetalRoughEffect );
256 mRoughnessMapParameter->setParent( mMetalRoughEffect );
257
258 mMetalRoughEffect->addParameter( mBaseColorParameter );
259 mMetalRoughEffect->addParameter( mMetalnessParameter );
260 mMetalRoughEffect->addParameter( mRoughnessParameter );
261 mMetalRoughEffect->addParameter( mTextureScaleParameter );
262
263 setEffect( mMetalRoughEffect );
264}
265
266void QgsMetalRoughMaterial::updateFragmentShader()
267{
268 // pre-process fragment shader and add #defines based on whether using maps for some properties
269 QByteArray fragmentShaderCode = Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/metalrough.frag" ) ) );
270 QStringList defines;
271 if ( mUsingBaseColorMap )
272 defines += "BASE_COLOR_MAP";
273 if ( mUsingMetalnessMap )
274 defines += "METALNESS_MAP";
275 if ( mUsingRoughnessMap )
276 defines += "ROUGHNESS_MAP";
277 if ( mUsingAmbientOcclusionMap )
278 defines += "AMBIENT_OCCLUSION_MAP";
279 if ( mUsingNormalMap )
280 defines += "NORMAL_MAP";
281
282 if ( mFlatShading )
283 defines += "FLAT_SHADING";
284
285 QByteArray finalShaderCode = addDefinesToShaderCode( fragmentShaderCode, defines );
286 mMetalRoughGL3Shader->setFragmentShaderCode( finalShaderCode );
287}
288
289void QgsMetalRoughMaterial::handleTextureScaleChanged( const QVariant &var )
290{
291 emit textureScaleChanged( var.toFloat() );
292}
293
294bool QgsMetalRoughMaterial::flatShadingEnabled() const
295{
296 return mFlatShading;
297}
298
299void QgsMetalRoughMaterial::setFlatShadingEnabled( bool enabled )
300{
301 if ( enabled != mFlatShading )
302 {
303 mFlatShading = enabled;
304 updateFragmentShader();
305 }
306}
307