Sur cette page

Qt Quick 3D - Rendu instancié personnalisé

Démonstration de l'instanciation avec des matériaux personnalisés et une table d'instances en C++.

Terrain en blocs comportant de l'eau, de l'herbe et des collines, illustrant le rendu par instances

Cet exemple montre comment créer des données d'instance par programmation en C++, et comment utiliser des matériaux personnalisés avec le rendu instancié.

Table d’instanciation personnalisée

Nous définissons notre table comme une sous-classe de ` QQuick3DInstancing ` et ajoutons quelques propriétés afin de pouvoir la contrôler depuis QML :

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)

La fonction virtuelle ` getInstanceBuffer ` est réimplémentée pour renvoyer les données d'instanciation :

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;
}

Matériau personnalisé

Nous utilisons un matériau personnalisé ombré, ce qui signifie que Qt nous fournit l’implémentation de base et que nous nous contentons de spécifier la logique supplémentaire.

La seule personnalisation dont nous avons besoin pour le shader de sommets consiste à transmettre des informations au shader de fragments. Par défaut, Qt ne fournit que les données d’instance au shader de sommets ; nous les transmettons donc sous la forme ` vCustomData`. Nous calculons également la position globale du sommet et la rendons disponible sous la forme ` vGlobalPosition`.

// 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;
}

Le shader de fragments réalise une animation simple de vagues pour les surfaces d’eau. Tout le reste bénéficie d’un dégradé radial subtil. La différence est déterminée par le premier élément des données personnalisées.

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);
}

Utilisation de la table et du matériau personnalisés dans QML

Nous créons un matériau personnalisé à l'aide des shaders que nous avons créés précédemment, puis ajoutons une nouvelle propriété uTime. Cette propriété est automatiquement mappée à l'uniform correspondant dans le shader de fragment.

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

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

Enfin, nous créons notre modèle et lui appliquons le matériau personnalisé ainsi que la table d’instances :

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 ]
}

Notez que nous ne créons qu’un seul cube : tout le travail est effectué par le GPU.

Exemple de projet sur 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.