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

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

本示例使用 QQuick3DGeometry 和模型 几何属性 来渲染一个网格,该网格的顶点、法线和纹理坐标是从 C++ 和 QML 中指定的,而不是预烘焙的资产。

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

本示例将重点放在提供自定义几何形状的代码上,因此让我们首先看看 C++ 的头文件 ExampleTriangleGeometry

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: [
        DefaultMaterial {
            Texture {
                id: baseColorMap
                source: "qt_logo_rect.png"
            }
            cullMode: DefaultMaterial.NoCulling
            diffuseMap: 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 坐标属性。

文件

图像

© 2024Qt公司。本文件中包含的文档贡献权属于各自所有者。提供的文档受GNU自由文档许可证版本1.3的条款约束,由自由软件基金会颁布。Qt及其相关标识是芬兰及/或其他国家/地区的商标,归Qt公司所有。其他所有商标归各自所有者所有。