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 | |
14 | QT_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 | |
1432 | static 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 | |
1470 | QQuick3DCustomMaterial::QQuick3DCustomMaterial(QQuick3DObject *parent) |
1471 | : QQuick3DMaterial(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::CustomMaterial)), parent) |
1472 | { |
1473 | } |
1474 | |
1475 | QQuick3DCustomMaterial::~QQuick3DCustomMaterial() {} |
1476 | |
1477 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::srcBlend() const |
1478 | { |
1479 | return m_srcBlend; |
1480 | } |
1481 | |
1482 | void QQuick3DCustomMaterial::setSrcBlend(BlendMode mode) |
1483 | { |
1484 | if (m_srcBlend == mode) |
1485 | return; |
1486 | |
1487 | m_srcBlend = mode; |
1488 | update(); |
1489 | emit srcBlendChanged(); |
1490 | } |
1491 | |
1492 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::dstBlend() const |
1493 | { |
1494 | return m_dstBlend; |
1495 | } |
1496 | |
1497 | void QQuick3DCustomMaterial::setDstBlend(BlendMode mode) |
1498 | { |
1499 | if (m_dstBlend == mode) |
1500 | return; |
1501 | |
1502 | m_dstBlend = mode; |
1503 | update(); |
1504 | emit dstBlendChanged(); |
1505 | } |
1506 | |
1507 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::srcAlphaBlend() const |
1508 | { |
1509 | return m_srcAlphaBlend; |
1510 | } |
1511 | |
1512 | void 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 | |
1522 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::dstAlphaBlend() const |
1523 | { |
1524 | return m_dstAlphaBlend; |
1525 | } |
1526 | |
1527 | void 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 | |
1537 | QQuick3DCustomMaterial::ShadingMode QQuick3DCustomMaterial::shadingMode() const |
1538 | { |
1539 | return m_shadingMode; |
1540 | } |
1541 | |
1542 | void 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 | |
1552 | QUrl QQuick3DCustomMaterial::vertexShader() const |
1553 | { |
1554 | return m_vertexShader; |
1555 | } |
1556 | |
1557 | void 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 | |
1567 | QUrl QQuick3DCustomMaterial::fragmentShader() const |
1568 | { |
1569 | return m_fragmentShader; |
1570 | } |
1571 | |
1572 | void 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 | |
1583 | QString QQuick3DCustomMaterial::vertexShaderCode() const |
1584 | { |
1585 | return m_vertexShaderCode; |
1586 | } |
1587 | |
1588 | void 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 | |
1598 | QString QQuick3DCustomMaterial::fragmentShaderCode() const |
1599 | { |
1600 | return m_fragmentShaderCode; |
1601 | } |
1602 | |
1603 | void 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 | |
1613 | float QQuick3DCustomMaterial::lineWidth() const |
1614 | { |
1615 | return m_lineWidth; |
1616 | } |
1617 | |
1618 | void 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 | |
1627 | void QQuick3DCustomMaterial::markAllDirty() |
1628 | { |
1629 | m_dirtyAttributes |= Dirty::AllDirty; |
1630 | QQuick3DMaterial::markAllDirty(); |
1631 | } |
1632 | |
1633 | void 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 | |
1641 | bool QQuick3DCustomMaterial::alwaysDirty() const |
1642 | { |
1643 | return m_alwaysDirty; |
1644 | } |
1645 | |
1646 | void QQuick3DCustomMaterial::setAlwaysDirty(bool alwaysDirty) |
1647 | { |
1648 | if (m_alwaysDirty == alwaysDirty) |
1649 | return; |
1650 | |
1651 | m_alwaysDirty = alwaysDirty; |
1652 | update(); |
1653 | emit alwaysDirtyChanged(); |
1654 | } |
1655 | |
1656 | static 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 | |
1703 | static 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 | |
1729 | QSSGRenderGraphObject *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 | |
2016 | void 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 | |
2034 | void QQuick3DCustomMaterial::onPropertyDirty() |
2035 | { |
2036 | markDirty(that&: *this, type: Dirty::PropertyDirty); |
2037 | update(); |
2038 | } |
2039 | |
2040 | void QQuick3DCustomMaterial::onTextureDirty() |
2041 | { |
2042 | markDirty(that&: *this, type: Dirty::TextureDirty); |
2043 | update(); |
2044 | } |
2045 | |
2046 | void 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 | |
2065 | QT_END_NAMESPACE |
2066 | |