Qt Quick 3D - Beispiel für benutzerdefinierte Geometrie

Veranschaulicht die Bereitstellung benutzerdefinierter Scheitelpunktdaten aus C++ und QML.

Dreieck mit Qt-Logo-Textur zur Veranschaulichung einer benutzerdefinierten Geometrie

Dieses Beispiel nutzt die Methoden „ QQuick3DGeometry “ und „ the geometry property “ des Modells, um ein Mesh mit Scheitelpunkt-, Normal- und Texturkoordinaten zu rendern, die in C++ und QML anstelle eines vorab erstellten Assets festgelegt werden.

Zudem wird die ` GridGeometry ` demonstriert. ` GridGeometry ` ist eine integrierte Implementierung von „ QQuick3DGeometry “, die ein Mesh mit Linienprimitiven bereitstellt, das sich zur Darstellung eines Gitters eignet.

Der Schwerpunkt dieses Beispiels liegt auf dem Code, der die benutzerdefinierte Geometrie bereitstellt. Schauen wir uns daher zunächst die C++-Header-Datei „ ExampleTriangleGeometry “ an:

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

Besonders wichtig ist, dass unsere Klasse ` ExampleTriangleGeometry ` von ` QQuick3DGeometry ` erbt und dass wir das Makro ` QML_NAMED_ELEMENT(ExampleTriangleGeometry) ` aufrufen, wodurch unsere Klasse in QML verfügbar wird. Außerdem sind einige Eigenschaften über das Makro ` Q_PROPERTY ` definiert, die automatisch in unserem QML-Objekt verfügbar sind. Sehen 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: [
        PrincipledMaterial {
            Texture {
                id: baseColorMap
                source: "qt_logo_rect.png"
            }
            cullMode: PrincipledMaterial.NoCulling
            baseColorMap: cbTexture.checked ? baseColorMap : null
            specularAmount: 0.5
        }
    ]
}

Beachten Sie, dass wir die Eigenschaft „ geometry “ so festlegen, dass unsere Klasse „ ExampleTriangleGeometry “ verwendet wird, wobei die relevanten Eigenschaften angegeben sind. 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 berechnet sie den Schritt für die Eckpunkte unter Berücksichtigung der Normalvektoren und UV-Koordinaten. Anschließend wird ein Byte-Array erstellt, um den Scheitelpunktpuffer aufzunehmen, der dann mit Scheitelpunkten für ein einzelnes Dreieck mit den Eckpunkten (-1, -1, 0), (1, -1, 0) und (0, 1, 0) gefüllt wird.

Anschließend werden die Scheitelpunktdaten hochgeladen und der Stride durch den Aufruf von ` setVertexData() ` und ` setStride()` festgelegt. Die Begrenzungen der Geometrie werden durch den Aufruf von ` setBounds` festgelegt. Obwohl dies in diesem Beispiel nicht verwendet wird, ist die Festlegung der Begrenzungen erforderlich, damit Schatten funktionieren. Anschließend wird der Primitivtyp durch den Aufruf von „ setPrimitiveType() “ festgelegt. Zuletzt legen wir fest, wie die Attribute für Position, Normalen und UV-Koordinaten im zuvor hochgeladenen Puffer im Speicher angeordnet sind, indem wir für jedes Attribut „ addAttribute() “ aufrufen.

Beispielprojekt @ 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.