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