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

请注意,我们只创建了一个立方体:所有繁重的工作都是由 GPU 执行的。

文件

© 2024 The Qt Company Ltd. 此处包含的文档贡献的版权属于其各自的所有者。此处提供的文档是根据由自由软件基金会发布的 GNU 自由文档许可证版本 1.3 的条款许可的。Qt 和相应的标志是芬兰的 The Qt Company Ltd 以及/或世界其他国家的商标。所有其他商标均为其各自所有者的财产。