跨越现实的屏障-腾讯视频中的VR技术实践
导语: 虚拟现实(Virtual Reality,VR)技术在过去的几年中从狂热逐渐的趋于冷静,随着技术的发展与积累,越来越多的VR应用出现在人们的视野里,随着王菲演唱会的顺利结束,腾讯视频在移动端也支持了VR视频的播放,本文主要探讨虚拟现实在视频渲染中的解决方案,以及有关虚拟现实视频的未来走向。
Vitrual reality was once the dream of science fiction. But the internet was also once a dream, and so were computers and smartphones. The future is coming.
-MARK ZUCKERBEG
回顾2016,虚拟现实技术与深度学习技术占据了科技界的各种头条,随着Tensorflow,MXnet,OpenAI等深度学习平台的完善以及公开,AlphaGo与Master的不断胜利,人们在不断地提升着计算机的思考能力,融入到我们生活的各个角落里,而虚拟现实技术,则是让人们能够沉浸在计算机运算所产生的虚拟世界里,正如美剧西部世界中描述的一样,我们在不断的推进人与机器的距离。
戴着镣铐起舞-移动端中的VR渲染技术
不愿被束缚,是我们的天性,为了获得更好的沉浸体验,各种各样的传感器将人与主机进行连接,这也导致了用户的活动空间是非常有限的,并不能随心所欲的移动。而移动端设备则挣脱了这些束缚,使人们可以随时随地的使用VR设备,但不可否认的是,虽然移动端的计算能力日益强大,但面对场景十分复杂的虚拟现实渲染仍然捉襟见肘。尽管如此,各大厂商们仍然发布了移动端的VR设备以及开发组件:
厂商 | VR设备 | 开发组件 |
DayDream/Cardboard | Google VR SDK(Android,iOS,Unity) | |
Samsung/Oculus | GearVR | Oculus Mobile SDK |
暴风影音 | 暴风魔镜 | AR SDK/ Gesture SDK |
乐相科技 | 大朋魔镜 | Deepon VR SDK |
移动端的虚拟现实设备一般分为两类,一类仅仅提供一组镜片,或者是简单的控制传感器,将计算任务交至手机,例如Cardboard设备,这应当是目前离消费者最近的VR设备了,另一类则是将其进行整合,提供VR专用的一体机。
在移动端,随着网络质量的提高,看视频逐渐成为人们消磨空闲的方式之一,虚拟现实视频也是一个非常重要的分支,3D电影早已被人们所接受,而虚拟现实视频则在3D的基础上,更近一步的提升了用户沉浸体验,相对于VR游戏需要各种实时复杂的场景建模,虚拟现实视频则在视频制作时就以完成,VR设备仅需要对视频进行解码,并将其正确的渲染至对应的显示设备上。目前移动端的虚拟现实频按照观看方式的不同,可以分为两类360全景视频,以及双目立体视频。本文将会分析这两种模式的区别,以及腾讯视频的解决方案:
从技术上来说,VR视频渲染与游戏渲染并无差异,因此很多游戏引擎都提供了VR渲染的支持,例如Unity,Unreal等游戏引擎,并且Google也提供了相应的开发插件,但是在很多场景下,我们不希望引入繁重的游戏引擎,希望能够简单轻量的实现虚拟现实视频的渲染,在腾讯视频中,我们主要使用OpenGL实现虚拟现实视频的渲染,减少第三方的依赖,以获得更好的适配性。
腾讯视频VR SDK 拥有产品特性
- 多平台支持,支持iOS,Android端的全景视频的播放
- 机型兼容性强:结合腾讯视频播放器SDK的使用,支持硬解码以及软解码模式,适配性强
- 具备lens-distortation(镜头矫正)模式,为佩戴VR眼镜的用户提供更好的观看体验
- 轻量快速,纯OpenGL调用,无需第三方组件即可轻松实现VR视频播放
- 可交互性,简单可控头部追踪以及触摸模式。
360度全景视频:
1) 普通视频与全景视频的移动端渲染方案对比:
Android端的视频渲染:
图1.普通视频的渲染流程
Android端视频的渲染的模式如图3所示,视频的解码一般可以分为硬解码模式与软解码两种渲染流程,在硬解码的模式的渲染中,通过将Surface设置给播放器使用GPU接管视频的解码以及渲染播放。如图3中蓝色标记的序号所示;而软解码则需要播放器输出YUV的数据,交由OpenGL渲染至texture,最终显示到终端屏幕上,一般情况下,硬解码需要硬件厂商的支持,不同硬件厂商在实现上可能有着差别,而软解码模式则相对比较可控,但占用了CPU的计算资源。
全景360视频
全景视频在拍摄时,利用多个摄像头对周围环境的影像进行捕捉,并采用图像拼接技术将多张图片拼接成一幅全景图像,在显示时,则与捕捉的过程相反,通过前端设备采集运动数据,依据计算出的可视角度,实时的更新显示数据,最大限度的还原拍摄的现场。
全景视频通过提供任意的观看视角以达到沉浸式的观看体验。在视频渲染时,我们将视频帧映射与球形渲染体进行映射以达到全景视角的目的,通过将用户的观看视角置于球体的中央以还原原始的拍摄场景。全景视频的采集以及播放渲染的整体流程如下图所示:
图2.全景视频的采集与渲染流程
全景视频数据的获取
在全景视频渲染播放中,渲染数据是必不可少的,在现有的技术框架下,全景视频与普通视频在文件的格式以及存储上是一致的,换言之,采用普通视频的播放以及渲染方式对于全景视频是完全可行的,当然,用户看到是整幅没有经过处理过的图像(如上图中的全景图片),所谓全景感也并不存在了。因此,为了达到全景渲染的目的,在解码端我们可以采用普通视频的解码方式,但由于全景视频的渲染的特殊性,解码后的数据需要进一步处理后,再驱动OpenGL进行渲染,从而达到“全景”的目的,因此全景视频的播放经历了解码,渲染数据获取与处理,OpenGL渲染这三个环节。下面我们将分别阐述Android端与iOS端的数据获取方式。
Android端的渲染数据获取
在上个小节中,我们简要介绍了Android端的两种播放渲染思路:GPU等专用芯片主导的硬件解码以及CPU主导的软件解码,在全景视频的渲染中,针对以上两种不同的模式也有着不同的处理思路:
硬解码:在普通视频的播放中,正如图3中的流程所示,需要应用层做的工作主要包含了初始化一个与OpenGL相关的Surfaceview以及对应的回四平播放地址,便可以实现播放功能,但对于全景视频来说,Android系统并没有直接支持全景模式的播放,因此我们需要另辟蹊径,在采用硬解码模式时截获播放器的渲染数据,从而进行全景模式的渲染。
获取数据时主要利用了Surfacetexture中的updateTexImage方法:
void updateTexImage ()
Update the texture image to the most recent frame from the image stream. This may only be called while the OpenGL ES context that owns the texture is current on the calling thread. It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
通过调用updateTexImage方法,我们可以将解码后的数据绑定至OpenGL的texture中,从而可以使用OpenGL自定义渲染。具体的使用方法如下(省略了一些无关的代码)
图3.使用surfacetexure获取解码数据
在实现中,我们利用onFrameAvailable接口回调通知OpenGL更新texture,但在实际的应用中,这个接口有时不能及时的回调刷新,因此我们采用了保持一个固定的频率去主动更新texture,而onFrameAvailable这个接口仅起到了辅助通知的作用。
软解码:在软解码的模式下,我们可以直接的获取到视频帧的YUV数据,因此可以直接去驱动OpenGL进行渲染,与硬解码使用Texture进行渲染不同,软解码直接使用YUV数据进行渲染(相当于渲染一帧bitmap),因此两者对应的fragmentshader也有所区别。
iOS端解决方案
iOS中获取数据的方式比较简单,主要通过AVPlayerItemVideoOutput中的copyPixelBufferFromItemTime这个接口获取播放器解码后的视频帧数据再进行OpenGL渲染,与Andorid端的软解码模式比较相似,但这里在数据处理上主要需要注意一下几点:
- 长时间获取不到PixelBuffer时需要重新初始化output防止黑屏
- Pixelbuffer中的数据格式在初始化时需要配置,在渲染时需要注意yuv的数据格式
- 全景视频的渲染可能会使得上层UI卡顿,因此需要异步的渲染保证UI的流畅性。
沉浸式体验:
360度全景视频给予了用户更高的自由度以及与视频内容进行交互便利,极大丰富了观看体验,但离上文所述的虚拟现实情景依然有着不小的距离,而沉浸式的观看体验可以更快速的带领用户进入虚拟现实世界的是,但相应的用户需要佩戴VR眼镜(例如cardboard眼镜)将视频分为左眼与右眼两个显示区域以配合虚拟现实的要求,VR眼镜中一般包含了两组镜片,通过镜片增大用户的可视区域(field of view,FOV),增强虚拟现实感,但同时也带来了图像畸变的代价。
在双目的VR视频中,其中单个眼睛的显示区域与360全景视频并无差异,因此一个最简单的想法是将原先的全景视频简单的复制一份,将屏幕分为两个相同的区域,对两个区域执行相同的渲染即可。利用OpenGL中的glScissor功能可以将glviewport分割为不同的渲染区域,用于执行不同的渲染指令,具体的渲染流程如下:
图4.多窗口的实现
更真实的双目视频:
由于VR眼镜中镜片的使用,导致用户眼中的成像会发生枕型畸变(Pincushion distortion),如图7所示
图5.枕型畸变与桶形畸变
若在视频渲染中不对视频帧预处理,用户所观察到的图像将由于物理镜头的原因产生形变,进而影响VR视频的观看体验。因此,为了减少镜头畸变产生的不良影响,我们在对视频帧渲染前,使用反畸变(anti-distortation)技术对视频帧进行预处理,反向抵消镜头的物理枕形畸变带来的影响,其可视化的效果如下图中A所示,图中B则是没有增加数字反畸变后的效果。
图6.反畸变示意图
反畸变技术
在图像处理领域,反畸变算法有着成熟的研究以及应用体系,其相关的数学计算公式也非常明确,但在具体的应用实现中,有着不同的实现方法,并且其运行效率也有着显著的差别:
- Fragment based solution:算法对整幅视频帧的数据利用OpenGL中的shader对每个像素进行反畸变处理,即类似于图像处理的原理,这种方式在实现上较为简单,但是由于在每次渲染前需要对整幅图像进行处理,将会严重的影响渲染的效率,用户可能会明显的感受到滞后与卡顿感。
- Mesh based solution(我们使用的方式): 这种算法通过使用额外的一个Texture作为渲染的中间层,存储渲染数据,并通过已经计算好固定的Mesh面对中间数据进行反畸变处理,例如当我们需要渲染一帧大小为1920*1080大小的视频帧时,采用第一种方法每帧将要处理百万级别的顶点数量,而采用Mesh based的方法,若我们将Mesh定为10*10大小,则处理的顶点将由上百万降至一百,并且大多数VR视频的分辨率远远不止1080P,因此使用临时的中间存储所带来的收益大于其相应的技术代价,因此在腾讯视频的VR模块中采用了这种技术
- Vertex displacement based solution: 这种方式是目前最有效也是最简便的一种方式,利用了geometry vertex displace 方法,直接利用vertexshader进行反畸变处理,但遗憾的是OpenGLES对这种支持并不完善,因此不是本文的讨论重点,有兴趣的同学可以参考下面的文章::http://www.gamasutra.com/blogs/BrianKehrer/20160125/264161/VR_Distortion_Correction_using_Vertex_Displacement.php
图7.腾讯视频中全景渲染mesh面
图8.反畸变后的全景渲染mesh面
图9使用反畸变后的视频变换
虚拟现实中的矩阵变换:
头部运动追踪
全景渲染以及双目渲染技术已经在视觉效果上达到了虚拟现实的标准,但仍然需配合头部追踪技术以定位用户的空间位置,已达到实时显示的目的。
移动端的头部追踪技术在硬件上主要传感器实现,手机类的虚拟现实设备则主要依靠加速度传感器以及陀螺仪确定用户的相对空间位置,在实现上则需要通过相应的矩阵变换与OpenGL控制显示区域。
OpenGL中的矩阵变换:
首先,OpenGL中进行空间转换(旋转,投影,变形)均使用4*4大小的矩阵,在不同的平台上可能存在着行主序或者列主序,但大多数都遵循着列主序的规则,如下图所示,在具体的实现中可以初始化一个大小为16的一维数组按序写入即可。
图10.OpenGL矩阵中的元素下标
在OpenGL中,分别有四种不同类型的矩阵,包含了:GL_MODELVIEW, GL_PROJECTION,GL_TEXTURE, 以及GL_COLOR。其中前两种主要涉及到物体的空间变化,后两者主要是关于纹理色彩变换的,在本文中将略去一些OpenGL的具体细节原理,主要阐述虚拟现实视频中的渲染与OpenGL矩阵的关系,这其中主要涉及到MODEL,VIEW,以及PROJECTION这三种矩阵。图13则解释了三种矩阵之间的的对应关系,不过值得注意的是,在OpenGL中,Model和View并非独立存在,而是以ModelView矩阵的形式出现。
在普通视频的播放中,我们面对的是一个二维的渲染世界,因此普通视频的渲染可以完全舍弃各种矩阵空间转换,直接进行纹理贴图即可,而虚拟现实视频则是一个三维的渲染世界,其边界为上小节所叙述的球形边界,而我们观察点则在球心,通过变换观察者的位置,我们可以观察到三维世界的各个角落,从而营造出虚拟现实的感觉。
图11.OpenGL中空间变换
Model: Model矩阵指的是渲染的物体,针对虚拟现实视频中,球形体则是我们需要渲染的对象,在OpenGL中,描述一个渲染物体遵循着固定的思路:
- 选定一个固定参考的坐标系(local object space)
- 针对不同的形状,写入对应的坐标数据,生成三角面信息
- 将坐标信息传入Vertex Shader.
图12.球形Sphere的生成计算
View:View矩阵是指OpenGL中摄像机的位置,对应在VR渲染中,View矩阵则代表了用户 头部的位置,通过对View矩阵施加空间变换,可以更新用户的可视区域,从而达到模拟头 部追踪的目的。
图13. 初始化摄像机矩阵以及旋转
Projection: Projection矩阵主要决定了渲染物体的投影方式,包含了透视投影以及正交投影 两种方式,在VR中,360全景使用透视投影表现出球体的立体感,双目渲染则使用正交投影 的方式展示反畸变技术。
矩阵旋转
在OpenGL中我们使用矩阵施加空间变换,我们通过传感器捕获到的角度以及重力等加速器 数据,换算为用户运动数据,将其映射为旋转矩阵,并将其施加至OpenGL中的摄像机,最 终实现空间变换。
虚拟现实视频的空间变换主要涉及到摄像机的旋转操作,旋转既可以通过角度来定义(X,Y,Z),也可以通过四元组的方式,通过四元组可以有效的规避万向节死锁的问题,并且四 元组与旋转矩阵之间的转换关系也十分明确,因此,在iOS端以及android端统一使用了四元组 作为旋 转的基本定义。并且针对手势操作,通过角度与四元组的转换进行统一,具体的 流程如下图所示:
图14. 旋转矩阵与四元组之间的转换
总结:
本文简要的梳理了虚拟现实视频渲染中的一些技术要点以及开发流程,主要包括了不同移动端下视频渲染数据获取方案的选择,以及OpenGLES在虚拟现实视频渲染中的应用,腾讯VR视频的渲染得益于播放器内核的支持,在原理以及流程上并不是一个非常复杂的过程,但在实际开发的过程中,仍然踩了不少坑,例如在实现旋转的功能时,可能旋转矩阵中某个数据的正负值取反会有这千奇百怪的差别;在Android端从播放器获取数据时,若Surface获取的时机与OpenGL Context初始化的时机不对,都会导致黑屏的现象,因此需要良好的设计去尽可能的规避这些坑的出现,写出稳定性更强的代码。
虽然虚拟现实的概念在最近几年十分火热,在PC端已经有着相对成熟的设备以及软件支持,但在移动端,VR一直以来都面对着以下问题:
- Framerate(刷新率):针对普通的游戏30-60fps便可以满足用户观看要求,但由于虚拟现实设备的特殊性,过低的fps将是使用产生眩晕感,无法持续的观看或者操纵游戏,尤其的,现在段移动端与PC相比,性能有着显著的差别,因此移动端的虚拟现实应用需要更加精心的设计,在资源消耗低的同时保证可正常运行的屏幕刷新率。
- Resolution(分辨率):在虚拟现实中,用户所处是整个虚拟的世界,但其可视范围仅仅是整个世界的一小部分,以虚拟现实视频为例,球形渲染体使得用户的可视区域只是整个视频帧的十几分之一,若期望每个可视区域的的分辨率都能达到1080P的标准,其待解码数据量将会十分巨大,可能在解码端就会消耗非常大的时间,与此同时,OpenGL渲染中也会存在着锯齿等现象,也需要额外的加入一些抗锯齿的算法提升图像的平滑性,但与之而来的是资源消耗的提升,因此在虚拟现实的世界中,我们需要很好的平衡分辨率与用户体验
- Thermals(发热控制):由于虚拟现实设备大多数都为头戴式设备,大家都不会愿意头上戴着五六十度的发热器来玩游戏或者看视频,因此,热量的控制也十分重要,但我们的确需要大量的计算资源去保证虚拟现实中的各种功能的正常运行,这两者也存在矛盾与平衡,例如某些手机在发热过高时会对部分协处理器进行降频处理,若恰好包括了运动传感器,最终将导致定位用户的空间位置时出现时滞,最终运动与屏幕显示脱节,使观看者产生严重的眩晕感。
不过随着硬件的发展,适配虚拟现实的硬件也逐渐出现,在当下一些游戏引擎厂商也不断的在VR领域发力,提供了很多好用的开发组件简化开发,例如英伟达提出的VRWorks(http://www.geforce.com/hardware/technology/vr/technology)直接在底层对虚拟现实开发进行适配,可以遇见,虚拟现实技术的发展将会越来越快,逐渐进入人们的日常生活中。
参考链接:
[1] 虚拟现实定义,https://en.wikipedia.org/wiki/Virtual_reality
[3] https://en.wikipedia.org/wiki/Gimbal_lock
[4] http://www.songho.ca/opengl/gl_transform.html
[5] 反畸变算法的实现,http://www.tomshardware.com/news/vr-tessellation-vertex-displacement-cardboard,31844.html
[8] https://mynameismjp.wordpress.com/2012/10/24/msaa-overview/