如何利用顶点位移技术实现VR畸变校正
映维网之所以会发布这篇文章是因为:这项技术确实值得借鉴学习
(映维网 2018年1月1日)虚拟现实面临的一个最大的挑战是如何同时实现高帧率和高分辨率。如果将顶点转移到“镜头空间”之后,我们就不需要再进行全屏材质渲染,从而大幅改善移动设备的性能表现。
下文讲述的技术是由 Cardboard Design Lab(CDL)利用谷歌的Cardboard Unity SDK进行开发和部署的。不过这种技术其实也可以通过镜头畸变效果应用到任何VR系统中——只需要采用合适的畸变系数来构建顶点着色器,还需要SDK允许禁用材质渲染功能。
1. 背景
在开始解释这技项术之前,我们有必要理解目前主流的虚拟现实系统是如何进行渲染的,从而明白为什么我们需要镜头畸变。
2. 镜头畸变
VR应用的镜头畸变效果可以抵消头显玻璃镜片的物理畸变——这样可以在没有明显图像失真的前提下形成较大范围的视场。
为了避免用户觉察到这种现象,高端的VR头显设备一般会先正常处理图像,然后对图像进行反向变形,从而抵消掉原本的镜头畸变效果,呈现出正确的图像。
本文讲述的顶点位移技术可以提前在顶点着色器进行图像变形,这样就不需要单独的渲染目标。在减少了渲染目标之后,移动设备的发热问题可以基本得到解决,并且在不明显影响发热和目标帧率的前提下实现全分辨率的2到4倍MSAA(多重采样抗锯齿)。
值得一提的是,我不是第一个提出这项技术的人,但据我所知,Cardboard Design Lab是首个利用这项技术开发的VR应用。此外,谷歌团队在这个项目刚开始的时候已经做出了原型版本的顶点变形,其代码最终将会包含在现在的SDK中。
映维网之所以会发布这篇文章是因为:首先,这项技术确实值得借鉴学习;其次,以后可能会有更多开发者采用这项技术,改善VR应用的体验;最后,引擎开发者也认识到了提前渲染和MSAA的好处,以及依赖全屏后期处理来实现核心功能的坏处。
3. 问题
在移动虚拟现实应用的开发过程中,下面的3个问题是开发者必须考虑的,而且或多或少会限制应用能够实现的效果:
- 帧率
- 表现分辨率
- 发热
4. 帧率
相信大部分人都知道帧率这个概念。在一般的游戏开发中,30-60fps是可以接受的。在虚拟现实中,60fps是最低标准,一般认为良好的虚拟现实体验需要达到90-120fps的帧率。
当然,在移动设备上我们只能尽力而为。许多设备都将最大帧率限制为60fps,所以接下来我都会使用60fps作为基准。
当VR体验的帧率低于60fps的时候,我们可以立即看到差别。用户会感觉到头部追踪跟不上显示屏的移动,在迷失方向的同时还可能会出现晕动症。不时出现的性能问题(比如背景加载、系统通知和垃圾回收)可能会造成短暂而强烈的不适感,而持续的低帧率(30到60之间)也可能造成长时间使用的不适。大部分的用户都不能判断体验的帧率到底是45fps还是60fps,在我们的测试中,用户在低帧率下更容易感到恶心,但是他们会将此归咎于帧率意外的其他原因。因此,我认为30fps的VR体验是非常糟糕的,而低于30fps的体验是毫无意义的。
但是,目前只有极少数的移动虚拟现实应用可以稳定地以60fps运行,而桌面应用则需要非常高的硬件配置。 我读过的很多文章都表示“在虚拟现实中让用户运动总是不好的”——虽然某些运动类型确实会产生问题,比如与视线方向相切的运动,但是对大多数用户来说,动作只要设计合理,而且以60fps的帧率运行就不会有太大的问题。现在过山车VR应用的流行证明运动还是值得我们好好研究的。
重点总结:低于60fps的帧率可以轻易毁掉好的体验,让人产生恶心的感觉——就算是有经验的用户和开发者都可能会误判这个问题的原因。
5. 表现分辨率
虚拟现实的表现分辨率是结合物理屏幕分辨率、渲染缓冲区分辨率、MSAA水平、物理镜头放大率和所有的后期处理效果(如镜头变形)等多种因素得出的。
我们到底需要多高的分辨率呢?不同的人给出的数字也不同,不过我会沿用Michael Abrash的估算:以现有的显示器为标准,单眼的分辨率需要达到4k x 4k(90度视场);以视网膜的分辨率为标准则需要达到16k x 16k的单眼分辨率(180度视场)。
很明显,现在几乎没有任何一台移动设备可以达到这个标准。三星的Galaxy S6可以达到2,560 x 1440px, 也就是单眼分辨率为1280 x 1440px,考虑到边缘的部分像素损失,Galaxy S6 大概可以达到1024×1024,也就是1K的分辨率,这个数字仅仅是4k的1/16。
因为我们没有时间机器,所以我们唯一能做的是提升MSAA的水平(如果你对MSAA的认识只是停留在消除锯齿的话,我建议你可以阅读一下这篇文章https://mynameismjp.wordpress.com/2012/10/24/msaa-overview/),启用4倍MSAA可以大大提升屏幕的表现分辨率。
Michael Abrash给出的数据是指在4k x 4k分辨率的基础上启用MSAA, 这样可以达到夸张的16k分辨率——不过我们的原则都是一样的:MSAA是你的得力助手。
重点总结:抗锯齿的成本虽高,但它具有无与伦比的重要性。我对高端手机的目标是4倍MSAA。打开你的提前渲染器吧。
附言:我知道支持延迟渲染的人都有各种各样的后期抗锯齿技术。我个人认为后期抗锯齿会让很多游戏的画面变得很糟糕,不过话说回来,本文的主旨就是将成本高昂的全屏渲染材质从移动制作中移除 。后期抗锯齿需要全屏的渲染材质,这点就不符合本文的主旨了。Unity的提前渲染速度非常快。我上次使用UE4的时候发现它还没有提前渲染模式,当我要求cg级的着色器自定义时,对方竟然讽刺地跟我说“你自己不是有资源吗?”——难道你们认为对于时间紧迫的独立开发者来说,重新编译这个引擎是一个可行的做法吗?话虽如此,可能Epic团队那边也有人正在做这方面的研发。
6. 发热
作为一名游戏开发者,我很清楚制约移动设备性能表现的瓶颈所在,按照出现频率排序:GPU填充率、CPU(通常是绘制调用)、CPU与显存之间的带宽、GPU顶点运算、以及令人抓狂的设备驱动问题(如GPU对NaN值或无限值的处理)。
这些问题在大多数情况下都是互不相关的。即使GPU完全被占用,开发者仍然可以继续利用CPU的性能。问题是手机的尺寸大小了,而各个处理器的位置通常都比较紧凑,这意味着发热问题会很普遍。
为了防止手机融化或者爆炸,手机在热量过高的时候会减少对处理器的供电,导致处理器的性能下降。尽管实现这个功能是硬件工程上的一项壮举,但是这也意味着你可以跟帧率告别了。热量限制不是为了平滑地平衡性能与发热,而是为了保护你的手机。当热量限制刚开始时,原本流畅的60fps会突然下降至40fps或更低。当系统发现手机仍然很热时,它会继续减少供电。如果你的VR应用受到了热量的限制,它将很难成为好的体验。
发热问题之所以会如此棘手,是因为我们很难确定热量的来源,这时我们上面提到的性能瓶颈就开始互相影响了。如果你可以减少汇编指令中的每一个函数调用,这样或许可以降低每个指令产生的固定热量,但是这里还没有考虑到硬件的效能和设备内部的物理结构,或者是你的应用权限控制之外的因素,如后台进程、Wifi等等。
为什么VR应用要关注这个问题呢?因为很少有其他移动应用需要60fps和4倍MSAA的高清3D渲染。此外,VR应用需要渲染两次场景(每只眼睛一次),而大部分SDK为了处理失真都会默认以高分辨率渲染材质。所以,渲染材质对处理器的要求很高,并会产生大量的热量。
John Carmack在GearVR刚发布的时候也谈论过发热的问题。但不幸的是,这个问题在近期还解决不了。
重点总结:如果你的设备在前几分钟还表现良好,但是突然莫名其妙地出现性能突降——这时你可以检查一下运行日志中的热量警报。
7. 顶点位移镜头校正
一开始的时候,Cardboard Unity SDK的默认方法是不使用MSAA渲染16位渲染材质。但即便是在这样的设置之下,大部分的低端设备都会在几分钟之内出现发热问题。采用类似设置的GearVR也面临同样的发热问题。不管你使用什么SDK,全分辨率的渲染材质都会严重影响手机的性能。
在转用了顶点位移技术之后,CDL选择渲染到全32位的屏幕缓冲区(减少色带),并根据设备配置启用2倍到4倍的MSAA,这样可以将帧率大致维持在60fps。
另外,在根据“镜头空间”变形场景之后,我们还避免了由变形渲染目标产生的像素丢失问题(也避免了渲染到更高分辨率缓冲区所造成的额外时间耗费)。这实际上跟英伟达最近发布的Multi-Res Shading技术的效果差不多,只不过顶点位移技术是免费的,还可以应用在6年前的旧款智能手机上。
8. 工作原理
目前用于Unity的Cardboard SDK有一个名为“CardboardDistortion.cginc”的CG包含文件。
它有一个可以将世界空间的顶点转换到反镜头畸变的屏幕空间(“镜头空间”)的方法, 这个方法利用了Brown-Conrady模型进行完全畸变校正。
下面是使用这个功能的一个简单单色着色器。注意我已经用条件编译在桌面和编辑器禁用了这个效果。在编辑器中进行反镜头畸变会让场景的编辑变得很困难。
CGPROGRAM
#pragma vertex VertexProgram
#pragma fragment FragmentProgram
#include "CardboardDistortion.cginc"struct VertexInput {
float4 position : POSITION;
};struct VertexToFragment {
half4 position : SV_POSITION;
};VertexToFragment VertexProgram (VertexInput vertex){
VertexToFragment output;#if SHADER_API_MOBILE
///easy as that.
output.position = undistortVertex(vertex.position);
#else///this is how a standard shader works
output.position = mul(UNITY_MATRIX_MVP, vertex.position);
#endifreturn output;
};fixed4 _Color;
fixed4 FragmentProgram (VertexToFragment fragment) : COLOR{
return _Color;
}
ENDCG
注意:在Cardboard SDK启用顶点变形模式之后,请确保将原本的Distortion Correction关闭。
9. 挑战
顶点位移技术看上去似乎很容易,但为什么人们没有广泛使用这项技术呢?为什么有人还会费心渲染材质呢?我们为什么不在“镜头空间”中渲染所有东西呢?
由于镜头畸变是非线性的,所以反镜头畸变也是非线性的。当我们在顶点着色器中变形顶点的时候,顶点之间仍然以直线相连,但实际上这应该是一条曲线。当你通过曲面透镜观看曲线时,它看上去又会变成一条直线。当你通过曲面透镜观看直线时,你会看到一个边缘弯曲的立方体,这可能不是你想要的效果。
在这种情况下,如果顶点(在屏幕空间)的位置过于分散,我们最后也很难校正多少畸变,这样用户在转头的时候会看到图像出现变形,这种现象可能会导致用户头晕。
或许更糟糕的是,所有的顶点运动都有可能加重物体之间的深度冲突。
幸好这个问题的解决方案也是很直接的,只需要增加足够的顶点细分,这些畸变就不胡被觉察到。事实上,大部分的高端移动设备每帧可以渲染20万-80万个顶点。可是我们需要渲染场景两次,所以我们的顶点数量限制为10万-40万。这个也需要根据你想支持的设备而定。
你可能会认为增加这么多顶点可能会再次引起发热的问题,但是现在手机的屏幕像素密度普遍可以达到1920x1080,这个分辨率大致相当于200万个像素,如果稍微透支一下,我们可以处理每帧1000万个着色片段。如果我们需要渲染用于畸变校正的单独渲染目标,这样会增加另外200万个像素,环境切换和大量材质内存访问的成本也是不可忽视的。
10. 额外的好处
因为我们不得不大量增加场景的顶点数量,这为我们带来了另一个好处。如果在片段程序计算灯光的话需要进行大约1000万次的运算,但在顶点程序则只需要运算40万次。这相当于1/25的运算量(产生的热量应该也会相应降低)。由于非线性镜头畸变需要较高的顶点密度,这个密度对于亮度来说已经足够了 (后者一般也是非线性的)。
注:我从来没有用过Unity的着色器,也没有用过他们的天空盒,我只会将他们的灯光引擎用于原型制作。CDL使用的每个着色器都是在CG/Shaderlab编写的(不使用Surface Shader), 片段程序可以从顶点着色器读取并显示色彩值;或者读取连个色彩值,访问材质,将两种颜色逐一添加到材质上。保持片段程序的简洁,移除渲染材质,并尽可能减少透支可以维持良好的GPU填充率。
11. UI与2D
虚拟现实的UI也是一个棘手的难题。为了保持正确的立体会聚,UI需要在3D空间内进行渲染,最好是按照它参考的3D物体的深度进行渲染(比如准星需要按照它遮挡的物体的3D深度进行渲染)。
UI也需要经过畸变校正——为了确保它能有足够的顶点数量, 诸如字符和材质这样的UI元素是更应该在方格平面(而非四边形)绘制的。方格的准确数量将取决于UI元素在屏幕上的最大尺寸。
当然,跟随头部移动的UI也会出现畸变,但是这种畸变不会随着用户转头而改变,所以用户或许不会注意到。
12. 优化
如果想减少顶点位移方法带来的误差,就意味着尽可能缩短顶尖在屏幕空间中的距离——也就是说物体在远离摄像头的过程不需要大量的细分,所以标准的LoD技术是有效的。
这里提供了一个明确的优化方向,尤其是对固定位置的渲染来说。在摄像头可以在场景中移动的情况下,优化的难度会提升。
13. 最后的想法
实话说,现有的桌面级VR头显既然有如此高的硬件要求,我对这项技术没有得到大范围应用还是感到有些意外的。基于顶点位移的镜头校正可以让智能手机在QHD分辨率(2560x1440)的屏幕上渲染60fps及高达4倍MSAA的VR内容。现在大部分的集成图形显卡应该都可以达到至少同等的性能表现,中低端的独立显卡将能远远超出这个表现。我不期望可以用英特尔HD 5000玩《Battlefront VR》,但是我相信现在的VR头显都可以轻易做到这点,这样用户就不需要四路泰坦才能体验到优秀的VR内容。