Qt Quick 3D supports instancing of
Model
objects. Instancing refers to a technique where one object is rendered multiple times with a single draw call. (For example the OpenGL function
glDrawElementsInstanced
)。
Instancing allows duplicating a model with variations. In contrast to using a Repeater3D , the model and its graphics resources are only allocated once. The rendering of the duplicated instances is done at a low level by the GPU. Depending on the complexity of the model, this can give a performance improvement of several orders of magnitude.
In practice, instancing is done by defining a table that specifies how each instance is modified relative to the base model.
The main principle of the instancing API is that it is explicit: It doesn't try to autodetect opportunities for instancing within the existing API. Instead, each model is marked individually by setting its instancing property to reference an Instancing object. The same Instancing object can be used on multiple models at the same time.
The Instancing object specifies a table that defines how each copy is rendered. The available modifications are:
Qt provides three QML types that inherit from Instancing:
The instancing example shows how to create a scene using the QML API.
Other kinds of instance tables can be defined in C++ by subclassing QQuick3DInstancing . For example, the particle system uses its own instancing table internally. It is available as ModelParticle3D.instanceTable .
By writing custom shader code, it is possible to use instancing to control additional properties, such as variables for physically based rendering, skeletal animation weights, distortion, or anything else that can be expressed with custom materials. The custom data in the instancing table consists of four floating point numbers.
The custom instancing example shows how to combine custom materials and an instance table implemented in C++.
Correct alpha blending requires that semi-transparent objects are rendered back-to-front. For this reason, QtQuick3D sorts opaque and semi-transparent objects separately, and renders them in the correct order. With instancing, however, the GPU will render the instances in the order specified by the instancing table, if depth-sorting is not turned on. For performance reasons, QtQuick3D does not sort the table by default as it can take long time with large number of instances. This means that if semi-transparent instances overlap with each other, or with other semi-transparent objects, the results may look wrong. In general, the error is less visible when the opacity is low.
Fully opaque objects together with non-overlapping semi-transparent objects will always be rendered correctly, since Qt uses depth buffer testing to avoid drawing behind opaque objects. However, the lack of sorting has potential performance implications for opaque objects: They may not be rendered in the optimal order, meaning that the same pixel can be written multiple times, making more work for the fragment shader.
The renderer does not inspect the contents of the instancing table, so it must be specified explicitly when an instance table contains semi-transparent alpha values: Set the
hasTransparency
property to to
true
to make sure that the renderer enables alpha blending. This applies to all the instances: Even fully opaque instances will be rendered without depth testing, potentially causing visible errors.
The rendering order relative to the rest of the scene can be adjusted by setting the depth bias of the model.
Each instance has its own transform in the instance table. This is combined with the transforms on the instanced model. This is slightly complex, since there are several use cases:
To support all these cases, The model’s transform is split into two parts: the local instance transform ,和 global instance transform . Conceptually, instancing is performed like this:
By default, the local instance transform of a model consists of the model’s scale and rotation, while the rest goes into the global instance transform.
This can be controlled by setting the model’s instanceRoot property. This defines the origin of the instance’s coordinate system. The most common use is when instancing a hierarchy of models. For example, a sphere orbiting around a cube:
Model { id: cube instancing: someInstanceTable source: "#Cube" materials: DefaultMaterial { diffuseColor: "lightgray" } Node { Model { source: "#Sphere" instanceRoot: cube instancing: cube.instancing x: 150 materials: DefaultMaterial { diffuseColor: "gray" } } NumberAnimation on eulerRotation.y { from: 0 to: 360 duration: 4000 loops: Animation.Infinite } } }
The
instanceRoot
is necessary to specify that the sphere instance should be positioned as if it were an element of the cube. Each model in a hierarchy still needs to specify the
instancing
property: in normal cases they should all be set to the same
Instancing
对象。
instanceRoot
can also be used when instancing a single model. For example, a cylinder rotating around an off-center point:
Node { id: parentNode Model { source: "#Cylinder" instanceRoot: parentNode instancing: anotherInstanceTable x: 25 materials: DefaultMaterial { diffuseColor: "white" } } NumberAnimation on eulerRotation.y { from: 0 to: 360 duration: 1000 loops: Animation.Infinite } }
Picking is a mechanism that enables selecting a model from a user interface interaction. With instanced rendering, there are several representations of the same model, so the pick result will include an instance index . Instanced picking is enabled by setting the pickable property on the base model.