Qt Quick 3D - Beispiel für benutzerdefinierte Geometrie

Demonstriert die Bereitstellung von benutzerdefinierten Scheitelpunktdaten aus C++ und QML.

In diesem Beispiel werden QQuick3DGeometry und the geometry property von Model verwendet, um ein Mesh mit Scheitelpunkt-, Normalen- und Texturkoordinaten zu rendern, die von C++ und QML anstelle eines vorgefertigten Assets angegeben werden.

Außerdem wird GridGeometry demonstriert. GridGeometry ist eine integrierte Implementierung von QQuick3DGeometry, die ein Mesh mit Linienprimitiven bereitstellt, die für die Darstellung eines Gitters geeignet sind.

Der Schwerpunkt dieses Beispiels liegt auf dem Code, der die benutzerdefinierte Geometrie bereitstellt. Werfen wir also zunächst einen Blick auf die ExampleTriangleGeometry C++ Header-Datei:

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

Das Wichtigste ist, dass unsere Klasse ExampleTriangleGeometry von QQuick3DGeometry erbt und dass wir das Makro QML_NAMED_ELEMENT(ExampleTriangleGeometry) aufrufen, wodurch unsere Klasse in QML zugänglich wird. Es gibt auch einige Eigenschaften, die durch das Q_PROPERTY -Makro definiert sind und automatisch in unserem QML-Objekt angezeigt werden. Schauen wir uns nun das QML-Modell an:

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

Beachten Sie, dass wir die Eigenschaft geometry angeben, um unsere Klasse ExampleTriangleGeometry zu verwenden, wobei die relevanten Eigenschaften angegeben werden. Das ist alles, was auf der QML-Seite erforderlich ist, um eine benutzerdefinierte Geometrie zu verwenden.

Sehen wir uns nun den anderen wichtigen Teil des C++-Codes an, nämlich die Methode updateData(). Diese Methode erstellt und lädt die Daten für unsere benutzerdefinierte Geometrie hoch, sobald eine ExampleTriangleGeometry -Klasse erstellt oder eine ihrer QML-Eigenschaften aktualisiert wird.

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

Die Methode beginnt mit dem Aufruf von clear(), um alle zuvor hochgeladenen Daten zu löschen. Anschließend wird die Schrittweite für die Scheitelpunkte berechnet, wobei das Vorhandensein von Normalen und Uv-Koordinaten berücksichtigt wird. Dann wird ein Byte-Array für den Scheitelpunktpuffer erstellt, der dann mit Scheitelpunkten für ein einzelnes Dreieck mit den Ecken (-1, -1, 0), (1, -1, 0) und (0, 1, 0) gefüllt wird.

Dann werden die Scheitelpunktdaten hochgeladen und die Schrittweite wird durch Aufruf von setVertexData() und setStride() festgelegt. Die Begrenzung der Geometrie wird durch den Aufruf von setBounds festgelegt. Obwohl in diesem Beispiel nicht verwendet, ist das Setzen der Grenzen notwendig, damit Schatten funktionieren. Dann wird der primitive Typ durch den Aufruf von setPrimitiveType() festgelegt. Schließlich wird festgelegt, wie die Attribute für Position, Normal und Uv-Koordinaten im Speicher des zuvor hochgeladenen Puffers angeordnet werden, indem addAttribute() für jedes Attribut aufgerufen wird.

Beispielprojekt @ code.qt.io

© 2025 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.