Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtquick3d-custom.qdoc
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4/*!
5\page qtquick3d-custom.html
6\title Programmable Materials, Effects, Geometry, and Texture data
7\brief Custom materials, effects, geometry and texture data providers in Qt Quick 3D
8
9While the built-in materials of Qt Quick 3D, \l DefaultMaterial and \l PrincipledMaterial,
10allow a wide degree of customization via their properties, they do not provide
11programmability on the vertex and fragment shader level. To allow that, the \l
12CustomMaterial type is provided.
13
14\table
15\header
16\li A model with PrincipledMaterial
17\li With a CustomMaterial transforming the vertices
18\row
19\li \image quick3d-custom-mat1.jpg {Teapot rendered with standard material}
20\li \image quick3d-custom-mat2.jpg
21 {Teapot with vertices transformed by custom material}
22\endtable
23
24Post-processing effects, where one or more passes of processing on the color buffer are
25performed, optionally taking the depth buffer into account, before the View3D's output is
26passed on to Qt Quick, also exist in two varieties:
27\list
28\li built-in post-processing steps that can be configured via \l ExtendedSceneEnvironment, such as
29glow/bloom, depth of field, vignette, lens flare,
30\li \c custom effects implemented by the application in form of fragment shader code and a
31specification of the processing passes in an \l Effect object.
32\endlist
33
34In practice there is a third category of post-processing effects: 2D effects
35implemented via Qt Quick, operating on the output of the \l View3D item without
36any involvement from the 3D renderer. For example, to apply a blur to a \l
37View3D item, the simplest approach is to use Qt Quick's existing facilities,
38such as \l MultiEffect. The 3D post-processing system becomes beneficial for
39complex effects that involve 3D scene concepts such as the depth buffer or the
40screen texture, or need to deal with HDR tonemapping or need multiple passes
41with intermediate buffers, etc. Simple 2D effects that do not require any
42insight into the 3D scene and renderer can always be implemented with \l
43ShaderEffect or \l MultiEffect instead.
44
45\table
46\header
47\li Scene without effect
48\li The same scene with a custom post-processing effect applied
49\row
50\li \image quick3d-custom-effect1.jpg
51 {Scene with sphere, cone, and cube without post-processing effect}
52\li \image quick3d-custom-effect2.jpg
53 {Scene with warped geometry from custom post-processing effect}
54\endtable
55
56In addition to programmable materials and post-processing, there are two types of data that is
57normally provided in form of files (\c{.mesh} files or images such as \c{.png}):
58
59\list
60
61\li vertex data, including the geometry for the mesh to be rendered, texture coordinates,
62normals, colors, and other data,
63
64\li the content for textures that are then used as texture maps for the rendered
65objects, or used with skybox or image based lighting.
66
67\endlist
68
69If they so wish, applications can provide such data from C++ in form of a QByteArray. Such
70data can also be changed over time, allowing to procedurally generate and later alter the
71data for a \l Model or \l Texture.
72
73\table
74\header
75\li A grid, rendered by specifying vertex data dynamically from C++
76\li A cube textured with image data generated from C++
77\row
78\li \image quick3d-custom-geom.jpg {Grid generated from custom geometry data}
79\li \image quick3d-custom-tex.jpg
80 {Cube with procedurally generated gradient texture}
81\endtable
82
83These four approaches to customizing and making materials, effects, geometry, and textures
84dynamic enable the programmability of shading and procedural generation of the data the
85shaders get as their input. The following sections provide an overview of these
86features. The full reference is available in the documentation pages for the respective
87types:
88
89\note For complete control over the entire rendering pipeline, including multiple
90render passes and custom render targets, see \l{User-Defined Render Passes in Qt Quick 3D}.
91User-defined render passes complement the features described on this page by allowing you
92to define the entire rendering pipeline architecture, while CustomMaterial and Effect focus
93on customizing individual materials and post-processing steps within the pipeline.
94
95\table
96\header
97\li Feature
98\li Reference Documentation
99\li Relevant Examples
100\row
101\li Custom materials
102\li \l CustomMaterial
103\li \l {Qt Quick 3D - Custom Shaders Example}, \l {Qt Quick 3D - Custom Materials
104Example}
105\row
106\li Custom post-processing effects
107\li \l Effect
108\li \l {Qt Quick 3D - Custom Effect Example}
109\row
110\li Custom geometry
111\li \l QQuick3DGeometry, \l{Model::geometry}
112\li \l {Qt Quick 3D - Custom Geometry Example}
113\row
114\li Custom texture data
115\li \l QQuick3DTextureData, \l{Texture::textureData}
116\li \l {Qt Quick 3D - Procedural Texture Example}
117\endtable
118
119\section1 Programmability for Materials
120
121Let's have a scene with a cube, and start with a default \l PrincipledMaterial and
122\l CustomMaterial:
123
124\table
125\header
126\li PrincipledMaterial
127\li CustomMaterial
128\row
129\li
130 \qml
131 import QtQuick
132 import QtQuick3D
133 Item {
134 View3D {
135 anchors.fill: parent
136 environment: SceneEnvironment {
137 backgroundMode: SceneEnvironment.Color
138 clearColor: "black"
139 }
140 PerspectiveCamera { z: 600 }
141 DirectionalLight { }
142 Model {
143 source: "#Cube"
144 scale: Qt.vector3d(2, 2, 2)
145 eulerRotation.x: 30
146 materials: PrincipledMaterial { }
147 }
148 }
149 }
150 \endqml
151\li
152 \qml
153 import QtQuick
154 import QtQuick3D
155 Item {
156 View3D {
157 anchors.fill: parent
158 environment: SceneEnvironment {
159 backgroundMode: SceneEnvironment.Color
160 clearColor: "black"
161 }
162 PerspectiveCamera { z: 600 }
163 DirectionalLight { }
164 Model {
165 source: "#Cube"
166 scale: Qt.vector3d(2, 2, 2)
167 eulerRotation.x: 30
168 materials: CustomMaterial { }
169 }
170 }
171 }
172 \endqml
173\endtable
174
175These both lead to the exact same result, because a \l CustomMaterial is effectively a \l
176PrincipledMaterial, when no vertex or fragment shader code is added to it.
177
178\image quick3d-custom-cube1.jpg {White cube with default material}
179
180\note Properties, such as, \l{PrincipledMaterial::baseColor}{baseColor},
181\l{PrincipledMaterial::metalness}{metalness},
182\l{PrincipledMaterial::baseColorMap}{baseColorMap}, and many others, have no equivalent
183properties in the \l CustomMaterial QML type. This is by design: customizing the material
184is done via shader code, not by merely providing a few fixed values.
185
186\section2 Our first vertex shader
187
188Let's add a custom vertex shader snippet. This is done by referencing a file in the
189\l{CustomMaterial::vertexShader}{vertexShader} property. The approach will be the same for
190fragment shaders. These references work like \l{Image::source}{Image.source} or
191\l{ShaderEffect::vertexShader}{ShaderEffect.vertexShader}: they are local or \c qrc URLs,
192and a relative path is treated relative to the \c{.qml} file's location. The common
193approach is therefore to place the \c{.vert} and \c{.frag} files into the Qt resource
194system (\c qt_add_resources when using CMake) and reference them using a relative path.
195
196In Qt 6.0 inline shader strings are no longer supported, neither in Qt Quick nor in Qt
197Quick 3D. (make note of the fact that these properties are URLs, not strings) However, due
198to their intrinsically dynamic nature, custom materials and post-processing effects in Qt
199Quick 3D still provide shader snippets in source form in the referenced files. This is a
200difference to \l ShaderEffect where the shaders are complete on their own, with no further
201amending by the engine, and so are expected to be provided as pre-conditioned \c{.qsb}
202shader packs.
203
204\note In Qt Quick 3D URLs can only refer to local resources. Schemes for remote content
205are not supported.
206
207\note The shading language used is Vulkan-compatible GLSL. The \c{.vert} and \c{.frag}
208files are not complete shaders on their own, hence being often called \c snippets. That is
209why there are no uniform blocks, input and output variables, or sampler uniforms provided
210directly by these snippets. Rather, the Qt Quick 3D engine will amend them as appropriate.
211
212\table
213\header
214\li Change in main.qml, material.vert
215\li Result
216\row
217 \li \qml
218 materials: CustomMaterial {
219 vertexShader: "material.vert"
220 }
221 \endqml
222 \badcode
223 void MAIN()
224 {
225 }
226 \endcode
227 \li \image quick3d-custom-cube1-small.jpg {White cube with default material}
228\endtable
229
230A custom vertex or fragment shader snippet is expected to provide one or more functions
231with pre-defined names, such as \c MAIN, \c DIRECTIONAL_LIGHT, \c POINT_LIGHT, \c
232SPOT_LIGHT, \c AMBIENT_LIGHT, \c SPECULAR_LIGHT. For now let's focus on \c MAIN.
233
234As shown here, the end result with an empty MAIN() is exactly the same as before.
235
236Before making it more interesting, let's look at an overview of the most commonly used
237special keywords in custom vertex shader snippets. This is not the full list. For a full
238reference, check the \l CustomMaterial page.
239
240\table
241\header
242\li Keyword
243\li Type
244\li Description
245\row
246\li MAIN
247\li
248\li void MAIN() is the entry point. This function must always be present in a custom
249vertex shader snippet, there is no point in providing one otherwise.
250\row
251\li VERTEX
252\li vec3
253\li The vertex position the shader receives as input. A common use case for vertex shaders
254in custom materials is to change (displace) the x, y, or z values of this vector, by simply
255assigning a value to the whole vector, or some of its components.
256\row
257\li NORMAL
258\li vec3
259\li The vertex normal from the input mesh data, or all zeroes if there were no normals provided.
260As with VERTEX, the shader is free to alter the value as it sees fit. The altered value is then
261used by the rest of the pipeline, including the lighting calculations in the fragment stage.
262\row
263\li UV0
264\li vec2
265\li The first set of texture coordinates from the input mesh data, or all zeroes if there
266were no UV values provided. As with VERTEX and NORMAL, the value can altered.
267\row
268\li MODELVIEWPROJECTION_MATRIX
269\li mat4
270\li The model-view-projection matrix. To unify the behavior regardless of which graphics API
271rendering happens with, all vertex data and transformation matrices follow OpenGL conventions
272on this level. (Y axis pointing up, OpenGL-compatible projection matrix) Read only.
273\row
274\li MODEL_MATRIX
275\li mat4
276\li The model (world) matrix. Read only.
277\row
278\li NORMAL_MATRIX
279\li mat3
280\li The transposed inverse of the top-left 3x3 slice of the model matrix. Read only.
281\row
282\li CAMERA_POSITION
283\li vec3
284\li The camera position in world space. In the examples on this page this is \c{(0, 0, 600)}. Read only.
285\row
286\li CAMERA_DIRECTION
287\li vec3
288\li The camera direction vector. In the examples on this page this is \c{(0, 0, -1)}. Read only.
289\row
290\li CAMERA_PROPERTIES
291\li vec2
292\li The near and far clip values of the camera. In the examples on this page this is \c{(10, 10000)}. Read only.
293\row
294\li POINT_SIZE
295\li float
296\li Relevant only when rendering with a topology of points, for example because the
297\l{QQuick3DGeometry}{custom geometry} provides such a geometry for the mesh. Writing to
298this value is equivalent to setting \l{PrincipledMaterial::pointSize}{pointSize on a
299PrincipledMaterial}.
300\row
301\li POSITION
302\li vec4
303\li Like \c gl_Position. When not present, a default assignment statement is generated
304automatically using \c MODELVIEWPROJECTION_MATRIX and \c VERTEX. This is why an empty
305MAIN() is functional, and in most cases there will be no need to assign a custom value to
306it.
307\endtable
308
309Let's make a custom material that displaces the vertices according to some pattern. To
310make it more interesting, have some animated QML properties, the values of which end up
311being exposed as uniforms in the shader code. (to be precise, most properties are going to
312be mapped to members in a uniform block, backed by a uniform buffer at run time, but Qt
313Quick 3D conveniently makes such details transparent to the custom material author)
314
315\table
316\header
317\li Change in main.qml, material.vert
318\li Result
319\row
320 \li \qml
321 materials: CustomMaterial {
322 vertexShader: "material.vert"
323 property real uAmplitude: 0
324 NumberAnimation on uAmplitude {
325 from: 0; to: 100; duration: 5000; loops: -1
326 }
327 property real uTime: 0
328 NumberAnimation on uTime {
329 from: 0; to: 100; duration: 10000; loops: -1
330 }
331 }
332 \endqml
333 \badcode
334 void MAIN()
335 {
336 VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude;
337 }
338 \endcode
339 \li \image quick3d-custom-cube2-anim.gif
340 {Cube with ground plane showing vertex displacement animation}
341\endtable
342
343\section2 Uniforms from QML properties
344
345Custom properties in the CustomMaterial object get mapped to uniforms. In the above
346example this includes \c uAmplitude and \c uTime. Any time the values change, the updated
347value will become visible in the shader. This concept may already be familiar from \l
348ShaderEffect.
349
350The name of the QML property and the GLSL variable must match. There is no separate
351declaration in the shader code for the individual uniforms. Rather, the QML property name
352can be used as-is. This is why the example above can just reference \c uTime and \c
353uAmplitude in the vertex shader snippet without any previous declaration for them.
354
355The following table lists how the types are mapped:
356
357\table
358\header
359\li QML Type
360\li Shader Type
361\li Notes
362\row
363\li real, int, bool
364\li float, int, bool
365\li
366\row
367\li color
368\li vec4
369\li sRGB to linear conversion is performed implicitly
370\row
371\li vector2d
372\li vec2
373\li
374\row
375\li vector3d
376\li vec3
377\li
378\row
379\li vector4d
380\li vec4
381\li
382\row
383\li matrix4x4
384\li mat4
385\li
386\row
387\li quaternion
388\li vec4
389\li scalar value is \c w
390\row
391\li rect
392\li vec4
393\li
394\row
395\li point, size
396\li vec2
397\li
398\row
399\li TextureInput
400\li sampler2D
401\li
402\endtable
403
404\section2 Improving the example
405
406Before moving further, let's make the example somewhat better looking. By adding a rotated
407rectangle mesh and making the \l DirectionalLight cast shadows, we can verify that the
408alteration to the cube's vertices is correctly reflected in all rendering passes,
409including shadow maps. To get a visible shadow, the light is now placed a bit higher on
410the Y axis, and a rotation is applied to have it pointing partly downwards. (this being a
411\c directional light, the rotation matters)
412
413\table
414\header
415\li main.qml, material.vert
416\li Result
417\row \li \qml
418import QtQuick
419import QtQuick3D
420Item {
421 View3D {
422 anchors.fill: parent
423 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
424 PerspectiveCamera { z: 600 }
425 DirectionalLight {
426 y: 200
427 eulerRotation.x: -45
428 castsShadow: true
429 }
430 Model {
431 source: "#Rectangle"
432 y: -250
433 scale: Qt.vector3d(5, 5, 5)
434 eulerRotation.x: -45
435 materials: PrincipledMaterial { baseColor: "lightBlue" }
436 }
437 Model {
438 source: "#Cube"
439 scale: Qt.vector3d(2, 2, 2)
440 eulerRotation.x: 30
441 materials: CustomMaterial {
442 vertexShader: "material.vert"
443 property real uAmplitude: 0
444 NumberAnimation on uAmplitude {
445 from: 0; to: 100; duration: 5000; loops: -1
446 }
447 property real uTime: 0
448 NumberAnimation on uTime {
449 from: 0; to: 100; duration: 10000; loops: -1
450 }
451 }
452 }
453 }
454}
455\endqml
456\badcode
457void MAIN()
458{
459 VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude;
460}
461\endcode
462\li \image quick3d-custom-cube3-anim.gif
463 {Cube with ground plane showing vertex displacement animation}
464\endtable
465
466\section2 Adding a fragment shader
467
468Many custom materials will want to have a fragment shader as well. In fact, many will want
469only a fragment shader. If there is no extra data to be passed from the vertex to fragment
470stage, and the default vertex transformation is sufficient, setting the \c vertexShader
471property can be left out from the \l CustomMaterial.
472
473\table
474\header
475\li Change in main.qml, material.frag
476\li Result
477\row \li \qml
478materials: CustomMaterial {
479 fragmentShader: "material.frag"
480}
481\endqml
482\badcode
483void MAIN()
484{
485}
486\endcode
487\li \image quick3d-custom-cube4.jpg {White cube with empty fragment shader}
488\endtable
489
490Our first fragment shader contains an empty MAIN() function. This is no different than not
491specifying a fragment shader snippet at all: what we get looks like what we get with a
492default PrincipledMaterial.
493
494Let's look at some of the commonly used keywords in fragment shaders. This is not the full
495list, refer to the \l CustomMaterial documentation for a complete reference. Many of these
496are read-write, meaning they have a default value, but the shader can, and often will want
497to, assign a different value to them.
498
499As the names suggest, many of these map to similarly named \l PrincipledMaterial
500properties, with the same meaning and semantics, following the
501\l{https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material}{metallic-roughness
502material model}. It is up the custom material implementation to decide how these values
503are calculated: for example, a value for BASE_COLOR can be hard coded in the shader, can
504be based on sampling a texture, or can be calculated based on QML properties exposed as
505uniforms or on interpolated data passed along from the vertex shader.
506
507\table
508\header
509\li Keyword
510\li Type
511\li Description
512\row
513\li BASE_COLOR
514\li vec4
515\li The base color and alpha value. Corresponds to \l{PrincipledMaterial::baseColor}. The
516final alpha value of the fragment is the model opacity multiplied by the base color
517alpha. The default value is \c{(1.0, 1.0, 1.0, 1.0)}.
518\row
519\li EMISSIVE_COLOR
520\li vec3
521\li The color of self-illumination. Corresponds to
522\l{PrincipledMaterial::emissiveFactor}. The default value is \c{(0.0, 0.0, 0.0)}.
523\row
524\li METALNESS
525\li float
526\li \l{PrincipledMaterial::metalness}{Metalness} value in range 0-1. Default to 0, which
527means the material is dielectric (non-metallic).
528\row
529\li ROUGHNESS
530\li float
531\li \l{PrincipledMaterial::roughness}{Roughness} value in range 0-1. The default value is
5320. Larger values soften specular highlights and blur reflections.
533\row
534\li SPECULAR_AMOUNT
535\li float
536\li \l{PrincipledMaterial::specularAmount}{The strength of specularity} in range 0-1. The
537default value is \c 0.5. For metallic objects with \c metalness set to \c 1 this value
538will have no effect. When both \c SPECULAR_AMOUNT and \c METALNESS have values larger than
5390 but smaller than 1, the result is a blend between the two material models.
540\row
541\li NORMAL
542\li vec3
543\li The interpolated normal in world space, adjusted for double-sidedness when face culling is disabled. Read only.
544\row
545\li UV0
546\li vec2
547\li The interpolated texture coordinates. Read only.
548\row
549\li VAR_WORLD_POSITION
550\li vec3
551\li Interpolated vertex position in world space. Read only.
552\endtable
553
554Let's make the cube's base color red:
555
556\table
557\header
558\li Change in main.qml, material.frag
559\li Result
560\row \li \qml
561materials: CustomMaterial {
562 fragmentShader: "material.frag"
563}
564\endqml
565\badcode
566void MAIN()
567{
568 BASE_COLOR = vec4(1.0, 0.0, 0.0, 1.0);
569}
570\endcode
571\li \image quick3d-custom-cube5.jpg {Red cube with custom base color}
572\endtable
573
574Now strengthen the level of self-illumination a bit:
575
576\table
577\header
578\li Change in main.qml, material.frag
579\li Result
580\row \li \qml
581materials: CustomMaterial {
582 fragmentShader: "material.frag"
583}
584\endqml
585\badcode
586void MAIN()
587{
588 BASE_COLOR = vec4(1.0, 0.0, 0.0, 1.0);
589 EMISSIVE_COLOR = vec3(0.4);
590}
591\endcode
592\li \image quick3d-custom-cube6.jpg {Bright red cube with emissive color}
593\endtable
594
595Instead of having values hardcoded in the shader, we could also use QML properties exposed
596as uniforms, even animated ones:
597
598\table
599\header
600\li Change in main.qml, material.frag
601\li Result
602\row \li \qml
603materials: CustomMaterial {
604 fragmentShader: "material.frag"
605 property color baseColor: "black"
606 ColorAnimation on baseColor {
607 from: "black"; to: "purple"; duration: 5000; loops: -1
608 }
609}
610\endqml
611\badcode
612void MAIN()
613{
614 BASE_COLOR = vec4(baseColor.rgb, 1.0);
615 EMISSIVE_COLOR = vec3(0.4);
616}
617\endcode
618\li \image quick3d-custom-cube7-anim.gif
619 {Cube with animated color transition from black to purple}
620\endtable
621
622Let's do something less trivial, something that is not implementable with a
623PrincipledMaterial and its standard, built-in properties. The following material
624visualizes the texture UV coordinates of the cube mesh. U runs 0 to 1, so from black to
625red, while V is also 0 to 1, black to green.
626
627\table
628\header
629\li Change in main.qml, material.frag
630\li Result
631\row \li \qml
632materials: CustomMaterial {
633 fragmentShader: "material.frag"
634}
635\endqml
636\badcode
637void MAIN()
638{
639 BASE_COLOR = vec4(UV0, 0.0, 1.0);
640}
641\endcode
642\li \image quick3d-custom-cube8.jpg
643 {Cube showing UV coordinates as red and green color gradient}
644\endtable
645
646While we are at it, why not visualize normals as well, this time on a sphere. Like with
647UVs, if a custom vertex shader snippet were to alter the value of NORMAL, the interpolated
648per-fragment value in the fragment shader, also exposed under the name NORMAL, would
649reflect those adjustments.
650
651\table
652\header
653\li Change in main.qml, material.frag
654\li Result
655\row \li \qml
656Model {
657 source: "#Sphere"
658 scale: Qt.vector3d(2, 2, 2)
659 materials: CustomMaterial {
660 fragmentShader: "material.frag"
661 }
662}
663\endqml
664\badcode
665void MAIN()
666{
667 BASE_COLOR = vec4(NORMAL, 1.0);
668}
669\endcode
670\li \image quick3d-custom-cube9.jpg
671 {Sphere visualizing surface normals as RGB colors}
672\endtable
673
674\section2 Colors
675
676Let's switch over to a teapot model for a moment, make the material a blend of metallic
677and dielectric, and try to set a green base color for it. The \c green QColor value maps
678to \c{(0, 128, 0)}, based on which our first attempt could be:
679
680\table
681\header
682\li main.qml, material.frag
683\row \li \qml
684import QtQuick
685import QtQuick3D
686Item {
687 View3D {
688 anchors.fill: parent
689 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
690 PerspectiveCamera { z: 600 }
691 DirectionalLight { }
692 Model {
693 source: "teapot.mesh"
694 scale: Qt.vector3d(60, 60, 60)
695 eulerRotation.x: 30
696 materials: CustomMaterial {
697 fragmentShader: "material.frag"
698 }
699 }
700 }
701}
702\endqml
703\badcode
704void MAIN()
705{
706 BASE_COLOR = vec4(0.0, 0.5, 0.0, 1.0);
707 METALNESS = 0.6;
708 SPECULAR_AMOUNT = 0.4;
709 ROUGHNESS = 0.4;
710}
711\endcode
712\endtable
713
714\image quick3d-custom-color1.jpg {Green teapot with direct RGB color values}
715
716This does not look entirely right. Compare with the second approach:
717
718\table
719\header
720\li Change in main.qml, material.frag
721\li Result
722\row \li \qml
723materials: CustomMaterial {
724 fragmentShader: "material.frag"
725 property color uColor: "green"
726}
727\endqml
728\badcode
729void MAIN()
730{
731 BASE_COLOR = vec4(uColor.rgb, 1.0);
732 METALNESS = 0.6;
733 SPECULAR_AMOUNT = 0.4;
734 ROUGHNESS = 0.4;
735}
736\endcode
737\li \image quick3d-custom-color2.jpg
738 {Green teapot with QML color property and sRGB conversion}
739\endtable
740
741Switching to a PrincipledMaterial, we can confirm that setting the
742\l{PrincipledMaterial::baseColor} to "green" and following the metalness and other
743properties, the result is identical to our second approach:
744
745\table
746\header
747\li Change in main.qml
748\li Result
749\row \li \qml
750materials: PrincipledMaterial {
751 baseColor: "green"
752 metalness: 0.6
753 specularAmount: 0.4
754 roughness: 0.4
755}
756\endqml
757\li \image quick3d-custom-color3.jpg
758 {Green teapot with standard material showing correct color}
759\endtable
760
761If the type of the \c uColor property was changed to \c vector4d, or any type other than
762\c color, the results would suddenly change and become identical to our first approach.
763
764Why is this?
765
766The answer lies in the sRGB to linear conversion that is performed implicitly for color
767properties of DefaultMaterial, PrincipledMaterial, and also for custom properties with a
768\c color type in a CustomMaterial. Such conversion is not performed for any other value,
769so if the shader hardcodes a color value, or bases it on a QML property with a type
770different from \c color, it will be up to the shader to perform linearization in case the
771source value was in sRGB color space. Converting to linear is important since Qt Quick 3D
772performs \l{SceneEnvironment::tonemapMode}{tonemapping} on the results of fragment
773shading, and that process assumes values in the sRGB space as its input.
774
775The built-in QColor constants, such as, \c{"green"}, are all given in sRGB
776space. Therefore, just assigning \c{vec4(0.0, 0.5, 0.0, 1.0)} to BASE_COLOR in the first
777attempt is insufficient if we wanted a result that matches an RGB value \c{(0, 128, 0)} in
778the sRGB space. See the \c BASE_COLOR documentation in \l CustomMaterial for a formula for
779linearizing such color values. The same applies to color values retrieved by sampling
780textures: if the source image data is not in the sRGB color space, a conversion is needed
781(unless \l{SceneEnvironment::tonemapMode}{tonemapping} is disabled).
782
783\section2 Blending
784
785Just writing a value less than \c 1.0 to \c{BASE_COLOR.a} is not sufficient if the
786expectation is to get alpha blending. Such materials will very often change the values of
787\l{CustomMaterial::sourceBlend}{sourceBlend} and
788\l{CustomMaterial::destinationBlend}{destinationBlend} properties to get the desired
789results.
790
791Also keep in mind that the combined alpha value is the \l{Node::opacity}{Node opacity}
792multiplied by the material alpha.
793
794To visualize, let's use a shader that assigns red with alpha \c 0.5 to \c BASE_COLOR:
795
796\table
797\header
798\li main.qml, material.frag
799\li Result
800\row \li \qml
801import QtQuick
802import QtQuick3D
803Item {
804 View3D {
805 anchors.fill: parent
806 environment: SceneEnvironment {
807 backgroundMode: SceneEnvironment.Color
808 clearColor: "white"
809 }
810 PerspectiveCamera {
811 id: camera
812 z: 600
813 }
814 DirectionalLight { }
815 Model {
816 source: "#Cube"
817 x: -150
818 eulerRotation.x: 60
819 eulerRotation.y: 20
820 materials: CustomMaterial {
821 fragmentShader: "material.frag"
822 }
823 }
824 Model {
825 source: "#Cube"
826 eulerRotation.x: 60
827 eulerRotation.y: 20
828 materials: CustomMaterial {
829 sourceBlend: CustomMaterial.SrcAlpha
830 destinationBlend: CustomMaterial.OneMinusSrcAlpha
831 fragmentShader: "material.frag"
832 }
833 }
834 Model {
835 source: "#Cube"
836 x: 150
837 eulerRotation.x: 60
838 eulerRotation.y: 20
839 materials: CustomMaterial {
840 sourceBlend: CustomMaterial.SrcAlpha
841 destinationBlend: CustomMaterial.OneMinusSrcAlpha
842 fragmentShader: "material.frag"
843 }
844 opacity: 0.5
845 }
846 }
847}
848\endqml
849\badcode
850void MAIN()
851{
852 BASE_COLOR = vec4(1.0, 0.0, 0.0, 0.5);
853}
854\endcode
855\li \image quick3d-custom-blend.jpg
856 {Three cubes showing blend mode opacity variations}
857\endtable
858
859The first cube is writing 0.5 to the alpha value of the color but it does not bring
860visible results since alpha blending is not enabled. The second cube enables simple alpha
861blending via the CustomMaterial properties. The third one also assigns an opacity of 0.5
862to the Model, which means that the effective opacity is 0.25.
863
864\section2 Passing data between the vertex and fragment shader
865
866Calculating a value per vertex (for example, assuming a single triangle, for the 3 corners
867of the triangle), and then passing it on to the fragment stage, where for each fragment
868(for example, every fragment covered by the rasterized triangle) an interpolated value is
869made accessible. In custom material shader snippets this is made possible by the \c
870VARYING keyword. This provides a syntax similar to GLSL 120 and GLSL ES 100, but will work
871regardless of the graphics API used at run time. The engine will take care of rewriting
872the varying declaration as appropriate.
873
874Let's see how the classic texture sampling with UV coordinates would look like. Textures
875are going to be covered in an upcoming section, for now let's focus on how we get the UV
876coordinates that can be passed to the \c{texture()} function in the shader.
877
878\table
879\header
880\li main.qml, material.vert, material.frag
881\row \li \qml
882import QtQuick
883import QtQuick3D
884Item {
885 View3D {
886 anchors.fill: parent
887 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
888 PerspectiveCamera { z: 600 }
889 DirectionalLight { }
890 Model {
891 source: "#Sphere"
892 scale: Qt.vector3d(4, 4, 4)
893 eulerRotation.x: 30
894 materials: CustomMaterial {
895 vertexShader: "material.vert"
896 fragmentShader: "material.frag"
897 property TextureInput someTextureMap: TextureInput {
898 texture: Texture {
899 source: "qt_logo_rect.png"
900 }
901 }
902 }
903 }
904 }
905}
906\endqml
907\badcode
908VARYING vec2 uv;
909void MAIN()
910{
911 uv = UV0;
912}
913\endcode
914\badcode
915VARYING vec2 uv;
916void MAIN()
917{
918 BASE_COLOR = texture(someTextureMap, uv);
919}
920\endcode
921\endtable
922
923\table
924\header
925\li qt_logo_rect.png
926\li Result
927\row \li \image quick3d-custom-varying-map.png
928 {Qt logo texture used as source data}
929\li \image quick3d-custom-varying1.jpg
930 {Cube with Qt logo texture applied to faces}
931\endtable
932
933Note that \c VARYING declarations. The name and type must match, \c uv in the fragment
934shader will expose the interpolated UV coordinate for the current fragment.
935
936Any other type of data can be passed on to the fragment stage in a similar manner. It is
937worth noting that in many cases setting up the material's own varyings is not necessary
938because there are builtins provided that cover many of typical needs. This includes making
939the (interpolated) normals, UVs, world position (\c VAR_WORLD_POSITION), or the vector
940pointing towards the camera (\c VIEW_VECTOR).
941
942The above example can in fact be simplified to the following as \c UV0 is automatically
943available in the fragment stage as well:
944
945\table
946\header
947\li Change in main.qml, material.frag
948\li Result
949\row \li \qml
950materials: CustomMaterial {
951 fragmentShader: "material.frag"
952 property TextureInput someTextureMap: TextureInput {
953 texture: Texture {
954 source: "qt_logo_rect.png"
955 }
956}
957\endqml
958\badcode
959void MAIN()
960{
961 BASE_COLOR = texture(someTextureMap, UV0);
962}
963\endcode
964\li \image quick3d-custom-varying1.jpg
965 {Cube with Qt logo texture applied to faces}
966\endtable
967
968To disable interpolation for a variable, use the \c flat keyword in both the
969vertex and fragment shader snippet. For example:
970\badcode
971VARYING flat vec2 v;
972\endcode
973
974\section2 Textures
975
976A \l CustomMaterial has no built-in texture maps, meaning there is no equivalent of, for
977example, \l{PrincipledMaterial::baseColorMap}. This is because implementing the same is
978often trivial, while giving a lot more flexibility than what DefaultMaterial and
979PrincipledMaterial has built in. Besides simply sampling a texture, custom fragment shader
980snippets are free to combine and blend data from various sources when calculating the
981values they assign to \c BASE_COLOR, \c EMISSIVE_COLOR, \c ROUGHNESS, etc. They can base
982these calculations on data provided via QML properties, interpolated data sent on from the
983vertex stage, values retrieved from sampling textures, and on hardcoded values.
984
985As the previous example shows, exposing a texture to the vertex, fragment, or both shaders
986is very similar to scalar and vector uniform values: a QML property with the type \l
987TextureInput will automatically get associated with a \c sampler2D in the shader code. As
988always, there is no need to declare this sampler in the shader code.
989
990A \l TextureInput references a \l Texture, with an additional
991\l{TextureInput::enabled}{enabled} property. A \l Texture can source its data in three
992ways: \l{Texture::source}{from an image file}, \l{Texture::sourceItem}{from a texture with
993live Qt Quick content}, or \l{Texture::textureData}{can be provided from C++} via
994QQuick3DTextureData.
995
996\note When it comes to \l Texture properties, the source, tiling, and filtering related
997ones are the only ones that are taken into account implicitly with custom materials, as
998the rest (such as, UV transformations) is up to the custom shaders to implement as they
999see fit.
1000
1001Let's see an example where a model, a sphere in this case, is textured using live Qt Quick
1002content:
1003
1004\table
1005\header
1006\li main.qml, material.frag
1007\row \li \qml
1008import QtQuick
1009import QtQuick3D
1010Item {
1011 View3D {
1012 anchors.fill: parent
1013 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
1014 PerspectiveCamera { z: 600 }
1015 DirectionalLight { }
1016 Model {
1017 source: "#Sphere"
1018 scale: Qt.vector3d(4, 4, 4)
1019 eulerRotation.x: 30
1020 materials: CustomMaterial {
1021 fragmentShader: "material.frag"
1022 property TextureInput someTextureMap: TextureInput {
1023 texture: Texture {
1024 sourceItem: Rectangle {
1025 width: 512; height: 512
1026 color: "red"
1027 Rectangle {
1028 width: 32; height: 32
1029 anchors.horizontalCenter: parent.horizontalCenter
1030 y: 150
1031 color: "gray";
1032 NumberAnimation on rotation { from: 0; to: 360; duration: 3000; loops: -1 }
1033 }
1034 Text {
1035 anchors.centerIn: parent
1036 text: "Texture Map"
1037 font.pointSize: 16
1038 }
1039 }
1040 }
1041 }
1042 }
1043 }
1044 }
1045}
1046\endqml
1047\badcode
1048void MAIN()
1049{
1050 vec2 uv = vec2(UV0.x, 1.0 - UV0.y);
1051 vec4 c = texture(someTextureMap, uv);
1052 BASE_COLOR = c;
1053}
1054\endcode
1055\endtable
1056
1057\image quick3d-custmat-tex1-anim.gif {Red sphere with animated texture map}
1058
1059Here the 2D subtree (Rectangle with two children: another Rectangle and the Text) is
1060rendered in to an 512x512 2D texture every time this mini-scene changes. The texture is
1061then exposed to the custom material under the name of \c someTextureMap.
1062
1063Note the flipping of the V coordinate in the shader. As noted above, custom materials,
1064where there is full programmability on shader level, do not offer the "fixed" features of
1065\l Texture and \l PrincipledMaterial. This means that any transformations to the UV
1066coordinates will need to be applied by the shader. Here we know that the texture is
1067generated via \l{Texture::sourceItem} and so V needs to be flipped to get something that
1068matches the UV set of the mesh we are using.
1069
1070What this example shows is possible to do with a \l PrincipledMaterial too. Let's make it
1071more interesting by doing a simple emboss effect in addition:
1072
1073\table
1074\header
1075\li material.frag
1076\li Result
1077\row \li \badcode
1078void MAIN()
1079{
1080 vec2 uv = vec2(UV0.x, 1.0 - UV0.y);
1081 vec2 size = vec2(textureSize(someTextureMap, 0));
1082 vec2 d = vec2(1.0 / size.x, 1.0 / size.y);
1083 vec4 diff = texture(someTextureMap, uv + d) - texture(someTextureMap, uv - d);
1084 float c = (diff.x + diff.y + diff.z) + 0.5;
1085 BASE_COLOR = vec4(c, c, c, 1.0);
1086}
1087\endcode
1088\li \image quick3d-custmat-tex2-anim.gif
1089 {Sphere with animated procedural texture}
1090\endtable
1091
1092With the features covered so far a wide range of possibilities are open for creating
1093materials that shade the meshes in visually impressive ways. To finish the basic tour,
1094let's look at an example that applies height and normal maps to a plane mesh. (a dedicated
1095\c{.mesh} file is used here because the builtin \c{#Rectangle} does not have enough
1096subdivisions) For better lighting results, we will use image based lighting with a 360
1097degree HDR image. The image is also set as the skybox to make it more clear what is
1098happening.
1099
1100First let's start with an empty CustomMaterial:
1101
1102\table
1103\header
1104\li main.qml
1105\li Result
1106\row \li \qml
1107import QtQuick
1108import QtQuick3D
1109Item {
1110 View3D {
1111 anchors.fill: parent
1112 environment: SceneEnvironment {
1113 backgroundMode: SceneEnvironment.SkyBox
1114 lightProbe: Texture {
1115 source: "00489_OpenfootageNET_snowfield_low.hdr"
1116 }
1117 }
1118 PerspectiveCamera {
1119 z: 600
1120 }
1121 Model {
1122 source: "plane.mesh"
1123 scale: Qt.vector3d(400, 400, 400)
1124 z: 400
1125 y: -50
1126 eulerRotation.x: -90
1127 materials: CustomMaterial { }
1128 }
1129 }
1130}
1131\endqml
1132\li \image quick3d-custom-tex3.jpg
1133 {Plane with winter landscape from custom texture data}
1134\endtable
1135
1136Now let's make some shaders that apply a height and normal map to the mesh:
1137
1138\table
1139\header
1140\li Height map
1141\li Normap map
1142\row
1143\li \image quick3d-custom-heightmap.png {Grayscale height map texture}
1144\li \image quick3d-custom-normalmap.jpg
1145 {Plane with height map applied as displacement}
1146\endtable
1147
1148\table
1149\header
1150\li material.vert, material.frag
1151\row
1152\li \badcode
1153float getHeight(vec2 pos)
1154{
1155 return texture(heightMap, pos).r;
1156}
1157
1158void MAIN()
1159{
1160 const float offset = 0.004;
1161 VERTEX.y += getHeight(UV0);
1162 TANGENT = normalize(vec3(0.0, getHeight(UV0 + vec2(0.0, offset)) - getHeight(UV0 + vec2(0.0, -offset)), offset * 2.0));
1163 BINORMAL = normalize(vec3(offset * 2.0, getHeight(UV0 + vec2(offset, 0.0)) - getHeight(UV0 + vec2(-offset, 0.0)), 0.0));
1164 NORMAL = cross(TANGENT, BINORMAL);
1165}
1166\endcode
1167\badcode
1168void MAIN()
1169{
1170 vec3 normalValue = texture(normalMap, UV0).rgb;
1171 normalValue.xy = normalValue.xy * 2.0 - 1.0;
1172 normalValue.z = sqrt(max(0.0, 1.0 - dot(normalValue.xy, normalValue.xy)));
1173 NORMAL = normalize(mix(NORMAL, TANGENT * normalValue.x + BINORMAL * normalValue.y + NORMAL * normalValue.z, 1.0));
1174}
1175\endcode
1176\endtable
1177
1178\table
1179\header
1180\li Change in main.qml
1181\li Result
1182\row
1183\li \qml
1184materials: CustomMaterial {
1185 vertexShader: "material.vert"
1186 fragmentShader: "material.frag"
1187 property TextureInput normalMap: TextureInput {
1188 texture: Texture { source: "normalmap.jpg" }
1189 }
1190 property TextureInput heightMap: TextureInput {
1191 texture: Texture { source: "heightmap.png" }
1192 }
1193}
1194\endqml
1195\li \image quick3d-custom-tex4.jpg
1196 {Plane with normal map showing surface detail}
1197\endtable
1198
1199\note The \l WasdController object can be immensely helpful during development and
1200troubleshooting as it allows navigating and looking around in the scene with the keyboard
1201and mouse in a familiar manner. Having a camera controlled by the WasdController is as
1202simple as:
1203
1204\qml
1205import QtQuick3D.Helpers
1206View3D {
1207 PerspectiveCamera {
1208 id: camera
1209 }
1210 // ...
1211}
1212WasdController {
1213 controlledObject: camera
1214}
1215\endqml
1216
1217\section2 Depth and screen textures
1218
1219When a custom shader snippet uses the \c DEPTH_TEXTURE or \c SCREEN_TEXTURE keywords, it
1220opts in to generating the corresponding textures in a separate render pass, which is not
1221necessarily a cheap operation, but allows implementing a variety of techniques, such as
1222refraction for glass-like materials.
1223
1224\c DEPTH_TEXTURE is a \c sampler2D that allows sampling a texture with the contents of the
1225depth buffer with all the \c opaque objects in the scene rendered. Similarly, \c
1226SCREEN_TEXTURE is a \c sampler2D that allows sampling a texture containing the contents of
1227the scene excluding any transparent materials or any materials also using the
1228SCREEN_TEXTURE. The texture can be used for materials that require the contents of the
1229framebuffer they are being rendered to. The SCREEN_TEXTURE texture uses the same clear mode
1230as the View3D. The size of these textures matches the size of the View3D in pixels.
1231
1232Let's have a simple demonstration by visualizing the depth buffer contents via \c
1233DEPTH_TEXTURE. The camera's \l{PerspectiveCamera::clipFar}{far clip value} is reduced here from the
1234default 10000 to 2000, in order to have a smaller range, and so have the visualized depth
1235value differences more obvious. The result is a rectangle that happens to visualize the
1236depth buffer for the scene over its surface.
1237
1238\table
1239\header
1240\li main.qml, material.frag
1241\li Result
1242\row
1243\li \qml
1244import QtQuick
1245import QtQuick3D
1246import QtQuick3D.Helpers
1247Rectangle {
1248 width: 400
1249 height: 400
1250 color: "black"
1251 View3D {
1252 anchors.fill: parent
1253 PerspectiveCamera {
1254 id: camera
1255 z: 600
1256 clipNear: 1
1257 clipFar: 2000
1258 }
1259 DirectionalLight { }
1260 Model {
1261 source: "#Cube"
1262 scale: Qt.vector3d(2, 2, 2)
1263 position: Qt.vector3d(150, 200, -1000)
1264 eulerRotation.x: 60
1265 eulerRotation.y: 20
1266 materials: PrincipledMaterial { }
1267 }
1268 Model {
1269 source: "#Cylinder"
1270 scale: Qt.vector3d(2, 2, 2)
1271 position: Qt.vector3d(400, 200, -1000)
1272 materials: PrincipledMaterial { }
1273 opacity: 0.3
1274 }
1275 Model {
1276 source: "#Sphere"
1277 scale: Qt.vector3d(2, 2, 2)
1278 position: Qt.vector3d(-150, 200, -600)
1279 materials: PrincipledMaterial { }
1280 }
1281 Model {
1282 source: "#Cone"
1283 scale: Qt.vector3d(2, 2, 2)
1284 position: Qt.vector3d(0, 400, -1200)
1285 materials: PrincipledMaterial { }
1286 }
1287 Model {
1288 source: "#Rectangle"
1289 scale: Qt.vector3d(3, 3, 3)
1290 y: -150
1291 materials: CustomMaterial {
1292 fragmentShader: "material.frag"
1293 }
1294 }
1295 }
1296 WasdController {
1297 controlledObject: camera
1298 }
1299}
1300\endqml
1301\badcode
1302void MAIN()
1303{
1304 float zNear = CAMERA_PROPERTIES.x;
1305 float zFar = CAMERA_PROPERTIES.y;
1306 float zRange = zFar - zNear;
1307 vec4 depthSample = texture(DEPTH_TEXTURE, vec2(UV0.x, 1.0 - UV0.y));
1308 float zn = 2.0 * depthSample.r - 1.0;
1309 float d = 2.0 * zNear * zFar / (zFar + zNear - zn * zRange);
1310 d /= zFar;
1311 BASE_COLOR = vec4(d, d, d, 1.0);
1312}
1313\endcode
1314\li \image quick3d-custom-depth-anim.gif
1315 {Animation showing depth buffer values as grayscale}
1316\endtable
1317
1318Note how the cylinder is not present in \c DEPTH_TEXTURE due to its reliance on
1319semi-transparency, which puts it into a different category than the other objects that are
1320all opaque. These objects do not write into the depth buffer, although they do test
1321against the depth values written by opaque objects, and rely on being rendered in back to
1322front order. Hence they are not present in \c DEPTH_TEXTURE either.
1323
1324What happens if we switch the shader to sample \c SCREEN_TEXTURE instead?
1325
1326\table
1327\header
1328\li material.frag
1329\li Result
1330\row \li \badcode
1331void MAIN()
1332{
1333 vec4 c = texture(SCREEN_TEXTURE, vec2(UV0.x, 1.0 - UV0.y));
1334 if (c.a == 0.0)
1335 c.rgb = vec3(0.2, 0.1, 0.3);
1336 BASE_COLOR = c;
1337}
1338\endcode
1339\li \image quick3d-custom-screen.jpg
1340 {Scene showing screen texture with distortion effect}
1341\endtable
1342
1343Here the rectangle is textured with \c SCREEN_TEXTURE, while replacing transparent pixels
1344with purple.
1345
1346\section2 Light processor functions
1347
1348An advanced feature of \l CustomMaterial is the ability to define functions in the
1349fragment shader that reimplement the lighting equations that are used to calculate the
1350fragment color. A light processor function, when present, is called once per each light in
1351the scene, for each fragment. There is a dedicated function for different light types, as
1352well as the ambient and specular contribution. When no corresponding light processor
1353function is present, the standard calculations are used, just like a PrincipledMaterial
1354would do. When a light processor is present, but the function body is empty, it means
1355there will be no contribution from a given type of lights in the scene.
1356
1357Refer to the \l CustomMaterial documentation for details on functions such as \c
1358DIRECTIONAL_LIGHT, \c POINT_LIGHT, \c SPOT_LIGHT, \c AMBIENT_LIGHT, and \c SPECULAR_LIGHT.
1359
1360\section2 Unshaded custom materials
1361
1362There is another type of \l CustomMaterial: \c unshaded custom materials. All the example
1363so far used \c shaded custom materials, with the
1364\l{CustomMaterial::shadingMode}{shadingMode} property left at its default
1365CustomMaterial.Shaded value.
1366
1367What happens if we switch this property to CustomMaterial.Unshaded?
1368
1369First of all, keywords like \c BASE_COLOR, \c EMISSIVE_COLOR, \c METALNESS, etc. no longer
1370have the desired effect. This is because an unshaded material, as the name suggests, does
1371not automatically get amended with much of the standard shading code, thus ignoring
1372lights, image based lighting, shadows, and ambient occlusion in the scene. Rather, an
1373unshaded material gives full control to the shader via the \c FRAGCOLOR keyword. This is
1374similar to gl_FragColor: the color assigned to \c FRAGCOLOR is the result and the final
1375color of the fragment, without any further adjustments by Qt Quick 3D.
1376
1377\table
1378\header
1379\li main.qml, material.frag, material2.frag
1380\li Result
1381\row \li \qml
1382import QtQuick
1383import QtQuick3D
1384Item {
1385 View3D {
1386 anchors.fill: parent
1387 environment: SceneEnvironment {
1388 backgroundMode: SceneEnvironment.Color
1389 clearColor: "black"
1390 }
1391 PerspectiveCamera { z: 600 }
1392 DirectionalLight { }
1393 Model {
1394 source: "#Cylinder"
1395 x: -100
1396 eulerRotation.x: 30
1397 materials: CustomMaterial {
1398 fragmentShader: "material.frag"
1399 }
1400 }
1401 Model {
1402 source: "#Cylinder"
1403 x: 100
1404 eulerRotation.x: 30
1405 materials: CustomMaterial {
1406 shadingMode: CustomMaterial.Unshaded
1407 fragmentShader: "material2.frag"
1408 }
1409 }
1410 }
1411}
1412\endqml
1413\badcode
1414void MAIN()
1415{
1416 BASE_COLOR = vec4(1.0);
1417}
1418\endcode
1419\badcode
1420void MAIN()
1421{
1422 FRAGCOLOR = vec4(1.0);
1423}
1424\endcode
1425\li \image quick3d-custom-unshaded1.jpg
1426 {Cube and cylinder with unshaded custom material}
1427\endtable
1428
1429Notice how the right cylinder ignores the DirectionalLight in the scene. Its shading knows
1430nothing about scene lighting, the final fragment color is all white.
1431
1432The vertex shader in an unshaded material still has the typical inputs available: \c
1433VERTEX, \c NORMAL, \c MODELVIEWPROJECTION_MATRIX, etc. and can write to \c POSITION. The
1434fragment shader no longer has the similar conveniences available, however: \c NORMAL, \c
1435UV0, or \c VAR_WORLD_POSITION are not available in an unshaded material's fragment
1436shader. Rather, it is now up to the shader code to calculate and pass on using \c VARYING
1437everything it needs to determine the final fragment color.
1438
1439Let's look at an example that has both a vertex and fragment shader. The altered vertex
1440position is passed on to the fragment shader, with an interpolated value made available to
1441every fragment.
1442
1443\table
1444\header
1445\li main.qml, material.vert, material.frag
1446\row \li \qml
1447import QtQuick
1448import QtQuick3D
1449Item {
1450 View3D {
1451 anchors.fill: parent
1452 environment: SceneEnvironment {
1453 backgroundMode: SceneEnvironment.Color
1454 clearColor: "black"
1455 }
1456 PerspectiveCamera { z: 600 }
1457 Model {
1458 source: "#Sphere"
1459 scale: Qt.vector3d(3, 3, 3)
1460 materials: CustomMaterial {
1461 property real time: 0.0
1462 NumberAnimation on time { from: 0; to: 100; duration: 20000; loops: -1 }
1463 property real amplitude: 10.0
1464 shadingMode: CustomMaterial.Unshaded
1465 vertexShader: "material.vert"
1466 fragmentShader: "material.frag"
1467 }
1468 }
1469 }
1470}
1471\endqml
1472\badcode
1473VARYING vec3 pos;
1474void MAIN()
1475{
1476 pos = VERTEX;
1477 pos.x += sin(time * 4.0 + pos.y) * amplitude;
1478 POSITION = MODELVIEWPROJECTION_MATRIX * vec4(pos, 1.0);
1479}
1480\endcode
1481\badcode
1482VARYING vec3 pos;
1483void MAIN()
1484{
1485 FRAGCOLOR = vec4(vec3(pos.x * 0.02, pos.y * 0.02, pos.z * 0.02), 1.0);
1486}
1487\endcode
1488\endtable
1489
1490\image quick3d-custom-unshaded-anim.gif
1491 {Animation showing lighting toggle between shaded and unshaded}
1492
1493Unshaded materials are useful when interacting with scene lighting is not necessary or
1494desired, and the material needs full control on the final fragment color. Notice how the
1495example above has neither a DirectionalLight nor any other lights, but the sphere with the
1496custom material shows up as expected.
1497
1498\note An unshaded material that only has a vertex shader snippet, but does not specify the
1499fragmentShader property, will still be functional but the results are as if the
1500shadingMode was set to Shaded. Therefore it makes little sense to switch shadingMode for
1501materials that only have a vertex shader.
1502
1503\section1 Programmability for Effects
1504
1505Post-processing effects apply one or more fragment shaders to the result of a \l
1506View3D. The output from these fragment shaders is then displayed instead of the original
1507rendering results. This is conceptually very similar to Qt Quick's \l ShaderEffect and \l
1508ShaderEffectSource.
1509
1510\note Post-processing effects are only available when the
1511\l{View3D::renderMode}{renderMode} for the View3D is set to View3D.Offscreen.
1512
1513Custom vertex shader snippets can also be specified for an effect, but they have limited
1514usefulness and therefore are expected to be used relatively rarely. The vertex input for a
1515post-processing effect is a quad (either two triangles or a triangle strip), transforming
1516or displacing the vertices of that is often not helpful. It can however make sense to have
1517a vertex shader in order to calculate and pass on data to the fragment shader using the \c
1518VARYING keyword. As usual, the fragment shader will then receive an interpolated value
1519based on the current fragment coordinate.
1520
1521The syntax of the shader snippets associated with a \l Effect is identical to the shaders
1522for an unshaded \l CustomMaterial. When it comes to the built-in special keywords, \c
1523VARYING, \c MAIN, \c FRAGCOLOR (fragment shader only), \c POSITION (vertex shader only), \c
1524VERTEX (vertex shader only), and \c MODELVIEWPROJECTION_MATRIX work identically to \l
1525CustomMaterial.
1526
1527The most important special keywords for \l Effect fragment shaders are the following:
1528
1529\table
1530\header
1531\li Name
1532\li Type
1533\li Description
1534\row
1535\li INPUT
1536\li sampler2D or sampler2DArray
1537\li The sampler for the input texture. An effect will typically sample this using \c INPUT_UV.
1538\row
1539\li INPUT_UV
1540\li vec2
1541\li UV coordinates for sampling \c INPUT.
1542\row
1543\li INPUT_SIZE
1544\li vec2
1545\li The size of the \c INPUT texture, in pixels. This is a convenient alternative to calling textureSize().
1546\row
1547\li OUTPUT_SIZE
1548\li vec2
1549\li The size of the output texture, in pixels. Equal to \c INPUT_SIZE in many cases, but a multi-pass effect
1550may have passes that output to intermediate textures with different sizes.
1551\row
1552\li DEPTH_TEXTURE
1553\li sampler2D
1554\li Depth texture with the depth buffer contents with the opaque objects in the scene. Like with CustomMaterial,
1555the presence of this keyword in the shader triggers generating the depth texture automatically.
1556\endtable
1557
1558\note When multiview rendering is enabled, the input texture is a 2D texture
1559array. GLSL functions such as texture() and textureSize() take/return a
1560vec3/ivec3, respectively, then. Use \c VIEW_INDEX for the layer. In VR/AR
1561applications that wish to function both with and without multiview rendering, the
1562portable approach is to write the shader code like this:
1563\badcode
1564 #if QSHADER_VIEW_COUNT >= 2
1565 vec4 c = texture(INPUT, vec3(INPUT_UV, VIEW_INDEX));
1566 #else
1567 vec4 c = texture(INPUT, INPUT_UV);
1568 #endif
1569\endcode
1570
1571\section2 A post-processing effect
1572
1573Let's start with a simple scene, this time using a few more objects, including a textured
1574rectangle that uses a checkerboard texture as its base color map.
1575
1576\table
1577\header
1578\li main.qml
1579\li Result
1580\row \li \qml
1581import QtQuick
1582import QtQuick3D
1583Item {
1584 View3D {
1585 anchors.fill: parent
1586 environment: SceneEnvironment {
1587 backgroundMode: SceneEnvironment.Color
1588 clearColor: "black"
1589 }
1590
1591 PerspectiveCamera { z: 400 }
1592
1593 DirectionalLight { }
1594
1595 Texture {
1596 id: checkerboard
1597 source: "checkerboard.png"
1598 scaleU: 20
1599 scaleV: 20
1600 tilingModeHorizontal: Texture.Repeat
1601 tilingModeVertical: Texture.Repeat
1602 }
1603
1604 Model {
1605 source: "#Rectangle"
1606 scale: Qt.vector3d(10, 10, 1)
1607 eulerRotation.x: -45
1608 materials: PrincipledMaterial {
1609 baseColorMap: checkerboard
1610 }
1611 }
1612
1613 Model {
1614 source: "#Cone"
1615 position: Qt.vector3d(100, -50, 100)
1616 materials: PrincipledMaterial { }
1617 }
1618
1619 Model {
1620 source: "#Cube"
1621 position.y: 100
1622 eulerRotation.y: 20
1623 materials: PrincipledMaterial { }
1624 }
1625
1626 Model {
1627 source: "#Sphere"
1628 position: Qt.vector3d(-150, 200, -100)
1629 materials: PrincipledMaterial { }
1630 }
1631 }
1632}
1633\endqml
1634\li \image quick3d-custom-effect-section-scene.jpg
1635 {Reference scene with sphere, cone, and cube}
1636\endtable
1637
1638Now let's apply an affect to the entire scene. More precisely, to the View3D. When there
1639are multiple View3D items in the scene, each has its own SceneEnvironment and therefore
1640have their own post-processing effect chain. In the example there is one single View3D
1641covering the entire window.
1642
1643\table
1644\header
1645\li Change in main.qml
1646\li effect.frag
1647\row \li \qml
1648 environment: SceneEnvironment {
1649 backgroundMode: SceneEnvironment.Color
1650 clearColor: "black"
1651 effects: redEffect
1652 }
1653
1654 Effect {
1655 id: redEffect
1656 property real uRed: 1.0
1657 NumberAnimation on uRed { from: 1; to: 0; duration: 5000; loops: -1 }
1658 passes: Pass {
1659 shaders: Shader {
1660 stage: Shader.Fragment
1661 shader: "effect.frag"
1662 }
1663 }
1664 }
1665\endqml
1666\li \badcode
1667void MAIN()
1668{
1669 vec4 c = texture(INPUT, INPUT_UV);
1670 c.r = uRed;
1671 FRAGCOLOR = c;
1672}
1673\endcode
1674\endtable
1675
1676This simple effect alters the red color channel value. Exposing QML properties as uniforms
1677works the same way with effects as with custom materials. The shader starts with a line
1678that is going to be very common when writing fragment shaders fro effects: sampling \c
1679INPUT at the UV coordinates \c INPUT_UV. It then performs its desired calculations, and
1680assigns the final fragment color to \c FRAGCOLOR.
1681
1682\image quick3d-custom-first-effect-anim.gif
1683 {Animation showing color inversion post-processing effect}
1684
1685Many properties set in the example are in plural (effects, passes, shaders). While the
1686list \c{[ ]} syntax can be omitted when having a single element only, all these properties
1687are lists, and can hold more than one element. Why is this?
1688
1689\list
1690
1691\li \l{SceneEnvironment::effects}{effects} is a list, because View3D allows chaining
1692multiple effects together. The effects are applied in the order in which they are added to
1693the list. This allows easily applying two or more effects together to the View3D, and is
1694similar to what one can achieve in Qt Quick by nesting \l ShaderEffect items. The \c INPUT
1695texture of the next effect is always a texture that contains the previous effect's
1696output. The output of the last effect in what gets used as the final output of the View3D.
1697
1698\li \l{Effect::passes}{passes} is a list, because unlike ShaderEffect, Effect has built-in
1699support for multiple passes. A multi-pass effect is more powerful than chaining together
1700multiple, independent effects in \l{SceneEnvironment::effects}{effects}: a pass can output
1701to a temporary, intermediate texture, which can then be used as input to subsequent
1702passes, in addition to the original input texture of the effect. This allows creating
1703complex effects that calculate, render, and blend together multiple textures in order to
1704get to the final fragment color. This advanced use case is not going to be covered
1705here. Refer to the \l Effect documentation page for details.
1706
1707\li \l{Pass::shaders}{shaders} is a list, because an effect may have both a vertex and a
1708fragment shader associated.
1709
1710\endlist
1711
1712\section2 Chaining multiple effects
1713
1714Let's look at an example where the effect from the previous example gets complemented by
1715another effect similar to the built-in \l DistortionSpiral effect.
1716
1717\table
1718\header
1719\li Change in main.qml
1720\li effect2.frag
1721\row \li \qml
1722 environment: SceneEnvironment {
1723 backgroundMode: SceneEnvironment.Color
1724 clearColor: "black"
1725 effects: [redEffect, distortEffect]
1726 }
1727
1728 Effect {
1729 id: redEffect
1730 property real uRed: 1.0
1731 NumberAnimation on uRed { from: 1; to: 0; duration: 5000; loops: -1 }
1732 passes: Pass {
1733 shaders: Shader {
1734 stage: Shader.Fragment
1735 shader: "effect.frag"
1736 }
1737 }
1738 }
1739
1740 Effect {
1741 id: distortEffect
1742 property real uRadius: 0.1
1743 NumberAnimation on uRadius { from: 0.1; to: 1.0; duration: 5000; loops: -1 }
1744 passes: Pass {
1745 shaders: Shader {
1746 stage: Shader.Fragment
1747 shader: "effect2.frag"
1748 }
1749 }
1750 }
1751\endqml
1752\li \badcode
1753void MAIN()
1754{
1755 vec2 center_vec = INPUT_UV - vec2(0.5, 0.5);
1756 center_vec.y *= INPUT_SIZE.y / INPUT_SIZE.x;
1757 float dist_to_center = length(center_vec) / uRadius;
1758 vec2 texcoord = INPUT_UV;
1759 if (dist_to_center <= 1.0) {
1760 float rotation_amount = (1.0 - dist_to_center) * (1.0 - dist_to_center);
1761 float r = radians(360.0) * rotation_amount / 4.0;
1762 float cos_r = cos(r);
1763 float sin_r = sin(r);
1764 mat2 rotation = mat2(cos_r, sin_r, -sin_r, cos_r);
1765 texcoord = vec2(0.5, 0.5) + rotation * (INPUT_UV - vec2(0.5, 0.5));
1766 }
1767 vec4 c = texture(INPUT, texcoord);
1768 FRAGCOLOR = c;
1769}
1770\endcode
1771\endtable
1772
1773\image quick3d-custom-chained-effect-anim.gif
1774 {Animation showing combined blur and color inversion effects}
1775
1776Now the perhaps surprising question: why is this a bad example?
1777
1778More precisely, it is not bad, but rather shows a pattern that can often be beneficial to
1779avoid.
1780
1781Chaining effects this way can be useful, but it is important to keep in mind the
1782performance implications: doing two render passes (one to generate a texture with the
1783adjusted red color channel, and then another one two calculate the distortion) is quite
1784wasteful when one would be enough. If the fragment shader snippets were combined, the same
1785result could have been achieved with one single effect.
1786
1787\section1 Defining Mesh and Texture Data from C++
1788
1789Procedurally generating mesh and texture image data both follow similar steps:
1790
1791\list
1792\li Subclass \l QQuick3DGeometry or \l QQuick3DTextureData
1793\li Set the desired vertex or image data upon construction by calling the protected member functions
1794from the base class
1795\li If dynamic changes are needed afterwards at some point, set the new data and call update()
1796\li Once the implementation is done, the class needs to be registered to make it visible in QML
1797\li \l Model and \l Texture objects in QML can now use the custom vertex or image data provider by
1798setting the \l{Model::geometry} or \l{Texture::textureData} property
1799\endlist
1800
1801\section2 Custom vertex data
1802
1803Vertex data refers to the sequence of (typically \c float) values that make up a
1804mesh. Instead of loading \c{.mesh} files, a custom geometry provider is responsible for
1805providing the same data. The vertex data consist of \c attributes, such as position,
1806texture (UV) coordinates, or normals. The specification of attributes describes what kind
1807of attributes are present, the component type (for example, a 3 component float vector for
1808vertex position consisting of x, y, z values), which offset they start at in the provided
1809data, and what the stride (the increment that needs to be added to the offset to point to
1810the next element for the same attribute) is.
1811
1812This may seem familiar if one has worked with graphics APIs, such as OpenGL or Vulkan
1813directly, because the way vertex input is specified with those APIs maps loosely to what a
1814\c{.mesh} file or a \l QQuick3DGeometry instance defines.
1815
1816In addition, the mesh topology (primitive type) must be specified too. For indexed
1817drawing, the data for an index buffer must be provided as well.
1818
1819There is one built-in custom geometry implementation: the QtQuick3D.Helpers module
1820includes a \l GridGeometry type. This allows rendering a grid in the scene with line
1821primitives, without having to implement a custom \l QQuick3DGeometry subclass.
1822
1823One other common use cases is rendering points. This is fairly simple to do since the
1824attribute specification is going to be minimal: we provide three floats (x, y, z) for each
1825vertex, nothing else. A QQuick3DGeometry subclass could implement a geometry consisting of
18262000 points similarly to the following:
1827
1828\badcode
1829 clear();
1830 const int N = 2000;
1831 const int stride = 3 * sizeof(float);
1832 QByteArray v;
1833 v.resize(N * stride);
1834 float *p = reinterpret_cast<float *>(v.data());
1835 QRandomGenerator *rg = QRandomGenerator::global();
1836 for (int i = 0; i < N; ++i) {
1837 const float x = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
1838 const float y = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
1839 *p++ = x;
1840 *p++ = y;
1841 *p++ = 0.0f;
1842 }
1843 setVertexData(v);
1844 setStride(stride);
1845 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Points);
1846 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
1847\endcode
1848
1849Combined with a material of
1850
1851\qml
1852DefaultMaterial {
1853 lighting: DefaultMaterial.NoLighting
1854 cullMode: DefaultMaterial.NoCulling
1855 diffuseColor: "yellow"
1856 pointSize: 4
1857}
1858\endqml
1859
1860the end result is similar to this (here viewed from an altered camera angle, with the help
1861of \l WasdController):
1862
1863\image quick3d-custom-points.jpg
1864 {Point cloud geometry rendered as individual points}
1865
1866\note Be aware that point sizes and line widths other than 1 may not be supported at run
1867time, depending on the underlying graphics API. This is not something Qt has control
1868over. Therefore, it can become necessary to implement alternative techniques instead of
1869relying on point and line drawing.
1870
1871\section2 Custom texture data
1872
1873With textures, the data that needs to be provided is a lot simpler structurally: it is the
1874raw pixel data, with a varying number of bytes per pixel, depending on the texture
1875format. For example, an \c RGBA texture expects four bytes per pixel, whereas \c RGBA16F
1876is four half-floats per pixel. This is similar to what a \l QImage stores
1877internally. However, Qt Quick 3D textures can have formats the data for which cannot be
1878represented by a QImage. For example, floating point HDR textures, or compressed
1879textures. Therefore the data for \l QQuick3DTextureData is always provided as a raw
1880sequence of bytes. This may seem familiar if one has worked with graphics APIs, such as
1881OpenGL or Vulkan directly.
1882
1883For details, refer to the \l QQuick3DGeometry and \l QQuick3DTextureData documentation pages.
1884
1885\sa CustomMaterial, Effect, QQuick3DGeometry, QQuick3DTextureData, {Qt Quick 3D - Custom
1886Effect Example}, {Qt Quick 3D - Custom Shaders Example}, {Qt Quick 3D - Custom Materials
1887Example}, {Qt Quick 3D - Custom Geometry Example}, {Qt Quick 3D - Procedural Texture
1888Example}
1889
1890*/