Qt Quick 3D - 程序化纹理示例
演示如何从 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` 类型以避免不必要的类型转换。
在前两个示例中,纹理的所有数据都在 CPU 端生成,然后上传到 GPU 作为纹理数据使用。也可以直接在 GPU 上生成纹理数据。在这种情况下,我们改用QQuick3DTextureProviderExtension 的子类。
class GradientTextureProvider : public QQuick3DTextureProviderExtension
{
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_ELEMENTQQuick3DTextureProviderExtension 基于QSSGRenderExtension 的类通过Qt RHI API定义渲染扩展。当调用QQuick3DTextureProviderExtension::updateSpatialNode()时,该子类必须返回一个基于 的对象。
QSSGRenderGraphObject *GradientTextureProvider::updateSpatialNode(QSSGRenderGraphObject *node)
{
if (!node)
node = new GradientTextureProviderNode(this);
// Update the state of the backend node
auto gradientNode = static_cast<GradientTextureProviderNode *>(node);
gradientNode->m_isDirty = true;
gradientNode->m_width = m_width;
gradientNode->m_height = m_height;
gradientNode->m_startColor = m_startColor;
gradientNode->m_endColor = m_endColor;
return node;
}实际渲染的内容由该QSSGRenderExtension 子类定义:
class GradientTextureProviderNode : public QSSGRenderTextureProviderExtension
{
public:
explicit GradientTextureProviderNode(GradientTextureProvider *ext);
~GradientTextureProviderNode() override;
bool prepareData(QSSGFrameData &data) override;
void prepareRender(QSSGFrameData &data) override;
void render(QSSGFrameData &data) override;
void resetForFrame() override;
bool m_isDirty = false;
// state
int m_width = 256;
int m_height = 256;
QColor m_startColor = QColor(Qt::red);
QColor m_endColor = QColor(Qt::blue);
private:
QPointer<GradientTextureProvider> m_ext;
QSSGExtensionId extensionId {};
std::unique_ptr<QRhiBuffer> quadGeometryVertexBuffer;
std::unique_ptr<QRhiBuffer> quadGeometryIndexBuffer;
//
std::unique_ptr<QRhiTexture> outputTexture; // the final output texture
std::unique_ptr<QRhiTextureRenderTarget> outputTextureRenderTarget;
std::unique_ptr<QRhiRenderPassDescriptor> ouputTextureRenderPassDescriptor;
std::unique_ptr<QRhiBuffer> gradientTextureUniformBuffer;
std::unique_ptr<QRhiShaderResourceBindings> gradientTextureShaderResouceBindings;
std::unique_ptr<QRhiGraphicsPipeline> gradientTexture2dPipeline;
};通过实现QSSGRenderExtension 的接口,该扩展可直接在GPU上渲染纹理数据。prepareDate的实现确保输出纹理以正确的尺寸和格式创建,并将其注册为要提供的纹理。
bool GradientTextureProviderNode::prepareData(QSSGFrameData &data)
{
if (!m_isDirty)
return false;
const auto &ctxIfx = data.contextInterface();
const auto &rhiCtx = ctxIfx->rhiContext();
QRhi *rhi = rhiCtx->rhi();
// If there is no available rhi context, then we can't create the texture
if (!rhiCtx)
return false;
extensionId = m_ext ? QQuick3DExtensionHelpers::getExtensionId(*m_ext) : QSSGExtensionId{};
if (QQuick3DExtensionHelpers::isNull(extensionId))
return false;
// Make sure that the output texture is created and registered as the texture provider
if (!outputTexture ||
outputTexture->pixelSize().width() != m_width ||
outputTexture->pixelSize().height() != m_height) {
outputTexture.reset(rhi->newTexture(QRhiTexture::Format::RGBA8, QSize(m_width, m_height), 1, QRhiTexture::RenderTarget | QRhiTexture::sRGB));
outputTexture->create();
outputTextureRenderTarget.reset(rhi->newTextureRenderTarget({ outputTexture.get() }));
ouputTextureRenderPassDescriptor.reset(outputTextureRenderTarget->newCompatibleRenderPassDescriptor());
outputTextureRenderTarget->setRenderPassDescriptor(ouputTextureRenderPassDescriptor.get());
outputTextureRenderTarget->create();
// Register the output as the texture provider
QSSGRenderExtensionHelpers::registerRenderResult(data, extensionId, outputTexture.get());
gradientTexture2dPipeline.reset();
}
// If m_isDirty is true than prepareRender and render will actually get called.
return m_isDirty;
}`prepareRender` 方法中会创建渲染管线,并更新统一缓冲区。
void GradientTextureProviderNode::prepareRender(QSSGFrameData &data)
{
const auto &ctxIfx = data.contextInterface();
const auto &rhiCtx = ctxIfx->rhiContext();
if (!rhiCtx)
return;
QRhi *rhi = rhiCtx->rhi();
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
// Create the pipeline if necessary
if (!gradientTexture2dPipeline) {
// 1 quad (2 trianges), pos + uv. 4 vertices, 5 values each (x, y, z, u, v)
quadGeometryVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, 5 * 4 * sizeof(float)));
quadGeometryVertexBuffer->create();
// 6 indexes (2 triangles)
quadGeometryIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, 6 * sizeof(uint16_t)));
quadGeometryIndexBuffer->create();
// Uniform buffer: packed into 2 * vec4 (2 RGBA colors)
const size_t uBufSize = (sizeof(float) * 4) * 2;
gradientTextureUniformBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, uBufSize));
gradientTextureUniformBuffer->create();
// Uniform buffer is only bound/used in Fragment Shader
gradientTextureShaderResouceBindings.reset(rhi->newShaderResourceBindings());
gradientTextureShaderResouceBindings->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::FragmentStage, gradientTextureUniformBuffer.get()),
});
gradientTextureShaderResouceBindings->create();
gradientTexture2dPipeline.reset(rhi->newGraphicsPipeline());
gradientTexture2dPipeline->setShaderStages({
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/shaders/gradient.vert.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/shaders/gradient.frag.qsb")) }
});
// 2 Attributes, Position (vec3) + UV (vec2)
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 5 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
{ 0, 1, QRhiVertexInputAttribute::Float2, 3 * sizeof(float) }
});
gradientTexture2dPipeline->setVertexInputLayout(inputLayout);
gradientTexture2dPipeline->setShaderResourceBindings(gradientTextureShaderResouceBindings.get());
gradientTexture2dPipeline->setRenderPassDescriptor(ouputTextureRenderPassDescriptor.get());
gradientTexture2dPipeline->create();
// Upload the static quad geometry part
resourceUpdates->uploadStaticBuffer(quadGeometryVertexBuffer.get(), g_vertexData);
resourceUpdates->uploadStaticBuffer(quadGeometryIndexBuffer.get(), g_indexData);
}
// Upload the uniform buffer data
const float colorData[8] = {
m_startColor.redF(), m_startColor.greenF(), m_startColor.blueF(), m_startColor.alphaF(),
m_endColor.redF(), m_endColor.greenF(), m_endColor.blueF(), m_endColor.alphaF()
};
resourceUpdates->updateDynamicBuffer(gradientTextureUniformBuffer.get(), 0, sizeof(colorData), colorData);
cb->resourceUpdate(resourceUpdates);
m_isDirty = false;
}该渲染阶段的实际渲染在 render 函数中定义。
void GradientTextureProviderNode::render(QSSGFrameData &data)
{
const auto &ctxIfx = data.contextInterface();
const auto &rhiCtx = ctxIfx->rhiContext();
if (!rhiCtx)
return;
QRhiCommandBuffer *cb = rhiCtx->commandBuffer();
// Render the quad with our pipeline to the outputTexture
cb->beginPass(outputTextureRenderTarget.get(), Qt::black, { 1.0f, 0 }, nullptr, rhiCtx->commonPassFlags());
cb->setViewport(QRhiViewport(0, 0, m_width, m_height));
cb->setGraphicsPipeline(gradientTexture2dPipeline.get());
cb->setShaderResources(gradientTextureShaderResouceBindings.get());
QRhiCommandBuffer::VertexInput vb(quadGeometryVertexBuffer.get(), 0);
cb->setVertexInput(0, 1, &vb, quadGeometryIndexBuffer.get(), QRhiCommandBuffer::IndexFormat::IndexUInt16);
cb->drawIndexed(6);
cb->endPass();
}该示例在 QML 中使用纹理提供程序如下:
Texture {
id: textureFromGPU
minFilter: applicationState.filterMode
magFilter: applicationState.filterMode
textureProvider: GradientTextureProvider {
startColor: applicationState.startColor
endColor: applicationState.endColor
width: applicationState.size
height: width
}
}© 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.