Qt3D建立的目标是能够让开发者快速地创建3D场景,通过各式各样的渲染技术来达到3D交互的场景。Qt3D除了和Qt一样跨平台之外,还能够选择OpenGL的具体实现,定制GLSL,从而能够根据不同性能的机器,来调整渲染效果。
Qt3D提供一个可充分配置的渲染器,开发人员通过渲染器可快速实现任何渲染管线。而且除了渲染外,Qt3D还提供一个近实时仿真的通用框架。
Qt3D由一个核心模块和附加模块组成。这些附加模块提供特定方面的功能,包括物理模型模块、音频模块、碰撞模块、人工智能模块和路径搜索模块。
Qt3D是一个可绘制并移动3D模型的框架,也可以移动相机。框架支持如下基本特征:
为C++和QtQuick应用提供2D和3D渲染
网状
材质
着色器
阴影贴图
环境碰撞
高动态范围(HDR)
延迟渲染
多重纹理
实例渲染
统一缓冲对象(UBO)
Qt3D提供的材质系统稳定且灵活,用户可以多级定制。它可以在不同平台或者不同OpenGL版本上实现材质渲染,通过传递不同的状态集进行多次渲染,提供不同级别的参数覆盖,允许在着色器间切换。这些特征通过C++或者使用QML属性绑定来实现。
在GLSL着色器程序中,材质类型属性能够通过统一变量映射,而着色器程序可在程序中来指定。
关于材质的例子,可参考 Qt3D: Materials C++ Example and Qt3D: Materials QML Example.
Qt3D支持所有的OpenGL可编程渲染管线阶段:顶点、排列控制、排列计算、几何和片段着色器。计算着色器(CS)将在以后的版本中支持。
着色器的例子可参考:theSimple Shaders QML Example, Qt3D: Tessellation Modes QML Example, Qt3D: Shadow Map QML Example, Qt3D: Wireframe QML Example, and Qt3D: Wave QML Example.
OpenGL并不直接支持阴影效果,但已提供多种实现技术。阴影贴图可以在非常低的性能损耗下产生不错的阴影效果。
阴影贴图通过二次渲染实现。在第一次渲染时产生阴影信息,在第二次渲染时通过特殊渲染技术产生场景,同时利用第一次渲染时产生的信息绘制阴影。
阴影贴图技术的原理就是只照亮最靠近光源的片段。其他片段的光线被遮挡,显示阴影效果。
因此,在第一次渲染阶段,以光源为视点绘制场景。在这个光照空间中,存储的信息仅仅只是最接近光源的片段实例。在OpenGL的术语中,这个实例对应一个缓冲对象(FBO),包含深度纹理信息。实际上,从眼睛到实例的距离就是深度的定义,OpenGL的默认深度测试实际上只存储最接近光源片段的深度。
颜色纹理附件甚至都不需要,对于阴影片段这是不必要的,仅仅只需要计算深度。
如下图片显示了带阴影贴图的飞机和三叶形纽结的场景:
图片显示了以光源为视点的场景渲染深度。阴暗颜色代表阴影深度(即靠近摄像机)。 此 光源放置在目标物体的上面,主摄像机的右边(与第一个截屏比较)。玩具飞机比其他目标更靠近摄像机。
当阴影贴图生成后,第二阶段的渲染就完成了。在第二阶段,渲染通过普通场景相机完成。此阶段可处理其他效果,例如补色着色。阴影贴图算法可用在片段着色器中,即靠近光源的片段绘制亮色,其他片段绘制阴影效果。
在第一阶段生成阴影贴图必要的信息,这些信息包含了片段到光源的距离信息。然后在光照空间重新映射片段,计算以光源为视点的深度,以及在阴影贴图纹理中的坐标。阴影贴图纹理以给定坐标系采样,并且片段深度可与采样结果进行对比。如果片段深度更远,则表示此片段在阴影中显示,否则此片段处在被光源照亮部分。
更多例子参考 Qt3D: Shadow Map QML Example.
实例化通过GPU绘制一个基本实例的多个副本,这些副本基于同一个实例变化。例如实例的位置、角度、颜色、材质属性、缩放比例等等参数的变化。Qt3D提供API访问QtQuickRepeater元素。那么,代理就是基本对象,而模型提供每一个实例的数据。因此一个带有网状的实体绘制过程将转换为对glDrawElements的调用,一个带有实例化组件的实体绘制过程将转换为对glDrawElementsInstanced的调用。
在将来的版本中,实例化渲染支持将会更多。
UBO可通过绑定到OpenGL着色器程序来提交大批量绘制数据。UBO的典型用例如材质和光照参数集。
为了让可充分配置的渲染器同时支持C++和QMLAPI,framegraph的概念诞生了。场景图形(scenegraph)和帧图形(framegraph)都是数据驱动的,两者的区别是,前者中的数据描述了绘制的内容,后者的数据描述了怎样绘制。
通过帧图形(framegraph),开发者可选择正向渲染器(按z轴深度顺序渲染),也可选择延迟渲染器,例如控制什么时候渲染透明对象等等。具体选择何种渲染器是可配置的,并且可在程序运行时配置,不修改任何C++代码。开发者可通过实现自定义的渲染算法创建自己的帧图形(framegraph),作为Qt3D的扩展。
除了在屏幕上渲染3D内容外,Qt3D还包含3D对象有关的如下特性:
物理仿真
碰撞检测
3D方位声频
刚体、骨骼、目标动画变换
路径寻找和其他人工智能
拾取
粒子
对象分裂
CPU核数越多,Qt3D的性能也会得到提升,现在的CPU都在通过增加核数而非提高时钟频率来提升性能。使用多核CPU可一定程度提升性能,许多任务间相互独立,可并行执行。例如,路径查找模块执行的动作和渲染器的渲染动作彼此间独立,可并发执行,获取渲染调试信息和统计信息时可单线执行。
Qt3D主要用于近实时对象仿真,同时在屏幕上渲染不同状态的对象。例子太空侵略者(TheSpace Invaders)包含如下对象:
玩家控制的地面大炮
地面
防御块
敌人的太空侵略船
敌人的指挥飞碟
敌人和玩家发射的子弹
传统的C++设计模式中,这些对象以继承关系的对象树类来描述。在对象树的各继承分支中可增加额外的功能和特性,例如:
接收用户输入
声音
生存/死亡状态
和其他对象的碰撞
是否要绘制在屏幕上
太空侵略者的例子包含这些特征。然而,哪怕为一个简单的例子设计一个优雅的继承树都并不简单。
这种继承的设计方法和其他类似的设计方法都存在如下问题:
深度和广度继承的等级难以理解、维护和扩展。
继承分类在编译时就已固定。
类继承树上的每一个层级只能基于一个单一的准则或主线来分类。
多个类共有的功能将上升到更上层类中。
无法预测开发者想要做什么。
扩展深度和广度继承树通常需要理解和认可原作者的分类方式。Qt3D是通过聚合而非继承的方式将功能赋予一个对象实例。为此,Qt3D实现了一个实体组建系统(ECS).
在一个ECS中,一个实体代表一个模拟的对象,但对象本身没有任何特定行为或特征。可以通过绑定实体组件增加ECS的行为。
在太空侵略者例子中,地面作为一个实体,包含一个附加的组件,此组件描述了实体需要渲染,使用哪种方式渲染。敌军的太空飞船是另一个实体,它的附加组件描述飞船的渲染、发出声音、碰撞、动态飞行、通过简单的人工智能控制飞行轨迹。
除了大炮没有人工智能组件外,玩家的大炮实体和敌军太空飞船实体两者的组件很相似。大炮有输入组件,用于帮助玩家移动大炮并发射子弹。
这里有一个重要的概念——面向方面。所以在其中你看到很多Aspect字眼。这是因为我们将一个实体(Entity)分成很多组件(Component),其中有很多组件组合起来成为一个方面,剩下的组成另外一个方面。比如说Qt3D有核心方面、渲染方面、声音方面、输入方面、Quick渲染方面。它们被组成一个个库,根据用户的需求来链接。
例如,渲染方面,会查找实体相关的网状、材质和转换(如果需要)组件。如果渲染方面找到这类实体,渲染器知道怎样获取数据并更好的绘制它们。如果实体没有这些组件,渲染方面会忽略此实体。
Qt 3D通过聚合提供附加能力的组件新建自定义实体。Qt3D引擎通过方面的指定组件处理和更新实体。
例如,物理方面会查找许多碰撞探测组件,模拟块状物理、摩擦系数等特定属性相关的组件。可发出声音的实体则有一个声音相关的组件,组件也会指定何时发出哪种声音。
因为ECS使用的是聚合而非继承,因此可在运行时通过简单的增删组件改变对象的行为。
例如,为了在某个通电状态使玩家突然穿越墙面,可在通电状态结束前临时移除实体的碰撞探测组件。但是我们并不需要实现特殊的开关子类例如PlayerWhoRunsThroughWalls
。
Qt 3D通过简单的类继承实现ECS。Qt3D基类是Qt3DCore::QNode,它是QObject的子类。当属性改变时,Qt3DCore::QNode会自动发出属性改变的通知,同时会在子线程中处理属性,Qt3DCore::QNode也会简化用户界面线程和属性相关子线程的数据传输。
一般来说,Qt3DCore::QNode的子类提供了对数据的额外支持,此功能用于组件中。例如渲染实体集时,QShaderProgram类指定使用GLSL代码。
Qt3D中的组件继承自Qt3DCore::QComponent,同时会为相关方面添加需要的属性。例如,渲染方面会使用网状组件检索每个应该发送到OpenGL管线的顶点数据。
最后,Qt3DCore::QEntity是一个能够聚合0个或多个Qt3DCore::QComponent实体的对象。
不论添加Qt3D的功能作为Qt的一部分,还是作为应用的一部分,多线程后端一般由如下任务组成:
确定和实现任何需要的组件和支持的数据。
使用QML引擎注册组件(仅在使用QMLAPI时)。
子类化QAbstractAspect并实现子系统功能。
在Qt3D中,在一个任务集的每一帧中都会询问使用哪方面执行。为了提升性能,调度器根据所有配置好的CPU核数分发任务。
Qt3D默认会提供Qt3DRender和Qt3DInput方面。这些方面会提供组件和其他支持的类,此类模块在文档中有说明。将来的Qt3D版本,额外的方面将会提供更多能力。
Summary
Qt 3D是Qt5.5.0中引入的新模块,目前处在技术预览状态。Qt3D模块在3D可视化、游戏和仿真领域应该有广阔的应用前景,能够使开发者比直接使用OpenGL接口更加快速和容易实现需要的特性。目前Qt3D相关的文档和支持还不是很全面,在未来的版本中会不断完善。