1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dcustommaterial_p.h"
5#include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h>
6#include <ssg/qssgrendercontextcore.h>
7#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
8#include <QtQuick/QQuickWindow>
9
10#include "qquick3dobject_p.h"
11#include "qquick3dviewport_p.h"
12#include "qquick3dscenemanager_p.h"
13
14QT_BEGIN_NAMESPACE
15
16/*!
17 \qmltype CustomMaterial
18 \inherits Material
19 \inqmlmodule QtQuick3D
20 \brief Base component for creating custom materials used to shade models.
21
22 The custom material allows using custom shader code for a material, enabling
23 programmability on graphics shader level. A vertex, fragment, or both
24 shaders can be provided. The \l vertexShader and \l fragmentShader
25 properties are URLs, referencing files containing shader snippets, and work
26 very similarly to ShaderEffect or \l{Image::source}{Image.source}. Only the
27 \c file and \c qrc schemes are supported with custom materials. It is also
28 possible to omit the \c file scheme, allowing to specify a relative path in
29 a convenient way. Such a path is resolved relative to the component's (the
30 \c{.qml} file's) location.
31
32 For a getting started guide to custom materials, see the page \l{Programmable
33 Materials, Effects, Geometry, and Texture data}.
34
35 \section1 Introduction
36
37 Consider the following versions of the same scene. On the left, the cylinder
38 is using a built-in, non-programmable material. Such materials are
39 configurable through a wide range of properties, but there is no further
40 control given over the shaders that are generated under the hood. On the
41 right, the same cylinder is now associated with a CustomMaterial referencing
42 application-provided vertex and fragment shader snippets. This allows
43 inserting custom, application-specific logic into the vertex shader to
44 transform the geometry, and to determine certain color properties in a
45 custom manner in the fragment shader. As this is a
46 \l{shadingMode}{shaded} custom material, the cylinder still
47 participates in the scene lighting normally.
48
49 \table 70%
50 \row
51 \li \qml
52 View3D {
53 anchors.fill: parent
54 PerspectiveCamera {
55 id: camera
56 position: Qt.vector3d(0, 0, 600)
57 }
58 camera: camera
59 DirectionalLight {
60 position: Qt.vector3d(-500, 500, -100)
61 color: Qt.rgba(0.2, 0.2, 0.2, 1.0)
62 ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
63 }
64 Model {
65 source: "#Cylinder"
66 eulerRotation: Qt.vector3d(30, 30, 0)
67 scale: Qt.vector3d(1.5, 1.5, 1.5)
68 materials: [
69 DefaultMaterial {
70 diffuseColor: Qt.rgba(0, 1, 0, 1)
71 }
72 ]
73 }
74 }
75 \endqml
76 \li \qml
77 View3D {
78 anchors.fill: parent
79 PerspectiveCamera {
80 id: camera
81 position: Qt.vector3d(0, 0, 600)
82 }
83 camera: camera
84 DirectionalLight {
85 position: Qt.vector3d(-500, 500, -100)
86 color: Qt.rgba(0.2, 0.2, 0.2, 1.0)
87 ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
88 }
89 Model {
90 source: "#Cylinder"
91 eulerRotation: Qt.vector3d(30, 30, 0)
92 scale: Qt.vector3d(1.5, 1.5, 1.5)
93 materials: [
94 CustomMaterial {
95 vertexShader: "material.vert"
96 fragmentShader: "material.frag"
97 property real uTime
98 property real uAmplitude: 50
99 NumberAnimation on uTime { from: 0; to: 100; duration: 10000; loops: -1 }
100 }
101 ]
102 }
103 }
104 \endqml
105 \endtable
106
107 Let's assume that the shader snippets in \c{material.vert} and \c{material.frag} are
108 the following:
109
110 \table 70%
111 \row
112 \li \badcode
113 void MAIN()
114 {
115 VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude;
116 }
117 \endcode
118 \li \badcode
119 void MAIN()
120 {
121 BASE_COLOR = vec4(0.0, 1.0, 0.0, 1.0);
122 }
123 \endcode
124 \endtable
125
126 Notice how \c uTime and \c uAmplitude are properties of the CustomMaterial
127 element. They can change values and get animated normally, the values will
128 be exposed to the shaders automatically without any further action from the
129 developer.
130
131 The result is a cylinder that animates its vertices:
132
133 \image custommaterial_cylinder.png
134
135 \section1 Two flavors of custom materials
136
137 There are two main types of custom materials. This is specified by the \l
138 shadingMode property. In \l{CustomMaterial::shadingMode}{unshaded} custom
139 materials the fragment shader outputs a single \c vec4 color, ignoring
140 lights, light probes, shadowing in the scene. In
141 \l{CustomMaterial::shadingMode}{shaded} materials the shader is expected to
142 implement certain functions and work with built-in variables to take
143 lighting and shadow contribution into account.
144
145 The default choice is typically a shaded material, this is reflected in the
146 default value of the \l shadingMode property. This fits materials that needs
147 to transform vertices or other incoming data from the geometry, or determine
148 values like \c BASE_COLOR or \c EMISSIVE_COLOR in a custom manner, perhaps
149 by sampling \c SCREEN_TEXTURE or \c DEPTH_TEXTURE, while still reciving
150 light and shadow contributions from the scene. Additionally, such materials
151 can also override and reimplement the equations used to calculate the
152 contributions from directional, point, and other lights. The
153 application-provided shader snippets are heavily amended by the Qt Quick 3D
154 engine under the hood, in order to provide the features, such as lighting,
155 the standard materials have.
156
157 Unshaded materials are useful when the object's appearance is determined
158 completely by the custom shader code. The shaders for such materials
159 receive minimal additions by the engine, and therefore it is completely up
160 to the shader to determine the final fragment color. This gives more
161 freedom, but also limits possiblities to integrate with other elements of
162 the scene, such as lights.
163
164 \note Shader code is always provided using Vulkan-style GLSL, regardless of
165 the graphics API used by Qt at run time.
166
167 \note The vertex and fragment shader code provided by the material are not
168 full, complete GLSL shaders on their own. Rather, they provide a set of
169 functions, which are then amended with further shader code by the engine.
170
171 \section1 Exposing data to the shaders
172
173 The dynamic properties of the CustomMaterial can be changed and animated
174 using QML and Qt Quick facilities, and the values are exposed to the
175 shaders automatically. This in practice is very similar ShaderEffect. The
176 following list shows how properties are mapped:
177
178 \list
179 \li bool, int, real -> bool, int, float
180 \li QColor, \l{QtQml::Qt::rgba()}{color} -> vec4, and the color gets
181 converted to linear, assuming sRGB space for the color value specified in
182 QML. The built-in Qt colors, such as \c{"green"} are in sRGB color space as
183 well, and the same conversion is performed for all color properties of
184 DefaultMaterial and PrincipledMaterial, so this behavior of CustomMaterial
185 matches those. Unlike Qt Quick, for Qt Quick 3D linearizing is essential as
186 there will typically be tonemapping performed on the 3D scene.
187 \li QRect, QRectF, \l{QtQml::Qt::rect()}{rect} -> vec4
188 \li QPoint, QPointF, \l{QtQml::Qt::point()}{point}, QSize, QSizeF, \l{QtQml::Qt::size()}{size} -> vec2
189 \li QVector2D, \l{QtQml::Qt::vector2d()}{vector2d} -> vec2
190 \li QVector3D, \l{QtQml::Qt::vector3d()}{vector3d} -> vec3
191 \li QVector4D, \l{QtQml::Qt::vector4d()}{vector4d} -> vec4
192 \li QMatrix4x4, \l{QtQml::Qt::matrix4x4()}{matrix4x4} -> mat4
193 \li QQuaternion, \l{QtQml::Qt::quaternion()}{quaternion} -> vec4, scalar value is \c w
194
195 \li TextureInput -> sampler2D or samplerCube, depending on whether \l
196 Texture or \l CubeMapTexture is used in the texture property of the
197 TextureInput. Setting the \l{TextureInput::enabled}{enabled} property to
198 false leads to exposing a dummy texture to the shader, meaning the shaders
199 are still functional but will sample a texture with opaque black image
200 content. Pay attention to the fact that properties for samplers must always
201 reference a \l TextureInput object, not a \l Texture directly. When it
202 comes to the \l Texture properties, the source, tiling, and filtering
203 related ones are the only ones that are taken into account implicitly with
204 custom materials, as the rest (such as, UV transformations) is up to the
205 custom shaders to implement as they see fit.
206
207 \endlist
208
209 \note When a uniform referenced in the shader code does not have a
210 corresponding property, it will cause a shader compilation error when
211 processing the material at run time. There are some exceptions to this,
212 such as, sampler uniforms, that get a dummy texture bound when no
213 corresponding QML property is present, but as a general rule, all uniforms
214 and samplers must have a corresponding property declared in the
215 CustomMaterial object.
216
217 \section1 Unshaded custom materials
218
219 The following is an example of an \l{CustomMaterial::shadingMode}{unshaded}
220 custom material.
221
222 \qml
223 CustomMaterial {
224 // These properties are automatically exposed to the shaders
225 property real time: 0.0
226 property real amplitude: 5.0
227 property real alpha: 1.0
228 property TextureInput tex: TextureInput {
229 enabled: true
230 texture: Texture { source: "image.png" }
231 }
232
233 shadingMode: CustomMaterial.Unshaded
234 sourceBlend: alpha < 1.0 ? CustomMaterial.SrcAlpha : CustomMaterial.NoBlend
235 destinationBlend: alpha < 1.0 ? CustomMaterial.OneMinusSrcAlpha : CustomMaterial.NoBlend
236 cullMode: CustomMaterial.BackFaceCulling
237
238 vertexShader: "customshader.vert"
239 fragmentShader: "customshader.frag"
240 }
241 \endqml
242
243 With the above example, the \l{CustomMaterial::shadingMode}{unshaded} vertex
244 and fragment shaders snippets could look like the following. Note how the
245 shaders do not, and must not, declare uniforms or vertex inputs as that is
246 taken care of by Qt when assembling the final shader code.
247
248 \badcode
249 VARYING vec3 pos;
250 VARYING vec2 texcoord;
251
252 void MAIN()
253 {
254 pos = VERTEX;
255 pos.x += sin(time * 4.0 + pos.y) * amplitude;
256 texcoord = UV0;
257 POSITION = MODELVIEWPROJECTION_MATRIX * vec4(pos, 1.0);
258 }
259 \endcode
260
261 \badcode
262 VARYING vec3 pos;
263 VARYING vec2 texcoord;
264
265 void MAIN()
266 {
267 vec4 c = texture(tex, texcoord);
268 FRAGCOLOR = vec4(pos.x * 0.02, pos.y * 0.02, pos.z * 0.02, alpha) * c;
269 }
270 \endcode
271
272 The following special, uppercase keywords are available:
273
274 \list
275
276 \li MAIN -> the name of the entry point in the vertex or fragment shader
277 snippet must always be \c MAIN. Providing this function is mandatory in
278 shader snippets for unshaded custom materials.
279
280 \li VARYING -> declares an output from the vertex shader or an input to the
281 fragment shader
282
283 \li POSITION -> vec4, the output from the vertex shader
284
285 \li FRAGCOLOR -> vec4, the output from the fragment shader. Available only
286 for unshaded custom materials.
287
288 \li VERTEX -> vec3, the vertex position in the vertex shader.
289
290 \li NORMAL -> vec3, the vertex normal in the vertex shader. When the mesh
291 for the associated model does not provide normals, the value is vec3(0.0).
292
293 \li UV0 -> vec2, the first set of texture coordinates in the vertex shader.
294 When the mesh for the associated model does not provide texture
295 coordinates, the value is vec2(0.0).
296
297 \li UV1 -> vec2, the second set of texture coordinates in the vertex
298 shader. When the mesh for the associated model does not provide a second
299 set of texture coordinates, the value is vec2(0.0).
300
301 \li COLOR -> vec4, the vertex color in the vertex shader. When the mesh for
302 the associated model does not provide per-vertex colors, the value is
303 vec4(1.0).
304
305 \li TANGENT -> vec3, tangent in the vertex shader. When the mesh for the
306 associated model does not provide tangent data, the value is vec3(0.0).
307
308 \li BINORMAL -> vec3, binormal in the vertex shader. When the mesh for the
309 associated model does not provide binormal data, the value is vec3(0.0).
310
311 \li JOINTS -> ivec4, joint indexes in the vertex shader. When the mesh for
312 the associated model does not provide joint indexes data, the value is
313 ivec4(0).
314
315 \li WEIGHTS -> vec4, joint weights in the vertex shader. When the mesh for
316 the associated model does not provide joint weights data, the value is
317 vec4(0.0).
318
319 \li MORPH_POSITION(\e{n}) -> vec3, the \e{n+1}th morph target position in the vertex
320 shader. The associated model should provide proper data.
321
322 \li MORPH_NORMAL(\e{n}) -> vec3, the \e{n+1}th morph target normal in the vertex
323 shader. The associated model should provide proper data.
324
325 \li MORPH_TANGENT(\e{n}) -> vec3, the \e{n+1}th morph target tangent in the vertex
326 shader. The associated model should provide proper data.
327
328 \li MORPH_BINORMAL(\e{n}) -> vec3, the \e{n+1}th morph target binormal in the vertex
329 shader. The associated model should provide proper data.
330
331 \li MODELVIEWPROJECTION_MATRIX -> mat4, the model-view-projection matrix.
332 Projection matrices always follow OpenGL conventions, with a baked-in
333 transformation for the Y axis direction and clip depth, depending on the
334 graphics API used at run time.
335
336 \li VIEWPROJECTION_MATRIX -> mat4, the view-projection matrix
337
338 \li PROJECTION_MATRIX -> mat4, the projection matrix
339
340 \li INVERSE_PROJECTION_MATRIX -> mat4, the inverse projection matrix
341
342 \li VIEW_MATRIX -> mat4, the view (camera) matrix
343
344 \li MODEL_MATRIX -> mat4, the model (world) matrix
345
346 \li NORMAL_MATRIX -> mat3, the normal matrix (the transpose of the inverse
347 of the top-left 3x3 part of the model matrix)
348
349 \li BONE_TRANSFORMS -> mat4[], the array of the model's bone matrixes
350
351 \li BONE_NORMAL_TRANSFORMS -> mat3[], the array of the model's bone normal
352 matrixes (the transpose of the inverse of the top-left 3x3 part of the each
353 bone matrixes)
354
355 \li MORPH_WEIGHTS -> float[], the array of the morph weights. The associated model
356 should provide proper data. For safety, \b {QT_MORPH_MAX_COUNT} is defined to the
357 size of this array.
358
359 \li CAMERA_POSITION -> vec3, the camera position in world space
360
361 \li CAMERA_DIRECTION -> vec3, the camera direction vector
362
363 \li CAMERA_PROPERTIES -> vec2, the near and far clip values for the camera
364
365 \li POINT_SIZE -> float, writable in the vertex shader only. When rendering
366 geometry with a topology of points, the custom vertex shader must set this
367 to either 1.0 or another value, both in shaded and unshaded custom
368 materials. See \l{PrincipledMaterial::pointSize} for further notes on
369 support for sizes other than 1.
370
371 \endlist
372
373 \section1 Shaded custom materials
374
375 A \l{CustomMaterial::shadingMode}{shaded} material \c augments the shader code
376 that would be generated by a PrincipledMaterial. Unlike unshaded materials,
377 that provide almost all logic for the vertex and fragment shader main
378 functions on their own, preventing adding generated code for lighting,
379 shadowing, global illumination, etc., shaded materials let shader
380 generation happen normally, as if the CustomMaterial was a
381 PrincipledMaterial. The vertex and fragment shader snippets are expected to
382 provide optional functions that are then invoked at certain points, giving
383 them the possibility to customize the colors and other values that are then
384 used for calculating lighting and the final fragment color.
385
386 Rather than implementing just a \c MAIN function, the fragment shader for a
387 shaded custom material can implement multiple functions. All functions,
388 including \c MAIN, are optional to implement in shaded custom materials. An
389 empty shader snippet, or, even, not specifying the
390 \l{CustomMaterial::vertexShader}{vertexShader} or
391 \l{CustomMaterial::fragmentShader}{fragmentShader} properties at all can be
392 perfectly valid too.
393
394 \section2 Vertex shader snippets in a shaded custom material
395
396 The following functions can be implemented in a vertex shader snippet:
397
398 \list
399
400 \li \c{void MAIN()} When present, this function is called in order to set
401 the value of \c POSITION, the vec4 output from the vertex shader, and,
402 optionally, to modify the values of \c VERTEX, \c COLOR, \c NORMAL, \c UV0,
403 \c UV1, \c TANGENT, \c BINORMAL, \c JOINTS, and \c WEIGHTS. Unlike in
404 unshaded materials, writing to these makes sense because the modified values
405 are then taken into account in the rest of the generated shader code
406 (whereas for unshaded materials there is no additional shader code
407 generated). For example, if the custom vertex shader displaces the vertices
408 or the normals, it will want to store the modified values to \c VERTEX or
409 \c NORMAL, to achieve correct lighting calculations afterwards.
410 Additionally, the function can write to variables defined with \c VARYING in
411 order to pass interpolated data to the fragment shader. When this function
412 or a redefinition of \c POSITION is not present, \c POSITION is calculated
413 based on \c VERTEX and \c MODELVIEWPROJECTION_MATRIX, just like a
414 PrincipledMaterial would do.
415
416 Example, with relying both on QML properties exposed as uniforms, and also
417 passing data to the fragment shader:
418 \badcode
419 VARYING vec3 vNormal;
420 VARYING vec3 vViewVec;
421
422 void MAIN()
423 {
424 VERTEX.x += sin(uTime * 4.0 + VERTEX.y) * uAmplitude;
425 vNormal = normalize(NORMAL_MATRIX * NORMAL);
426 vViewVec = CAMERA_POSITION - (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
427 POSITION = MODELVIEWPROJECTION_MATRIX * vec4(VERTEX, 1.0);
428 }
429 \endcode
430
431 \note In the above example, assigning a value to \c POSITION is optional as
432 the usage in this case is identical to the default behavior.
433
434 \endlist
435
436 \note To pass data without interpolation from the vertex to the fragment
437 stage, add the \c flat keyword before the type in the \c VARYING
438 declarations.
439
440 \section2 Fragment shader snippets in a shaded custom material
441
442 The following functions can be implemented in a fragment shader snippet:
443
444 \list
445
446 \li \c{void MAIN()} When present, this function is called to set the values
447 of the special writable variables \c BASE_COLOR, \c METALNESS, \c ROUGHNESS, \c
448 SPECULAR_AMOUNT, NORMAL, CLEARCOAT_FRESNEL_POWER, CLEARCOAT_FRESNEL_SCALE,
449 CLEARCOAT_FRESNEL_BIAS, CLEARCOAT_AMOUNT, CLEARCOAT_ROUGHNESS, CLEARCOAT_NORMAL,
450 FRESNEL_BIAS, FRESNEL_SCALE, FRESNEL_POWER, IOR, \c TRANSMISSION_FACTOR,
451 THICKNESS_FACTOR, ATTENUATION_COLOR, ATTENUATION_DISTANCE and \c OCCLUSION_AMOUNT.
452
453 One common use case is to set the value of \c BASE_COLOR based on sampling
454 a texture, be it a base color map, \c SCREEN_TEXTURE, or some other kind of
455 source. This can be relevant and convenient especially when no custom light
456 processor functions are implemented. Setting \c{BASE_COLOR.a} to something
457 other than the default 1.0 allows affecting the final alpha value of the
458 fragment. (note that this will often require also enabling alpha blending
459 in \l sourceBlend and \l destinationBlend)
460
461 Another scenario is when there is no custom \c SPECULAR_LIGHT function
462 provided, or when there is a light probe set in the SceneEnvironment. The
463 metalness, roughness, and other values that affect the specular
464 contribution calculation can be set in \c MAIN to their desired custom
465 values.
466
467 The function can write to the following special variables. The values
468 written to these will typically be either hardcoded or be calculated based
469 on QML properties mapped to uniforms. The semantics are identical to
470 PrincipledMaterial.
471
472 \list
473
474 \li vec4 \c BASE_COLOR - The base color and material alpha value.
475 Corresponds to the \l{PrincipledMaterial::baseColor}{built-in materials'
476 color property}. When light processor functions are not implemented, it can
477 be convenient to set a custom base color in \c MAIN because that is then
478 taken into account in the default lighting calculations. The default value
479 is \c{vec4(1.0)}, meaning white with an alpha of 1.0. The alpha value
480 effects the final alpha of the fragment. The final alpha value is the
481 object (model) opacity multiplied by the base color alpha. When specifying
482 the value directly in shader code, not relying on uniform values exposed
483 from \b color properties in QML, be aware that it is up to the shader to
484 perform the sRGB to linear conversion, if needed. For example, assuming
485 a \c{vec3 color} and \c{float alpha} this can be achieved like the following:
486 \badcode
487 float C1 = 0.305306011;
488 vec3 C2 = vec3(0.682171111, 0.682171111, 0.682171111);
489 vec3 C3 = vec3(0.012522878, 0.012522878, 0.012522878);
490 BASE_COLOR = vec4(rgb * (rgb * (rgb * C1 + C2) + C3), alpha);
491 \endcode
492
493 \li vec3 \c EMISSIVE_COLOR - The color of self-illumination. Corresponds to
494 the built-in materials' emissive color which is combined by
495 \l {PrincipledMaterial::emissiveFactor}{built-in materials's emissiveFactor property}
496 and \l {PrincipledMaterial::emissiveMap}{built-in materials's emissiveMap property}.
497 The default value is \c{vec3(0.0)}. When specifying the value
498 directly in shader code, not relying on uniform values exposed from \b color
499 properties in QML, be aware that it is up to the shader to perform the sRGB
500 to linear conversion, if needed.
501
502 \li float \c IOR Specifies the index of refraction of the material. A typical value,
503 and also the default, is \c{1.5} as that is what a PrincipledMaterial would use.
504
505 \li float \c TRANSMISSION_FACTOR Specifies the amount of the translucency. A typical value,
506 would be \c{1.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use.
507
508 \li float \c THICKNESS_FACTOR Specifies the amount of the translucent material thickness. A typical value,
509 would be \c{10.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use.
510
511 \li vec3 \c ATTENUATION_COLOR Specifies the color shift of the translucent material by distance. A typical value,
512 would be \c{vec3(1.0, 0.0, 0.0)} and also the default, is \c{vec3(1.0)} as that is what a PrincipledMaterial would use.
513
514 \li float \c ATTENUATION_DISTANCE Specifies the distance attenuation of color shift of the translucent material. A typical value,
515 would be \c{100.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use.
516
517 \li float \c METALNESS Metalness amount in range 0.0 - 1.0. The default
518 value is 0. Must be set to a non-zero value to have effect.
519
520 \li float \c ROUGHNESS Roughness value in range 0.0 - 1.0. The default value is 0.
521
522 \li float \c CLEARCOAT_FRESNEL_POWER Specifies the fresnel power of the clearcoat layer. A typical value,
523 and also the default, is \c{5.0} as that is what a PrincipledMaterial would use.
524
525 \li float \c CLEARCOAT_FRESNEL_SCALE Specifies the fresnel scale of the clearcoat layer. A typical value,
526 and also the default, is \c{1.0} as that is what a PrincipledMaterial would use.
527
528 \li float \c CLEARCOAT_FRESNEL_BIAS Specifies the fresnel bias of the clearcoat layer. A typical value,
529 and also the default, is \c{0.0} as that is what a PrincipledMaterial would use.
530
531 \li float \c CLEARCOAT_AMOUNT Specifies the amount of the clearcoat layer on top of the material. A typical value,
532 would be \c{1.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use.
533
534 \li float \c CLEARCOAT_ROUGHNESS Specifies the roughness of the clearcoat layer. A typical value,
535 would be \c{1.0} for fully blurred clearcoat layer and also the default, is \c{0.0} as that is
536 what a PrincipledMaterial would use.
537
538 \li vec3 \c CLEARCOAT_NORMAL - The clearcoat layer normal that comes from the vertex shader in world
539 space. While this property has the same initial value as \c VAR_WORLD_NORMAL,
540 only changing the value of \c CLEARCOAT_NORMAL will have an effect on clearcoat layer normal.
541
542 \li float \c FRESNEL_POWER Specifies the fresnel power. A typical value,
543 and also the default, is \c{5.0} as that is what a PrincipledMaterial would use.
544
545 \li float \c FRESNEL_SCALE Specifies the fresnel scale. A typical value,
546 and also the default, is \c{1.0} as that is what a PrincipledMaterial would use.
547
548 \li float \c FRESNEL_BIAS Specifies the fresnel bias. A typical value,
549 and also the default, is \c{0.0} as that is what a PrincipledMaterial would use.
550
551 \li float \c SPECULAR_AMOUNT Specular amount in range 0.0 - 1.0. The
552 default value is \c{0.5}, matching \l{PrincipledMaterial::specularAmount}. Must
553 be set to a non-zero value to have effect.
554
555 \li float \c OCCLUSION_AMOUNT Specifies the AO factor. A typical value,
556 and also the default, is \c{1.0} as that is what a PrincipledMaterial would use.
557
558 \li vec3 \c NORMAL - The normal that comes from the vertex shader in world
559 space. While this property has the same initial value as \c VAR_WORLD_NORMAL,
560 only changing the value of \c NORMAL will have an effect on lighting.
561
562 \li vec3 \c TANGENT - The tanget that comes from the vertex shader in world
563 space. This value is potentially adjusted for double-sidedness.
564
565 \li vec3 \c BINORMAL - The binormal that comes from the vertex shader in
566 world space. This value is potentially adjusted for double-sidedness.
567
568 \li vec2 \c UV0 - The first set of texture coordinates from the vertex shader.
569 This property is readonly in the fragment shader.
570
571 \li vec2 \c UV1 - The second set of texture coordinates from the vertex shader.
572 This property is readonly in the fragment shader.
573
574 \endlist
575
576 \note Unlike with unshaded materials, the fragment \c MAIN for a shaded
577 material has no direct control over \c FRAGCOLOR. Rather, it is the \c
578 DIFFUSE and \c SPECULAR values written in the light processor functions
579 that decide what the final fragment color is. When a light processor
580 function is not implemented, the relevant default shading calculations are
581 performed as with a PrincipledMaterial, taking \c BASE_COLOR and other
582 values from the list above into account.
583
584 An example of a simple, metallic custom material shader could be the following:
585 \badcode
586 void MAIN()
587 {
588 METALNESS = 1.0;
589 ROUGHNESS = 0.5;
590 FRESNEL_POWER = 5.0;
591 }
592 \endcode
593
594 Another example, where the base color and alpha are set by sampling a texture:
595 \badcode
596 VARYING vec2 texcoord;
597 void MAIN()
598 {
599 BASE_COLOR = texture(uColorMap, texcoord);
600 }
601 \endcode
602
603 \li \c{void AMBIENT_LIGHT()} When present, this function is called once for
604 each fragment. The task of the function is to add the total ambient
605 contribution to a writable special variable \c DIFFUSE. It can of course
606 choose to calculate a different value, or not touch \c DIFFUSE at all (to
607 ignore ambient lighting completely). When this function is not present at
608 all, the ambient contribution is calculated normally, like a
609 PrincipledMaterial would do.
610
611 The function can write to the following special variables:
612
613 \list
614
615 \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per
616 fragment. The light processor functions will typically add (\c{+=}) to it,
617 since overwriting the value would lose the contribution from other lights.
618
619 \endlist
620
621 The function can read the following special variables, in addition to the
622 matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION)
623 uniforms from the table above:
624
625 \list
626 \li vec3 \c TOTAL_AMBIENT_COLOR The total ambient contribution in the scene.
627 \endlist
628
629 Example:
630 \badcode
631 void AMBIENT_LIGHT()
632 {
633 DIFFUSE += TOTAL_AMBIENT_COLOR;
634 }
635 \endcode
636
637 \li \c{void DIRECTIONAL_LIGHT()} When present, this function is called for
638 each active directional light in the scene for each fragment. The task of
639 the function is to add the diffuse contribution to a writable special
640 variable \c DIFFUSE. The function can also choose to do nothing, in which
641 case diffuse contributions from directional lights are ignored. When the
642 function is not present at all, the diffuse contributions from directional
643 lights are accumulated normally, like a PrincipledMaterial would do.
644
645 The function can write to the following special variables:
646
647 \list
648
649 \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per
650 fragment. The light processor functions will typically add (\c{+=}) to it,
651 since overwriting the value would lose the contribution from other lights.
652
653 \endlist
654
655 The function can read the following special variables, in addition to the
656 matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION)
657 uniforms from the table above:
658
659 \list
660
661 \li vec3 \c LIGHT_COLOR Diffuse light color.
662 \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows.
663 \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source.
664 \li vec3 \c NORMAL The normal vector in world space.
665 \li vec4 \c BASE_COLOR The base color and material alpha value.
666 \li float \c METALNESS The Metalness amount.
667 \li float \c ROUGHNESS The Roughness amount.
668
669 \endlist
670
671 Example:
672 \badcode
673 void DIRECTIONAL_LIGHT()
674 {
675 DIFFUSE += LIGHT_COLOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR)));
676 }
677 \endcode
678
679 \li \c{void POINT_LIGHT()} When present, this function is called for
680 each active point light in the scene for each fragment. The task of
681 the function is to add the diffuse contribution to a writable special
682 variable \c DIFFUSE. The function can also choose to do nothing, in which
683 case diffuse contributions from point lights are ignored. When the
684 function is not present at all, the diffuse contributions from point
685 lights are accumulated normally, like a PrincipledMaterial would do.
686
687 The function can write to the following special variables:
688
689 \list
690 \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per fragment.
691 \endlist
692
693 The function can read the following special variables, in addition to the
694 matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION)
695 uniforms from the table above:
696
697 \list
698 \li vec3 \c LIGHT_COLOR Diffuse light color.
699 \li float \c LIGHT_ATTENUATION Light attenuation.
700 \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows.
701 \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source.
702 \li vec3 \c NORMAL The normal vector in world space.
703 \li vec4 \c BASE_COLOR The base color and material alpha value.
704 \li float \c METALNESS The Metalness amount.
705 \li float \c ROUGHNESS The Roughness amount.
706 \endlist
707
708 Example:
709 \badcode
710 void POINT_LIGHT()
711 {
712 DIFFUSE += LIGHT_COLOR * LIGHT_ATTENUATION * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR)));
713 }
714 \endcode
715
716 \li \c{void SPOT_LIGHT()} When present, this function is called for
717 each active spot light in the scene for each fragment. The task of
718 the function is to add the diffuse contribution to a writable special
719 variable \c DIFFUSE. The function can also choose to do nothing, in which
720 case diffuse contributions from spot lights are ignored. When the
721 function is not present at all, the diffuse contributions from spot
722 lights are accumulated normally, like a PrincipledMaterial would do.
723
724 The function can write to the following special variables:
725
726 \list
727 \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per fragment.
728 \endlist
729
730 The function can read the following special variables, in addition to the
731 matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION)
732 uniforms from the table above:
733
734 \list
735 \li vec3 \c LIGHT_COLOR Diffuse light color.
736 \li float \c LIGHT_ATTENUATION Light attenuation.
737 \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows.
738 \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source.
739 \li float \c SPOT_FACTOR Spot light factor.
740 \li vec3 \c NORMAL The normal vector in world space.
741 \li vec4 \c BASE_COLOR The base color and material alpha value.
742 \li float \c METALNESS The Metalness amount.
743 \li float \c ROUGHNESS The Roughness amount.
744 \endlist
745
746 Example:
747 \badcode
748 void SPOT_LIGHT()
749 {
750 DIFFUSE += LIGHT_COLOR * LIGHT_ATTENUATION * SPOT_FACTOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR)));
751 }
752 \endcode
753
754 \li \c{void SPECULAR_LIGHT()} When present, this function is called for
755 each active light in the scene for each fragment. The task of the function
756 is to add the specular contribution to a writable special variable \c
757 SPECULAR. The function can also choose to do nothing, in which case
758 specular contributions from lights are ignored. When the function is not
759 present at all, the specular contributions from lights are accumulated
760 normally, like a PrincipledMaterial would do.
761
762 The function can write to the following special variables:
763
764 \list
765
766 \li vec3 \c SPECULAR Accumulates the specular light contributions, per
767 frament. The light processor functions will typically add (\c{+=}) to it,
768 since overwriting the value would lose the contribution from other lights.
769
770 \endlist
771
772 The function can read the following special variables, in addition to the
773 matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION)
774 uniforms from the table above:
775
776 \list
777 \li vec3 \c LIGHT_COLOR Specular light color.
778 \li float \c LIGHT_ATTENUATION Light attenuation. For directional lights the value is 1.0. For spot lights the value is the same as \c {LIGHT_ATTENUATION * SPOT_FACTOR} of \c {void SPOT_LIGHT()}.
779 \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows.
780 \li vec3 \c FRESNEL_CONTRIB Fresnel contribution from built in Fresnel calculation.
781 \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source.
782 \li vec3 \c NORMAL The normal vector in world space.
783 \li vec4 \c BASE_COLOR The base color and material alpha value.
784 \li float \c METALNESS The Metalness amount.
785 \li float \c ROUGHNESS The Roughness amount.
786 \li float \c SPECULAR_AMOUNT The specular amount. This value will be between
787 0.0 and 1.0 will be the same value set in the custom \c MAIN function. This
788 value will useful for calculating Fresnel contributions when not using the
789 built-in Fresnel contribution provided by \c FRESNEL_CONTRIB.
790 \endlist
791
792 \badcode
793 void SPECULAR_LIGHT()
794 {
795 vec3 H = normalize(VIEW_VECTOR + TO_LIGHT_DIR);
796 float cosAlpha = max(0.0, dot(H, normalize(NORMAL)));
797 float shine = pow(cosAlpha, exp2(15.0 * (1.0 - ROUGHNESS) + 1.0) * 0.25);
798 SPECULAR += shine * LIGHT_COLOR * FRESNEL_CONTRIB * SHADOW_CONTRIB * LIGHT_ATTENUATION;
799 }
800 \endcode
801
802 \li \c{void POST_PROCESS()} When present, this function is called at the
803 end of the fragment pipeline. The task of the function is to finalize
804 \c COLOR_SUM with final diffuse, specular and emissive terms. Unlike
805 \c FRAGCOLOR for a unshaded material, \c COLOR_SUM will be automatically
806 tonemapped before written to the framebuffer. For debugging purposes it is
807 sometimes useful to output a value that should not be treated as a color.
808 To avoid the tonemapping distorting this value it can be disabled by
809 setting the \l {SceneEnvironment::tonemapMode}{tonemapMode} property
810 to \c TonemapModeNone
811
812 The function can write to the following special variables:
813
814 \list
815 \li vec4 \c COLOR_SUM the output from the fragment shader. The default value
816 is vec4(DIFFUSE.rgb + SPECULAR + EMISSIVE, DIFFUSE.a)
817 \endlist
818
819 The function can read the following special variables.
820
821 \list
822 \li vec4 \c DIFFUSE The final diffuse term of the fragment pipeline.
823 \li vec3 \c SPECULAR The final specular term of the fragment pipeline.
824 \li vec3 \c EMISSIVE The final emissive term of the fragment pipeline.
825 \li vec2 \c UV0 - The first set of texture coordinates from the vertex shader.
826 \li vec2 \c UV1 - The second set of texture coordinates from the vertex shader.
827 \endlist
828
829 \badcode
830 void POST_PROCESS()
831 {
832 float center_x = textureSize(SCREEN_TEXTURE, 0).x * 0.5;
833 if (gl_FragCoord.x > center_x)
834 COLOR_SUM = DIFFUSE;
835 else
836 COLOR_SUM = vec4(EMISSIVE, DIFFUSE.a);
837 }
838 \endcode
839
840 \li \c{void IBL_PROBE()} When present, this function is called for IBL
841 (Image-Based Lighting).
842 The task of the function is to add both the diffuse and the specular
843 contributions of IBL to writable special variables \c DIFFUSE and
844 \c SPECULAR.
845
846 The function can write to the following special variables:
847
848 \list
849 \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per fragment.
850 \li vec3 \c SPECULAR Accumulates the specular light contributions, per
851 frament.
852 \endlist
853
854 The function can read the following special variables.
855
856 \list
857 \li vec4 \c BASE_COLOR The base color and material alpha value.
858 \li float \c AO_FACTOR The screen space occlusion factor.
859 \li float \c SPECULAR_AMOUNT The specular amount.
860 \li float \c ROUGHNESS The final emissive term of the fragment pipeline.
861 \li vec3 \c NORMAL The normal vector in world space.
862 \li vec3 \c VIEW_VECTOR Points towards the camera.
863 \li mat3 \c IBL_ORIENTATION The orientation of the light probe. It comes
864 from \l {SceneEnvironment::probeOrientation}.
865 \endlist
866
867 \badcode
868 void IBL_PROBE()
869 {
870 vec3 smpDir = IBL_ORIENTATION * NORMAL;
871 DIFFUSE += AO_FACTOR * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, smpDir, IBL_MAXMIPMAP).rgb;
872 }
873 \endcode
874
875 \endlist
876
877 \sa SceneEnvironment::tonemapMode, {Using Image-Based Lighting}
878
879 \section2 Custom variables between functions
880
881 Additional variables can be delivered from the MAIN function to the others.
882 The \c SHARED_VARS keyword can be used for defining new custom variables.
883 These user-defined variables can be accessed with SHARED.<variable name>.
884
885 For example, a shaded custom material can fetch a shared value in the MAIN
886 and use it in other functions.
887
888 \badcode
889 SHARED_VARS {
890 vec3 colorThreshold;
891 };
892 void MAIN()
893 {
894 BASE_COLOR = texture(baseColorMap, UV0);
895 SHARED.colorThreshold = texture(thresholdMap, UV0).rgb;
896 }
897 void DIRECTIONAL_LIGHT()
898 {
899 if (DIFFUSE >= SHARED.colorThreshold) {
900 DIFFUSE = SHARED.colorThreshold;
901 return;
902 }
903 DIFFUSE += LIGHT_COLOR * SHADOW_CONTRIB;
904 }
905 \endcode
906
907 \note SHARED can be written on all the functions without POST_PROCESS but it
908 is safe to write it on MAIN and read on the other functions.
909
910 \note A recommended use case to write SHARED on LIGHT functions is
911 reseting it on MAIN first and then accumulating it on each LIGHT functions.
912
913 \badcode
914 SHARED_VARS {
915 float sheenIntensity;
916 float sheenRoughness;
917 vec3 sheenColor;
918 vec3 outSheenColor;
919 };
920 void MAIN()
921 {
922 ...
923 vec4 tex = texture(uSheenMap, UV0);
924 SHARED.sheenColor = tex.rgb;
925 SHARED.sheenIntensity = tex.a;
926 SHARED.sheenRoughness = uSheenRoughness;
927 SHARED.outSheenColor = vec3(0.0);
928 }
929 void SPECULAR_LIGHT()
930 {
931 SHARED.outSheenColor += ...;
932 }
933 void POST_PROCESS()
934 {
935 COLOR_SUM = DIFFUSE + SPECULAR + EMISSIVE + SHARED.outSheenColor;
936 }
937 \endcode
938
939 \note MAIN is called before others, and POST_PROCESS after all others,
940 but that there is no guarantee for any other ordering for light processors.
941
942 \section2 Additional special keywords
943
944 The custom fragment shader code can freely access uniforms (such as, \c
945 CAMERA_DIRECTION or \c CAMERA_POSITION), and varyings passed on from the
946 custom vertex shader. Additionally, there are a number of built-in varyings
947 available as special keywords. Some of these are optional in the sense that
948 a vertex \c MAIN could calculate and pass on these on its own, but to
949 reduce duplicated data fragment shaders can also rely on these built-ins
950 instead. These built-ins are available in light processor functions and in
951 the fragment MAIN.
952
953 \list
954
955 \li vec3 \c VAR_WORLD_NORMAL - Interpolated normal transformed by \c
956 NORMAL_MATRIX.
957
958 \li vec3 \c VAR_WORLD_TANGENT - Interpolated tangent transformed by \c
959 MODEL_MATRIX.
960
961 \li vec3 \c VAR_WORLD_BINORMAL - Interpolated binormal transformed by \c
962 MODEL_MATRIX
963
964 \li vec3 \c NORMAL - Unlike \c VAR_WORLD_NORMAL, which is the
965 interpolated normal as-is, this value is potentially adjusted for
966 double-sidedness: when rendering with culling disabled, the normal will get
967 inverted as necessary. Therefore lighting and other calculations are
968 recommended to use \c NORMAL instead of \c VAR_WORLD_NORMAL in order
969 behave correctly with all culling modes.
970
971 \li vec3 \c TANGENT - Like \c NORMAL, this value is potentially adjusted for
972 double-sidedness: when rendering with culling disabled, the tangent will get
973 inverted as necessary.
974
975 \li vec3 \c BINORMAL - Like \c NORMAL, this value is potentially adjusted for
976 double-sidedness: when rendering with culling disabled, the binormal will get
977 inverted as necessary.
978
979 \li vec3 \c VAR_WORLD_POSITION - Interpolated world space vertex position
980 (\c{(MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz})
981
982 \li vec4 \c VAR_COLOR - The interpolated vertex color when colors are
983 provided in the mesh. \c{vec4(1.0)} otherwise.
984
985 \li vec3 \c VIEW_VECTOR - Points towards the camera. This is
986 effectively the \c{CAMERA_POSITION - VAR_WORLD_POSITION} vector normalized.
987
988 \li vec4 \c FRAGCOORD - Contains the window-relative coordinates of the
989 current fragment.
990
991 \li float \c FRAMEBUFFER_Y_UP - The value is \c 1 when the Y axis points up
992 in the coordinate system for framebuffers (textures), meaning \c{(0, 0)} is
993 the bottom-left corner. The value is \c{-1} when the Y axis points down,
994 \c{(0, 0)} being the top-left corner. Such differences in the underlying
995 graphics APIs do not concern most custom materials. One notable exception
996 is sampling \c SCREEN_TEXTURE with texture coordinates \b not based on
997 \c FRAGCOORD. As the orientation of \c SCREEN_TEXTURE is tied to the
998 underlying graphics API by nature, using texture coordinates from a mesh
999 may need appropriate adjustments to the Y coordinate.
1000
1001 For example, the following fragment shader, suitable for Rectangle or Cube
1002 meshes, will display the opaque objects from the scene on the model:
1003
1004 \badcode
1005 VARYING vec2 texcoord;
1006 void MAIN()
1007 {
1008 vec2 screencoord = texcoord;
1009 if (FRAMEBUFFER_Y_UP < 0.0) // effectively: if not OpenGL
1010 screencoord.y = 1.0 - screencoord.y;
1011 BASE_COLOR = texture(SCREEN_TEXTURE, screencoord);
1012 }
1013 \endcode
1014
1015 When sampling textures other than \c SCREEN_TEXTURE and \c DEPTH_TEXTURE,
1016 or when \c FRAGCOORD is used to calculate the texture coordinate (which
1017 would be the typical use case for accessing the screen and depth textures),
1018 such an adjustment is not necessary.
1019
1020 \li float \c NDC_Y_UP - The value is \c 1 when the Y axis points up in
1021 normalized device coordinate space, and \c{-1} when the Y axis points down.
1022 Y pointing down is the case when rendering happens with Vulkan. Most
1023 materials do not need to be concerned by this, but being able to branch
1024 based on this can become useful in certain advanced use cases.
1025
1026 \li float \c NEAR_CLIP_VALUE - The value is \c -1 for when the clipping plane
1027 range's starts at \c -1 and goes to \c 1. This is true when using OpenGL for
1028 rendering. For other rendering backends the value of this property will be
1029 \c 0 meaning the clipping plane range is \c 0 to \c 1. This value is useful
1030 with certain techniques involving the \c DEPTH_TEXTURE
1031
1032 For example, the following fragment shader demonstrates a technique for
1033 reconstructing the position of a value from the depth buffer to determine
1034 the distance from the current position being rendered. When used in
1035 combination with \c INVERSE_PROJECTION_MATRIX the value of depth needs
1036 to be in normalized device coordinates so it is important to make sure that
1037 the range of depth value reflects that. When the \c NEAR_CLIP_VALUE is
1038 \c -1 then the depth value gets scaled to be between \c -1 and \c 1.
1039
1040 \badcode
1041 void MAIN() {
1042 vec2 screen_uv = FRAGCOORD.xy / vec2(textureSize(SCREEN_TEXTURE, 0));
1043 float depth = texture(DEPTH_TEXTURE, screen_uv).r;
1044
1045 if (NEAR_CLIP_VALUE < 0.0) // effectively: if opengl
1046 depth = depth * 2.0 - 1.0;
1047
1048 vec4 unproject = INVERSE_PROJECTION_MATRIX * vec4(screen_uv, depth, 1.0);
1049 depth = (unproject.xyz / unproject.w).z;
1050 float viewVectorZ = (VIEW_MATRIX * vec4(VAR_WORLD_POSITION, 1.0)).z;
1051 depth = viewVectorZ - depth;
1052
1053 BASE_COLOR = vec4(depth, depth, depth, 1.0);
1054 }
1055 \endcode
1056
1057 \li float \c IBL_EXPOSE - The amount of light emitted by the light probe.
1058 It comes from \l {SceneEnvironment::probeExposure}.
1059 \badcode
1060 DIFFUSE += AO_FACTOR * IBL_EXPOSE * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, NORMAL, IBL_MAXMIPMAP).rgb;
1061 \endcode
1062
1063 \li float \c IBL_HORIZON - The horizontal cut-off value of reflections from
1064 the lower half environment. It comes from \l {SceneEnvironment::probeHorizon}
1065 {Horizon Cut-Off} but remapped to [-1, 0).
1066 \badcode
1067 vec3 diffuse += AO_FACTOR * IBL_EXPOSE * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, NORMAL, IBL_MAXMIPMAP).rgb;
1068 if (IBL_HORIZON > -1.0) {
1069 float ctr = 0.5 + 0.5 * IBL_HORIZON;
1070 float vertWt = smoothstep(ctr * 0.25, ctr + 0.25, NORMAL.y);
1071 float wtScaled = mix(1.0, vertWt, IBL_HORIZON + 1.0);
1072 diffuse *= wtScaled;
1073 }
1074 \endcode
1075
1076 \li float \c IBL_MAXMIPMAP - The maximum mipmap level of IBL_TEXTURE.
1077
1078 \endlist
1079
1080 \section2 Instancing
1081
1082 When doing instanced rendering, some of the keywords above do not apply.
1083 The following keywords are only available with instancing:
1084
1085 \list
1086 \li \c INSTANCE_MODEL_MATRIX -> mat4, replacement for \c MODEL_MATRIX, including the instancing transformation.
1087 \li \c INSTANCE_MODELVIEWPROJECTION_MATRIX -> mat4, replacement for \c MODELVIEWPROJECTION_MATRIX, including the instancing transformation.
1088 \li \c INSTANCE_COLOR -> vec4, the instance color: to be combined with \c {COLOR}.
1089 \li \c INSTANCE_DATA -> vec4, instance custom data.
1090 \li \c INSTANCE_INDEX -> int, the instance number, and index into the instancing table.
1091 \endlist
1092
1093 \section1 Screen, depth, and other textures
1094
1095 The rendering pipeline can expose a number of textures to the custom
1096 material shaders with content from special render passes. This applies both
1097 to shaded and unshaded custom materials.
1098
1099 For example, a shader may want access to a depth texture that contains the
1100 depth buffer contents for the opaque objects in the scene. This is achieved
1101 by sampling \c DEPTH_TEXTURE. Such a texture is not normally generated,
1102 unless there is a real need for it. Therefore, the presence of the
1103 following keywords in the vertex or fragment shader also acts as a toggle
1104 for opting in to the - potentially expensive - passes for generating the
1105 texture in question. (of course, it could be that some of these become
1106 already enabled due to other settings, such as the ambient occlusion
1107 parameters in SceneEnvironment or due to a post-processing effect relying
1108 on the depth texture, in which case the textures in question are generated
1109 regardless of the custom material and so sampling these special textures in
1110 the material comes at no extra cost apart from the texture access itself)
1111
1112 \list
1113
1114 \li \c SCREEN_TEXTURE - When present, a texture (\c sampler2D or \c
1115 sampler2DArray) with the color buffer from a rendering pass containing the
1116 contents of the scene excluding any transparent materials or any materials
1117 also using the SCREEN_TEXTURE is exposed to the shader under this name. The
1118 texture can be used for techniques that require the contents of the
1119 framebuffer they are being rendered to. The SCREEN_TEXTURE texture uses the
1120 same clear mode as the View3D. The size of these textures matches the size
1121 of the View3D in pixels. For example, a fragment shader could contain the
1122 following:
1123 \badcode
1124 vec2 uv = FRAGCOORD.xy / vec2(textureSize(SCREEN_TEXTURE, 0));
1125 vec2 displace = vec2(0.1);
1126 vec4 c = texture(SCREEN_TEXTURE, uv + displace);
1127 \endcode
1128
1129 Be aware that using \c SCREEN_TEXTURE requires appropriate, conscious
1130 design of the scene. Objects using such materials have to be positioned
1131 carefully, typically above all other objects that are expected to be
1132 visible in the texture. Objects that employ semi-transparency in some form
1133 are never part of the \c SCREEN_TEXTURE. Often \c SCREEN_TEXTURE will be
1134 used in combination with \c BASE_COLOR in \c MAIN. For example, the
1135 following custom fragment shader applies an emboss effect, while keeping
1136 fragments not touched by opaque objects transparent. This assumes that the
1137 object with the material is placed in the front, and that it has blending
1138 enabled. \badcode
1139 void MAIN()
1140 {
1141 vec2 size = vec2(textureSize(SCREEN_TEXTURE, 0));
1142 vec2 uv = FRAGCOORD.xy / size;
1143
1144 // basic emboss effect
1145 vec2 d = vec2(1.0 / size.x, 1.0 / size.y);
1146 vec4 diff = texture(SCREEN_TEXTURE, uv + d) - texture(SCREEN_TEXTURE, uv - d);
1147 float c = (diff.x + diff.y + diff.z) + 0.5;
1148
1149 float alpha = texture(SCREEN_TEXTURE, uv).a;
1150 BASE_COLOR = vec4(vec3(c), alpha);
1151 }
1152 \endcode
1153 With \l{Multiview Rendering}{multiview rendering}, \c SCREEN_TEXTURE is a \c
1154 sampler2DArray. Use \c VIEW_INDEX to select the layer to use. For VR/AR
1155 applications that wish to support both types of rendering, the portable
1156 approach is the following:
1157 \badcode
1158 #if QSHADER_VIEW_COUNT >= 2
1159 vec4 c = texture(SCREEN_TEXTURE, vec3(uv, VIEW_INDEX));
1160 #else
1161 vec4 c = texture(SCREEN_TEXTURE, uv);
1162 #endif
1163 \endcode
1164
1165 \li \c SCREEN_MIP_TEXTURE - Identical to \c SCREEN_TEXTURE in most ways,
1166 the difference being that this texture has mipmaps generated. This can be
1167 an expensive feature performance-wise, depending on the screen size, and
1168 due to having to generate the mipmaps every time the scene is rendered.
1169 Therefore, prefer using \c SCREEN_TEXTURE always, unless a technique
1170 relying on the texture mip levels (e.g. using \c textureLod in the shader)
1171 is implemented by the custom material.
1172
1173 \li \c DEPTH_TEXTURE - When present, a texture (\c sampler2D or \c
1174 sampler2DArray) with the (non-linearized) depth buffer contents is exposed
1175 to the shader under this name. Only opaque objects are included.
1176 For example, a fragment shader could contain the following: \badcode
1177 ivec2 dtSize = textureSize(DEPTH_TEXTURE, 0);
1178 vec2 dtUV = (FRAGCOORD.xy) / vec2(dtSize);
1179 vec4 depthSample = texture(DEPTH_TEXTURE, dtUV);
1180 float zNear = CAMERA_PROPERTIES.x;
1181 float zFar = CAMERA_PROPERTIES.y;
1182 float zRange = zFar - zNear;
1183 float z_n = 2.0 * depthSample.r - 1.0;
1184 float d = 2.0 * zNear * zFar / (zFar + zNear - z_n * zRange);
1185 d /= zFar;
1186 \endcode
1187 With \l{Multiview Rendering}{multiview rendering}, \c DEPTH_TEXTURE is a \c
1188 sampler2DArray. Use \c VIEW_INDEX to select the layer to use. For VR/AR
1189 applications that wish to support both types of rendering, the portable
1190 approach is the following:
1191 \badcode
1192 #if QSHADER_VIEW_COUNT >= 2
1193 vec4 depthSample = texture(DEPTH_TEXTURE, vec3(uv, VIEW_INDEX));
1194 #else
1195 vec4 depthSample = texture(DEPTH_TEXTURE, uv);
1196 #endif
1197 \endcode
1198
1199 \li \c AO_TEXTURE - When present and screen space ambient occlusion is
1200 enabled (meaning when the AO strength and distance are both non-zero) in
1201 SceneEnvironment, the SSAO texture (\c sampler2D or \c sampler2DArray) is
1202 exposed to the shader under this name. Sampling this texture can be useful
1203 in unshaded materials. Shaded materials have ambient occlusion support built
1204 in. This means that the ambient occlusion factor is taken into account
1205 automatically. Whereas in a fragment shader for an unshaded material one
1206 could write the following to achieve the same: \badcode
1207 ivec2 aoSize = textureSize(AO_TEXTURE, 0);
1208 vec2 aoUV = (FRAGCOORD.xy) / vec2(aoSize);
1209 float aoFactor = texture(AO_TEXTURE, aoUV).x;
1210 \endcode
1211 With \l{Multiview Rendering}{multiview rendering}, \c AO_TEXTURE is a \c
1212 sampler2DArray. Use \c VIEW_INDEX to select the layer to use. For VR/AR
1213 applications that wish to support both types of rendering, the portable
1214 approach is the following:
1215 \badcode
1216 #if QSHADER_VIEW_COUNT >= 2
1217 ivec2 aoSize = textureSize(AO_TEXTURE, 0).xy;
1218 vec2 aoUV = (FRAGCOORD.xy) / vec2(aoSize);
1219 float aoFactor = texture(AO_TEXTURE, vec3(aoUV, VIEW_INDEX)).x;
1220 #else
1221 ivec2 aoSize = textureSize(AO_TEXTURE, 0);
1222 vec2 aoUV = (FRAGCOORD.xy) / vec2(aoSize);
1223 float aoFactor = texture(AO_TEXTURE, aoUV).x;
1224 #endif
1225 \endcode
1226
1227 \li \c IBL_TEXTURE - It will not enable any special rendering pass, but it can
1228 be used when the material has \l {Material::lightProbe} or the model is in the scope of
1229 \l {SceneEnvironment::lightProbe}.
1230
1231 \badcode
1232 void IBL_PROBE()
1233 {
1234 DIFFUSE += AO_FACTOR * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, NORMAL, IBL_MAXMIPMAP).rgb;
1235 }
1236 \endcode
1237
1238 \li \c VIEW_INDEX - When used in the custom shader code, this is a
1239 (non-interpolated) uint variable. When \l{Multiview Rendering}{multiview
1240 rendering} is not used, the value is always 0. With multiview rendering, the
1241 value is the current view index (e.g., gl_ViewIndex). Useful in particular
1242 in combination with \c DEPTH_TEXTURE and similar when multiview rendering is
1243 enabled.
1244
1245 \endlist
1246
1247 \sa {Qt Quick 3D - Custom Shaders Example}, {Qt Quick 3D - Custom Materials Example}, {Programmable Materials, Effects, Geometry, and Texture data}
1248*/
1249
1250/*!
1251 \qmlproperty url CustomMaterial::vertexShader
1252
1253 Specfies the file with the snippet of custom vertex shader code.
1254
1255 The value is a URL and must either be a local file or use the qrc scheme to
1256 access files embedded via the Qt resource system. Relative file paths
1257 (without a scheme) are also accepted, in which case the file is treated as
1258 relative to the component (the \c{.qml} file).
1259
1260 \sa fragmentShader
1261*/
1262
1263/*!
1264 \qmlproperty url CustomMaterial::fragmentShader
1265
1266 Specfies the file with the snippet of custom fragment shader code.
1267
1268 The value is a URL and must either be a local file or use the qrc scheme to
1269 access files embedded via the Qt resource system. Relative file paths
1270 (without a scheme) are also accepted, in which case the file is treated as
1271 relative to the component (the \c{.qml} file).
1272
1273 \sa vertexShader
1274*/
1275
1276/*!
1277 \qmlproperty enumeration CustomMaterial::shadingMode
1278 Specifies the type of the material. The default value is Shaded.
1279
1280 \value CustomMaterial.Unshaded
1281 \value CustomMaterial.Shaded
1282*/
1283
1284/*!
1285 \qmlproperty bool CustomMaterial::alwaysDirty
1286 Specifies that the material state is always dirty, which indicates that the material needs
1287 to be refreshed every time it is used by the QtQuick3D.
1288*/
1289
1290/*!
1291 \qmlproperty enumeration CustomMaterial::sourceBlend
1292
1293 Specifies the source blend factor. The default value is \c
1294 CustomMaterial.NoBlend.
1295
1296 \value CustomMaterial.NoBlend
1297 \value CustomMaterial.Zero
1298 \value CustomMaterial.One
1299 \value CustomMaterial.SrcColor
1300 \value CustomMaterial.OneMinusSrcColor
1301 \value CustomMaterial.DstColor
1302 \value CustomMaterial.OneMinusDstColor
1303 \value CustomMaterial.SrcAlpha
1304 \value CustomMaterial.OneMinusSrcAlpha
1305 \value CustomMaterial.DstAlpha
1306 \value CustomMaterial.OneMinusDstAlpha
1307 \value CustomMaterial.ConstantColor
1308 \value CustomMaterial.OneMinusConstantColor
1309 \value CustomMaterial.ConstantAlpha
1310 \value CustomMaterial.OneMinusConstantAlpha
1311 \value CustomMaterial.SrcAlphaSaturate
1312
1313 \note Both \l sourceBlend and \l destinationBlend needs to be set to a non-default
1314 value before blending is enabled.
1315
1316 \sa destinationBlend
1317*/
1318
1319/*!
1320 \qmlproperty enumeration CustomMaterial::destinationBlend
1321
1322 Specifies the destination blend factor. The default value is \c
1323 CustomMaterial.NoBlend.
1324
1325 \value CustomMaterial.NoBlend
1326 \value CustomMaterial.Zero
1327 \value CustomMaterial.One
1328 \value CustomMaterial.SrcColor
1329 \value CustomMaterial.OneMinusSrcColor
1330 \value CustomMaterial.DstColor
1331 \value CustomMaterial.OneMinusDstColor
1332 \value CustomMaterial.SrcAlpha
1333 \value CustomMaterial.OneMinusSrcAlpha
1334 \value CustomMaterial.DstAlpha
1335 \value CustomMaterial.OneMinusDstAlpha
1336 \value CustomMaterial.ConstantColor
1337 \value CustomMaterial.OneMinusConstantColor
1338 \value CustomMaterial.ConstantAlpha
1339 \value CustomMaterial.OneMinusConstantAlpha
1340 \value CustomMaterial.SrcAlphaSaturate
1341
1342 \note Both \l sourceBlend and \l destinationBlend needs to be set to a non-default
1343 value before blending is enabled.
1344
1345 \sa sourceBlend
1346*/
1347
1348/*!
1349 \qmlproperty real CustomMaterial::lineWidth
1350
1351 This property determines the width of the lines rendered, when the geometry
1352 is using a primitive type of lines or line strips. The default value is
1353 1.0. This property is not relevant when rendering other types of geometry,
1354 such as, triangle meshes.
1355
1356 \warning Line widths other than 1 may not be suported at run time,
1357 depending on the underlying graphics API. When that is the case, the
1358 request to change the width is ignored. For example, none of the following
1359 can be expected to support wide lines: Direct3D, Metal, OpenGL with core
1360 profile contexts.
1361
1362 \note Unlike the line width, the value of which is part of the graphics
1363 pipeline object, the point size for geometries with a topology of points is
1364 controlled by the vertex shader (when supported), and has therefore no
1365 corresponding QML property.
1366*/
1367
1368/*!
1369 \qmlproperty enumeration CustomMaterial::sourceAlphaBlend
1370 \since 6.7
1371
1372 Specifies the source alpha blend factor. The default value is \c
1373 CustomMaterial.NoBlend. This value is only actively used if \l sourceBlend and
1374 \l destinationBlend is set to a non-default value.
1375
1376 \value CustomMaterial.NoBlend
1377 \value CustomMaterial.Zero
1378 \value CustomMaterial.One
1379 \value CustomMaterial.SrcColor
1380 \value CustomMaterial.OneMinusSrcColor
1381 \value CustomMaterial.DstColor
1382 \value CustomMaterial.OneMinusDstColor
1383 \value CustomMaterial.SrcAlpha
1384 \value CustomMaterial.OneMinusSrcAlpha
1385 \value CustomMaterial.DstAlpha
1386 \value CustomMaterial.OneMinusDstAlpha
1387 \value CustomMaterial.ConstantColor
1388 \value CustomMaterial.OneMinusConstantColor
1389 \value CustomMaterial.ConstantAlpha
1390 \value CustomMaterial.OneMinusConstantAlpha
1391 \value CustomMaterial.SrcAlphaSaturate
1392
1393 \note For backwards compatibility purposes, when left to its default value,
1394 will be assigned the same value as \l sourceBlend when \l sourceBlend and
1395 \l destinationBlend is set to non-default values.
1396
1397 \sa sourceBlend
1398*/
1399
1400/*!
1401 \qmlproperty enumeration CustomMaterial::destinationAlphaBlend
1402 \since 6.7
1403
1404 Specifies the destination alpha blend factor. The default value is \c
1405 CustomMaterial.NoBlend. This value is only actively used if \l sourceBlend and
1406 \l destinationBlend is set to a non-default value.
1407
1408 \value CustomMaterial.NoBlend
1409 \value CustomMaterial.Zero
1410 \value CustomMaterial.One
1411 \value CustomMaterial.SrcColor
1412 \value CustomMaterial.OneMinusSrcColor
1413 \value CustomMaterial.DstColor
1414 \value CustomMaterial.OneMinusDstColor
1415 \value CustomMaterial.SrcAlpha
1416 \value CustomMaterial.OneMinusSrcAlpha
1417 \value CustomMaterial.DstAlpha
1418 \value CustomMaterial.OneMinusDstAlpha
1419 \value CustomMaterial.ConstantColor
1420 \value CustomMaterial.OneMinusConstantColor
1421 \value CustomMaterial.ConstantAlpha
1422 \value CustomMaterial.OneMinusConstantAlpha
1423 \value CustomMaterial.SrcAlphaSaturate
1424
1425 \note For backwards compatibility purposes, when left to its default value,
1426 will be assigned the same value as \l destinationBlend when \l sourceBlend and
1427 \l destinationBlend is set to non-default values.
1428
1429 \sa destinationBlend
1430*/
1431
1432static inline QRhiGraphicsPipeline::BlendFactor toRhiBlendFactor(QQuick3DCustomMaterial::BlendMode mode)
1433{
1434 switch (mode) {
1435 case QQuick3DCustomMaterial::BlendMode::Zero:
1436 return QRhiGraphicsPipeline::Zero;
1437 case QQuick3DCustomMaterial::BlendMode::One:
1438 return QRhiGraphicsPipeline::One;
1439 case QQuick3DCustomMaterial::BlendMode::SrcColor:
1440 return QRhiGraphicsPipeline::SrcColor;
1441 case QQuick3DCustomMaterial::BlendMode::OneMinusSrcColor:
1442 return QRhiGraphicsPipeline::OneMinusSrcColor;
1443 case QQuick3DCustomMaterial::BlendMode::DstColor:
1444 return QRhiGraphicsPipeline::DstColor;
1445 case QQuick3DCustomMaterial::BlendMode::OneMinusDstColor:
1446 return QRhiGraphicsPipeline::OneMinusDstColor;
1447 case QQuick3DCustomMaterial::BlendMode::SrcAlpha:
1448 return QRhiGraphicsPipeline::SrcAlpha;
1449 case QQuick3DCustomMaterial::BlendMode::OneMinusSrcAlpha:
1450 return QRhiGraphicsPipeline::OneMinusSrcAlpha;
1451 case QQuick3DCustomMaterial::BlendMode::DstAlpha:
1452 return QRhiGraphicsPipeline::DstAlpha;
1453 case QQuick3DCustomMaterial::BlendMode::OneMinusDstAlpha:
1454 return QRhiGraphicsPipeline::OneMinusDstAlpha;
1455 case QQuick3DCustomMaterial::BlendMode::ConstantColor:
1456 return QRhiGraphicsPipeline::ConstantColor;
1457 case QQuick3DCustomMaterial::BlendMode::OneMinusConstantColor:
1458 return QRhiGraphicsPipeline::OneMinusConstantColor;
1459 case QQuick3DCustomMaterial::BlendMode::ConstantAlpha:
1460 return QRhiGraphicsPipeline::ConstantAlpha;
1461 case QQuick3DCustomMaterial::BlendMode::OneMinusConstantAlpha:
1462 return QRhiGraphicsPipeline::OneMinusConstantAlpha;
1463 case QQuick3DCustomMaterial::BlendMode::SrcAlphaSaturate:
1464 return QRhiGraphicsPipeline::SrcAlphaSaturate;
1465 default:
1466 return QRhiGraphicsPipeline::One;
1467 }
1468}
1469
1470QQuick3DCustomMaterial::QQuick3DCustomMaterial(QQuick3DObject *parent)
1471 : QQuick3DMaterial(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::CustomMaterial)), parent)
1472{
1473}
1474
1475QQuick3DCustomMaterial::~QQuick3DCustomMaterial() {}
1476
1477QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::srcBlend() const
1478{
1479 return m_srcBlend;
1480}
1481
1482void QQuick3DCustomMaterial::setSrcBlend(BlendMode mode)
1483{
1484 if (m_srcBlend == mode)
1485 return;
1486
1487 m_srcBlend = mode;
1488 update();
1489 emit srcBlendChanged();
1490}
1491
1492QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::dstBlend() const
1493{
1494 return m_dstBlend;
1495}
1496
1497void QQuick3DCustomMaterial::setDstBlend(BlendMode mode)
1498{
1499 if (m_dstBlend == mode)
1500 return;
1501
1502 m_dstBlend = mode;
1503 update();
1504 emit dstBlendChanged();
1505}
1506
1507QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::srcAlphaBlend() const
1508{
1509 return m_srcAlphaBlend;
1510}
1511
1512void QQuick3DCustomMaterial::setSrcAlphaBlend(QQuick3DCustomMaterial::BlendMode mode)
1513{
1514 if (m_srcAlphaBlend == mode)
1515 return;
1516
1517 m_srcAlphaBlend = mode;
1518 update();
1519 emit srcAlphaBlendChanged();
1520}
1521
1522QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::dstAlphaBlend() const
1523{
1524 return m_dstAlphaBlend;
1525}
1526
1527void QQuick3DCustomMaterial::setDstAlphaBlend(QQuick3DCustomMaterial::BlendMode mode)
1528{
1529 if (m_dstAlphaBlend == mode)
1530 return;
1531
1532 m_dstAlphaBlend = mode;
1533 update();
1534 emit dstAlphaBlendChanged();
1535}
1536
1537QQuick3DCustomMaterial::ShadingMode QQuick3DCustomMaterial::shadingMode() const
1538{
1539 return m_shadingMode;
1540}
1541
1542void QQuick3DCustomMaterial::setShadingMode(ShadingMode mode)
1543{
1544 if (m_shadingMode == mode)
1545 return;
1546
1547 m_shadingMode = mode;
1548 markDirty(that&: *this, type: Dirty::ShaderSettingsDirty);
1549 emit shadingModeChanged();
1550}
1551
1552QUrl QQuick3DCustomMaterial::vertexShader() const
1553{
1554 return m_vertexShader;
1555}
1556
1557void QQuick3DCustomMaterial::setVertexShader(const QUrl &url)
1558{
1559 if (m_vertexShader == url)
1560 return;
1561
1562 m_vertexShader = url;
1563 markDirty(that&: *this, type: Dirty::ShaderSettingsDirty);
1564 emit vertexShaderChanged();
1565}
1566
1567QUrl QQuick3DCustomMaterial::fragmentShader() const
1568{
1569 return m_fragmentShader;
1570}
1571
1572void QQuick3DCustomMaterial::setFragmentShader(const QUrl &url)
1573{
1574 if (m_fragmentShader == url)
1575 return;
1576
1577 m_fragmentShader = url;
1578 markDirty(that&: *this, type: Dirty::ShaderSettingsDirty);
1579 emit fragmentShaderChanged();
1580}
1581
1582
1583QString QQuick3DCustomMaterial::vertexShaderCode() const
1584{
1585 return m_vertexShaderCode;
1586}
1587
1588void QQuick3DCustomMaterial::setVertexShaderCode(const QString &code)
1589{
1590 if (m_vertexShaderCode == code)
1591 return;
1592
1593 m_vertexShaderCode = code;
1594 markDirty(that&: *this, type: Dirty::ShaderSettingsDirty);
1595 emit vertexShaderCodeChanged();
1596}
1597
1598QString QQuick3DCustomMaterial::fragmentShaderCode() const
1599{
1600 return m_fragmentShaderCode;
1601}
1602
1603void QQuick3DCustomMaterial::setFragmentShaderCode(const QString &code)
1604{
1605 if (m_fragmentShaderCode == code)
1606 return;
1607
1608 m_fragmentShaderCode = code;
1609 markDirty(that&: *this, type: Dirty::ShaderSettingsDirty);
1610 emit fragmentShaderCodeChanged();
1611}
1612
1613float QQuick3DCustomMaterial::lineWidth() const
1614{
1615 return m_lineWidth;
1616}
1617
1618void QQuick3DCustomMaterial::setLineWidth(float width)
1619{
1620 if (qFuzzyCompare(p1: m_lineWidth, p2: width))
1621 return;
1622 m_lineWidth = width;
1623 update();
1624 emit lineWidthChanged();
1625}
1626
1627void QQuick3DCustomMaterial::markAllDirty()
1628{
1629 m_dirtyAttributes |= Dirty::AllDirty;
1630 QQuick3DMaterial::markAllDirty();
1631}
1632
1633void QQuick3DCustomMaterial::markDirty(QQuick3DCustomMaterial &that, Dirty type)
1634{
1635 if (!(that.m_dirtyAttributes & quint32(type))) {
1636 that.m_dirtyAttributes |= quint32(type);
1637 that.update();
1638 }
1639}
1640
1641bool QQuick3DCustomMaterial::alwaysDirty() const
1642{
1643 return m_alwaysDirty;
1644}
1645
1646void QQuick3DCustomMaterial::setAlwaysDirty(bool alwaysDirty)
1647{
1648 if (m_alwaysDirty == alwaysDirty)
1649 return;
1650
1651 m_alwaysDirty = alwaysDirty;
1652 update();
1653 emit alwaysDirtyChanged();
1654}
1655
1656static void setCustomMaterialFlagsFromShader(QSSGRenderCustomMaterial *material, const QSSGCustomShaderMetaData &meta)
1657{
1658 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesScreenTexture))
1659 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenTexture, on: true);
1660 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesScreenMipTexture))
1661 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenMipTexture, on: true);
1662 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesDepthTexture))
1663 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::DepthTexture, on: true);
1664 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesAoTexture))
1665 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::AoTexture, on: true);
1666 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesProjectionMatrix))
1667 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ProjectionMatrix, on: true);
1668 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesInverseProjectionMatrix))
1669 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::InverseProjectionMatrix, on: true);
1670 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesVarColor))
1671 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::VarColor, on: true);
1672 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesIblOrientation))
1673 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::IblOrientation, on: true);
1674 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesLightmap))
1675 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Lightmap, on: true);
1676 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesSkinning))
1677 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Skinning, on: true);
1678 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesMorphing))
1679 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Morphing, on: true);
1680 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesViewIndex))
1681 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ViewIndex, on: true);
1682 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesClearcoat))
1683 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Clearcoat, on: true);
1684 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesClearcoatFresnelScaleBias))
1685 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ClearcoatFresnelScaleBias, on: true);
1686 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesFresnelScaleBias))
1687 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::FresnelScaleBias, on: true);
1688 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesTransmission)) {
1689 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Transmission, on: true);
1690 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenTexture, on: true);
1691 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenMipTexture, on: true);
1692 }
1693
1694 // vertex only
1695 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::OverridesPosition))
1696 material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::OverridesPosition, on: true);
1697
1698 // fragment only
1699 if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesSharedVars))
1700 material->m_usesSharedVariables = true;
1701}
1702
1703static QByteArray prepareCustomShader(QSSGRenderCustomMaterial *customMaterial,
1704 const QSSGShaderCustomMaterialAdapter::StringPairList &uniforms,
1705 const QByteArray &snippet,
1706 QSSGShaderCache::ShaderType shaderType,
1707 QSSGCustomShaderMetaData &meta,
1708 bool multiViewCompatible)
1709{
1710 if (snippet.isEmpty())
1711 return QByteArray();
1712
1713 QByteArray sourceCode = snippet;
1714 QByteArray buf;
1715 auto result = QSSGShaderCustomMaterialAdapter::prepareCustomShader(dst&: buf,
1716 shaderCode: sourceCode,
1717 type: shaderType,
1718 baseUniforms: uniforms,
1719 baseInputs: {},
1720 baseOutputs: {},
1721 multiViewCompatible);
1722 sourceCode = result.first;
1723 sourceCode.append(a: buf);
1724 meta = result.second;
1725 setCustomMaterialFlagsFromShader(material: customMaterial, meta);
1726 return sourceCode;
1727}
1728
1729QSSGRenderGraphObject *QQuick3DCustomMaterial::updateSpatialNode(QSSGRenderGraphObject *node)
1730{
1731 using namespace QSSGShaderUtils;
1732
1733 const auto &renderContext = QQuick3DObjectPrivate::get(item: this)->sceneManager->wattached->rci();
1734 if (!renderContext) {
1735 qWarning(msg: "QQuick3DCustomMaterial: No render context interface?");
1736 return nullptr;
1737 }
1738
1739 QSSGShaderCustomMaterialAdapter::StringPairList uniforms;
1740 QSSGRenderCustomMaterial *customMaterial = static_cast<QSSGRenderCustomMaterial *>(node);
1741 bool newBackendNode = false;
1742 bool shadersMayChange = false;
1743 if (!customMaterial) {
1744 customMaterial = new QSSGRenderCustomMaterial;
1745 newBackendNode = true;
1746 } else if (m_dirtyAttributes & ShaderSettingsDirty) {
1747 shadersMayChange = true;
1748 }
1749
1750 if (newBackendNode || shadersMayChange) {
1751 markAllDirty();
1752
1753 customMaterial->m_properties.clear();
1754 customMaterial->m_textureProperties.clear();
1755
1756 customMaterial->m_shadingMode = QSSGRenderCustomMaterial::ShadingMode(int(m_shadingMode));
1757
1758 QMetaMethod propertyDirtyMethod;
1759 const int idx = metaObject()->indexOfSlot(slot: "onPropertyDirty()");
1760 if (idx != -1)
1761 propertyDirtyMethod = metaObject()->method(index: idx);
1762
1763 const int propCount = metaObject()->propertyCount();
1764 int propOffset = metaObject()->propertyOffset();
1765
1766 // Custom materials can have multilayered inheritance structure, so find the actual propOffset
1767 const QMetaObject *superClass = metaObject()->superClass();
1768 while (superClass && qstrcmp(str1: superClass->className(), str2: "QQuick3DCustomMaterial") != 0) {
1769 propOffset = superClass->propertyOffset();
1770 superClass = superClass->superClass();
1771 }
1772
1773 using TextureInputProperty = QPair<QQuick3DShaderUtilsTextureInput *, const char *>;
1774 QVector<TextureInputProperty> textureProperties; // We'll deal with these later
1775
1776 for (int i = propOffset; i != propCount; ++i) {
1777 const auto property = metaObject()->property(index: i);
1778 if (Q_UNLIKELY(!property.isValid()))
1779 continue;
1780
1781 const auto name = property.name();
1782 QMetaType propType = property.metaType();
1783 QVariant propValue = property.read(obj: this);
1784 if (propType == QMetaType(QMetaType::QVariant))
1785 propType = propValue.metaType();
1786
1787 if (propType.id() >= QMetaType::User) {
1788 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
1789 if (QQuick3DShaderUtilsTextureInput *texture = property.read(obj: this).value<QQuick3DShaderUtilsTextureInput *>())
1790 textureProperties.push_back(t: {texture, name});
1791 }
1792 } else if (propType == QMetaType(QMetaType::QObjectStar)) {
1793 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(object: propValue.value<QObject *>()))
1794 textureProperties.push_back(t: {texture, name});
1795 } else {
1796 const auto type = uniformType(type: propType);
1797 if (type != QSSGRenderShaderValue::Unknown) {
1798 uniforms.append(t: { uniformTypeName(type: propType), name });
1799 customMaterial->m_properties.push_back(t: { name, propValue, uniformType(type: propType), i});
1800 if (newBackendNode) {
1801 // Track the property changes
1802 if (property.hasNotifySignal() && propertyDirtyMethod.isValid())
1803 connect(sender: this, signal: property.notifySignal(), receiver: this, method: propertyDirtyMethod);
1804 } // else already connected
1805 } else {
1806 // ### figure out how _not_ to warn when there are no dynamic
1807 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
1808 //qWarning("No known uniform conversion found for effect property %s. Skipping", property.name());
1809 }
1810 }
1811 }
1812
1813 const auto processTextureProperty = [&](QQuick3DShaderUtilsTextureInput &texture, const QByteArray &name) {
1814 texture.name = name;
1815
1816 QSSGRenderCustomMaterial::TextureProperty textureData;
1817 textureData.texInput = &texture;
1818 textureData.name = name;
1819 textureData.shaderDataType = QSSGRenderShaderValue::Texture;
1820
1821 if (newBackendNode) {
1822 connect(sender: &texture, signal: &QQuick3DShaderUtilsTextureInput::enabledChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
1823 connect(sender: &texture, signal: &QQuick3DShaderUtilsTextureInput::textureChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
1824 } // else already connected
1825
1826 QQuick3DTexture *tex = texture.texture(); // may be null if the TextureInput has no 'texture' set
1827 if (tex && QQuick3DObjectPrivate::get(item: tex)->type == QQuick3DObjectPrivate::Type::ImageCube)
1828 uniforms.append(t: { QByteArrayLiteral("samplerCube"), textureData.name });
1829 else if (tex && tex->textureData() && tex->textureData()->depth() > 0)
1830 uniforms.append(t: { QByteArrayLiteral("sampler3D"), textureData.name });
1831 else
1832 uniforms.append(t: { QByteArrayLiteral("sampler2D"), textureData.name });
1833
1834 customMaterial->m_textureProperties.push_back(t: textureData);
1835 };
1836
1837 for (const auto &textureProperty : std::as_const(t&: textureProperties))
1838 processTextureProperty(*textureProperty.first, textureProperty.second);
1839
1840 if (customMaterial->incompleteBuildTimeObject || (m_dirtyAttributes & DynamicPropertiesDirty)) { // This object came from the shadergen tool
1841 const auto names = dynamicPropertyNames();
1842 for (const auto &name : names) {
1843 QVariant propValue = property(name: name.constData());
1844 QMetaType propType = propValue.metaType();
1845 if (propType == QMetaType(QMetaType::QVariant))
1846 propType = propValue.metaType();
1847
1848 if (propType.id() >= QMetaType::User) {
1849 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
1850 if (QQuick3DShaderUtilsTextureInput *texture = propValue.value<QQuick3DShaderUtilsTextureInput *>())
1851 textureProperties.push_back(t: {texture, name});
1852 }
1853 } else if (propType.id() == QMetaType::QObjectStar) {
1854 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(object: propValue.value<QObject *>()))
1855 textureProperties.push_back(t: {texture, name});
1856 } else {
1857 const auto type = uniformType(type: propType);
1858 if (type != QSSGRenderShaderValue::Unknown) {
1859 uniforms.append(t: { uniformTypeName(type: propType), name });
1860 customMaterial->m_properties.push_back(t: { name, propValue,
1861 uniformType(type: propType), -1 /* aka. dynamic property */});
1862 // We don't need to track property changes
1863 } else {
1864 // ### figure out how _not_ to warn when there are no dynamic
1865 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
1866 qWarning(msg: "No known uniform conversion found for custom material property %s. Skipping", name.constData());
1867 }
1868 }
1869 }
1870
1871 for (const auto &property : std::as_const(t&: textureProperties))
1872 processTextureProperty(*property.first, property.second);
1873 }
1874
1875 const QQmlContext *context = qmlContext(this);
1876 QByteArray vertex;
1877 QByteArray fragment;
1878 QByteArray vertexProcessed[2];
1879 QSSGCustomShaderMetaData vertexMeta;
1880 QByteArray fragmentProcessed[2];
1881 QSSGCustomShaderMetaData fragmentMeta;
1882 QByteArray shaderPathKey("custom material --");
1883
1884 customMaterial->m_renderFlags = {};
1885
1886 if (!m_vertexShader.isEmpty())
1887 vertex = QSSGShaderUtils::resolveShader(fileUrl: m_vertexShader, context, shaderPathKey);
1888 else if (!m_vertexShaderCode.isEmpty())
1889 vertex = m_vertexShaderCode.toLatin1();
1890
1891 if (!m_fragmentShader.isEmpty())
1892 fragment = QSSGShaderUtils::resolveShader(fileUrl: m_fragmentShader, context, shaderPathKey);
1893 else if (!m_fragmentShaderCode.isEmpty())
1894 fragment = m_fragmentShaderCode.toLatin1();
1895
1896 // Multiview is a problem, because we will get a dedicated snippet after
1897 // preparation (the one that has [qt_viewIndex] added where it matters).
1898 // But at least the view count plays no role here on this level. So one
1899 // normal and one multiview "variant" is good enough.
1900
1901 vertexProcessed[QSSGRenderCustomMaterial::RegularShaderPathKeyIndex] =
1902 prepareCustomShader(customMaterial, uniforms, snippet: vertex, shaderType: QSSGShaderCache::ShaderType::Vertex, meta&: vertexMeta, multiViewCompatible: false);
1903 fragmentProcessed[QSSGRenderCustomMaterial::RegularShaderPathKeyIndex] =
1904 prepareCustomShader(customMaterial, uniforms, snippet: fragment, shaderType: QSSGShaderCache::ShaderType::Fragment, meta&: fragmentMeta, multiViewCompatible: false);
1905
1906 vertexProcessed[QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex] =
1907 prepareCustomShader(customMaterial, uniforms, snippet: vertex, shaderType: QSSGShaderCache::ShaderType::Vertex, meta&: vertexMeta, multiViewCompatible: true);
1908 fragmentProcessed[QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex] =
1909 prepareCustomShader(customMaterial, uniforms, snippet: fragment, shaderType: QSSGShaderCache::ShaderType::Fragment, meta&: fragmentMeta, multiViewCompatible: true);
1910
1911 // At this point we have snippets that look like this:
1912 // - the original code, with VARYING ... lines removed
1913 // - followed by QQ3D_SHADER_META block for uniforms
1914 // - followed by QQ3D_SHADER_META block for inputs/outputs
1915
1916 customMaterial->m_customShaderPresence = {};
1917 for (int i : { QSSGRenderCustomMaterial::RegularShaderPathKeyIndex, QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex }) {
1918 if (vertexProcessed[i].isEmpty() && fragmentProcessed[i].isEmpty())
1919 continue;
1920
1921 const QByteArray key = shaderPathKey + ':' + QCryptographicHash::hash(data: QByteArray(vertexProcessed[i] + fragmentProcessed[i]), method: QCryptographicHash::Algorithm::Sha1).toHex();
1922 // the processed snippet code is different for regular and multiview, so 'key' reflects that already
1923 customMaterial->m_shaderPathKey[i] = key;
1924 if (!vertexProcessed[i].isEmpty()) {
1925 customMaterial->m_customShaderPresence.setFlag(flag: QSSGRenderCustomMaterial::CustomShaderPresenceFlag::Vertex);
1926 renderContext->shaderLibraryManager()->setShaderSource(inShaderPathKey: key, type: QSSGShaderCache::ShaderType::Vertex, inSource: vertexProcessed[i], meta: vertexMeta);
1927 }
1928 if (!fragmentProcessed[i].isEmpty()) {
1929 customMaterial->m_customShaderPresence.setFlag(flag: QSSGRenderCustomMaterial::CustomShaderPresenceFlag::Fragment);
1930 renderContext->shaderLibraryManager()->setShaderSource(inShaderPathKey: key, type: QSSGShaderCache::ShaderType::Fragment, inSource: fragmentProcessed[i], meta: fragmentMeta);
1931 }
1932 }
1933 }
1934
1935 customMaterial->setAlwaysDirty(m_alwaysDirty);
1936 if (m_srcBlend != BlendMode::NoBlend && m_dstBlend != BlendMode::NoBlend) { // both must be set to something other than NoBlend
1937 customMaterial->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Blending, on: true);
1938 customMaterial->m_srcBlend = toRhiBlendFactor(mode: m_srcBlend);
1939 customMaterial->m_dstBlend = toRhiBlendFactor(mode: m_dstBlend);
1940 // alpha blending is only active if rgb blending is
1941 if (m_srcAlphaBlend != BlendMode::NoBlend && m_dstAlphaBlend != BlendMode::NoBlend) {
1942 customMaterial->m_srcAlphaBlend = toRhiBlendFactor(mode: m_srcAlphaBlend);
1943 customMaterial->m_dstAlphaBlend = toRhiBlendFactor(mode: m_dstAlphaBlend);
1944 } else {
1945 customMaterial->m_srcAlphaBlend = customMaterial->m_srcBlend;
1946 customMaterial->m_dstAlphaBlend = customMaterial->m_dstBlend;
1947 }
1948 } else {
1949 customMaterial->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Blending, on: false);
1950 }
1951 customMaterial->m_lineWidth = m_lineWidth;
1952
1953 QQuick3DMaterial::updateSpatialNode(node: customMaterial);
1954
1955 if (m_dirtyAttributes & Dirty::PropertyDirty) {
1956 for (auto &prop : customMaterial->m_properties) {
1957 auto p = metaObject()->property(index: prop.pid);
1958 if (Q_LIKELY(p.isValid()))
1959 prop.value = p.read(obj: this);
1960 }
1961 }
1962
1963 if (m_dirtyAttributes & Dirty::TextureDirty) {
1964 for (QSSGRenderCustomMaterial::TextureProperty &prop : customMaterial->m_textureProperties) {
1965 QQuick3DTexture *tex = prop.texInput->texture();
1966 if (tex) {
1967 if (prop.texInput->enabled)
1968 prop.texImage = tex->getRenderImage();
1969 else
1970 prop.texImage = nullptr;
1971 prop.minFilterType = tex->minFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest
1972 : QSSGRenderTextureFilterOp::Linear;
1973 prop.magFilterType = tex->magFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest
1974 : QSSGRenderTextureFilterOp::Linear;
1975 prop.mipFilterType = tex->generateMipmaps() ? (tex->mipFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest
1976 : QSSGRenderTextureFilterOp::Linear)
1977 : QSSGRenderTextureFilterOp::None;
1978 prop.horizontalClampType = tex->horizontalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
1979 : (tex->horizontalTiling() == QQuick3DTexture::ClampToEdge) ? QSSGRenderTextureCoordOp::ClampToEdge
1980 : QSSGRenderTextureCoordOp::MirroredRepeat;
1981 prop.verticalClampType = tex->verticalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
1982 : (tex->verticalTiling() == QQuick3DTexture::ClampToEdge) ? QSSGRenderTextureCoordOp::ClampToEdge
1983 : QSSGRenderTextureCoordOp::MirroredRepeat;
1984 prop.zClampType = tex->depthTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
1985 : (tex->depthTiling() == QQuick3DTexture::ClampToEdge) ? QSSGRenderTextureCoordOp::ClampToEdge
1986 : QSSGRenderTextureCoordOp::MirroredRepeat;
1987 } else {
1988 prop.texImage = nullptr;
1989 }
1990
1991 if (tex != prop.lastConnectedTexture) {
1992 prop.lastConnectedTexture = tex;
1993 disconnect(prop.minFilterChangedConn);
1994 disconnect(prop.magFilterChangedConn);
1995 disconnect(prop.mipFilterChangedConn);
1996 disconnect(prop.horizontalTilingChangedConn);
1997 disconnect(prop.verticalTilingChangedConn);
1998 disconnect(prop.depthTilingChangedConn);
1999 if (tex) {
2000 prop.minFilterChangedConn = connect(sender: tex, signal: &QQuick3DTexture::minFilterChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
2001 prop.magFilterChangedConn = connect(sender: tex, signal: &QQuick3DTexture::magFilterChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
2002 prop.mipFilterChangedConn = connect(sender: tex, signal: &QQuick3DTexture::mipFilterChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
2003 prop.horizontalTilingChangedConn = connect(sender: tex, signal: &QQuick3DTexture::horizontalTilingChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
2004 prop.verticalTilingChangedConn = connect(sender: tex, signal: &QQuick3DTexture::verticalTilingChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
2005 prop.depthTilingChangedConn = connect(sender: tex, signal: &QQuick3DTexture::depthTilingChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty);
2006 }
2007 }
2008 }
2009 }
2010
2011 m_dirtyAttributes = 0;
2012
2013 return customMaterial;
2014}
2015
2016void QQuick3DCustomMaterial::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
2017{
2018 QQuick3DMaterial::itemChange(change, value);
2019 if (change == QQuick3DObject::ItemSceneChange) {
2020 if (auto sceneManager = value.sceneManager) {
2021 for (const auto &it : std::as_const(t&: m_dynamicTextureMaps)) {
2022 if (auto tex = it->texture())
2023 QQuick3DObjectPrivate::refSceneManager(obj: tex, mgr&: *sceneManager);
2024 }
2025 } else {
2026 for (const auto &it : std::as_const(t&: m_dynamicTextureMaps)) {
2027 if (auto tex = it->texture())
2028 QQuick3DObjectPrivate::derefSceneManager(obj: tex);
2029 }
2030 }
2031 }
2032}
2033
2034void QQuick3DCustomMaterial::onPropertyDirty()
2035{
2036 markDirty(that&: *this, type: Dirty::PropertyDirty);
2037 update();
2038}
2039
2040void QQuick3DCustomMaterial::onTextureDirty()
2041{
2042 markDirty(that&: *this, type: Dirty::TextureDirty);
2043 update();
2044}
2045
2046void QQuick3DCustomMaterial::setDynamicTextureMap(QQuick3DShaderUtilsTextureInput *textureMap)
2047{
2048 // There can only be one texture input per property, as the texture input is a combination
2049 // of the texture used and the uniform name!
2050 auto it = m_dynamicTextureMaps.constFind(value: textureMap);
2051
2052 if (it == m_dynamicTextureMaps.constEnd()) {
2053 // Track the object, if it's destroyed we need to remove it from our table.
2054 connect(sender: textureMap, signal: &QQuick3DShaderUtilsTextureInput::destroyed, context: this, slot: [this, textureMap]() {
2055 auto it = m_dynamicTextureMaps.constFind(value: textureMap);
2056 if (it != m_dynamicTextureMaps.constEnd())
2057 m_dynamicTextureMaps.erase(i: it);
2058 });
2059 m_dynamicTextureMaps.insert(value: textureMap);
2060
2061 update();
2062 }
2063}
2064
2065QT_END_NAMESPACE
2066

source code of qtquick3d/src/quick3d/qquick3dcustommaterial.cpp