Qt Quick 3D - カスタムインスタンスレンダリング

カスタム マテリアルと C++ インスタンス テーブルを使用したインスタンス化のデモです。

この例では、C++ でプログラム的にインスタンスデータを作成する方法と、インスタンス化レンダリングでカスタムマテリアルを使用する方法を示します。

カスタムインスタンステーブル

テーブルをQQuick3DInstancing のサブクラスとして定義し、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)

仮想関数getInstanceBuffer は、インスタンス化データを返すために再実装されています:

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

カスタムマテリアル

つまり、Qtは基本的な実装を提供してくれ、私たちは追加のロジックを指定するだけです。

頂点シェーダに必要なカスタマイズは、フラグメントシェーダに情報を渡すことだけです。デフォルトでは、Qt は頂点シェーダにインスタンスデータしか提供しないので、vCustomData として渡します。また、頂点のグローバル位置を計算し、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;
}

フラグメントシェーダは、水面に対して単純な波のアニメーションを行います。それ以外はすべて、微妙な放射状のグラデーションになります。その差はカスタムデータの最初の要素で決まります。

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

QMLでカスタムテーブルとマテリアルを使う

先ほど作成したシェーダを使ってカスタムマテリアルを作成し、新しいプロパティuTime を追加します。このプロパティは、フラグメントシェーダの対応するユニフォームに自動的にマッピングされます。

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

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

最後に、モデルを作成し、カスタムマテリアルとインスタンステーブルを適用します:

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

キューブは1つしか作 成していないことに注意してください:力仕事はすべて GPU が行います。

プロジェクト例 @ code.qt.io

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。