Qt Quick 3D - 程序化纹理示例

演示如何从 C++ 或 QML 提供自定义纹理数据。

此示例使用 QQuick3DTextureData 和纹理的 textureData 属性 来提供在运行时动态生成的纹理数据,而不是从静态资源中加载。出于演示目的,此示例分别使用 C++ 和 QML 生成两个渐变纹理。

首先,我们定义一个 C++ 类来处理我们的纹理数据。我们将其设为 QQuick3DTextureData 的子类。尽管这不是必须的,因为没有虚拟函数,但将所有内容放入一个类中会更方便。我们定义将要使用的属性,并使用 QML_NAMED_ELEMENT 确保它可以从 QML 访问。

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)
    ...

我们添加了一个更新纹理的功能。它使用 setSize 和 setFormat 来配置纹理,并使用 setTextureData 来设置图像数据。

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

generateTexture 函数创建正确大小的 QByteArray,并填充图像数据。

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

每次属性改变时,我们都调用 updateTexture

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

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

最后,我们可以从 QML 使用我们的新纹理。

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

在 QML 中也可以生成相同的纹理数据。在这种情况下,我们使用 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;
        }
    }
}

与 C++ 类似,我们在 QByteArray 中填充反映纹理大小和格式的图像数据。当从 QML 进行此操作时,使用 ArrayBuffer 类型以避免不必要的数据类型转换。

文件

© 2024 Qt 公司 Ltd. 本文档的贡献包括其中各自所有者的版权。本文档根据 Free Software Foundation 发布的 GNU 自由文档许可证版本 1.3 的条款提供许可。Qt 和相应的标志是芬兰以及世界的商标,商标权属于 Qt 公司 Ltd。所有其他商标均为其各自所有者的财产。