场景图 - RHI 纹理项
演示如何实现一个自定义的 QQuickItem,用于显示由 QRhi-渲染的纹理。
此示例演示了如何实现一个项,该项使用 QRhi API 在纹理中执行跨平台的、可移植的 3D 渲染,并将该图像显示出来。
注意:此示例演示了高级、低级别的功能,可通过有限的兼容性保证的 Qt Gui 模块执行可移植、跨平台的 3D 渲染。为了能够使用 QRhi APIs,应用程序连接到 Qt::GuiPrivate
并包含 <rhi/qrhi.h>
。
与其他方法的比较
RHI Under QML 示例说明如何以自定义渲染先于 Qt Quick 场景图自己的渲染方式实现可移植、跨平台的 3D 渲染。这种方法是高效的,因为它现在不需要额外的渲染目标和渲染传递,自定义渲染在场景图的自己的 draw calls 之前注入到主渲染传递中。
相比之下,此示例涉及一个单独的渲染目标,一个 QRhiTexture,其 尺寸 与场景中 QQuickItem 的大小相同,以及一个用于清除并绘制到该纹理的整个渲染传递。然后在该纹理中采样,并用于纹理化四边形,有效显示 2D 图像。
与覆盖/叠加方法相比,这允许在 Qt Quick 场景中的任何位置显示、混合和转换 3D 渲染的扁平 2D 图像,因为我们有一个真正的 QQuickItem。然而,这会使资源使用和性能成本更高,因为它涉及首先对纹理进行渲染。
概览
示例使用 QQuickRhiItem 和 QQuickRhiItemRenderer 实现。可以继承 QQuickRhiItem 以轻松快速地获得一个功能齐全的自定义 QQuickItem,并通过使用幕后 QSGSimpleTextureNode 显示 QRhiTexture 的内容。纹理的内容由应用在其 QQuickRhiItemRenderer 子类的逻辑中提供的逻辑生成。
ExampleRhiItem
是一个 QQuickRhiItem 子类,提供了一些属性,例如 angle
和 backgroundAlpha
。这些属性将从 QML 中进行读取、写入和动画处理。为了支持 Qt Quick 的多线程渲染模型,QQQuickRhiItemRenderer 有一个临时的 synchronize() 函数,可以重新实现,以便安全地在属于主/GUI 线程的 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(); }
initialize() 的其余部分是简单的 QRhi-相关代码。
3D 场景使用透视投影,该投影基于查询自 QRhiRenderTarget 的输出大小进行计算(因为这无论是否使用多重采样都有效,而访问 colorTexture() 和 msaaColorBuffer() 都需要根据哪些对象有效进行分支逻辑)
注意使用 QRhi::clipSpaceCorrMatrix() 以适应用户界面坐标系统和 3D 图形 API 之间的坐标系统差异。
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 矩阵的统一缓冲区,因为我们预期旋转角度会改变。清除颜色包含项目提供的背景不透明度。记住需要将红、绿和蓝组件中的不透明度预乘。
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。
© 2024 Qt 公司。本文档的文档贡献的版权归各自所有者所有。提供在本文档中的文档是根据自由软件基金会发布的 GNU 自由文档许可协议版本 1.3 的条款许可的。Qt 和相应的标志是芬兰和/或其他国家/地区的 The Qt Company Ltd. 的商标。所有其他商标均为其各自所有者财产。