一个全景图渲染引擎到底包含了些什么
发布于:2018-01-10 13:32,阅读数:978,点赞数:3
# 引言 这是我去年十一月在公司里做的一个分享,现整理成博客。视频资源来自我以前就职的实验室,未经授权禁止转载。 现在各大 App 都出现全景图乃至全景视频的样式了,那么一个合格的全景图引擎到底包含了什么呢?本文面向没有太多图形学基础的同学,算是一个简单的科普。 # 正文 ## 全景能力 首先列出一般的全景图所拥有的能力: - 360 度无死角可观看 - 通过手势可以旋转视角 - 利用手机传感器自动旋转视角 - 悬浮标签、点击交互 有了这些能力,就可以算是一个合格的全景图渲染引擎了。 ## 模型 目前全景图的渲染方法主要有`球面投影`、`柱面投影`、`天空包围盒`。 ### 球面投影 球面投影的物料是一张球面展开贴图。 最常见的球面投影就是世界地图。地球的近似球面展开图大概是这样:  经过拉伸后得到:  可以看出,球面投影的物料,在不同纬度都会有不同程度的拉伸。最极端的南北两极的极点都只有**1个像素**,因此球面展开图的最上面一行和最下面一行的颜色都是一样的。 <video controls preload="none" width=100% poster="//cdn.blog.yuusann.com/img/corpus/18002_3.jpg"> <source src="//cdn.blog.yuusann.com/img/corpus/18002_3.m3u8" type="video/mp4"> </video> 将视角置于球心,效果: <video controls preload="none" width=100% poster="//cdn.blog.yuusann.com/img/corpus/18002_4.jpg"> <source id="mp4" src="//cdn.blog.yuusann.com/img/corpus/18002_4.m3u8" type="video/mp4"> </video> ### 柱面投影 柱面投影的物料是一张柱面展开贴图。 <video controls preload="none" width=100% poster="//cdn.blog.yuusann.com/img/corpus/18002_5.jpg"> <source id="mp4" src="//cdn.blog.yuusann.com/img/corpus/18002_5.m3u8" type="video/mp4"> </video> ### 天空包围盒 天空包围盒的物料是六张小贴图。也可以把六张小图拼成一张大图一起生成纹理,在渲染层面这样更快,不用重复绑定纹理。  天空包围盒很容易理解,就是在一个立方体四周贴上图,形成一个全景。 ## 渲染 渲染这块,必然需要`OpenGL`的帮助,抛开具体实现,说下大概的步骤: ### 选择模型 选择渲染模型是`球面`还是`柱面`还是`天空盒`。 ### 建立模型 - 球面就是一个球。 - 柱面是一个曲面即圆柱的侧面。 - 天空盒就是一个立方体。 顶点坐标方面:天空盒的顶点非常简单,因此可以用手算完写死在代码里。柱面和球面比较繁琐,有大量三角函数运算,一般需要根据经纬度通过代码去`三角化网格`,所以用建模工具做好导入会比较理想。不过加载模型,需要选择支持的模型格式。解析这些模型的库大多是开源且轻量的,但目前最流行最强大的`fbx`格式是闭源的,而且很大,在移动端一般不会考虑支持。因此模型格式的支持也是一个需要考虑到点。 贴图坐标方面:同样的,天空盒可以手算,其他也有大量三角函数运算,需要通过程序算,或者事先建模。 ### 加载物料 根据不同的模型加载物料。物料自然就是各种图了,图像读取,解码成裸像素数据,喂给`OpenGL`生成贴图备用,具体不再赘述。 ### 渲染模型 这一步需要根据视角的具体情况上传坐标数据和变换矩阵。传感器数据会在这一步参与计算,得出旋转角度,进行矩阵变换。计算完毕后与着色器做数据交换,这里包含大量`OpenGL`和`Shader`操作,最后渲染出全景图像。 ### 渲染其他组件 常规的全景图到上一步就结束了。 如果需要在全景里面绘制标签,按钮等,会在这一步绘制上去。 ## 控制 ### 手势 手势是最常规的旋转方法,根据屏幕上滑动的距离,乘上系数就可以了。是不是很简单?不,**没那么简单**。 首先,会有参考坐标系的问题。极端情况当全景旋转到上下颠倒时,全景的整个世界坐标系和现实世界的坐标系是反的。那么此时本来向左滑动,全景应该向左转,此时会变成向右转。因此当`Z`轴颠倒时,其他坐标轴也需要倒置。 其次,参考坐标系偏移。上面提到的是手机颠倒的极端情况下,实际上平时使用过程中,只要用户的视角不是水平的,那么全景坐标系和现实坐标系是有夹角的,此时也是做不到向普通列表页拖动时那种粘手的感觉的。 要解决这个问题,必须考虑手机目前的姿态,将手机目前的旋转矩阵参与变换才行。 ### 传感器 传感器是虚拟现实技术的重要部分。 手机上内置了`九轴传感器`(三轴加速度计、三轴陀螺仪、三轴磁罗盘)看似能完全胜任虚拟现实头盔的重任,但目前市面上很流行的`VR`套件`HTC Vive`用的是激光定位,为什么呢?事实上通过九轴传感器做`VR`的坑非常大。 加速度计得到的加速度,和时间通过一次积分能得到速度,两次积分能得到位移。陀螺仪能测出角加速度,同样一次积分,两次积分能得到角速度和角位移。磁罗盘更能精确定位偏航角。理论上手头的数据完全够支撑虚拟现实了,但事实上,传感器的值本身在时间上是**离散**的,因此积分本身就存在误差,尤其是经过两次积分,会使误差大到无法接受。因此光靠九轴传感器是无法承担虚拟现实的重任的。 幸运的是普通全景图不需要测量位移,只需要测量手机的姿态就可以了,因此这些数据,够用了。 ### 如何确定手机姿态 你可能觉得陀螺仪和加速度计就可以实现效果了,再加上有磁罗盘用于校准稳得一逼。但仔细想想就会发现,并没有这么简单。 加速度计可以获得重力方向,可以用于计算俯仰角。但当你横向移动手机时,横向也会产生加速度,因此这个值只能作参考。你可能会想到低通滤波器,由于重力的方向是长期存在的,而移动是突发的,可以看成高频信号,低通滤波器能稳定拿到重力方向。没错,你说对了,安卓上的线速度传感器可以用在确定俯仰角上。 陀螺仪是测量角加速度的,那么要计算出移动了多少角度,需要与时间做两次积分。在手机平稳移动时,误差暂可接受,但手机快速移动的情况下,误差会非常大。 磁罗盘,由于手机姿态可能是倒着的,竖着的,翻滚的,磁罗盘的数据说实话,并没有什么大用,在某些需要和地磁极性关联的需求例如看星空,才用得上。 因此,通过传感器确定姿态是一件非常不容易的事情。 ### 欧拉角  欧拉角经常用于描述物体姿态,通过描述`偏航角`、`俯仰角`和`横滚角`三个角度可以确定物体在空间中的姿态。 在 iOS 中很早就已经可以直接读取设备欧拉角了,其可能是基于硬件,也有可能是通过协处理器软件实现。这三个角度值能描述手机在空间中的位置,直接将其计算入变换矩阵即可。 但欧拉角在临界角度会发生很严重的问题。例如:在设备处于俯仰角90度时,此时会发现世界坐标系中偏航轴和手机坐标系的横滚轴重合了,世界坐标系的横滚轴和手机坐标系的偏航轴重合了。这时候会产生`奇异点`和`万向节死锁`。此时再横滚手机,会发现横滚角转不动,全景突然转到后面等现象。 欧拉角无法解决临界角度`奇异点`和`万向节死锁`的问题。 ### 汉密尔顿四元数 汉密尔顿四元数是姿态的四维描述,包含一个三维向量和一个标量,模`1`。它可以完美描述物体在空间中的位置。每一种角度都对应一个四元数,且无奇异点。 在 iOS 中,系统同样提供了四元数接口,可以读取四元数,用于描述姿态。既然每一个四元数都对应了一个姿态,那么只要将四元数反向计算出欧拉角,这个欧拉角就是正确的欧拉角了。此时参与变换矩阵的计算就可以得到最后的结果了。 ### 低通滤波 低通滤波能有效滤掉手抖造成的画面抖动,以及能让旋转更柔和。 可以选择使用`卡尔曼滤波`,也可以自己写`低通滤波`甚至直接做`加权平均`来滤掉高频噪声。 滤波是把双刃剑,权值太高容易让场景看起来像浮在水面上,缺乏真实感。 ## 交互 ### 悬浮标签 悬浮标签是常常用在全景里的一种交互方式。 标签有几个特点: - 存在于三维空间的固定位置。 - 文字时刻正对着用户,且文字不能倾斜,变形。 因为要时刻正对,不能倾斜变形的性质,标签并不能像传统物体一样渲染。 关于实现方法,大概的思路是取变换矩阵的逆反矩阵,乘上物体的坐标后可以得到一个四维向量,前两维就是屏幕上的位置,第三维为深度,深度小于零代表物体出现在摄像机脑后,应不予显示,第四维为冗余。 ### 点击交互 既然有了标签,就可以实现点击交互。三维世界中点击可以通过`射线拾取`的方式实现。  大概的思路是,屏幕点击处的二维坐标转化为在`视锥`近平面(深度为0)和远平面(深度为1)的两个点,生成射线。此射线与物体的`三维包围盒`进行碰撞检测,即可拾取到点击对象。 # 结语 全景的渲染上主要还是常规的图形学渲染,传感器方面的算法包含了惯性导航的一些知识,硬件专业的同学可能对`惯性导航`这个课题比较熟悉,感兴趣的同学也可以自己查查资料。 那么对于移动端全景来说,全景图的时代已经到来了,那么全景视频呢?全景视频目前在带宽上受到了极大地限制,服务端带宽无法承受大量用户的在线观看。在网络带宽有重大突破以前,还需继续等待。
评论:0条