Auf dieser Seite

Qt Quick 3D - Benutzerdefiniertes Instanz-Rendering

Veranschaulicht das Instanzieren mit benutzerdefinierten Materialien und einer C++-Instanztabelle.

Blockartiges Gelände mit Wasser, Gras und Hügeln zur Veranschaulichung des Instanz-Renderings

Dieses Beispiel zeigt, wie Instanzdaten programmgesteuert in C++ erstellt werden und wie benutzerdefinierte Materialien beim Instanz-Rendering verwendet werden.

Benutzerdefinierte Instanztabelle

Wir definieren unsere Tabelle als Unterklasse von ` QQuick3DInstancing ` und fügen einige Eigenschaften hinzu, damit wir sie über QML 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 neu implementiert, um die Instanzierungsdaten 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 benutzerdefiniertes Material mit Shading, was bedeutet, dass Qt uns die grundlegende Implementierung bereitstellt und wir lediglich zusätzliche Logik festlegen.

Die einzige Anpassung, die wir für den Vertex-Shader benötigen, dient der Weitergabe von Informationen an den Fragment-Shader. Standardmäßig stellt Qt dem Vertex-Shader nur die Instanzdaten zur Verfügung, daher leiten wir diese als ` vCustomData` weiter. Außerdem berechnen wir die globale Position des Vertex und stellen sie als ` vGlobalPosition` bereit.

// 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 dezenten radialen Farbverlauf. 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 unter Verwendung der zuvor erstellten Shader und fügen eine neue Eigenschaft „ uTime “ hinzu. Diese Eigenschaft wird automatisch auf das 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"
}

Abschließend erstellen wir unser Modell und wenden das benutzerdefinierte Material sowie 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 gesamte Rechenarbeit wird von der GPU übernommen.

Beispielprojekt @ code.qt.io

© 2026 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.