Sur cette page

Qt Quick 3D - Rendu instancié personnalisé

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

Terrain en blocs avec de l'eau, de l'herbe et des collines démontrant un rendu instancié

Cet exemple montre comment créer des données d'instance par programme en C++ et comment utiliser des matériaux personnalisés avec un 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 à partir de 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 ne faisons que spécifier une logique supplémentaire.

La seule personnalisation dont nous avons besoin pour le vertex shader concerne la transmission d'informations au fragment shader. Par défaut, Qt XML ne fournit que les données d'instance au vertex shader, nous les transmettons donc à vCustomData. Nous calculons également la position globale du sommet et la rendons disponible à l'adresse 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 fragment shader réalise une simple animation de vagues pour les surfaces d'eau. Tout le reste reçoit un gradient 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 en QML

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

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 appliquons le matériau personnalisé et la table d'instance :

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 @ 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.