在云栖TechDay 活动第二十七期,来自阿里GM Lab的费义云(云魂)给大家带来了题为《VR大观》的分享。本次分享主要是从技术的角度梳理了VR方面知识的脉络,同时为听众们带来一次由浅入深的VR技术之旅。分享中所涉及的技术点主要包括:Head Tracking、镜片矫正、深度感产生、立体感渲染以及OpenGL坐标系变换等内容。

以下内容是根据PPT和现场分享整理而成。

VR核心问题

初学者要了解VR内的技术点是相对困难的,它要求的东西相对零乱:一方面需要一些偏图形学的知识;另一方面需要知道一些相对新的技术,例如陀螺仪、滤波等。初学VR时会感觉不知从哪里下手,如果要梳理清除其中的技术点,则需要确认VR中最为关键、最需要解决的问题。

VR中要解决的三个核心问题分别是:一解决沉浸感的问题;二是解决立体感的问题;三是解决3D感的问题。

VR要完成的使命是让用户在虚拟的环境中感知它和真实环境一样的交互。要达成这样的目的,首先需要隔绝外界对人的输入,最好的方式是通过盒子类的设备直接扣在眼睛前面,杜绝其看到外界的设备和环境,完全感应虚拟成像的环境。但这种方式导致了弊端,当特别近的物品到人眼前,人眼是无法聚焦的,因此为了便于聚焦,引入镜片,让特别近的画面能够看的清除。但是镜片的引入,带来了更多的问题,如放大镜效应和色散问题。

沉浸感能够让用户在虚拟环境中像真实环境感受一样,脑袋左右摆动,也要能够感知像真实环境一样摆动,所以需要准确的跟踪用户的头部行为;另一个问题是立体声,比如左耳听到相对较大的声音,就转头看一下发生了什么事情;此外,输入方面,需要各种各样的输入来模拟用户在真实环境下的交互。

立体感和3D感是两个不同的东西,3D感更多的是单目视觉,传统游戏常采用的方式;立体感在影院或者裸眼3D都能够感受到,它有一种纵深的感觉。

Immersion

引入棱镜导致了透镜畸变,透镜畸变有两种类型:一种是直线向外凸,称之为桶形畸变;另一种是线是向内凹的,称之为枕形畸变。在数学运算中,最基准的公式就是左边的三个公式,其中Xc和Yc表示图像的中心,X/Y分别指当前对应的像素点,经过畸变函数就变成带小尖号的X/Y,表示的是畸变后像素点所在的位置。L是r的函数,r是该像素点相对于光心的距离,两者之间有一定的比例关系,L可以像泰勒级数一样多项展开,实际情况下取到二次展开即可获得较好的矫正效果,也就是只需要确定K0/K1/K2,就可以得到一个校正函数。

直线在VR眼镜中是凹进去的,因此需要做的事情是将凹进去的直线进行一次反畸变,变成桶型畸变;然后再通过透镜来看,曲线就会重新恢复变直。

除此之外,透镜的引入还会造成色散现象,需要进行色散的校正,但由于色散校正太损耗设备性能,同时正常使用VR时也并不是很明显,所以这一点一般忽略。

下面来探讨下 Head Tracking方面的问题。一般手机上会有IMU带着三轴旋转,能够在一定情况下估计现在用户的姿势。但实际上最大的问题是传输过来的数据会因为硬件的不同、用户操作频率不同导致曲线的噪声较大,如果画成函数曲线的话,会发现波形呈锯齿状。我们需要做的任务就是将这些锯齿平滑掉。在head tracking中,最重要的命题是将曲线变得平滑,因此需要用到一些滤波器。常用的滤波器是卡尔曼滤波器和补偿滤波器。

手机中有三个重要的传感器:地磁、加速度和陀螺仪,实际上可以通过地磁以及加速度传感器推导出手机旋转的姿态,当然陀螺仪本身就能够独立测算出现在旋转的姿态。这两个结果和两个高斯分布一样都不可信,两者中间可以做一个高斯分布叠加求出比较靠谱的值,这是滤波在VR Head Tracking的一个应用。

另外一个应用,采用了补偿滤波器,例如在采用陀螺仪测算出的角速度,实际上是一个短期靠谱的值,长期就未必靠谱,因为每次测算要根据角速度然后乘以时间,一次一次的迭代上去,如果的每一个量都有非常小的误差,随着这个时间增长上去,误差量会越来越大,所以说陀螺仪短期是精确的,长期是不靠谱的;但是,加速度恰恰相反,它长期是靠谱的,短期不靠谱的。所以在决定一个值时,需要用陀螺仪这个短期靠谱的去纠正那个大趋势的幅度。

Stereoscopic

要了解立体感,首先需要知道人是如何感到深度的。单眼之所以能够感受都深度,是因为人脑学习的结果。因此,想要感受深度,双目是非常必要的。产生深度的原因包括透视投影、图片的细节度、遮挡以及透过光照和阴影、运动速率等,但最主要的原因还是双目产生视差造成的。

下面来具体看下视差如何计算的。

如上图所示,左右眼的视线方向是平行的,是非常垂直的发射出去的;竖着的平面是指投影面。在视差中存在正负关系,V1点分别投影到左右眼时会和投影平面有两个焦点,在这个焦点上可以看到在左眼的投影点和在右眼的投影点方向是不变的,形成正视差。对于V2而言,投影点刚好在物理平面上,此时视差是零;对于投影点在屏幕以外,视差会出现反转,形成负视差。因此,正视差人的感受是物体在屏幕以内,负视差的感到在屏幕以外,零视差则准确出现在屏幕上。

视差的正负以及零的情况对应于上图中曲线,它会随着深度的加大而慢慢变大。距离极大的情况下,视差变化率逐渐变慢,因此一个东西在相对较远的情况下,大家对它的深度感知并不明显,但等到距离近了,深度感知是相对明显的。另外,英伟达建议尽量不要去进行负视差的操作,尽管可以投影在屏幕外很酷,但实际上由于距离太近,人眼很难聚焦,容易让人眼部肌肉紧绷,产生眩晕感。一些文献建议如果存在负视差,不应超过收敛平面的二分之一。

最上面视差的计算公式,Separation是指瞳距65毫米;Convergence是收敛平面,指的是眼睛和投影面之间的距离;W指的是所在点的深度。根据深度测算出来分别投影到两个画面上的视差。

3D

在OpenGL Pipline中会有一些顶点的数据在Primitive Processing中进行一些图元的组装;然后在Vertex Shader中对图元的顶点进行一些相应的顶点变换;之后把变换好的顶点组装成对应的图元,如三角形、线段或者点;当图元组装完成后,由图元围成的区域去做一些光栅化;光栅化完成之后,可以交到Fragment Shader 当中对光栅化出来的每一个片源点去做相对应的着色;该步之后,对每一个片源点进行深度检测,如果太深直接抛弃,不再走后面的流程,如果没有被丢弃,接着送至Frame Buffer 上去上颜色的话,就需要来查看这个颜色是否透明,是否把之前的Frame Buffer颜色直接替换掉,还是做一下ColorBuffer Blend,之后再进行Dither,然后再走到Frame buffer里面。

实际上,进行透镜的畸变校正可以在Vertex Shader和Fragment Shader两个步骤中完成,在前者进行操作会比后者的性能好一些。

物体变换的过程如上图所示,是由模型空间投影到世界空间上;再由世界空间变到视的空间;再由视的空间变到剪裁空间NDC。实际上,在VR当中做Head Tracking 更多的一步是在世界空间转变到视空间的过程;游戏中的Camera已经变成人的真实眼睛所辐射进去的操作。

精彩问答:

提问者:3D感和立体感的具体区别?

云魂:在做3D时,只涉及单目的渲染;但是在做立体感时,需要双目的渲染,才能形成立体的感觉。3D感实际上并没有深度感,例如看一个立方体在旋转,时间久了就无法区分是顺时针还是逆时针旋转,但是如果把立方体能够带视差渲染成两个,并通过VR盒子去看,则不会造成这种混淆。

提问者:我的理解是:“如果一个东西它是立体的话,他肯定就是3D的。”

云魂:你可以再试一下,刚才我所说的那个两个笔尖相碰,并且你闭一只眼睛,然后你看他能不能对得上去可能会有一点感受,因为这个确实有非常多的人把这两个东西混淆,但是实际上他确实并不是一回事。或者再举一个例子,我们平常看裸眼3D的屏幕,实际上我们用正常的屏幕就已经能够看一个3D的场景了,但是我们还是需要类似的裸眼3D的屏幕去看立体感的东西,所看到那个屏幕是有深度的,也就是立体感。