在UE4和Unity中的平面/立体混合渲染
为 Gear VR 设计游戏和体验极具挑战性。由于移动设备渲染能力有限,三角形数量和着色器都需精心优化。在虚拟现实渲染中,一个较为耗费渲染资源的地方在于,需要为双眼在略微不同的角度对同一场景进行重复渲染。这种“双目视差”能为用户带来景深立体的感觉,即左眼和右眼从稍有不同的位置看到同一物体,大脑将这种差异解析为物体与自己的距离。
这种左右眼所见的差异,在物体离自己较近时较为明显;随着物体距离变远,差异会逐渐减小。双眼之间的距离以及生成的像素,在物体远离时差别也相对变小。为利用这一特性,我们制作了一个 Unity 示例和一个改进后的 UE4 移动前置渲染器,对近处物品进行立体渲染(为两眼分别渲染一次),对远处物体进行单目渲染(为两眼同时渲染一次)。以下将解释我们在制作单目渲染示例过程中的一些选择以及折中的考量。
UE4 移动设备渲染器
我们在代表左右眼的立体摄像机位中间放置了第三个摄像机位,且三个机位处于同一直线,以确保视角匹配。对于 Gear VR,两只眼睛的摄像机视锥体左右对称,因此中间这个摄像机位的投射矩阵与双眼立体摄像机的投射矩阵相同。而在 Rift 上,两眼的摄像机位视锥体是非对称的(靠内朝鼻子的角度比靠外的角度稍小),所以中间这个平面摄像机位的视锥体实际上是左右眼视锥体的集合体。这包含了两眼看到的所有物体,但会使它渲染出来的目标比两只眼分别渲染的目标稍大。
我们添加了一个分裂平面,用于选择哪些内容由双眼立体摄像机渲染,哪些内容由中间的平面摄像机渲染。经测试,以 10 米作为默认值效果较好(十米内立体渲染,十米外平面渲染)。该距离值可在游戏的任意帧随意修改,也能在编辑器的 World Settings / VR / Mono Culling Distance 下进行配置。
我们还对 UE4 针对移动设备的前向渲染器进行了修改,以实现以下效果:
- 用立体摄像机渲染非透明内容。
- 通过改变和结合输出来制作一个平面遮罩,该遮罩会预先设定平面的深度缓存。
- 用平面摄像机渲染非透明内容。
- 将平面摄像机的结果整合进立体缓存。
- 立体化渲染所有透明内容并进行所有后期处理。
各步骤结果说明
- 第一步的结果:纯立体缓存加上近景深度剔除。
- 第三步的结果:远景是平面的,此截图特意做成左右两眼。
- 第四步的结果:把第三步结果整合进第一步,加上近景深度剔除。
和摄像机进行交互
为分开立体和平面渲染的内容,我们采用了基于深度缓存的方法:所有超过离摄像机位预定距离的像素的立体效果,会通过清除该距离以外的深度缓存被放弃;同样,近处的平面投射也从该预定距离开始,放弃已被渲染为立体的碎片。
这种方法使立体像素和平面像素有清晰的深度顺序,即所有立体像素离摄像机位的距离都比平面像素小。这避免了在最终场景整合阶段因深度比较带来的昂贵性能开销,减少了像素着色器的调用数量。
然而,与基于物体的方式相比,基于像素的分裂平面方式的最大缺陷是绘制调用数量较难控制。任何穿过此分裂平面的物体,都需同时进行立体和平面渲染,尽管最终没有像素会被绘制两次。即便使用普通的视锥体剔除技巧来最小化递交到立体缓存的绘制调用数量,对于很近的远平面的视锥体剔除也较为麻烦。例如,一个远处的物体有很大的包围盒,像环境的立方体贴图永远会通过视锥体剔除绘制;即使因远平面距离较短,其任何像素都未显示,但它的包围盒与摄像机位的视锥体产生了交叉。
为避免这种情况,我们增加了一种方式,可在平面渲染引擎中手动标记一些物体,使其永远不会被立体缓存渲染。为确定哪些物体需要标记,我们增加了两种渲染模式:一种只显示立体缓存,不显示包含平面远景部分的整合层;另一种显示立体缓存的同时,加上用于视锥体剔除的区分平面和立体部分的远景平面,但不进行深度测试。简单来说,任何被渲染到第二张图像上而未渲染到第一张图像上的物体,都需要一个绘制调用,但因远平面距离较短而不会显示出来。
例如,在某个例子中,远景的地形由于其很大的包围球通过了视锥体剔除测试,但因距离超过三十英尺的深度分裂平面而不应被渲染。我们可在 UE4 编辑器的物体细节/渲染区域下,将其标记为“Force Mono”,以节省宝贵的绘制调用资源。
避免过度渲染
远景平面渲染的主要问题是,其通过最小化像素着色器开销节省的性能,可能会因立体层和平面层之间缺乏互相遮挡而浪费。由于我们不对离摄像机较近的物体进行平面渲染,那些在最终场景整合后不会被看到的远景部分,仍会在平面缓存中被着色,因为没有物体遮挡它们。
为解决这一问题,我们先渲染立体部分,再读取其深度缓存并计算交叉。所有在左眼和右眼摄像机同时被渲染过的像素,都会被写入远景平面的深度缓存,以避免它们在平面摄像机中被渲染。最终效果是,前面立体视觉下的大柱子会在平面缓存中遮挡后面的物体,确保后面的像素不被渲染。
场景整合
为避免立体缓存和平面缓存在分裂平面上出现割裂,我们计算了分裂平面的 3D 点在平面摄像机和左右眼立体摄像机投射之间的差别。经测试,在 1024 像素分辨率、90° 可视角度和 30 英尺距离下,差别仅有两个像素。尽管差别很小,我们在场景整合时仍对平面缓存进行了位置补偿。以下是扩大后的截屏,展示了立体到平面的转场,以及包含和未包含这两个像素补偿的效果对比。
- 无补偿的立体 - 平面转场
- 包含了补偿
当立体像素和平面像素有清晰的深度顺序(平面像素在立体像素之后)时,我们只需一个全屏的 Compositing Pass,通过使用(1, 1 - dest_alpha)作为混合功能,将平面像素整合到未写入立体缓存的位置。
结果
平面渲染带来的性能提升与所在场景密切相关,但在前置渲染管线下,提升效果显著。在 Epic 的日落寺庙示例中,帧渲染时间从 45 毫秒稳定提升到 34 毫秒,提升幅度达 25%。即便在寺庙内远景和近景物体较多的情况下,画面质量和深度感也无明显下降。同样,在采用修改后的 Rift 前置渲染器后,Dreamdeck 中的恐高城市场景 Vertigo 也取得了类似的效果。
然而,对于采用昂贵近景内容的场景,性能可能不升反降。因为采用平面渲染时,会有固定的第三个摄像机位开销、平面深度遮挡开销以及最终场景整合开销。但由于实时激活或关闭平面渲染模式不会导致卡顿或帧率降低,可在合适的场景下开启该模式。
平面渲染模式对 UE4 的延迟渲染器帮助不大,主要原因如下:延迟光照渲染使用了许多必须在场景整合后的屏幕空间效果,且必须进行立体渲染;很多延迟渲染应用的瓶颈在于带宽而非像素着色器,增加新的渲染目标和 Composition Pass 会使问题更复杂。因此,我们仅为 UE4 的前置渲染器增加了平面渲染模式,目前仅针对移动设备。
这个 UE 4.12 的修改版本最初未得到 Oculus 和 Epic 官方支持(在 11 月 16 日发布的 UE 4.14 中已正式支持),但我们鼓励大家尝试。我们也在与 Epic 合作,期望该功能能成为主版本的一部分(现已成功)。
Unity 样品
Unity 的混合平面渲染思路与 UE4 版本基本一致,但完全通过 C# 和着色器实现。
它使用了两个摄像机,一个对近景进行立体渲染,另一个对远景进行平面渲染。远近景的分裂平面用于限制渲染到合适的深度范围,我们借助 Unity 的视锥体剔除功能,避免对视锥体外的物体进行绘制调用。
基本步骤如下:
- 将远景部分渲染到纹理(平面摄像机)。
- 对近景部分进行立体渲染,为每只眼分别渲染一次(左右眼立体摄像机)。
- 针对每只眼,通过 Unity 图像效果将立体单眼图像放置在平面图像之上。
该平面渲染器通过将摄像机的“Target Eye”设置为“Left”而非“Both”实现,且摄像机物体位置经过调整,实际通过中间眼进行渲染。
这已打包成一个可取代 Oculus 摄像机的预制包。此 Unity 实现包未采用类似 UE4 的过着色优化,因为我们主要关注节省绘制调用。当然,后续可将相关优化整合进来。
以下是采用混合立体/平面渲染后的包含多个球体的合成测试场景结果:
- 未开启平面渲染:10846 绘制调用,48.7 FPS
- 混合立体/平面渲染(平面区域显示为红色):8583 绘制调用,97.7 FPS
显然,该场景并非常见场景,主要展示了最佳情况下的效果。当将平面远景距离设为十米时,基本无法区分全立体和混合式场景的差异。
我们计划将此作为 Unity 的一项内置功能,让所有开发者都能轻松使用,并会继续对所有引擎的立体渲染进行优化。