渲染引擎就是把设计做好的模型按要求显示到屏幕上。广义上来说,浏览器就是一个多媒体渲染引擎,office excel是一个图表渲染引擎。而3D渲染引擎就是要把3D动画模型显示到屏幕上。大家玩的3D游戏都是需要实时3D渲染引擎(游戏引擎)驱动的。比如王者荣耀就是使用Unity的游戏引擎。所有的游戏引擎都包含渲染引擎、物理引擎、粒子系统等。
照片底图 + 动画前景 = AR效果
先说一个3D动画模型怎么呈现到屏幕上吧。
一个3D动画模型包含组成模型的:
· 几何网格mesh(一般是三角面片),用于描述模型的几何外形;
· 骨骼关节joint/bone与mesh关联,就是蒙皮数据,用于描述哪些几何顶点跟哪些关节绑定;
· 动画animation,动画由多帧组成,每帧记录所有骨骼关节joint的transform转移矩阵,vertex绑定到joint上,所以也跟着一起transform(就是旋转,平移,缩放)。这样这个模型就动起来了;
· 材质material,用于描述面片是由什么材料组成的,主要描述材料的光学属性,即漫反射系数,镜面反射系数,颜色,以及各种贴图等等。所有几何面片只有赋予了材质,才能在灯光的照射下被看见。
渲染过程,简单来说就是:CPU加载模型文件,通过这些属性得到每一个时刻所有三角面片的物理位置,法线,切线等几何属性,贴图颜色等材质属性,然后交给GPU中的shader并行计算出应该显示在屏幕上每个像素的RGBA颜色值,然后通知显示器。具体过程比较复杂,此处只用几幅图让大家明白大致过程。
不夸张的说,一个3D动画引擎就是一个物理仿真器,要在计算机中模拟相机拍摄动画的物理过程,主要是光影的交互过程。
动画模型的组成:mesh,joint,animation,material
渲染的过程类似摄像机拍照过程
openGL 渲染过程
目前有很多商业与开源的游戏引擎,比如PC级别的虚幻UNREAL,移动端的Unity。但是这些引擎都是专业的游戏引擎,一个动态库.so大小动辄上M,甚至10M级别。而微信安装包的大小增长受限,无法使用这种引擎。所以为了让AR功能进入微信,就必须开发一款轻量级的3D渲染引擎。
AR需要的渲染引擎与游戏引擎有一些区别。渲染引擎的设计指标如下:
--跨平台 ios/android
--轻量级 <1M
--高效率 FPS>30 CPU<15%
--3D模型兼容性好,文件最小化
--支持骨骼动画
--支持动态阴影
--支持多种贴图
--支持选择交互
--方便易用的AR接口
其中CPU占用率是一个比较高的指标,因为AR本身需要占用CPU做物体识别,SLAM定位等运算,所以渲染不能占用太多CPU。
AR使用的3D模型一般是在线加载的,所以对模型的尺寸与加载方式有要求,需要模型尽量小,加载速度尽量快。
同时AR需要虚实交融,所以对渲染质量与实时性要求都比较高,需要实现动态阴影,支持一些次世代模型特性,比如法线贴图,高光贴图,AO贴图等。
AR还需要交互操作,比如旋转查看模型,点击模型特定部位触发动画等,所以需要实现选择机制。
首先为了跨平台,我们就需要使用与系统无关的第三方图形接口,openGL ES是目前唯一的选择。编程语言选择C++。第一版的引擎以openGL ES2.0为基础。
一般渲染引擎对CPU占用比较多的是Draw Call。CPU准备数据并通知GPU渲染的过程称为一次Draw Call。尽量减少Draw Call的次数可以有效降低CPU占用率。所以在绘制过程中我们根据mesh的material不同进行拆分subMesh。以subMesh为单位统一绘制,将一次渲染的Draw Call次数降低到最低
一般动画中每一帧的关节位姿计算在CPU中完成,此处涉及大量类似的矩阵乘法运算。我们将此部分移动到shader中实现,进一步降低了CPU的消耗。
一般的选择操作是通过ray casting计算射线与面片的空间关系。这部分涉及到大量运算,在交互过程中耗费CPU。我们使用GPU加速这个过程,将不同区域的mesh通过颜色编码进行绘制,然后直接读取渲染后buffer中的颜色值进行选择判断,加速了交互过程,同时降低了CPU消耗。
自定义模型文件格式,从面向模型交互到面向渲染。比如FBX文件中描述蒙皮绑骨是以关节joint为中心,以列表描述每个joint对vertex的影响权重。而渲染是以vertex为中心,所以我们构建了以vertex为中心的蒙皮绑骨数据结构。同时为提高效率,限制一个vertex的相关joint数目为8个以内,通过两个vec4的uniform来实现。为了避免大于8个的相关joint出现问题,我们队joint的weight权重进行排序后,截取前8个。渲染过程中要求先渲染不透明材质的物体,然后渲染半透明材质的物体,我们可以直接在模型文件中排好渲染顺序,不用显示时再分析处理。类似的处理还有几处,目的是保证模型文件加载后可以尽量少解析,直接可以渲染。
因为我们的模型一般是通过网络下载,所以模型文件的大小很重要。一个10M的模型文件一般下载也要3秒以上。所以尽量精简模型文件大小对体验非常重要。模型一般分为几个部分,几何数据 + 动画数据 + 材质数据 + 场景数据。
几何数据主要是顶点的position,normal,tangent,UV,以及三角面片的vertex index与material index,此处可以将一些double类型的数据转为float。
动画数据主要是绑骨信息以及frame的transform矩阵信息,此处转为以vertex为中心的绑骨结构,同时4*4的transform 矩阵精简为3 float的四元数类型。
材质数据主要是一些材质属性(漫反射系数,环境光系数,默认颜色等等)以及贴图数据。其中贴图数据一般比较大,一副2048*2048的非压缩贴图一般也会上10M,所以首先我们将贴图进行压缩。一些游戏使用非压缩的贴图,目的是减少贴图频繁解压的消耗,而AR应用一般不存在这个问题。当然,引入图像编码模块会增大渲染引擎体积,但一般APP都包含图像编解码库,所以不会增大最终APP尺寸。最终我们采用PNG格式,且尽量使用PNG8的索引方式。同时,我们支持将镜面反射高光系数压缩为1个字节写入法线贴图的alpha通道,这样可以将法线贴图与高光/镜面反射贴图合并。
场景数据一般包含模型在场景中的位姿,light信息以及camera信息,这部分数据量很少。
此外,为了可以在下载与加载过程中有更好的用户体验,我们考虑将几何数据,动画数据,材质数据分阶段传输。这样可以让用户先看到形状,动画,最后加上材质贴图。
虽然我们使用自己定义的wx模型文件进行渲染,但我们支持广泛使用的FBX文件交互格式。我们提供了一个工具可以将FBX模型转换为wx模型。FBX模型中的一些信息我们尽量都予以保留,特别是场景相关信息。一般游戏制作都需要将模型导入到场景中,然后设置材质,设置位置,调节灯光等。而AR中隐含了场景,所以我们尽量利用FBX文件中的场景信息。
理论上,模型的面片数目越多越精细,渲染结果越真实。一个由36个三角形拼接形成的圆形肯定没有一个由360个三角形拼接形成的圆形边缘光滑。但是手机端GPU的资源有限,一般要求全屏幕不要超过10W面片。在面数受限的情况下,现在游戏使用的次世代模型都通过多种贴图来提高模型精细度。比如使用漫反射贴图,高光贴图,法线贴图,AO贴图等。可以说,现在各种渲染引擎最大的不同就在材质与贴图的应用上。
经典的phong光照模型如下:最终的颜色=环境光反射+多个光源的漫反射+多个光源的镜面反射。
phong光照模型
一个人体次世代模型的效果图与4个贴图(右侧4个贴图分别是,漫反射,镜面反射,法线,AO)
一个人体次世代模型的效果图与4个贴图(右侧4个贴图分别是,漫反射,镜面反射,法线,AO)
各种贴图的效果(从左到右,从上到下:无贴图,漫反射贴图,加光照,带法线贴图的光照,带法线贴图与漫反射贴图的光照,带光照阴影)
4.1选择
AR中涉及到用于对模型的选择与交互,而现有的引擎不支持选择操作。
目前游戏引擎一般选择ray casting的射线检测方式实现,即通过选择的成像平面UV坐标与摄像机参数得到一条通过摄像机光心的射线,然后判断这条射线与所有三角面片的交点,最后通过深度判断得到最前方的选择物体。这种方法需要CPU来解算,在面数较多时效率并不高。
所以我们采用另外一种方法即颜色索引法,将需要区分的不同物体或者物体的不同部分根据mesh的ID赋予不同的颜色值,然后进行一次渲染,最后通过读取选择成像平面UV坐标上的像素颜色值来确定物体mesh的ID。比如,在模型创建过程中根据需要将模型的不同部位分割为不同的mesh,一个人头部与身体,左胳膊的mesh ID分别是1,2,3。对应的颜色可以是(32,0,0,),(64,0,0),(128,0,0)。当我们点击头部时,读取点击像素的颜色为(32,0,0),则我们通过反查ID-颜色对应表可以知道选择的是头部。
当模型是绑骨动画模型时,我们也可以将joint关节的ID与颜色对应,这个可以返回选择的joint关节,然后触发相应的动作。实际上RGB有三个通道可以用于索引,可以分别对应meshID,subMeshID以及boneID/jointID。
这种选择机制的好处是完全由GPU实现,不消耗CPU,而且可以直接返回有意义的mesh与joint的ID,而不是面片索引。
ray casting 示意图
动画模型与非动画模型在选择模式下的渲染错误! 未指定文件名。
4.2交互
现有的引擎支持旋转,缩放,平移交互操作,相当于是一个3D动画模型的浏览器操作。但AR中最重要的是模型要出现在“适合”的地方,而且就像环境中的真实物体一样,不随手机运动而移动。我们利用了手机的IMU信息,得到手机的姿态,然后将手机姿态与虚拟相机姿态绑定实现此效果。同时,利用IMU的重力信息还可以使模型始终“站”在地面上。
实现上用户对模型的操作修改ModelMatrix矩阵,手机姿态调整ViewMatrix矩阵,ProjectMatrix与相机内参一致,最后相乘得的MVP矩阵用于坐标变换。
手机上下左右渲染时模型的显示位置与实景物体一致
几何变换流程
动态阴影是中高端3D游戏引擎的必备功能,但在AR的应用中目前还没有普及。目前市面上的一些AR应用都是使用虚假静态阴影,就是把阴影当做模型一部分进行绘制,而不是实时通过解算得到阴影。动态阴影的实现需要多次渲染,所以GPU消耗稍高,但优化后在手机端还是可以实现的。
关于动态阴影的实现方式网上有很多教程,此处就不详解解释了。只简单描述,就是先在光源位置渲染一次,得到深度图,然后在摄像机位置渲染的时候读取深度图判断是否是阴影区域,然后做相应处理。
动态阴影示意图(左上角小图是光源位置渲染的深度图)
阴影效果对比(左:虚假静态阴影,右:动态阴影)
AR是将虚拟渲染的动画与实景照片结合,所以也要将摄像机拍摄的画面进行渲染。一般来说,照片最为背景,动画作为前景。就是引擎支持2D图像的渲染。同时摄像机的参数还要与真实手机的内参一致。
其次,有时需要渲染曲面视频,比如在一个瓶子上渲染广告宣传片,此时引擎要支持3D曲面视频的渲染。
更进一步,有时AR要求物体不仅出现在前景,而要部分位于实景后面,比如一个小精灵躲在瓶子后面。此时,还需要将实景模型(预先重建或实时重建)进行预渲染,遮挡部分动画,然后再用实景照片替换渲染的实景。
在实景照片上渲染
曲面视频渲染
目前Q3D引擎处于内部测试中,我们转换了一批教育,游戏,文物,家居类的模型进行测试。
我们此次将Q3D引擎升级到0.2版本,虽然增加了一些功能,但距离完美的AR渲染引擎还有很长的距离。比如真正做到虚实交融,还有估计环境光的位置与强度,实现虚实的光影一致。后续我们会继续努力。
平面 MARK 下 AR 效果
曲面 MARK 下 AR 效果大放送~