Qt 3D 简介

Qt 3D 提供了一个完全可配置的渲染器,使开发人员能够快速实现所需的任何渲染管线。此外,Qt 3D 还提供了一种通用的框架,用于实现渲染以外的近实时模拟。

Qt 3D 清晰地分为核心和多个 方面,这些方面可以实现他们希望实现的功能。方面通过 组件实体 交互,以提供一些功能切片。方面的示例包括物理、音频、碰撞、人工智能 (AI) 和路径搜索。

基本 3D 特性

Qt 3D 是一个 3D 框架,它使绘制 3D 形状并将它们在周围移动以及移动摄像机成为可能。它支持以下基本功能

材质

Qt 3D 拥有一个强大且非常灵活的材质系统,可提供多级定制。它支持不同平台或 OpenGL 版本上的不同渲染方法,实现了具有不同状态集的多重渲染过程,提供了在不同级别上覆盖参数的机制,并允许轻松切换着色器。所有这一切都可以通过 C++ 或使用 QML 属性绑定完成。

材质 类型的属性可以通过引用的属性中的效果,映射到 GLSL 着色程序中的统一变量中。

有关使用材质的示例,请参阅以下示例

着色器

Qt 3D 支持所有 OpenGL 可编程渲染管线阶段:顶点、片元、几何和片段着色器。计划在未来版本中提供计算着色器。

有关使用着色器的示例,请参阅Qt 3D:线框 QML 示例

投影映射

OpenGL 直接不支持阴影,但有无数技术可以用于生成它们。投影映射用于生成美观的阴影,同时具有非常小的性能开销。

阴影映射通常使用两阶段渲染来实现。在第一阶段,生成阴影信息。在第二阶段,使用特定的渲染技术生成场景,同时使用第一阶段收集到的信息来绘制阴影。

阴影映射背后的理念是只有离光源最近的片段被照亮。位于其他片段之后的片段被遮挡,因此处于阴影状态。

因此,在第一阶段,从光源的角度绘制场景。存储的信息仅仅是此 光空间 中最近片段的距离。在OpenGL的术语中,这对应于具有附加深度纹理的帧缓冲对象(FBO)。事实上,眼睛与距离 是深度定义,OpenGL默认的深度测试实际上只存储最近片段的深度。

甚至不需要颜色纹理附件,因为不需要对片段进行着色,只需要计算它们的深度。

以下图像显示了一个具有自阴影平板和三叶结的场景

以下图像显示了场景的夸张阴影映射纹理

该图像显示了自光源点绘制场景时存储的深度。较深的颜色表示深度较浅(即,离摄像机较近)。在这个场景中,光源位于场景中的物体上方的某个地方,相对于主摄像机来说在右侧(比较第一张截图)。这与玩具飞机比其他物体更接近摄像机的实际情况相符。

一旦生成阴影映射,就进行第二次渲染。在这第二次渲染中,使用正常场景的摄像机进行渲染。这里可以应用任何效果,例如Phong着色。重要的是在片段着色器中应用阴影映射算法。也就是说,离光源最近的片段被绘制为照亮,而其他片段则以阴影的形式绘制。

第一次生成的阴影映射提供有关片段到光源距离的必要信息。然后只需在光空间中对片段进行重映射,从而从光源点计算它的深度以及它在阴影映射纹理上的坐标。接着可以在给定坐标处采样阴影映射纹理,并将片段的深度与采样结果进行比较。如果片段离得更远,则它在阴影中;否则它是照亮的。

实例化渲染

实例化 是一种让GPU绘制多个(实例)副本的方式,每个副本在某种程度上都是不同的。通常,包括位置、方向、颜色、材质属性、缩放等。Qt 3D提供了一个类似于Qt Quick Repeater 元素的API。在这种情况下,代理是基础对象,而模型提供每个实例的数据。因此,一个带有 网格 组件的实体最终会转换为 glDrawElements 调用,而具有实例化组件的实体将转换为 glDrawElementsInstanced 调用。

实例化渲染计划在未来版本中实现。

统一缓冲对象

统一缓冲对象(UBO)可以绑定到OpenGL着色程序上,以使大量数据易于访问。UBO的典型用例是为材质或照明参数集。

有用提示

在此页面上可以找到一些非常有用的3D渲染编程技巧:Qt 3D Render Pro Tips

可配置渲染器

为了结合支持C++和QML API,同时拥有一个完全可配置的渲染器,引入了帧图的概念。虽然场景图是对渲染内容的数据驱动描述,但帧图是对渲染方式的数据驱动描述。

帧图允许开发者选择简单的向前渲染器,包括z-fill过程,或者使用例如延迟渲染器。它还控制他们何时渲染任何透明对象等。由于这一切都是仅从数据配置的,所以修改非常容易,即使在运行时动态修改也无需触及相关C++代码。可以通过创建实现自定义渲染算法的自定义帧图来扩展Qt 3D。

3D扩展

除了在屏幕上显示3D内容的基本要求之外,Qt 3D足够灵活和可扩展,可以成为以下与3D对象相关的扩展的宿主:

  • 物理仿真
  • 碰撞检测
  • 3D空间音频
  • 刚体、骨骼和形变目标动画
  • 路径查找和其他人工智能
  • 选择
  • 粒子
  • 对象生成

性能

Qt 3D旨在性能良好,并且可以根据可用的CPU核心数扩展。因为现代硬件通过增加核心数来提高性能,而不是通过基础时钟速度。使用多个核心效果良好,因为许多任务是相互独立的。例如,路径查找模块执行的操作与渲染器执行的任务几乎没有重叠,除非渲染调试信息或统计信息时可能会发生重叠。

Qt 3D架构

Qt 3D的主要用例是在近实时对象上模拟对象并将这些对象的状态渲染到屏幕上。Space Invaders示例包含以下对象

  • 玩家的地面加农炮
  • 地面
  • 防御块
  • 敌人太空入侵者飞船
  • 敌人飞行器上司飞船
  • 敌人射向玩家和敌人的子弹

在传统的C++设计中,这些类型的对象通常作为某种继承树结构中的类来实现。继承树的各种分支可能会为根类添加功能,例如

  • 接受用户输入
  • 播放声音
  • 有动画效果
  • 与其他对象发生碰撞
  • 在屏幕上绘制

Space Invaders示例中的类型可以根据这些功能进行分类。然而,即使是这么简单的例子,设计一个优雅的继承树也并不是一件容易的事。

这种方法和其他基于继承的变体存在许多问题

  • 深广继承层次结构难以理解、维护和扩展。
  • 派生分类在编译时就已经确定。
  • 类继承树中的每一级只能根据单一标准或轴进行分类。
  • 共享功能往往会在一段时间内向上冒泡到类层次结构中。
  • 无法预测开发者将要做什么。

扩展深广的继承树通常需要理解并同意原始作者的分类法。因此,Qt 3D将重点放在聚合函数上,而不是继承作为向对象实例传达功能的方法。具体来说,Qt 3D实现了实体组件系统(ECS)。

使用ECS

在ECS(实体组件系统)中,实体代表一个模拟对象,但本身没有特定的行为或特征。可以通过让实体聚合一个或多个组件来在实体上附加额外的行为。每个组件都是某一种对象类型的垂直切片行为。

在《太空侵略者》示例中,地面是一个带有附加组件的实体,告诉系统该实体需要进行渲染以及需要哪种类型的渲染。敌人太空侵略者飞船是另一个带有附加组件的实体,这些组件使飞船可被渲染,并使其能够发出声音、被碰撞、动画处理以及由简单的AI控制。

玩家的地面炮台实体与敌人太空侵略者飞船具有绝大多数类似的组件,除了它没有AI组件。取而代之的是,炮台拥有一个输入组件,允许玩家移动它并发射子弹。

ECS后端

Qt 3D的后端通过方面的形式实现了ECS范式的系统部分。一个方面通过组合一个或多个聚合组件,为实体提供了特定的垂直切片功能。

例如,渲染方面会寻找具有网格、材质和可选变换组件的实体。如果渲染方面发现这样的实体,它知道如何从这个数据中提取信息并绘制出美好的图像。如果一个实体没有那些组件,渲染方面则会忽略它。

Qt 3D通过聚合具有额外能力的组件来构建自定义实体。Qt 3D引擎使用方面来处理和更新具有特定组件的实体。

例如,物理方面会寻找具有某种类型的碰撞体积组件,以及指定该模拟所需的其他属性(如质量、摩擦系数等)的另一个组件。一个发出声音的实体有一个指定其为声音发生器的组件,以及指定何时以及播放哪些声音。

由于ECS使用聚合而不是继承,因此可以在运行时动态地仅通过添加或移除组件来简单地改变对象的行为。

例如,为了使玩家在获得能量提升后能够突然穿过墙壁,可以将该实体的碰撞体积组件临时移除,直到能量提升时间结束。无需为PlayerWhoRunsThroughWalls创建特殊的单例子类。

Qt 3D ECS实现

Qt 3D将ECS实现为一个简单的类层次结构。Qt 3D的基类是Qt3DCore::QNode,它是QObject的一个子类。Qt3DCore::QNode增强了QObject,使其能够自动将属性更改通知方面,并拥有在整个应用程序中唯一的ID。方面存在于额外的线程中,而Qt3DCore::QNode简化了用户界面对象与方面之间的数据传输。

通常,Qt3DCore::QNode的子类提供额外的支持数据,这些数据被组件引用。例如,QShaderProgram类指定了渲染一组实体时要使用的GLSL代码。

Qt 3D中的组件通过扩展Qt3DCore::QComponent并添加相应方面所需的数据来实现。例如,网格组件由渲染方面使用,以检索应发送到OpenGL管道的逐顶点数据。

最后,Qt3DCore::QEntity只是一个可以聚合零个或多个Qt3DCore::QComponent实例的对象。

扩展 Qt 3D

将功能添加到 Qt 3D 中,无论是作为 Qt 的一部分还是针对您自己的应用程序,以利用多线程后端,包括以下任务

  • 识别和实现任何必要的组件和支持数据。
  • 将组件注册到 QML 引擎(仅当您使用 QML API 时)。
  • 继承 QAbstractAspect 并实现子系统功能。

Qt 3D 基于任务的引擎

在 Qt 3D 中,在每一帧中都会要求各组件执行一套任务,以及它们之间的依赖关系。调度程序将任务分配到所有配置的核心上以提高性能。

Qt 3D 的方面

默认情况下,Qt 3D 提供了 Qt3DRenderQt3DInput 方面。这些方面提供的组件和其他支持类在相应模块的文档中进行讨论。

Qt 3D 将在未来的版本中添加更多功能方面。

© 2024 The Qt Company Ltd. 本文档中包含的贡献均为其各自所有者的版权所有。本文档根据由自由软件基金会发布的 GNU 自由文档许可协议 version 1.3 的条款进行许可。Qt 以及相应的标志是 The Qt Company Ltd. 在芬兰及全球其他国家的商标。所有其他商标均为其各自所有者的财产。