Qt Quick 3D - Beispiel für prozedurale Texturen

Zeigt, wie man benutzerdefinierte Texturdaten aus C++ oder QML bereitstellt.

Dieses Beispiel nutzt QQuick3DTextureData und the textureData property von Texture, um Texturdaten bereitzustellen, die zur Laufzeit dynamisch generiert werden, anstatt sie aus einem statischen Asset zu laden. Zu Demonstrationszwecken werden in diesem Beispiel zwei Farbverlaufstexturen in C++ bzw. QML erzeugt.

Zunächst definieren wir eine C++-Klasse für unsere Texturdaten. Wir machen sie zu einer Unterklasse von QQuick3DTextureData. Das ist nicht unbedingt notwendig, da es keine virtuellen Funktionen gibt, aber es ist viel bequemer, alles in einer Klasse zu haben. Wir definieren die Eigenschaften, die wir verwenden werden, und fügen QML_NAMED_ELEMENT hinzu, um sie von QML aus verfügbar zu machen:

class GradientTexture : public QQuick3DTextureData
{
    Q_OBJECT
    Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged)
    Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged)
    Q_PROPERTY(QColor startColor READ startColor WRITE setStartColor NOTIFY startColorChanged)
    Q_PROPERTY(QColor endColor READ endColor WRITE setEndColor NOTIFY endColorChanged)
    QML_NAMED_ELEMENT(GradientTexture)
    ...

Wir fügen eine Funktion zum Aktualisieren der Textur hinzu. Sie verwendet setSize und setFormat, um die Textur zu konfigurieren, und setTextureData, um die Bilddaten zu setzen:

void GradientTexture::updateTexture()
{
    setSize(QSize(m_width, m_height));
    setFormat(QQuick3DTextureData::RGBA8);
    setHasTransparency(false);
    setTextureData(generateTexture());
}

Die Funktion generateTexture erstellt eine QByteArray in der richtigen Größe und füllt sie mit Bilddaten:

QByteArray GradientTexture::generateTexture()
{
    QByteArray imageData;
    // Create a horizontal gradient between startColor and endColor

    // Create a single scanline and reuse that data for each
    QByteArray gradientScanline;
    gradientScanline.resize(m_width * 4); // RGBA8

    for (int x = 0; x < m_width; ++x) {
        QColor color = linearInterpolate(m_startColor, m_endColor, x / float(m_width));
        int offset = x * 4;
        gradientScanline.data()[offset + 0] = char(color.red());
        gradientScanline.data()[offset + 1] = char(color.green());
        gradientScanline.data()[offset + 2] = char(color.blue());
        gradientScanline.data()[offset + 3] = char(255);
    }

    for (int y = 0; y < m_height; ++y)
        imageData += gradientScanline;

    return imageData;
}

Wir rufen updateTexture jedes Mal auf, wenn eine Eigenschaft geändert wird:

void GradientTexture::setStartColor(QColor startColor)
{
    if (m_startColor == startColor)
        return;

    m_startColor = startColor;
    emit startColorChanged(m_startColor);
    updateTexture();
}

Schließlich können wir unsere neue Textur aus QML verwenden:

Texture {
    id: textureFromCpp

    minFilter: applicationState.filterMode
    magFilter: applicationState.filterMode
    textureData: gradientTexture

    GradientTexture {
        id: gradientTexture
        startColor: applicationState.startColor
        endColor: applicationState.endColor
        width: applicationState.size
        height: width
    }
}

Es ist auch möglich, die gleichen Texturdaten in QML zu erzeugen. In diesem Fall verwenden wir die Komponente ProceduralTextureData:

Texture {
    id: textureFromQML
    minFilter: applicationState.filterMode
    magFilter: applicationState.filterMode
    textureData: gradientTextureDataQML

    ProceduralTextureData {
        id: gradientTextureDataQML

        property color startColor: applicationState.startColor
        property color endColor: applicationState.endColor
        width: applicationState.size
        height: width
        textureData: generateTextureData()

        function linearInterpolate(startColor : color, endColor : color, fraction : real) : color{
            return Qt.rgba(
                        startColor.r + (endColor.r - startColor.r) * fraction,
                        startColor.g + (endColor.g - startColor.g) * fraction,
                        startColor.b + (endColor.b - startColor.b) * fraction,
                        startColor.a + (endColor.a - startColor.a) * fraction
                        );
        }

        function generateTextureData() {
            let dataBuffer = new ArrayBuffer(width * height * 4)
            let data = new Uint8Array(dataBuffer)

            let gradientScanline = new Uint8Array(width * 4);

            for (let x = 0; x < width; ++x) {
                let color = linearInterpolate(startColor, endColor, x / width);
                let offset = x * 4;
                gradientScanline[offset + 0] = color.r * 255;
                gradientScanline[offset + 1] = color.g * 255;
                gradientScanline[offset + 2] = color.b * 255;
                gradientScanline[offset + 3] = color.a * 255;
            }

            for (let y = 0; y < height; ++y) {
                data.set(gradientScanline, y * width * 4);
            }

            return dataBuffer;
        }
    }
}

Genau wie in C++ füllen wir ein QByteArray mit Bilddaten, die die Größe und das Format der Textur widerspiegeln. Wenn Sie dies von QML aus tun, verwenden Sie den Typ ArrayBuffer, um unnötige Typkonvertierungen zu vermeiden.

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.