目录
1.Qt 3D 概述
Qt 3D提供了一个完全可配置的渲染器,使开发人员能够快速实现他们需要的任何渲染管道。此外,Qt 3D提供了一个超越渲染的接近实时模拟的通用框架。
Qt 3D被清晰地分为一个核心和许多方面,这些方面可以实现他们希望的任何功能。方面与组件和实体交互以提供一些功能片段。这方面的例子包括物理、音频、碰撞、人工智能(AI)和寻径。
2.基础的3D特性
Qt 3D是一个3D框架,可以绘制3D形状并移动它们,以及移动摄像机。它支持以下基本特性:
- 2D and 3D rendering for C++ and Qt Quick applications(2D和3D渲染的c++和Qt Quick应用程序)
- Meshes and Geometry(网格和几何)
- Materials(材料)
- Shaders(着色器)
- Shadow mapping(阴影映射)
- Ambient occlusion(环境闭塞)
- High dynamic range(高动态范围)
- Deferred rendering(延迟渲染)
- Multitexturing(多重纹理)
- Instanced rendering(实例化呈现)
- Uniform Buffer Objects(统一的缓冲区对象)
- Pro Tips(专业技巧)
3.Materials(材料)
Qt 3D有一个强大的和非常灵活的材料系统,允许多个级别的定制。它迎合了不同平台或OpenGL版本上的不同渲染方法,支持使用不同状态集的多个渲染通道,提供了在不同级别上覆盖参数的机制,并允许轻松切换着色器。所有这些都来自c++或使用QML属性绑定。
材质类型的属性可以很容易地映射到GLSL着色程序中的统一变量,该程序本身在引用的效果属性中指定。
关于使用材料的例子,请参见Qt 3D: materials c++ Example和Qt 3D: Materials QML Example.
4.Shaders(着色器)
Qt 3D支持所有OpenGL可编程渲染管道阶段:顶点、镶嵌控制、镶嵌评估、几何和碎片着色器。计算着色器计划在未来发布。
关于使用着色器的例子,请参见Simple shaders QML示例,Qt 3D: Shadow Map QML示例,Qt 3D: Wireframe QML示例,以及Qt 3D: Wave QML示例。
5.Shadow Mapping(阴影映射)
OpenGL并不直接支持阴影,但是有无数的技术可以用来生成阴影。阴影映射用于生成好看的阴影非常简单,同时性能成本非常小。
阴影映射通常使用双通道渲染实现。在第一个过程中,生成阴影信息。在第二遍中,场景使用特定的渲染技术生成,同时使用在第一遍中收集的信息来绘制阴影。
阴影映射背后的想法是,只有最接近光线的片段被照亮。其他片段后面的片段被遮挡,因此在阴影中。
因此,在第一个过程中,场景是从光的角度绘制的。存储的信息仅仅是这个光空间中最近的碎片的距离。在OpenGL术语中,这相当于拥有一个带有深度纹理的帧缓冲对象(Framebuffer Object,简称FBO)。实际上,到眼睛的距离是深度的定义,OpenGL所做的默认深度测试实际上只存储最近片段的深度。
一个颜色纹理附件甚至是不需要的,因为不需要阴影碎片,只需要计算它们的深度。
下图显示了一个带有自阴影平面和三叶结的场景:
下图展示了场景的一个夸张的阴影贴图纹理:
图像表示从光的角度渲染场景时存储的深度。较深的颜色表示较浅的深度(也就是说,更接近相机)。在这个场景中,光线被放置在场景中物体上方的某个地方,相对于主摄像机的右侧(与第一个截图比较)。这与玩具飞机比其他物体更接近相机的事实相吻合。
一旦阴影贴图生成,第二个渲染通道就完成了。在第二步中,渲染是使用普通场景的相机完成的。任何效果都可以在这里使用,比如Phong阴影。在碎片着色器中应用阴影映射算法是很重要的。也就是说,最接近光线的碎片被照亮,而其他碎片被画在阴影中。
在第一遍生成的阴影贴图提供了关于碎片到光线的距离的必要信息。然后它就足以在光空间中重新映射碎片,从而从光的角度计算它的深度,以及它在阴影贴图纹理上的坐标。然后可以在给定的坐标上对阴影贴图纹理进行采样,碎片的深度可以与采样结果进行比较。如果碎片离得更远,那么它就在阴影中;否则它被点亮。
示例代码,请参见Qt 3D: Shadow Map QML示例。
6.实例化呈现
实例化是一种让GPU绘制基础对象的多个副本(实例)的方法,每个副本都有不同的方式。通常,在位置、方向、颜色、材质属性、比例等方面。Qt 3D提供了一个类似于Qt Quick Repeater元素的API。在本例中,委托是基本对象,模型提供每个实例的数据。因此,一个带有Mesh组件的实体最终会被转换为对glDrawElements的调用,而一个带有实例化组件的实体则会被转换为对gldrawelementsinstance的调用。
实例化呈现计划在未来的版本中使用。
7.统一的缓冲区对象
统一缓冲对象(UBO)可以绑定到OpenGL着色程序,使大量数据容易获得。UBOs的典型用例是一组材料或照明参数。
8.Pro Tips(专业技巧)
因为墨菲定律在3D渲染中经常被证明是正确的,这里有几点可能会对你有所帮助。
9.可配置的渲染器
为了将对c++和QML api的支持与完全可配置的渲染器结合起来,引入了框架图的概念。场景图是对渲染内容的数据驱动描述,而帧图是对如何渲染内容的数据驱动描述。
框架图使开发人员能够在简单的正向呈现器(包括z-fill通道)和延迟呈现器之间进行选择。它还让他们可以控制何时渲染任何透明对象,等等。因为这是完全由数据配置的,所以即使在运行时动态修改它也很容易,而不涉及任何c++代码。可以通过创建自己的实现自定义渲染算法的帧来扩展Qt 3D。
10.三维扩展
除了在屏幕上显示3D内容的基本功能外,Qt 3D还具有足够的可扩展性和灵活性,可以作为与3D对象相关的以下类型扩展的宿主:
- Physics simulation(物理模拟)
- Collision detection(碰撞检测)
- 3D positional audio(3D位置音频)
- Rigid body, skeletal, and morph target animation(刚体,骨骼和变形目标动画)
- Path finding and other AI(寻径和其他人工智能)
- Picking(挑选)
- Particles(粒子)
- Object spawning(对象产卵)
11.性能
Qt 3D被设计为性能良好,并随着可用CPU核数的增加而扩展,因为现代硬件通过增加核数而不是基本时钟速度来提高性能。使用多个内核效果很好,因为许多任务是相互独立的。例如,寻径模块执行的操作与渲染器执行的任务并不强烈重叠,除非在渲染调试信息或统计信息时。
12.Qt 3D体系结构
Qt 3D的主要用途是模拟接近实时的对象,并将这些对象的状态呈现在屏幕上。《太空入侵者》的例子包含以下对象:
- 玩家的地面大炮
- 地面
- 防御块
- 敌人的太空入侵飞船
- 敌人的老板飞碟
- 敌人和玩家射出的子弹
在传统的c++设计中,这些类型的对象通常是作为安排在某种继承树中的类实现的。继承树中的各个分支可能会为根类添加额外的功能,例如:
- 接受用户输入
- 扮演了一个声音
- 是动画
- 与其他物体碰撞
- 画在屏幕上
《太空入侵者》中的类型可以根据这些功能进行分类。然而,即使是这样一个简单的示例,也不容易设计一个优雅的继承树。
这种方法和继承的其他变体带来了一些问题:
- 深入和广泛的继承层次结构很难理解、维护和扩展。
- 继承分类在编译时被设置为石头。
- 类继承树中的每个级别只能根据单个标准或轴进行分类。
- 随着时间的推移,共享功能倾向于在类层次结构中冒泡。
- 不可能预测开发人员想要做什么。
扩展深入和广泛的继承树通常需要理解并同意原始作者使用的分类法。因此,Qt 3D将重点放在聚合而不是继承上,作为将功能传递到对象实例的方法。具体来说,Qt 3D实现了一个实体组件系统(ECS)。
13.使实体组件系统(ECS)
在ECS中,一个实体代表一个模拟对象,但它本身没有任何特定的行为或特征。通过让实体聚合一个或多个组件,可以将附加行为移植到实体上。每个组件都是一个对象类型的垂直行为片段。
在《太空入侵者》的例子中,地面是一个带有附加组件的实体,它告诉系统该实体需要渲染以及它需要哪种渲染。敌人的太空入侵船是另一种带有附加组件的实体,可以让船呈现,但也可以让它发出声音,与之发生碰撞,产生动画,并由简单的AI控制。
玩家的地面炮实体拥有与敌人的太空入侵船相似的组件,除了它没有AI组件。取而代之的是,大炮有一个输入组件,让玩家能够移动它并发射子弹。
14.实体组件系统(ECS)后端
Qt 3D的后端以切面的形式实现了ECS范式的系统部分。切面实现了一个或多个聚合组件的组合提供给实体的功能的特定垂直部分。
例如,渲染器切面查找具有网格、材质和可选转换组件的实体。如果渲染器切面发现了这样一个实体,它就知道如何获取数据并从中绘制出更好的东西。如果一个实体没有这些组件,呈现程序切面就会忽略它。
Qt 3D通过聚合提供额外功能的组件来构建自定义实体。Qt 3D引擎使用切面来处理和更新带有特定组件的实体。
例如,物理元素会寻找具有某种碰撞体积成分的实体,以及指定此类模拟所需的其他属性(如质量、摩擦系数等)的其他成分。发出声音的实体有一个组件,该组件指定它是声音发射器,并指定何时以及播放哪些声音。
因为ECS使用聚合而不是继承,所以可以通过添加或删除组件来动态更改对象在运行时的行为。
例如,为了让玩家在升级后突然穿过墙壁,可以暂时移除实体的碰撞体积组件,直到升级超时。没有必要为playerwhoorunsthroughwalls创建一个特殊的一次性子类。
15.Qt 3D 实体组件系统(ECS)实现
Qt 3D将ECS实现为一个简单的类层次结构。Qt3D基类是Qt3DCore::QNode,它是QObject的子类。Qt3DCore::QNode增加了QObject的能力,自动通信属性变化切面和ID是唯一的整个应用程序。切面存在于额外的线程中,Qt3DCore::QNode简化了面向用户对象和切面之间的数据传输。
通常情况下,Qt3DCore::QNode的子类提供了由组件引用的额外支持数据。例如,QShaderProgram类指定了当渲染一组实体时要使用的GLSL代码。
Qt3D中的组件是通过子类化Qt3DCore::QComponent并添加相应切面工作所需的数据来实现的。例如,渲染器切面使用网格组件来检索应该发送到OpenGL管道的逐顶点数据。
最后,Qt3DCore::QEntity只是一个可以聚合零个或多个Qt3DCore::QComponent实例的对象。
16.扩展Qt 3D
为Qt 3D添加功能,无论是作为Qt的一部分,还是特定于您自己的应用程序,以从多线程后端受益,包括以下任务:
识别和实现任何必要的组件和支持数据。
向QML引擎注册组件(仅当您使用QML API时)。
子类QAbstractAspect并实现子系统功能。
17.Qt 3D任务型引擎
在Qt 3D中,切面在每一帧中被要求执行一组任务,以及它们之间的依赖关系。任务通过调度程序分布在所有配置的核心上,以提高性能。
18.Qt 3D的切面
默认情况下,Qt3D提供了Qt3DRender和Qt3DInput切面。这些切面提供的组件和其他支持类在这些模块的文档中进行了讨论。
提供更多功能的其他切面将在未来的Qt 3D版本中添加。