场景图 - RHI 纹理项
展示如何实现自定义QQuickItem ,以显示QRhi 渲染的纹理。
本示例展示了如何使用QRhi API 实现跨平台、可移植的三维渲染到纹理的项目,然后显示该图像。
注: 本例演示了执行可移植、跨平台 3D 渲染的高级底层功能,同时依赖于 Qt GUI 模块中兼容性保证有限的 API。为使用QRhi API,应用程序链接到Qt::GuiPrivate
并包含<rhi/qrhi.h>
。
与其他方法的比较
RHI Under QML示例展示了如何使用QRhi API 实现可移植的跨平台 3D 渲染,其方式是在Qt Quick 场景图自身渲染之前发布自定义渲染,有效地提供了一个 "底层"。这种方法非常高效,因为现在需要额外的渲染目标和渲染传递,而自定义渲染是在场景图自身的绘制调用之前注入主渲染传递中的。
相比之下,本示例涉及一个单独的渲染目标、一个QRhiTexture dimensions ,其大小与场景中的QQuickItem 匹配,以及一个用于清除并绘制到该纹理的完整渲染传递。然后在主渲染通道中对纹理进行采样,并用于对一个四边形进行纹理绘制,从而有效地显示 2D 图像。
与底层/叠加方法相比,这种方法允许在Qt Quick 场景的任何位置显示、混合和转换 3D 渲染的扁平化 2D 图像,因为这里有一个真正的QQuickItem 。这种方法的代价是在资源和性能方面更加昂贵,因为它需要首先渲染到纹理。
概述
本示例使用QQuickRhiItem 和QQuickRhiItemRenderer 实现。QQuickRhiItem 是一个方便的类,可以通过子类化轻松快速地获得一个功能齐全的自定义QQuickItem ,通过在引擎盖下使用QSGSimpleTextureNode 显示QRhiTexture 的内容。纹理的内容由QQuickRhiItemRenderer 子类中实现的应用程序提供的逻辑生成。
ExampleRhiItem
是一个 子类,它提供了一些属性,如 和 。这些属性将通过 QML 读取、写入和动画。为了支持 的线程渲染模型,QQQuickRhiItemRenderer 有一个虚拟的 () 函数,可以重新实现该函数,以便在 (属于主/用户界面线程)和 (属于渲染线程,如果有的话)之间安全地执行数据复制。QQuickRhiItem angle
backgroundAlpha
Qt Quick synchronize QQuickRhiItem QQuickRhiItemRenderer
QQuickRhiItemRenderer *ExampleRhiItem::createRenderer() { return new ExampleRhiItemRenderer; } void ExampleRhiItem::setAngle(float a) { if (m_angle == a) return; m_angle = a; emit angleChanged(); update(); } void ExampleRhiItem::setBackgroundAlpha(float a) { if (m_alpha == a) return; m_alpha = a; emit backgroundAlphaChanged(); update(); } void ExampleRhiItemRenderer::synchronize(QQuickRhiItem *rhiItem) { ExampleRhiItem *item = static_cast<ExampleRhiItem *>(rhiItem); if (item->angle() != m_angle) m_angle = item->angle(); if (item->backgroundAlpha() != m_alpha) m_alpha = item->backgroundAlpha(); }
initialize()至少会在首次调用 render() 之前被调用一次,但实际上可能会被多次调用:如果QQuickItem 的几何图形发生变化(由于布局变化、窗口大小调整等原因),如果 的设置(如窗口大小调整)发生变化,或者在渲染线程中使用了......如果QQuickRhiItem 的设置(如采样计数和纹理格式)发生变化,或者如果项目被重新分配,从而属于一个新的QQuickWindow ,这些都会触发再次调用 initialize(),因为它们意味着QQuickRhiItem 管理的一个或多个资源发生了变化,这也会对子类产生影响。这里的示例代码就是为处理这些特殊情况(更改QRhi 、更改采样计数、更改纹理格式)而准备的。(由于它不会保留用作颜色缓冲区的纹理,因此当纹理因大小不同而重新创建时无需特殊处理)。
void ExampleRhiItemRenderer::initialize(QRhiCommandBuffer *cb) { if (m_rhi != rhi()) { m_rhi = rhi(); m_pipeline.reset(); } if (m_sampleCount != renderTarget()->sampleCount()) { m_sampleCount = renderTarget()->sampleCount(); m_pipeline.reset(); } QRhiTexture *finalTex = m_sampleCount > 1 ? resolveTexture() : colorTexture(); if (m_textureFormat != finalTex->format()) { m_textureFormat = finalTex->format(); m_pipeline.reset(); }
初始化()程序的其余部分是基于QRhi 的直接代码。
三维场景使用透视投影,该投影根据输出尺寸计算,为方便起见,可从QRhiRenderTarget 查询(因为无论是否使用多重采样,该投影都能正常工作,而访问colorTexture() 和msaaColorBuffer() 则需要根据哪个对象恰好有效而进行分支逻辑)。
请注意,QRhi::clipSpaceCorrMatrix() 的使用是为了适应 3D 图形应用程序接口之间的坐标系差异。
if (!m_pipeline) { m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData))); m_vbuf->create(); m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64)); m_ubuf->create(); m_srb.reset(m_rhi->newShaderResourceBindings()); m_srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()), }); m_srb->create(); m_pipeline.reset(m_rhi->newGraphicsPipeline()); m_pipeline->setShaderStages({ { QRhiShaderStage::Vertex, getShader(QLatin1String(":/scenegraph/rhitextureitem/shaders/color.vert.qsb")) }, { QRhiShaderStage::Fragment, getShader(QLatin1String(":/scenegraph/rhitextureitem/shaders/color.frag.qsb")) } }); QRhiVertexInputLayout inputLayout; inputLayout.setBindings({ { 5 * sizeof(float) } }); inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } }); m_pipeline->setSampleCount(m_sampleCount); m_pipeline->setVertexInputLayout(inputLayout); m_pipeline->setShaderResourceBindings(m_srb.get()); m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor()); m_pipeline->create(); QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData); cb->resourceUpdate(resourceUpdates); } const QSize outputSize = renderTarget()->pixelSize(); m_viewProjection = m_rhi->clipSpaceCorrMatrix(); m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); m_viewProjection.translate(0, 0, -4);
render() 的实现记录了单个三角形的绘制。带有 4x4 矩阵的统一缓冲区每次都会更新,因为我们预计旋转角度会发生变化。透明色中包含了项目提供的背景 alpha 值。请记住,还需要对红、绿、蓝三部分的 Alpha 值进行预乘法。
void ExampleRhiItemRenderer::render(QRhiCommandBuffer *cb) { QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); QMatrix4x4 modelViewProjection = m_viewProjection; modelViewProjection.rotate(m_angle, 0, 1, 0); resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData()); // Qt Quick expects premultiplied alpha const QColor clearColor = QColor::fromRgbF(0.5f * m_alpha, 0.5f * m_alpha, 0.7f * m_alpha, m_alpha); cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates); cb->setGraphicsPipeline(m_pipeline.get()); const QSize outputSize = renderTarget()->pixelSize(); cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height())); cb->setShaderResources(); const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0); cb->setVertexInput(0, 1, &vbufBinding); cb->draw(3); cb->endPass(); }
另请参阅 QQuickRhiItem 、场景图 - QML 下的 RHI 和场景图 - 自定义 QSGRenderNode。
© 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.