Qt 6.0中的图形:QRhi、Qt Quick、Qt Quick 3D

2020-10-27 19:49:07

去年,我们有一个关于Qt使用3D图形API和着色语言的新方法的博客系列,分为三部分:第一部分,第二部分,第三部分。对于Qt Quick,新渲染架构的早期选择加入预览在Qt5.14中发布,在Qt5.15中有一些改进。随着Qt6.0的发布即将到来,让我们看看自Qt5.15以来发生了什么。这里不可能涵盖Qt Quick图形堆栈改进的每一个细节,更不用说深入研究Qt Quick3D的大量特性了,其中许多特性是Qt6.0中的新特性或改进特性。相反,其目的只是为了概述今年晚些时候Qt6.0发布时从图形堆栈的角度可以看到的情况。

请注意,文档链接引用Qt6快照文档。这允许查看最新的C++和QML API页面,包括所有更改的和新的函数,但内容也不是最终的。这些联系稍后也可能会中断。

QRHI,即Qt渲染硬件接口,当涉及到OpenGL、Vulkan、Metal和Direct 3D等3DAPI时,它是Qt';的内部图形抽象。与5.15相比,6.0的主要改进是这里和那里进行了大量的改进,最重要的是,进行了大量的性能优化。在使Qt Quick受益的同时,当涉及到包含许多可渲染对象的复杂场景时,这些在Qt Quick 3D中变得特别重要。

通过一些简化,Qt 6.0图形堆栈的主要层可以如下所示:

Qt着色器工具模块现在是安装程序中的可选模块。对于应用程序,这可能是相关的,因为这是提供QSB命令行工具(不要与QB混淆)及其关联的CMake构建系统集成的模块。此外,目前该模块是Qt Quick 3D的强制依赖项。

Qt6不再直接使用与OpenGL兼容的GLSL源代码片段。相反,着色器都是用Vulkan风格的GLSL编写的,然后反射并转换为其他着色语言,最后打包成可序列化的QShader对象,供QRhi使用。Qt 6中的着色器准备管道如下所示:

在使用Qt Quick的QML应用程序中,每当使用ShaderEffect或子类化QSGMaterialShader时,应用程序都需要以.qsb文件的形式提供烘焙的着色器包。这些是由QSB工具生成的。但是,这并不意味着开发人员必须直接开始使用新工具:使用CMake集成,可以通过qt6_add_shaders()CMake函数轻松列出CMakeLists.txt中的顶点、碎片和计算着色器。调用QSB并将生成的.qsb文件打包到Qt资源系统中,然后由构建系统负责。

有关如何在Qt6中处理图形和计算着色器的概述,以及QSB工具及其CMake集成的详细信息,请参见shadertools文档。

在Qt 5.14和5.15中,Qt Quick附带了一个可选的基于QRhi的呈现路径,可以通过设置环境变量QSG_RHI来启用该路径。这允许轻松地试验新堆栈,同时保持传统的、经过战斗测试的直接OpenGL代码路径为默认路径。

在Qt6.0中,所有这样的开关都消失了。没有办法获得渲染直接到OpenGL的Qt快速场景。相反,新的默认设置是Qt快速场景图的基于QRHI的渲染路径。与Qt5.15相比,除了更改默认设置之外,配置什么QRhi后端以及使用哪个图形API的方式几乎没有变化。有关详细信息,请参阅文档。一个不同之处在于更好的API命名:在请求的C++代码中,现在通过QQuickWindow::setGraphicsApi()函数来完成给定的QRhi后端(以及扩展的图形API),而在5.15中,此任务通常被推到setSceneGraphBackend()的重载上,从而导致相当不准确的命名。

虽然许多应用程序不会注意到其中的任何一个,但这其中有很多含义。如果应用程序既不使用着色器代码(ShaderEffect、QSGMaterial),也不直接使用OpenGL执行自己的渲染,则很有可能根本不需要迁移步骤。(至少不是因为图形)。

如果应用程序以这样或那样的方式直接使用OpenGL,并且对使用其他图形API不感兴趣呢?例如,使用QQuickFrameBufferObject或连接到QQuickWindow::beforeRending()等信号的应用程序在Qt Quick场景下或之上注入其自己的OpenGL渲染。这就是上面提到的setGraphicsApi()函数真正发挥作用的时候:如果应用程序愿意,它总是可以声明它只需要OpenGL(或Vulkan、Metal或D3D),而不需要其他任何东西。这样可以保证Qt Quick将使用相应的QRhi后端(否则它将无法初始化),因此应用程序可以安全地假设直接转到OpenGL是安全的,因为Qt Quick最终也将通过OpenGL呈现。请注意,这并不免除应用程序必须执行其他类型的移植步骤:例如,如果应用程序另外使用ShaderEffect或创建自己的自定义材质,则仍需要迁移到处理着色器和材质的新方法。

API的变化主要分为三类。这不会是一个详尽的清单,而只是一些重要变化的一瞥。详细的更改列表和移植指南预计将在最终的Qt6.0版本中提供。

不同的着色器和材质方法:QSGMaterialShader进行了全面更新(与5.14和5.15中已移除的QSGMaterialRhiShader或多或少相同)。ShaderEffect不再允许内联着色器字符串。相反,vertexShader和framentShader属性是URL,类似于image.Source和其他属性。它们可以引用本地.qsb文件,或通过Qt资源系统(QRC)嵌入的.qsb文件。

从QQuickWindow、QSGTexture和其他地方删除特定于OpenGL的信息。GLuint textureId()、createTextureFromId(GLuint textureId,...)或setRenderTarget(GLuint FboId)等函数现在消失了,这并不奇怪。采用(包装)现有的OpenGL纹理、Vulkan图像、Metal纹理或D3D11纹理,或者访问QSGTexture的底层原生纹理仍然是完全可能的,但现在是通过一组不同的API完成的,例如QSGVulkanTexture和其他类似的类,其实例可从QSGTexure查询。

集成应用程序自己的自定义渲染与Qt Quick渲染使用的图形API完全受支持,不仅适用于OpenGL,还适用于Vulkan、Metal和D3D11。然而,由于它们的性质,其中一些API需要连接到多个单一信号,如beforeRenender()或After Renender()。例如,我们现在还拥有beforeRenderPassRecord()。有关更多详细信息和示例链接,请参阅场景图概述文档中的相关部分。最后,可以通过QSGRendererInterface查询的原生图形资源的数量已经扩展,现在也涵盖了Vulkan、Metal和Direct3D。

扩展对将Qt快速场景重定向到屏幕外渲染目标的支持。QQuickRenderControl和相关的基础设施得到了极大的增强。这样做不仅是为了能够以与Qt5中相同的方式使用OpenGL以外的图形API(例如,在没有屏幕窗口的情况下将Qt Quick场景渲染到Vulkan VkImage中),而且还为了能够与AR/VR框架和诸如OpenXR的API集成(与Vulkan、D3D11或OpenGL中的任何一个相结合)。除了略微更改的QQuickRenderControl接口之外,我们现在还有许多帮助器类来改进QQuickWindow的可配置性:QQuickRenderTarget、QQuickGraphicsDevice和QQuickGraphicsConfiguration。在需要更细粒度控制的场景中,这些是必不可少的:当涉及到现有的呈现引擎时,与OpenXR这样的API集成并不总是简单的;当涉及到实例、设备和其他图形对象的创建、初始化和所有权时,会有许多潜在的鸡生蛋问题:Qt Quick应该使用哪个Vulkan实例,或者应该在第一次初始化场景图时创建一个新的Vulkan实例?Qt应该快速选择哪个Vulkan物理设备或DXGI适配器,还是只保留默认设置?除了Qt本身需要什么之外,还应该启用哪些VkDevice扩展?渲染应该以什么2D图像/纹理为目标,由谁创建,何时创建?期望Qt6.0做好充分的准备,为在Qt6.x系列的其余部分中进一步探索AR/VR世界奠定基础。

ShaderEffect中的着色器代码新方法的一个综合示例是经典的Qt5电影体验演示的Qt6端口。(GitHub Repo)此版本已移植到CMake,并且具有所有图形API的完整功能,包括所有着色器和粒子效果。

查看QML源代码,例如窗帘效果的代码显示,它确实删除了所有内联GLSL字符串。

相反,顶点着色器和碎片着色器现在在源树中作为普通文件存在,不再与应用程序可执行文件捆绑在一起。

现在由构建系统和Qt着色器工具在构建时编译、反射和转换-着色器编译错误成为正确的构建错误而不是模糊的运行时问题!-然后将生成的.qsb文件与应用程序捆绑在一起。这就是qt6_add_shaders()函数在项目的CMakeLists.txt中实现的功能。

那些对一些较低级别的主题感兴趣的人,例如直接使用场景图,或将3D渲染代码与其集成,建议查看Qt附带的场景图示例的修订列表,请参阅此处的场景图部分。所有这些都是在Qt6.0中更新的,而其中一些是全新的。

例如,专门介绍了自定义材质示例,以关注如何实现使用其自己的材质的自定义QQuickItem。

同样值得注意的是特定于图形API的示例,下面是Qt5的openglunderqml示例,现在演示如何使用Vulkan、Metal和Direct3D 11实现相同的功能。当然,这些示例仅适用于相关的图形API。查看它们的main()函数会发现它们都强制执行相关的RHI后端。

其中一些方法比经典的参考底图/覆盖方法走得更远。例如,metaltextureimport和vulkantextureimport示例演示了如何将QQuickItem添加到场景中,该场景实际上是一个带纹理的四边形,使用创建并渲染到Qt Quick Scenegraph控件外部的MTLTexture或VkImage进行纹理处理。

在Qt5.15中,主要的新闻是Qt Quick3D的引入,使3D世界、3D模型和PBR材料成为Qt Quick世界的头等公民。在很多方面,这仅仅是对Qt6.0即将推出的内容的一个预览。

虽然在Qt5.15中仍然与OpenGL捆绑在一起,但Qt6.0发布时更新了Qt Quick3D的内部结构,现在基于基于QRhi的基础设施。正如文档页面所述,适用于Qt Quick的配置选项也隐含地适用于Qt Quick 3D。例如,如果Qt Quick配置为使用Vulkan,则通过设置QSG_RHI_BACKEND=Vulkan或使用等效的C++API,同样的情况也适用于Qt Quick 3D。

整个功能集,包括材质、照明、阴影、基于图像的照明、更新的自定义材质和后处理效果系统,都具有所有支持的图形API(OpenGL、Vulkan、Metal、Direct 3D 11)的完整功能。

随着在Qt中处理着色器的方式的普遍更新,自定义材质的概念在Qt 6.0中也经历了重大变化。现在,Qt Quick 3D材质系统有了一个全新的扩展,它允许创建可编程材质,其中网格着色的方式由应用程序提供的着色器代码控制,这些代码以与Vulkan兼容的GLSL代码片段的形式提供,通过上述Qt的标准着色器管道,从而在运行时与任何支持的图形API一起工作,同时仍由引擎修改以执行所有预期的照明、阴影、遮挡和其他步骤,将来自场景环境的所有贡献与应用程序提供的着色逻辑相结合。

现在对查看细节感兴趣的人,欢迎查看CustomMaterial页面和快照文档中的工作入门指南。

QRhi部分中的图表提到了窗口小部件,尽管乍一看它的位置可能很奇怪,远离QRhi和Qt Quick。这试图表明什么呢?

一般说来,在Qt5中工作的所有东西都可以在Qt6中工作,除了过时的和现在被删除的功能,比如所有带有QGL前缀的Qt4纪元类(最值得注意的是QGLWidget)。如果Qt5应用程序将其自己的OpenGL内容呈现到QWindow中,或者使用QOpenGLWidget,它将像以前一样运行。(在最坏的情况下,使用一些非常小的迁移步骤,例如,由于QOpenGLWidget移动到自己的模块openglwidgets而不得不更新应用程序项目文件)。

QRhi或新的着色器管道目前在这里没有作用,至少在Qt6.0中是这样。小部件的呈现类似于Qt5,而QOpenGLWindow或QOpenGLWidget中的OpenGL内容继续直接使用OpenGL API。

QQuickWidget是Qt6.0中一个有趣的混合体,需要应用程序强制使用OpenGL。这是因为虽然Qt Quick可以与其他图形API配合使用,但widget(QOpenGLWidget和QQuickWidget所依赖的)中的组合架构暂时继续直接使用OpenGL。

一个值得注意的变化是删除了角度,这意味着在Windows上角度不再与Qt捆绑在一起。这不会影响绝大多数应用程序,除了那些在幕后将OpenGL ES角度转换为Direct3D的应用程序。根据其依赖性的性质和复杂性,这类应用程序应该考虑使用OpenGL正确启用功能,或者考虑直接使用Direct3D。对于基于Qt Quick和Qt Quick3D的应用程序来说,没有角度在实践中很可能不是问题,因为Direct3D11现在是Qt6中的一流渲染选项。

这就是目前的情况。还有更多的Qt6图形主题可以谈论,但希望上面的内容能为Qt6.0中的更改和新特性提供一个很好的起点。期待在不久的将来会有更多的帖子,特别是在Qt Quick3D的背景下。