Qt Quick 3D - Benutzerdefiniertes instanziertes Rendering

Demonstriert die Instanzierung mit benutzerdefinierten Materialien und einer C++-Instanztabelle.

Dieses Beispiel zeigt, wie Instanzdaten programmatisch in C++ erstellt werden und wie benutzerdefinierte Materialien mit Instanzrendering verwendet werden können.

Benutzerdefinierte Instanztabelle

Wir definieren unsere Tabelle als Unterklasse von QQuick3DInstancing und fügen einige Eigenschaften hinzu, damit wir sie von QML aus steuern können:

class CppInstanceTable : public QQuick3DInstancing
{
    Q_OBJECT
    QML_ELEMENT

    Q_PROPERTY(int gridSize READ gridSize WRITE setGridSize NOTIFY gridSizeChanged)
    Q_PROPERTY(float gridSpacing READ gridSpacing WRITE setGridSpacing NOTIFY gridSpacingChanged)
    Q_PROPERTY(int randomSeed READ randomSeed WRITE setRandomSeed NOTIFY randomSeedChanged)

Die virtuelle Funktion getInstanceBuffer wird reimplementiert, um die Instanzdaten zurückzugeben:

QByteArray CppInstanceTable::getInstanceBuffer(int *instanceCount)
{
    if (m_dirty) {
        BlockTable blocks(m_gridSize, m_randomSeed);
        m_instanceData.resize(0);

        auto idxToPos = [this](int i) -> float { return m_gridSpacing * (i - m_gridSize / 2); };

        int instanceNumber = 0;
        for (int i = 0; i < m_gridSize; ++i) {
            float xPos = idxToPos(i);
            for (int j = 0; j < m_gridSize; ++j) {
                float zPos = idxToPos(j);
                int lowest = blocks.lowestVisible(i, j);
                int highest = blocks.highestBlock(i, j);
                for (int k = lowest; k <= highest; ++k) {
                    float yPos = idxToPos(k);
                    QColor color = blocks.getBlockColor(i, j, k);
                    float waterAnimation = blocks.isWaterSurface(i, j, k) ? 1.0 : 0.0;
                    auto entry = calculateTableEntry({ xPos, yPos, zPos }, { 1.0, 1.0, 1.0 }, {}, color, { waterAnimation, 0, 0, 0 });
                    m_instanceData.append(reinterpret_cast<const char *>(&entry), sizeof(entry));
                    instanceNumber++;
                }
            }
        }
        m_instanceCount = instanceNumber;
        m_dirty = false;
    }
    if (instanceCount)
        *instanceCount = m_instanceCount;

    return m_instanceData;
}

Benutzerdefiniertes Material

Wir verwenden ein schattiertes benutzerdefiniertes Material, was bedeutet, dass Qt uns die grundlegende Implementierung liefert und wir nur zusätzliche Logik angeben.

Die einzige Anpassung, die wir für den Vertex-Shader benötigen, ist die Übergabe von Informationen an den Fragment-Shader. Standardmäßig stellt Qt dem Vertex-Shader nur die Instanzdaten zur Verfügung, also geben wir sie als vCustomData weiter. Wir berechnen auch die globale Position des Scheitelpunktes und stellen sie als vGlobalPosition zur Verfügung.

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

VARYING vec4 vCustomData;
VARYING vec3 vGlobalPosition;
void MAIN()
{
    vCustomData = INSTANCE_DATA;

    // MODEL_MATRIX does not exist when instancing
    vec4 pos = INSTANCE_MODEL_MATRIX * vec4(VERTEX, 1.0);
    vGlobalPosition = pos.xyz;
}

Der Fragment-Shader führt eine einfache Wellenanimation für Wasseroberflächen durch. Alles andere erhält einen subtilen radialen Gradienten. Der Unterschied wird durch das erste Element der benutzerdefinierten Daten bestimmt.

VARYING vec4 vCustomData;
VARYING vec3 vGlobalPosition;

void MAIN()
{
    METALNESS = 0.0;
    ROUGHNESS = 1.0;
    FRESNEL_POWER = 5.0;

    float c;

    if (vCustomData.x > 0)
        c = 1.0 - (1.0 + sin(sqrt(vGlobalPosition.x*vGlobalPosition.x + vGlobalPosition.z*vGlobalPosition.z) - uTime/200.0)) * 0.2;
    else
        c = 1.0 - 0.25 * (UV0.x*UV0.x + UV0.y*UV0.y);

    BASE_COLOR = vec4(c, c, c, 1.0);
}

Verwendung der benutzerdefinierten Tabelle und des Materials in QML

Wir erstellen ein benutzerdefiniertes Material mit den Shadern, die wir zuvor erstellt haben, und fügen eine neue Eigenschaft uTime hinzu. Diese Eigenschaft wird automatisch auf die entsprechende Uniform im Fragment-Shader abgebildet.

CustomMaterial {
    id: cubeMaterial
    property real uTime: frametimer.elapsedTime
    FrameAnimation {
        id: frametimer
        running: true
    }

    vertexShader: "cubeMaterial.vert"
    fragmentShader: "cubeMaterial.frag"
}

Schließlich erstellen wir unser Modell und wenden das benutzerdefinierte Material und die Instanztabelle an:

Model {
    id: instancedCube
    property real cubeSize: 15
    scale: Qt.vector3d(cubeSize/100, cubeSize/100, cubeSize/100)
    source: "#Cube"
    instancing: CppInstanceTable {
        gridSize: 65
        gridSpacing: instancedCube.cubeSize
        randomSeed: 1522562186
    }
    materials: [ cubeMaterial ]
}

Beachten Sie, dass wir nur einen einzigen Würfel erstellen: Die ganze schwere Arbeit wird von der GPU erledigt.

Beispielprojekt @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.