VR 立体渲染优化
VR Stereo Rendering Optimization
VR 每帧要为两只眼渲染,并经合成器做镜头畸变与色差校正。本页讲厂商 SDK/API 的帧流程与内建优化(分层合成、隐藏区域网格、裁剪过的畸变网格),立体渲染如何避免工作量翻倍(合并视锥剔除、实例化、多视图扩展、多 GPU 广播、远景单目渲染),以及利用光学和人眼特性减少外围像素着色的技术:多分辨率着色、镜头匹配着色、径向密度遮罩、固定注视点渲染与注视点渲染。
始终使用厂商 SDK(21.3 节)
除非有极好的理由,始终使用系统提供方的 SDK 与 API。比如你可能认为自己的畸变着色器更快、看起来差不多,但可能导致严重不适,不经大量测试无从得知;为此所有主流 API 都已移除应用控制的畸变——正确显示 VR 是系统级任务。
flowchart LR A["估计本帧显示时刻"] --> B["按预计延迟向 API 查询每只眼的预测姿态"] B --> C["渲染两个目标, 或一个分成两视图的目标"] C --> D["作为纹理提交给合成器, 可附加 HUD 等图层"] D --> E["合成器: 畸变, 色差校正, 图层合成后显示"]
如上图。OpenVR 还需要知道用户站立还是坐着,这影响原点(追踪区域中心或头部位置)。合成器可合成多个不同分辨率和格式的图层,例如单目 HUD 作为单独图层叠在每只眼的视图上;这使得可以动态降低三维场景图层分辨率以节省渲染时间,同时保持其他图层的高质量。Oculus 并不先合成再校正,而是先分别校正每个图层再合成:每层按自身分辨率扭曲,例如文字的重采样和过滤只针对文字内容,质量更好。
隐藏区域网格与畸变网格
- 隐藏区域网格 (hidden area mesh):人眼视场近似圆形,图像四角外围的像素虽然会显示却几乎察觉不到。渲染开始时先把一张网格写入模板缓冲作为遮罩,或写在 z-buffer 最前面,后续片元在这些区域被提前丢弃。HTC Vive 上约减少 17% 填充率;双眼图像拼接处被扭曲变换移除的中间条带也可以一并遮掉。
- 畸变扭曲:光线投射可按波长精确重映射,但多数硬件不现实;全屏四边形在像素着色器中逐像素求畸变方程也较贵。更高效的是把渲染图像贴到三角形网格上:网格每个三角形渲染一次,但每个像素用三组 (u, v) 分别采样红绿蓝通道,以校正色差。
- 规则网格既可以放在渲染图像上扭曲到显示图像,也可以反过来。把网格放在显示图像上、只调整顶点纹理坐标扭曲回渲染图像的好处是不会出现细长三角形,生成的 2×2 quad 更少。典型网格每只眼 48×48 个四边形,纹理坐标用逐通道的显示到渲染变换预先算好存在网格中,着色时无需复杂变换;可用 GPU 各向异性采样得到清晰图像。把网格裁剪到只覆盖可见区域(画黑色三角形毫无意义),最终畸变 pass 开销约减少 15%。
立体渲染不是两倍工作(21.3.1 节)
阴影贴图生成、模拟和动画与视图无关;像素着色调用数不会翻倍,因为显示被两个视图平分;后处理与分辨率相关,开销也不变。真正翻倍的是视相关的顶点处理,以及 API 调用。
| 方法 | 做法 | 评价 |
|---|---|---|
| 合并视锥剔除 | 用一个视锥包住双眼视锥;剔除后才取精确视图,所以需要安全边距,建议视场角加约 5 度 | 简单有效 |
| 顺序渲染 | 先完整渲染一只眼再另一只 | 状态切换翻倍;分块渲染器频繁换视图和渲染目标性能很差 |
| 逐物体渲染两次 | 每个物体中间切换相机变换 | API 绘制调用仍翻倍 |
| 几何着色器复制 | 生成两个视图的三角形并发送到不同目标 | 几何吞吐量降到三分之一甚至更低,实践中不用 |
| 实例化 | 一次绘制调用画两份,用用户裁剪平面分开两眼 | 比几何着色器快得多,无额外 GPU 支持时的好方案 |
| 命令列表重放 | 为一只眼录制命令列表,改引用的常量缓冲为另一只眼的变换后重放 | 节省 CPU |
| 多视图扩展 | 如 OpenGL ES 3.0 multi-view:几何只发送一次,渲染到两个或更多视图 | 可在驱动中用实例化实现,或由 GPU 把每个三角形送到各视图;总能降低 API 开销,复杂实现还能提高纹理缓存效率、视无关属性只着色一次 |
多 GPU:AMD 和 NVIDIA 提供为 VR 调优的方案,两块 GPU 各渲染一只眼。CPU 用亲和掩码指定哪些 GPU 接收某个 API 调用(两眼不同的调用仍要调用两次);NVIDIA 的广播用一次绘制调用发给所有 GPU,用常量缓冲传不同的眼睛位置,CPU 开销几乎与单视图相同。合成器需要单幅图像时,用专门的子矩形传输命令在 1 ms 内把数据从一块 GPU 异步搬到另一块。两块 GPU 各自生成阴影缓冲虽然重复,但比并行化再传输更简单、通常更快。整体渲染提速约 30%~35%;已为单 GPU 调优的程序可把多余算力用于更多抗锯齿采样。
远景单目渲染(Palandri 与 Green,GearVR):立体视差对近处重要、远处可忽略。设一个垂直于视线的分隔平面(默认约 10 m),更近的不透明物体立体渲染,更远的用位于两个立体相机之间的单目相机渲染。先画立体视图,用两者深度缓冲的交集初始化单目渲染的 z-buffer 以减少过度绘制,再把远景合成到每个立体视图,透明内容最后对每个视图渲染。跨越分隔平面的物体需要额外一遍,但稳定节省约 25%,画质和深度感无损。
减少外围像素着色
由于光学畸变,渲染图像外围的像素密度更高(外围较大的渲染区域只对应较小的显示区域),而且用户多数时间看向中心,外围不那么重要。
- 多分辨率着色(NVIDIA;AMD 称可变速率着色):把屏幕分成如 3×3 块,外围以较低分辨率渲染(例如边块减少 50% 像素、角块减少 75%),视场不变;共享边上通常最好保持相同分辨率。
- 同时多投影 (SMP):Pascal 起支持,几何最多可经 16 个投影 × 2 个眼睛位置处理,网格可被复制 32 次而无应用侧额外开销;第二个眼睛位置必须是第一个沿 x 轴的偏移,每个投影可独立倾斜或旋转。
- 镜头匹配着色:用 SMP 渲染四个平面倾斜的视锥,让中心像素密度更高、外围更低,比多分辨率着色过渡更平滑;缺点是泛光等效果需要重做才能正确显示。Unity 和 UE4 已集成。也有每只眼用最多 3×3 个视图进一步减少着色的研究,SMP 可同时作用于双眼。
- 径向密度遮罩:应用层方法,外围以棋盘格方式隔一个 2×2 quad 不渲染,再用后处理从邻居重建缺失像素。对单块低端 GPU 尤其有价值;但若跳过和重建的开销太高就没有收益。Sony 伦敦工作室更进一步,越靠近边缘在每组 2×2 quad 中丢弃 1~3 个,每帧改变抖动模式,并用时间抗锯齿掩盖阶梯瑕疵,约节省 25% GPU 时间。
- 固定注视点渲染(Vlachos):每只眼渲染两幅图——中心圆形区域和外围环形区域,外围用低分辨率,再合成并扭曲。代价是几何要送到四幅图,正好契合 GPU 多视图支持,也天然适合 2 或 4 块 GPU 分工。
注视点渲染(21.3.2 节)
中央凹 (fovea) 是视网膜上密布视锥细胞的小凹陷,视敏度最高。离中心前 30 度内大约每 2.5 度视敏度下降 50%,更远下降更陡。人眼双目视场水平约 114 度,第一代消费头显约 80~100 度。中央 20 度在 2016 年的头显上约占显示的 3.6%,预计 2020 年左右降至 2%,而显示分辨率可能提高一个数量级。
注视点渲染 (foveated rendering) 只在眼睛注视处以高分辨率和高质量渲染,其余处少花力气。难点:眼睛会动,例如扫视 (saccade) 速度可达每秒 900 度,即 90 FPS 下每帧可能 10 度,需要精确的眼动追踪硬件,而这是技术挑战;外围渲染"更大"的像素会加剧走样,可通过保持对比度、避免随时间剧烈变化让外围在感知上更可接受。
出处
- realtime_rendering_4ed.pdf 第 21 章 Virtual and Augmented Reality