1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
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
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.
16\li A model with PrincipledMaterial
17\li With a CustomMaterial transforming the vertices
19\li \image quick3d-custom-mat1.jpg
20\li \image quick3d-custom-mat2.jpg
23Post-processing effects, where one or more passes of processing on the color buffer are
24performed, optionally taking the depth buffer into account, before the View3D's output is
25passed on to Qt Quick, also exist in two varieties:
27\li built-in post-processing steps that can be configured via \l ExtendedSceneEnvironment, such as
28glow/bloom, depth of field, vignette, lens flare,
29\li \c custom effects implemented by the application in form of fragment shader code and a
30specification of the processing passes in an \l Effect object.
33In practice there is a third category of post-processing effects: 2D effects
34implemented via Qt Quick, operating on the output of the \l View3D item without
35any involvement from the 3D renderer. For example, to apply a blur to a \l
36View3D item, the simplest approach is to use Qt Quick's existing facilities,
37such as \l MultiEffect. The 3D post-processing system becomes beneficial for
38complex effects that involve 3D scene concepts such as the depth buffer or the
39screen texture, or need to deal with HDR tonemapping or need multiple passes
40with intermediate buffers, etc. Simple 2D effects that do not require any
41insight into the 3D scene and renderer can always be implemented with \l
42ShaderEffect or \l MultiEffect instead.
46\li Scene without effect
47\li The same scene with a custom post-processing effect applied
49\li \image quick3d-custom-effect1.jpg
50\li \image quick3d-custom-effect2.jpg
53In addition to programmable materials and post-processing, there are two types of data that is
54normally provided in form of files (\c{.mesh} files or images such as \c{.png}):
58\li vertex data, including the geometry for the mesh to be rendered, texture coordinates,
59normals, colors, and other data,
61\li the content for textures that are then used as texture maps for the rendered
62objects, or used with skybox or image based lighting.
66If they so wish, applications can provide such data from C++ in form of a QByteArray. Such
67data can also be changed over time, allowing to procedurally generate and later alter the
68data for a \l Model or \l Texture.
72\li A grid, rendered by specifying vertex data dynamically from C++
73\li A cube textured with image data generated from C++
75\li \image quick3d-custom-geom.jpg
76\li \image quick3d-custom-tex.jpg
79These four approaches to customizing and making materials, effects, geometry, and textures
80dynamic enable the programmability of shading and procedural generation of the data the
81shaders get as their input. The following sections provide an overview of these
82features. The full reference is available in the documentation pages for the respective
88\li Reference Documentation
93\li \l {Qt Quick 3D - Custom Shaders Example}, \l {Qt Quick 3D - Custom Materials
96\li Custom post-processing effects
98\li \l {Qt Quick 3D - Custom Effect Example}
101\li \l QQuick3DGeometry, \l{Model::geometry}
102\li \l {Qt Quick 3D - Custom Geometry Example}
104\li Custom texture data
105\li \l QQuick3DTextureData, \l{Texture::textureData}
106\li \l {Qt Quick 3D - Procedural Texture Example}
109\section1 Programmability for Materials
111Let's have a scene with a cube, and start with a default \l PrincipledMaterial and
116\li PrincipledMaterial
126 environment: SceneEnvironment {
127 backgroundMode: SceneEnvironment.Color
130 PerspectiveCamera { z: 600 }
134 scale: Qt.vector3d(2, 2, 2)
136 materials: PrincipledMaterial { }
148 environment: SceneEnvironment {
149 backgroundMode: SceneEnvironment.Color
152 PerspectiveCamera { z: 600 }
156 scale: Qt.vector3d(2, 2, 2)
158 materials: CustomMaterial { }
165These both lead to the exact same result, because a \l CustomMaterial is effectively a \l
166PrincipledMaterial, when no vertex or fragment shader code is added to it.
168\image quick3d-custom-cube1.jpg
170\note Properties, such as, \l{PrincipledMaterial::baseColor}{baseColor},
171\l{PrincipledMaterial::metalness}{metalness},
172\l{PrincipledMaterial::baseColorMap}{baseColorMap}, and many others, have no equivalent
173properties in the \l CustomMaterial QML type. This is by design: customizing the material
174is done via shader code, not by merely providing a few fixed values.
176\section2 Our first vertex shader
178Let's add a custom vertex shader snippet. This is done by referencing a file in the
179\l{CustomMaterial::vertexShader}{vertexShader} property. The approach will be the same for
180fragment shaders. These references work like \l{Image::source}{Image.source} or
181\l{ShaderEffect::vertexShader}{ShaderEffect.vertexShader}: they are local or \c qrc URLs,
182and a relative path is treated relative to the \c{.qml} file's location. The common
183approach is therefore to place the \c{.vert} and \c{.frag} files into the Qt resource
184system (\c qt_add_resources when using CMake) and reference them using a relative path.
186In Qt 6.0 inline shader strings are no longer supported, neither in Qt Quick nor in Qt
187Quick 3D. (make note of the fact that these properties are URLs, not strings) However, due
188to their intrinsically dynamic nature, custom materials and post-processing effects in Qt
189Quick 3D still provide shader snippets in source form in the referenced files. This is a
190difference to \l ShaderEffect where the shaders are complete on their own, with no further
191amending by the engine, and so are expected to be provided as pre-conditioned \c{.qsb}
194\note In Qt Quick 3D URLs can only refer to local resources. Schemes for remote content
197\note The shading language used is Vulkan-compatible GLSL. The \c{.vert} and \c{.frag}
198files are not complete shaders on their own, hence being often called \c snippets. That is
199why there are no uniform blocks, input and output variables, or sampler uniforms provided
200directly by these snippets. Rather, the Qt Quick 3D engine will amend them as appropriate.
204\li Change in main.qml, material.vert
208 materials: CustomMaterial {
209 vertexShader: "material.vert"
217 \li \image quick3d-custom-cube1-small.jpg
220A custom vertex or fragment shader snippet is expected to provide one or more functions
221with pre-defined names, such as \c MAIN, \c DIRECTIONAL_LIGHT, \c POINT_LIGHT, \c
222SPOT_LIGHT, \c AMBIENT_LIGHT, \c SPECULAR_LIGHT. For now let's focus on \c MAIN.
224As shown here, the end result with an empty MAIN() is exactly the same as before.
226Before making it more interesting, let's look at an overview of the most commonly used
227special keywords in custom vertex shader snippets. This is not the full list. For a full
228reference, check the \l CustomMaterial page.
238\li void MAIN() is the entry point. This function must always be present in a custom
239vertex shader snippet, there is no point in providing one otherwise.
243\li The vertex position the shader receives as input. A common use case for vertex shaders
244in custom materials is to change (displace) the x, y, or z values of this vector, by simply
245assigning a value to the whole vector, or some of its components.
249\li The vertex normal from the input mesh data, or all zeroes if there were no normals provided.
250As with VERTEX, the shader is free to alter the value as it sees fit. The altered value is then
251used by the rest of the pipeline, including the lighting calculations in the fragment stage.
255\li The first set of texture coordinates from the input mesh data, or all zeroes if there
256were no UV values provided. As with VERTEX and NORMAL, the value can altered.
258\li MODELVIEWPROJECTION_MATRIX
260\li The model-view-projection matrix. To unify the behavior regardless of which graphics API
261rendering happens with, all vertex data and transformation matrices follow OpenGL conventions
262on this level. (Y axis pointing up, OpenGL-compatible projection matrix) Read only.
266\li The model (world) matrix. Read only.
270\li The transposed inverse of the top-left 3x3 slice of the model matrix. Read only.
274\li The camera position in world space. In the examples on this page this is \c{(0, 0, 600)}. Read only.
278\li The camera direction vector. In the examples on this page this is \c{(0, 0, -1)}. Read only.
282\li The near and far clip values of the camera. In the examples on this page this is \c{(10, 10000)}. Read only.
286\li Relevant only when rendering with a topology of points, for example because the
287\l{QQuick3DGeometry}{custom geometry} provides such a geometry for the mesh. Writing to
288this value is equivalent to setting \l{PrincipledMaterial::pointSize}{pointSize on a
293\li Like \c gl_Position. When not present, a default assignment statement is generated
294automatically using \c MODELVIEWPROJECTION_MATRIX and \c VERTEX. This is why an empty
295MAIN() is functional, and in most cases there will be no need to assign a custom value to
299Let's make a custom material that displaces the vertices according to some pattern. To
300make it more interesting, have some animated QML properties, the values of which end up
301being exposed as uniforms in the shader code. (to be precise, most properties are going to
302be mapped to members in a uniform block, backed by a uniform buffer at run time, but Qt
303Quick 3D conveniently makes such details transparent to the custom material author)
307\li Change in main.qml, material.vert
311 materials: CustomMaterial {
312 vertexShader: "material.vert"
313 property real uAmplitude: 0
314 NumberAnimation on uAmplitude {
315 from: 0; to: 100; duration: 5000; loops: -1
317 property real uTime: 0
318 NumberAnimation on uTime {
319 from: 0; to: 100; duration: 10000; loops: -1
326 VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude;
329 \li \image quick3d-custom-cube2-anim.gif
332\section2 Uniforms from QML properties
334Custom properties in the CustomMaterial object get mapped to uniforms. In the above
335example this includes \c uAmplitude and \c uTime. Any time the values change, the updated
336value will become visible in the shader. This concept may already be familiar from \l
339The name of the QML property and the GLSL variable must match. There is no separate
340declaration in the shader code for the individual uniforms. Rather, the QML property name
341can be used as-is. This is why the example above can just reference \c uTime and \c
342uAmplitude in the vertex shader snippet without any previous declaration for them.
344The following table lists how the types are mapped:
358\li sRGB to linear conversion is performed implicitly
378\li scalar value is \c w
393\section2 Improving the example
395Before moving further, let's make the example somewhat better looking. By adding a rotated
396rectangle mesh and making the \l DirectionalLight cast shadows, we can verify that the
397alteration to the cube's vertices is correctly reflected in all rendering passes,
398including shadow maps. To get a visible shadow, the light is now placed a bit higher on
399the Y axis, and a rotation is applied to have it pointing partly downwards. (this being a
400\c directional light, the rotation matters)
404\li main.qml, material.vert
412 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
413 PerspectiveCamera { z: 600 }
422 scale: Qt.vector3d(5, 5, 5)
424 materials: PrincipledMaterial { baseColor: "lightBlue" }
428 scale: Qt.vector3d(2, 2, 2)
430 materials: CustomMaterial {
431 vertexShader: "material.vert"
432 property real uAmplitude: 0
433 NumberAnimation on uAmplitude {
434 from: 0; to: 100; duration: 5000; loops: -1
436 property real uTime: 0
437 NumberAnimation on uTime {
438 from: 0; to: 100; duration: 10000; loops: -1
448 VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude;
451\li \image quick3d-custom-cube3-anim.gif
454\section2 Adding a fragment shader
456Many custom materials will want to have a fragment shader as well. In fact, many will want
457only a fragment shader. If there is no extra data to be passed from the vertex to fragment
458stage, and the default vertex transformation is sufficient, setting the \c vertexShader
459property can be left out from the \l CustomMaterial.
463\li Change in main.qml, material.frag
466materials: CustomMaterial {
467 fragmentShader: "material.frag"
475\li \image quick3d-custom-cube4.jpg
478Our first fragment shader contains an empty MAIN() function. This is no different than not
479specifying a fragment shader snippet at all: what we get looks like what we get with a
480default PrincipledMaterial.
482Let's look at some of the commonly used keywords in fragment shaders. This is not the full
483list, refer to the \l CustomMaterial documentation for a complete reference. Many of these
484are read-write, meaning they have a default value, but the shader can, and often will want
485to, assign a different value to them.
487As the names suggest, many of these map to similarly named \l PrincipledMaterial
488properties, with the same meaning and semantics, following the
489\l{https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material}{metallic-roughness
490material model}. It is up the custom material implementation to decide how these values
491are calculated: for example, a value for BASE_COLOR can be hard coded in the shader, can
492be based on sampling a texture, or can be calculated based on QML properties exposed as
493uniforms or on interpolated data passed along from the vertex shader.
503\li The base color and alpha value. Corresponds to \l{PrincipledMaterial::baseColor}. The
504final alpha value of the fragment is the model opacity multiplied by the base color
505alpha. The default value is \c{(1.0, 1.0, 1.0, 1.0)}.
509\li The color of self-illumination. Corresponds to
510\l{PrincipledMaterial::emissiveFactor}. The default value is \c{(0.0, 0.0, 0.0)}.
514\li \l{PrincipledMaterial::metalness}{Metalness} value in range 0-1. Default to 0, which
515means the material is dielectric (non-metallic).
519\li \l{PrincipledMaterial::roughness}{Roughness} value in range 0-1. The default value is
5200. Larger values soften specular highlights and blur reflections.
524\li \l{PrincipledMaterial::specularAmount}{The strength of specularity} in range 0-1. The
525default value is \c 0.5. For metallic objects with \c metalness set to \c 1 this value
526will have no effect. When both \c SPECULAR_AMOUNT and \c METALNESS have values larger than
5270 but smaller than 1, the result is a blend between the two material models.
531\li The interpolated normal in world space, adjusted for double-sidedness when face culling is disabled. Read only.
535\li The interpolated texture coordinates. Read only.
537\li VAR_WORLD_POSITION
539\li Interpolated vertex position in world space. Read only.
542Let's make the cube's base color red:
546\li Change in main.qml, material.frag
549materials: CustomMaterial {
550 fragmentShader: "material.frag"
556 BASE_COLOR = vec4(1.0, 0.0, 0.0, 1.0);
559\li \image quick3d-custom-cube5.jpg
562Now strengthen the level of self-illumination a bit:
566\li Change in main.qml, material.frag
569materials: CustomMaterial {
570 fragmentShader: "material.frag"
576 BASE_COLOR = vec4(1.0, 0.0, 0.0, 1.0);
577 EMISSIVE_COLOR = vec3(0.4);
580\li \image quick3d-custom-cube6.jpg
583Instead of having values hardcoded in the shader, we could also use QML properties exposed
584as uniforms, even animated ones:
588\li Change in main.qml, material.frag
591materials: CustomMaterial {
592 fragmentShader: "material.frag"
593 property color baseColor: "black"
594 ColorAnimation on baseColor {
595 from: "black"; to: "purple"; duration: 5000; loops: -1
602 BASE_COLOR = vec4(baseColor.rgb, 1.0);
603 EMISSIVE_COLOR = vec3(0.4);
606\li \image quick3d-custom-cube7-anim.gif
609Let's do something less trivial, something that is not implementable with a
610PrincipledMaterial and its standard, built-in properties. The following material
611visualizes the texture UV coordinates of the cube mesh. U runs 0 to 1, so from black to
612red, while V is also 0 to 1, black to green.
616\li Change in main.qml, material.frag
619materials: CustomMaterial {
620 fragmentShader: "material.frag"
626 BASE_COLOR = vec4(UV0, 0.0, 1.0);
629\li \image quick3d-custom-cube8.jpg
632While we are at it, why not visualize normals as well, this time on a sphere. Like with
633UVs, if a custom vertex shader snippet were to alter the value of NORMAL, the interpolated
634per-fragment value in the fragment shader, also exposed under the name NORMAL, would
635reflect those adjustments.
639\li Change in main.qml, material.frag
644 scale: Qt.vector3d(2, 2, 2)
645 materials: CustomMaterial {
646 fragmentShader: "material.frag"
653 BASE_COLOR = vec4(NORMAL, 1.0);
656\li \image quick3d-custom-cube9.jpg
661Let's switch over to a teapot model for a moment, make the material a blend of metallic
662and dielectric, and try to set a green base color for it. The \c green QColor value maps
663to \c{(0, 128, 0)}, based on which our first attempt could be:
667\li main.qml, material.frag
674 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
675 PerspectiveCamera { z: 600 }
678 source: "teapot.mesh"
679 scale: Qt.vector3d(60, 60, 60)
681 materials: CustomMaterial {
682 fragmentShader: "material.frag"
691 BASE_COLOR = vec4(0.0, 0.5, 0.0, 1.0);
693 SPECULAR_AMOUNT = 0.4;
699\image quick3d-custom-color1.jpg
701This does not look entirely right. Compare with the second approach:
705\li Change in main.qml, material.frag
708materials: CustomMaterial {
709 fragmentShader: "material.frag"
710 property color uColor: "green"
716 BASE_COLOR = vec4(uColor.rgb, 1.0);
718 SPECULAR_AMOUNT = 0.4;
722\li \image quick3d-custom-color2.jpg
725Switching to a PrincipledMaterial, we can confirm that setting the
726\l{PrincipledMaterial::baseColor} to "green" and following the metalness and other
727properties, the result is identical to our second approach:
731\li Change in main.qml
734materials: PrincipledMaterial {
741\li \image quick3d-custom-color3.jpg
744If the type of the \c uColor property was changed to \c vector4d, or any type other than
745\c color, the results would suddenly change and become identical to our first approach.
749The answer lies in the sRGB to linear conversion that is performed implicitly for color
750properties of DefaultMaterial, PrincipledMaterial, and also for custom properties with a
751\c color type in a CustomMaterial. Such conversion is not performed for any other value,
752so if the shader hardcodes a color value, or bases it on a QML property with a type
753different from \c color, it will be up to the shader to perform linearization in case the
754source value was in sRGB color space. Converting to linear is important since Qt Quick 3D
755performs \l{SceneEnvironment::tonemapMode}{tonemapping} on the results of fragment
756shading, and that process assumes values in the sRGB space as its input.
758The built-in QColor constants, such as, \c{"green"}, are all given in sRGB
759space. Therefore, just assigning \c{vec4(0.0, 0.5, 0.0, 1.0)} to BASE_COLOR in the first
760attempt is insufficient if we wanted a result that matches an RGB value \c{(0, 128, 0)} in
761the sRGB space. See the \c BASE_COLOR documentation in \l CustomMaterial for a formula for
762linearizing such color values. The same applies to color values retrieved by sampling
763textures: if the source image data is not in the sRGB color space, a conversion is needed
764(unless \l{SceneEnvironment::tonemapMode}{tonemapping} is disabled).
768Just writing a value less than \c 1.0 to \c{BASE_COLOR.a} is not sufficient if the
769expectation is to get alpha blending. Such materials will very often change the values of
770\l{CustomMaterial::sourceBlend}{sourceBlend} and
771\l{CustomMaterial::destinationBlend}{destinationBlend} properties to get the desired
774Also keep in mind that the combined alpha value is the \l{Node::opacity}{Node opacity}
775multiplied by the material alpha.
777To visualize, let's use a shader that assigns red with alpha \c 0.5 to \c BASE_COLOR:
781\li main.qml, material.frag
789 environment: SceneEnvironment {
790 backgroundMode: SceneEnvironment.Color
803 materials: CustomMaterial {
804 fragmentShader: "material.frag"
811 materials: CustomMaterial {
812 sourceBlend: CustomMaterial.SrcAlpha
813 destinationBlend: CustomMaterial.OneMinusSrcAlpha
814 fragmentShader: "material.frag"
822 materials: CustomMaterial {
823 sourceBlend: CustomMaterial.SrcAlpha
824 destinationBlend: CustomMaterial.OneMinusSrcAlpha
825 fragmentShader: "material.frag"
835 BASE_COLOR = vec4(1.0, 0.0, 0.0, 0.5);
838\li \image quick3d-custom-blend.jpg
841The first cube is writing 0.5 to the alpha value of the color but it does not bring
842visible results since alpha blending is not enabled. The second cube enables simple alpha
843blending via the CustomMaterial properties. The third one also assigns an opacity of 0.5
844to the Model, which means that the effective opacity is 0.25.
846\section2 Passing data between the vertex and fragment shader
848Calculating a value per vertex (for example, assuming a single triangle, for the 3 corners
849of the triangle), and then passing it on to the fragment stage, where for each fragment
850(for example, every fragment covered by the rasterized triangle) an interpolated value is
851made accessible. In custom material shader snippets this is made possible by the \c
852VARYING keyword. This provides a syntax similar to GLSL 120 and GLSL ES 100, but will work
853regardless of the graphics API used at run time. The engine will take care of rewriting
854the varying declaration as appropriate.
856Let's see how the classic texture sampling with UV coordinates would look like. Textures
857are going to be covered in an upcoming section, for now let's focus on how we get the UV
858coordinates that can be passed to the \c{texture()} function in the shader.
862\li main.qml, material.vert, material.frag
869 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
870 PerspectiveCamera { z: 600 }
874 scale: Qt.vector3d(4, 4, 4)
876 materials: CustomMaterial {
877 vertexShader: "material.vert"
878 fragmentShader: "material.frag"
879 property TextureInput someTextureMap: TextureInput {
881 source: "qt_logo_rect.png"
900 BASE_COLOR = texture(someTextureMap, uv);
909\row \li \image quick3d-custom-varying-map.png
910\li \image quick3d-custom-varying1.jpg
913Note that \c VARYING declarations. The name and type must match, \c uv in the fragment
914shader will expose the interpolated UV coordinate for the current fragment.
916Any other type of data can be passed on to the fragment stage in a similar manner. It is
917worth noting that in many cases setting up the material's own varyings is not necessary
918because there are builtins provided that cover many of typical needs. This includes making
919the (interpolated) normals, UVs, world position (\c VAR_WORLD_POSITION), or the vector
920pointing towards the camera (\c VIEW_VECTOR).
922The above example can in fact be simplified to the following as \c UV0 is automatically
923available in the fragment stage as well:
927\li Change in main.qml, material.frag
930materials: CustomMaterial {
931 fragmentShader: "material.frag"
932 property TextureInput someTextureMap: TextureInput {
934 source: "qt_logo_rect.png"
941 BASE_COLOR = texture(someTextureMap, UV0);
944\li \image quick3d-custom-varying1.jpg
949A \l CustomMaterial has no built-in texture maps, meaning there is no equivalent of, for
950example, \l{PrincipledMaterial::baseColorMap}. This is because implementing the same is
951often trivial, while giving a lot more flexibility than what DefaultMaterial and
952PrincipledMaterial has built in. Besides simply sampling a texture, custom fragment shader
953snippets are free to combine and blend data from various sources when calculating the
954values they assign to \c BASE_COLOR, \c EMISSIVE_COLOR, \c ROUGHNESS, etc. They can base
955these calculations on data provided via QML properties, interpolated data sent on from the
956vertex stage, values retrieved from sampling textures, and on hardcoded values.
958As the previous example shows, exposing a texture to the vertex, fragment, or both shaders
959is very similar to scalar and vector uniform values: a QML property with the type \l
960TextureInput will automatically get associated with a \c sampler2D in the shader code. As
961always, there is no need to declare this sampler in the shader code.
963A \l TextureInput references a \l Texture, with an additional
964\l{TextureInput::enabled}{enabled} property. A \l Texture can source its data in three
965ways: \l{Texture::source}{from an image file}, \l{Texture::sourceItem}{from a texture with
966live Qt Quick content}, or \l{Texture::textureData}{can be provided from C++} via
969\note When it comes to \l Texture properties, the source, tiling, and filtering related
970ones are the only ones that are taken into account implicitly with custom materials, as
971the rest (such as, UV transformations) is up to the custom shaders to implement as they
974Let's see an example where a model, a sphere in this case, is textured using live Qt Quick
979\li main.qml, material.frag
986 environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" }
987 PerspectiveCamera { z: 600 }
991 scale: Qt.vector3d(4, 4, 4)
993 materials: CustomMaterial {
994 fragmentShader: "material.frag"
995 property TextureInput someTextureMap: TextureInput {
997 sourceItem: Rectangle {
998 width: 512; height: 512
1001 width: 32; height: 32
1002 anchors.horizontalCenter: parent.horizontalCenter
1005 NumberAnimation on rotation { from: 0; to: 360; duration: 3000; loops: -1 }
1008 anchors.centerIn: parent
1023 vec2 uv = vec2(UV0.x, 1.0 - UV0.y);
1024 vec4 c = texture(someTextureMap, uv);
1030\image quick3d-custmat-tex1-anim.gif
1032Here the 2D subtree (Rectangle with two children: another Rectangle and the Text) is
1033rendered in to an 512x512 2D texture every time this mini-scene changes. The texture is
1034then exposed to the custom material under the name of \c someTextureMap.
1036Note the flipping of the V coordinate in the shader. As noted above, custom materials,
1037where there is full programmability on shader level, do not offer the "fixed" features of
1038\l Texture and \l PrincipledMaterial. This means that any transformations to the UV
1039coordinates will need to be applied by the shader. Here we know that the texture is
1040generated via \l{Texture::sourceItem} and so V needs to be flipped to get something that
1041matches the UV set of the mesh we are using.
1043What this example shows is possible to do with a \l PrincipledMaterial too. Let's make it
1044more interesting by doing a simple emboss effect in addition:
1053 vec2 uv = vec2(UV0.x, 1.0 - UV0.y);
1054 vec2 size = vec2(textureSize(someTextureMap, 0));
1055 vec2 d = vec2(1.0 / size.x, 1.0 / size.y);
1056 vec4 diff = texture(someTextureMap, uv + d) - texture(someTextureMap, uv - d);
1057 float c = (diff.x + diff.y + diff.z) + 0.5;
1058 BASE_COLOR = vec4(c, c, c, 1.0);
1061\li \image quick3d-custmat-tex2-anim.gif
1064With the features covered so far a wide range of possibilities are open for creating
1065materials that shade the meshes in visually impressive ways. To finish the basic tour,
1066let's look at an example that applies height and normal maps to a plane mesh. (a dedicated
1067\c{.mesh} file is used here because the builtin \c{#Rectangle} does not have enough
1068subdivisions) For better lighting results, we will use image based lighting with a 360
1069degree HDR image. The image is also set as the skybox to make it more clear what is
1072First let's start with an empty CustomMaterial:
1083 anchors.fill: parent
1084 environment: SceneEnvironment {
1085 backgroundMode: SceneEnvironment.SkyBox
1086 lightProbe: Texture {
1087 source: "00489_OpenfootageNET_snowfield_low.hdr"
1094 source: "plane.mesh"
1095 scale: Qt.vector3d(400, 400, 400)
1098 eulerRotation.x: -90
1099 materials: CustomMaterial { }
1104\li \image quick3d-custom-tex3.jpg
1107Now let's make some shaders that apply a height and normal map to the mesh:
1114\li \image quick3d-custom-heightmap.png
1115\li \image quick3d-custom-normalmap.jpg
1120\li material.vert, material.frag
1123float getHeight(vec2 pos)
1125 return texture(heightMap, pos).r;
1130 const float offset = 0.004;
1131 VERTEX.y += getHeight(UV0);
1132 TANGENT = normalize(vec3(0.0, getHeight(UV0 + vec2(0.0, offset)) - getHeight(UV0 + vec2(0.0, -offset)), offset * 2.0));
1133 BINORMAL = normalize(vec3(offset * 2.0, getHeight(UV0 + vec2(offset, 0.0)) - getHeight(UV0 + vec2(-offset, 0.0)), 0.0));
1134 NORMAL = cross(TANGENT, BINORMAL);
1140 vec3 normalValue = texture(normalMap, UV0).rgb;
1141 normalValue.xy = normalValue.xy * 2.0 - 1.0;
1142 normalValue.z = sqrt(max(0.0, 1.0 - dot(normalValue.xy, normalValue.xy)));
1143 NORMAL = normalize(mix(NORMAL, TANGENT * normalValue.x + BINORMAL * normalValue.y + NORMAL * normalValue.z, 1.0));
1150\li Change in main.qml
1154materials: CustomMaterial {
1155 vertexShader: "material.vert"
1156 fragmentShader: "material.frag"
1157 property TextureInput normalMap: TextureInput {
1158 texture: Texture { source: "normalmap.jpg" }
1160 property TextureInput heightMap: TextureInput {
1161 texture: Texture { source: "heightmap.png" }
1165\li \image quick3d-custom-tex4.jpg
1168\note The \l WasdController object can be immensely helpful during development and
1169troubleshooting as it allows navigating and looking around in the scene with the keyboard
1170and mouse in a familiar manner. Having a camera controlled by the WasdController is as
1174import QtQuick3D.Helpers
1182 controlledObject: camera
1186\section2 Depth and screen textures
1188When a custom shader snippet uses the \c DEPTH_TEXTURE or \c SCREEN_TEXTURE keywords, it
1189opts in to generating the corresponding textures in a separate render pass, which is not
1190necessarily a cheap operation, but allows implementing a variety of techniques, such as
1191refraction for glass-like materials.
1193\c DEPTH_TEXTURE is a \c sampler2D that allows sampling a texture with the contents of the
1194depth buffer with all the \c opaque objects in the scene rendered. Similarly, \c
1195SCREEN_TEXTURE is a \c sampler2D that allows sampling a texture containing the contents of
1196the scene excluding any transparent materials or any materials also using the
1197SCREEN_TEXTURE. The texture can be used for materials that require the contents of the
1198framebuffer they are being rendered to. The SCREEN_TEXTURE texture uses the same clear mode
1199as the View3D. The size of these textures matches the size of the View3D in pixels.
1201Let's have a simple demonstration by visualizing the depth buffer contents via \c
1202DEPTH_TEXTURE. The camera's \l{PerspectiveCamera::clipFar}{far clip value} is reduced here from the
1203default 10000 to 2000, in order to have a smaller range, and so have the visualized depth
1204value differences more obvious. The result is a rectangle that happens to visualize the
1205depth buffer for the scene over its surface.
1209\li main.qml, material.frag
1215import QtQuick3D.Helpers
1221 anchors.fill: parent
1228 DirectionalLight { }
1231 scale: Qt.vector3d(2, 2, 2)
1232 position: Qt.vector3d(150, 200, -1000)
1235 materials: PrincipledMaterial { }
1239 scale: Qt.vector3d(2, 2, 2)
1240 position: Qt.vector3d(400, 200, -1000)
1241 materials: PrincipledMaterial { }
1246 scale: Qt.vector3d(2, 2, 2)
1247 position: Qt.vector3d(-150, 200, -600)
1248 materials: PrincipledMaterial { }
1252 scale: Qt.vector3d(2, 2, 2)
1253 position: Qt.vector3d(0, 400, -1200)
1254 materials: PrincipledMaterial { }
1257 source: "#Rectangle"
1258 scale: Qt.vector3d(3, 3, 3)
1260 materials: CustomMaterial {
1261 fragmentShader: "material.frag"
1266 controlledObject: camera
1273 float zNear = CAMERA_PROPERTIES.x;
1274 float zFar = CAMERA_PROPERTIES.y;
1275 float zRange = zFar - zNear;
1276 vec4 depthSample = texture(DEPTH_TEXTURE, vec2(UV0.x, 1.0 - UV0.y));
1277 float zn = 2.0 * depthSample.r - 1.0;
1278 float d = 2.0 * zNear * zFar / (zFar + zNear - zn * zRange);
1280 BASE_COLOR = vec4(d, d, d, 1.0);
1283\li \image quick3d-custom-depth-anim.gif
1286Note how the cylinder is not present in \c DEPTH_TEXTURE due to its reliance on
1287semi-transparency, which puts it into a different category than the other objects that are
1288all opaque. These objects do not write into the depth buffer, although they do test
1289against the depth values written by opaque objects, and rely on being rendered in back to
1290front order. Hence they are not present in \c DEPTH_TEXTURE either.
1292What happens if we switch the shader to sample \c SCREEN_TEXTURE instead?
1301 vec4 c = texture(SCREEN_TEXTURE, vec2(UV0.x, 1.0 - UV0.y));
1303 c.rgb = vec3(0.2, 0.1, 0.3);
1307\li \image quick3d-custom-screen.jpg
1310Here the rectangle is textured with \c SCREEN_TEXTURE, while replacing transparent pixels
1313\section2 Light processor functions
1315An advanced feature of \l CustomMaterial is the ability to define functions in the
1316fragment shader that reimplement the lighting equations that are used to calculate the
1317fragment color. A light processor function, when present, is called once per each light in
1318the scene, for each fragment. There is a dedicated function for different light types, as
1319well as the ambient and specular contribution. When no corresponding light processor
1320function is present, the standard calculations are used, just like a PrincipledMaterial
1321would do. When a light processor is present, but the function body is empty, it means
1322there will be no contribution from a given type of lights in the scene.
1324Refer to the \l CustomMaterial documentation for details on functions such as \c
1325DIRECTIONAL_LIGHT, \c POINT_LIGHT, \c SPOT_LIGHT, \c AMBIENT_LIGHT, and \c SPECULAR_LIGHT.
1327\section2 Unshaded custom materials
1329There is another type of \l CustomMaterial: \c unshaded custom materials. All the example
1330so far used \c shaded custom materials, with the
1331\l{CustomMaterial::shadingMode}{shadingMode} property left at its default
1332CustomMaterial.Shaded value.
1334What happens if we switch this property to CustomMaterial.Unshaded?
1336First of all, keywords like \c BASE_COLOR, \c EMISSIVE_COLOR, \c METALNESS, etc. no longer
1337have the desired effect. This is because an unshaded material, as the name suggests, does
1338not automatically get amended with much of the standard shading code, thus ignoring
1339lights, image based lighting, shadows, and ambient occlusion in the scene. Rather, an
1340unshaded material gives full control to the shader via the \c FRAGCOLOR keyword. This is
1341similar to gl_FragColor: the color assigned to \c FRAGCOLOR is the result and the final
1342color of the fragment, without any further adjustments by Qt Quick 3D.
1346\li main.qml, material.frag, material2.frag
1353 anchors.fill: parent
1354 environment: SceneEnvironment {
1355 backgroundMode: SceneEnvironment.Color
1358 PerspectiveCamera { z: 600 }
1359 DirectionalLight { }
1364 materials: CustomMaterial {
1365 fragmentShader: "material.frag"
1372 materials: CustomMaterial {
1373 shadingMode: CustomMaterial.Unshaded
1374 fragmentShader: "material2.frag"
1383 BASE_COLOR = vec4(1.0);
1389 FRAGCOLOR = vec4(1.0);
1392\li \image quick3d-custom-unshaded1.jpg
1395Notice how the right cylinder ignores the DirectionalLight in the scene. Its shading knows
1396nothing about scene lighting, the final fragment color is all white.
1398The vertex shader in an unshaded material still has the typical inputs available: \c
1399VERTEX, \c NORMAL, \c MODELVIEWPROJECTION_MATRIX, etc. and can write to \c POSITION. The
1400fragment shader no longer has the similar conveniences available, however: \c NORMAL, \c
1401UV0, or \c VAR_WORLD_POSITION are not available in an unshaded material's fragment
1402shader. Rather, it is now up to the shader code to calculate and pass on using \c VARYING
1403everything it needs to determine the final fragment color.
1405Let's look at an example that has both a vertex and fragment shader. The altered vertex
1406position is passed on to the fragment shader, with an interpolated value made available to
1411\li main.qml, material.vert, material.frag
1417 anchors.fill: parent
1418 environment: SceneEnvironment {
1419 backgroundMode: SceneEnvironment.Color
1422 PerspectiveCamera { z: 600 }
1425 scale: Qt.vector3d(3, 3, 3)
1426 materials: CustomMaterial {
1427 property real time: 0.0
1428 NumberAnimation on time { from: 0; to: 100; duration: 20000; loops: -1 }
1429 property real amplitude: 10.0
1430 shadingMode: CustomMaterial.Unshaded
1431 vertexShader: "material.vert"
1432 fragmentShader: "material.frag"
1443 pos.x += sin(time * 4.0 + pos.y) * amplitude;
1444 POSITION = MODELVIEWPROJECTION_MATRIX * vec4(pos, 1.0);
1451 FRAGCOLOR = vec4(vec3(pos.x * 0.02, pos.y * 0.02, pos.z * 0.02), 1.0);
1456\image quick3d-custom-unshaded-anim.gif
1458Unshaded materials are useful when interacting with scene lighting is not necessary or
1459desired, and the material needs full control on the final fragment color. Notice how the
1460example above has neither a DirectionalLight nor any other lights, but the sphere with the
1461custom material shows up as expected.
1463\note An unshaded material that only has a vertex shader snippet, but does not specify the
1464fragmentShader property, will still be functional but the results are as if the
1465shadingMode was set to Shaded. Therefore it makes little sense to switch shadingMode for
1466materials that only have a vertex shader.
1468\section1 Programmability for Effects
1470Post-processing effects apply one or more fragment shaders to the result of a \l
1471View3D. The output from these fragment shaders is then displayed instead of the original
1472rendering results. This is conceptually very similar to Qt Quick's \l ShaderEffect and \l
1475\note Post-processing effects are only available when the
1476\l{View3D::renderMode}{renderMode} for the View3D is set to View3D.Offscreen.
1478Custom vertex shader snippets can also be specified for an effect, but they have limited
1479usefulness and therefore are expected to be used relatively rarely. The vertex input for a
1480post-processing effect is a quad (either two triangles or a triangle strip), transforming
1481or displacing the vertices of that is often not helpful. It can however make sense to have
1482a vertex shader in order to calculate and pass on data to the fragment shader using the \c
1483VARYING keyword. As usual, the fragment shader will then receive an interpolated value
1484based on the current fragment coordinate.
1486The syntax of the shader snippets associated with a \l Effect is identical to the shaders
1487for an unshaded \l CustomMaterial. When it comes to the built-in special keywords, \c
1488VARYING, \c MAIN, \c FRAGCOLOR (fragment shader only), \c POSITION (vertex shader only), \c
1489VERTEX (vertex shader only), and \c MODELVIEWPROJECTION_MATRIX work identically to \l
1492The most important special keywords for \l Effect fragment shaders are the following:
1502\li The sampler for the input texture. An effect will typically sample this using \c INPUT_UV.
1506\li UV coordinates for sampling \c INPUT.
1510\li The size of the \c INPUT texture, in pixels. This is a convenient alternative to calling textureSize().
1514\li The size of the output texture, in pixels. Equal to \c INPUT_SIZE in many cases, but a multi-pass effect
1515may have passes that output to intermediate textures with different sizes.
1519\li Depth texture with the depth buffer contents with the opaque objects in the scene. Like with CustomMaterial,
1520the presence of this keyword in the shader triggers generating the depth texture automatically.
1523\section2 A post-processing effect
1525Let's start with a simple scene, this time using a few more objects, including a textured
1526rectangle that uses a checkerboard texture as its base color map.
1537 anchors.fill: parent
1538 environment: SceneEnvironment {
1539 backgroundMode: SceneEnvironment.Color
1543 PerspectiveCamera { z: 400 }
1545 DirectionalLight { }
1549 source: "checkerboard.png"
1552 tilingModeHorizontal: Texture.Repeat
1553 tilingModeVertical: Texture.Repeat
1557 source: "#Rectangle"
1558 scale: Qt.vector3d(10, 10, 1)
1559 eulerRotation.x: -45
1560 materials: PrincipledMaterial {
1561 baseColorMap: checkerboard
1567 position: Qt.vector3d(100, -50, 100)
1568 materials: PrincipledMaterial { }
1575 materials: PrincipledMaterial { }
1580 position: Qt.vector3d(-150, 200, -100)
1581 materials: PrincipledMaterial { }
1586\li \image quick3d-custom-effect-section-scene.jpg
1589Now let's apply an affect to the entire scene. More precisely, to the View3D. When there
1590are multiple View3D items in the scene, each has its own SceneEnvironment and therefore
1591have their own post-processing effect chain. In the example there is one single View3D
1592covering the entire window.
1596\li Change in main.qml
1599 environment: SceneEnvironment {
1600 backgroundMode: SceneEnvironment.Color
1607 property real uRed: 1.0
1608 NumberAnimation on uRed { from: 1; to: 0; duration: 5000; loops: -1 }
1611 stage: Shader.Fragment
1612 shader: "effect.frag"
1620 vec4 c = texture(INPUT, INPUT_UV);
1627This simple effect alters the red color channel value. Exposing QML properties as uniforms
1628works the same way with effects as with custom materials. The shader starts with a line
1629that is going to be very common when writing fragment shaders fro effects: sampling \c
1630INPUT at the UV coordinates \c INPUT_UV. It then performs its desired calculations, and
1631assigns the final fragment color to \c FRAGCOLOR.
1633\image quick3d-custom-first-effect-anim.gif
1635Many properties set in the example are in plural (effects, passes, shaders). While the
1636list \c{[ ]} syntax can be omitted when having a single element only, all these properties
1637are lists, and can hold more than one element. Why is this?
1641\li \l{SceneEnvironment::effects}{effects} is a list, because View3D allows chaining
1642multiple effects together. The effects are applied in the order in which they are added to
1643the list. This allows easily applying two or more effects together to the View3D, and is
1644similar to what one can achieve in Qt Quick by nesting \l ShaderEffect items. The \c INPUT
1645texture of the next effect is always a texture that contains the previous effect's
1646output. The output of the last effect in what gets used as the final output of the View3D.
1648\li \l{Effect::passes}{passes} is a list, because unlike ShaderEffect, Effect has built-in
1649support for multiple passes. A multi-pass effect is more powerful than chaining together
1650multiple, independent effects in \l{SceneEnvironment::effects}{effects}: a pass can output
1651to a temporary, intermediate texture, which can then be used as input to subsequent
1652passes, in addition to the original input texture of the effect. This allows creating
1653complex effects that calculate, render, and blend together multiple textures in order to
1654get to the final fragment color. This advanced use case is not going to be covered
1655here. Refer to the \l Effect documentation page for details.
1657\li \l{Pass::shaders}{shaders} is a list, because an effect may have both a vertex and a
1658fragment shader associated.
1662\section2 Chaining multiple effects
1664Let's look at an example where the effect from the previous example gets complemented by
1665another effect similar to the built-in \l DistortionSpiral effect.
1669\li Change in main.qml
1672 environment: SceneEnvironment {
1673 backgroundMode: SceneEnvironment.Color
1675 effects: [redEffect, distortEffect]
1680 property real uRed: 1.0
1681 NumberAnimation on uRed { from: 1; to: 0; duration: 5000; loops: -1 }
1684 stage: Shader.Fragment
1685 shader: "effect.frag"
1692 property real uRadius: 0.1
1693 NumberAnimation on uRadius { from: 0.1; to: 1.0; duration: 5000; loops: -1 }
1696 stage: Shader.Fragment
1697 shader: "effect2.frag"
1705 vec2 center_vec = INPUT_UV - vec2(0.5, 0.5);
1706 center_vec.y *= INPUT_SIZE.y / INPUT_SIZE.x;
1707 float dist_to_center = length(center_vec) / uRadius;
1708 vec2 texcoord = INPUT_UV;
1709 if (dist_to_center <= 1.0) {
1710 float rotation_amount = (1.0 - dist_to_center) * (1.0 - dist_to_center);
1711 float r = radians(360.0) * rotation_amount / 4.0;
1712 float cos_r = cos(r);
1713 float sin_r = sin(r);
1714 mat2 rotation = mat2(cos_r, sin_r, -sin_r, cos_r);
1715 texcoord = vec2(0.5, 0.5) + rotation * (INPUT_UV - vec2(0.5, 0.5));
1717 vec4 c = texture(INPUT, texcoord);
1723\image quick3d-custom-chained-effect-anim.gif
1725Now the perhaps surprising question: why is this a bad example?
1727More precisely, it is not bad, but rather shows a pattern that can often be beneficial to
1730Chaining effects this way can be useful, but it is important to keep in mind the
1731performance implications: doing two render passes (one to generate a texture with the
1732adjusted red color channel, and then another one two calculate the distortion) is quite
1733wasteful when one would be enough. If the fragment shader snippets were combined, the same
1734result could have been achieved with one single effect.
1736\section1 Defining Mesh and Texture Data from C++
1738Procedurally generating mesh and texture image data both follow similar steps:
1741\li Subclass \l QQuick3DGeometry or \l QQuick3DTextureData
1742\li Set the desired vertex or image data upon construction by calling the protected member functions
1744\li If dynamic changes are needed afterwards at some point, set the new data and call update()
1745\li Once the implementation is done, the class needs to be registered to make it visible in QML
1746\li \l Model and \l Texture objects in QML can now use the custom vertex or image data provider by
1747setting the \l{Model::geometry} or \l{Texture::textureData} property
1750\section2 Custom vertex data
1752Vertex data refers to the sequence of (typically \c float) values that make up a
1753mesh. Instead of loading \c{.mesh} files, a custom geometry provider is responsible for
1754providing the same data. The vertex data consist of \c attributes, such as position,
1755texture (UV) coordinates, or normals. The specification of attributes describes what kind
1756of attributes are present, the component type (for example, a 3 component float vector for
1757vertex position consisting of x, y, z values), which offset they start at in the provided
1758data, and what the stride (the increment that needs to be added to the offset to point to
1759the next element for the same attribute) is.
1761This may seem familiar if one has worked with graphics APIs, such as OpenGL or Vulkan
1762directly, because the way vertex input is specified with those APIs maps loosely to what a
1763\c{.mesh} file or a \l QQuick3DGeometry instance defines.
1765In addition, the mesh topology (primitive type) must be specified too. For indexed
1766drawing, the data for an index buffer must be provided as well.
1768There is one built-in custom geometry implementation: the QtQuick3D.Helpers module
1769includes a \l GridGeometry type. This allows rendering a grid in the scene with line
1770primitives, without having to implement a custom \l QQuick3DGeometry subclass.
1772One other common use cases is rendering points. This is fairly simple to do since the
1773attribute specification is going to be minimal: we provide three floats (x, y, z) for each
1774vertex, nothing else. A QQuick3DGeometry subclass could implement a geometry consisting of
17752000 points similarly to the following:
1780 const int stride = 3 * sizeof(float);
1782 v.resize(N * stride);
1783 float *p = reinterpret_cast<float *>(v.data());
1784 QRandomGenerator *rg = QRandomGenerator::global();
1785 for (int i = 0; i < N; ++i) {
1786 const float x = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
1787 const float y = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
1794 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Points);
1795 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
1798Combined with a material of
1802 lighting: DefaultMaterial.NoLighting
1803 cullMode: DefaultMaterial.NoCulling
1804 diffuseColor: "yellow"
1809the end result is similar to this (here viewed from an altered camera angle, with the help
1810of \l WasdController):
1812\image quick3d-custom-points.jpg
1814\note Be aware that point sizes and line widths other than 1 may not be supported at run
1815time, depending on the underlying graphics API. This is not something Qt has control
1816over. Therefore, it can become necessary to implement alternative techniques instead of
1817relying on point and line drawing.
1819\section2 Custom texture data
1821With textures, the data that needs to be provided is a lot simpler structurally: it is the
1822raw pixel data, with a varying number of bytes per pixel, depending on the texture
1823format. For example, an \c RGBA texture expects four bytes per pixel, whereas \c RGBA16F
1824is four half-floats per pixel. This is similar to what a \l QImage stores
1825internally. However, Qt Quick 3D textures can have formats the data for which cannot be
1826represented by a QImage. For example, floating point HDR textures, or compressed
1827textures. Therefore the data for \l QQuick3DTextureData is always provided as a raw
1828sequence of bytes. This may seem familiar if one has worked with graphics APIs, such as
1829OpenGL or Vulkan directly.
1831For details, refer to the \l QQuick3DGeometry and \l QQuick3DTextureData documentation pages.
1833\sa CustomMaterial, Effect, QQuick3DGeometry, QQuick3DTextureData, {Qt Quick 3D - Custom
1834Effect Example}, {Qt Quick 3D - Custom Shaders Example}, {Qt Quick 3D - Custom Materials
1835Example}, {Qt Quick 3D - Custom Geometry Example}, {Qt Quick 3D - Procedural Texture