Qt Quick 3D - 自定义几何体示例

演示如何从 C++ 和 QML 提供自定义顶点数据。

带有 Qt 徽标纹理的三角形,用于演示自定义几何形状

本示例利用 Model 的QQuick3DGeometry 和the geometry property 方法,渲染一个网格,其顶点、法线和纹理坐标由 C++ 和 QML 指定,而非使用预烘焙资源。

此外,还演示了GridGeometry 。GridGeometry 是QQuick3DGeometry 的内置实现,它提供了一个包含线段基元的网格,适用于显示网格。

本示例的重点在于提供自定义几何体的代码,因此让我们先来看看 `ExampleTriangleGeometry ` 的 C++ 头文件:

class ExampleTriangleGeometry : public QQuick3DGeometry
{
    Q_OBJECT
    QML_NAMED_ELEMENT(ExampleTriangleGeometry)
    Q_PROPERTY(bool normals READ normals WRITE setNormals NOTIFY normalsChanged)
    Q_PROPERTY(float normalXY READ normalXY WRITE setNormalXY NOTIFY normalXYChanged)
    Q_PROPERTY(bool uv READ uv WRITE setUV NOTIFY uvChanged)
    Q_PROPERTY(float uvAdjust READ uvAdjust WRITE setUVAdjust NOTIFY uvAdjustChanged)

public:
    ExampleTriangleGeometry();

    bool normals() const { return m_hasNormals; }
    void setNormals(bool enable);

    float normalXY() const { return m_normalXY; }
    void setNormalXY(float xy);

    bool uv() const { return m_hasUV; }
    void setUV(bool enable);

    float uvAdjust() const { return m_uvAdjust; }
    void setUVAdjust(float f);

signals:
    void normalsChanged();
    void normalXYChanged();
    void uvChanged();
    void uvAdjustChanged();

private:
    void updateData();

    bool m_hasNormals = false;
    float m_normalXY = 0.0f;
    bool m_hasUV = false;
    float m_uvAdjust = 0.0f;
};

最需要注意的是,我们的ExampleTriangleGeometry 类继承自QQuick3DGeometry ,并且我们调用了QML_NAMED_ELEMENT(ExampleTriangleGeometry) 宏,从而使该类可在 QML 中使用。此外,还有一些通过Q_PROPERTY 宏定义的属性,这些属性会自动在我们的 QML 对象中暴露出来。现在,让我们来看一下 QML 模型:

Model {
    id: triangleModel
    visible: false
    scale: Qt.vector3d(100, 100, 100)
    geometry: ExampleTriangleGeometry {
        normals: cbNorm.checked
        normalXY: sliderNorm.value
        uv: cbUV.checked
        uvAdjust: sliderUV.value
    }
    materials: [
        PrincipledMaterial {
            Texture {
                id: baseColorMap
                source: "qt_logo_rect.png"
            }
            cullMode: PrincipledMaterial.NoCulling
            baseColorMap: cbTexture.checked ? baseColorMap : null
            specularAmount: 0.5
        }
    ]
}

请注意,我们通过指定geometry 属性来使用ExampleTriangleGeometry 类,并指定了相关属性。在QML端,使用自定义几何体仅需这些设置。

现在,让我们看看 C++ 代码中的另一个重要部分,即 `updateData() ` 方法。每当创建 `ExampleTriangleGeometry ` 类或更新其任何 QML 属性时,该方法都会创建并上传自定义几何体的数据。

void ExampleTriangleGeometry::updateData()
{
    clear();

    int stride = 3 * sizeof(float);
    if (m_hasNormals)
        stride += 3 * sizeof(float);
    if (m_hasUV)
        stride += 2 * sizeof(float);

    QByteArray vertexData(3 * stride, Qt::Initialization::Uninitialized);
    float *p = reinterpret_cast<float *>(vertexData.data());

    // a triangle, front face = counter-clockwise
    *p++ = -1.0f; *p++ = -1.0f; *p++ = 0.0f;
    if (m_hasNormals) {
        *p++ = m_normalXY; *p++ = m_normalXY; *p++ = 1.0f;
    }
    if (m_hasUV) {
        *p++ = 0.0f + m_uvAdjust; *p++ = 0.0f + m_uvAdjust;
    }
    *p++ = 1.0f; *p++ = -1.0f; *p++ = 0.0f;
    if (m_hasNormals) {
        *p++ = m_normalXY; *p++ = m_normalXY; *p++ = 1.0f;
    }
    if (m_hasUV) {
        *p++ = 1.0f - m_uvAdjust; *p++ = 0.0f + m_uvAdjust;
    }
    *p++ = 0.0f; *p++ = 1.0f; *p++ = 0.0f;
    if (m_hasNormals) {
        *p++ = m_normalXY; *p++ = m_normalXY; *p++ = 1.0f;
    }
    if (m_hasUV) {
        *p++ = 1.0f - m_uvAdjust; *p++ = 1.0f - m_uvAdjust;
    }

    setVertexData(vertexData);
    setStride(stride);
    setBounds(QVector3D(-1.0f, -1.0f, 0.0f), QVector3D(+1.0f, +1.0f, 0.0f));

    setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);

    addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
                 0,
                 QQuick3DGeometry::Attribute::F32Type);

    if (m_hasNormals) {
        addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
                     3 * sizeof(float),
                     QQuick3DGeometry::Attribute::F32Type);
    }

    if (m_hasUV) {
        addAttribute(QQuick3DGeometry::Attribute::TexCoordSemantic,
                     m_hasNormals ? 6 * sizeof(float) : 3 * sizeof(float),
                     QQuick3DGeometry::Attribute::F32Type);
    }
}

该方法首先调用clear() 来清除之前上传的所有数据。随后,它计算顶点的步长,同时考虑法线和UV坐标的存在。 接着创建一个字节数组来存储顶点缓冲区,并向其中填充一个单三角形的顶点数据,该三角形的顶点坐标分别为 (-1, -1, 0)、(1, -1, 0) 和 (0, 1, 0)。

接着通过调用setVertexData() 和setStride() 上传顶点数据并设置步长。通过调用setBounds 设置几何体的边界。尽管在本示例中未使用,但设置边界对于阴影效果的正常运行是必要的。 接着通过调用setPrimitiveType() 设置基元类型。最后,针对每个属性调用addAttribute() ,指定位置、法线和 UV 坐标的属性在先前上传的缓冲区中如何在内存中布局。

示例项目 @ 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.