Sine.

移动端VR TBDR GPU的优化思路

围绕移动端 VR 的 TBDR 架构,整理 Fragment、Overdraw、带宽、Tile Flush、渲染路径和 URP 设置的优化重点。

先理解TBDR GPU 的工作方式

TBDR

Tile-Based Deferred Rendering

T 表示 分块渲染

D 表示 延迟写回外部显存

简化流程:

CPU 提交 DrawCall

GPU 先 Bin/Tiling

把三角形分到 Tile

每个 Tile 在片上缓存中渲染

最后一次性写回显存

例如:

  • 屏幕 2048×2048
  • 分成很多 32×32 / 64×64 tiles

GPU:

  • 不会立刻写 framebuffer
  • 而是在 GMEM/tile memory 里做
  • 最后统一 resolve

这就是为什么:

“避免中途打断 Tile”

极其重要。

移动端最重要的优化目标

优先级基本是

  1. 减少 Fragment Cost
  2. 减少 Overdraw
  3. 减少带宽
  4. 减少 Tile Flush
  5. 减少 Shader ALU
  6. 减少 Vertex Cost

移动端和 PC 最大区别:

Fragment/Bandwidth >>> Vertex

PC:

  • 往往 vertex 更贵

移动:

  • fragment + bandwidth 才是真正瓶颈

不透明物体渲染原则

尽量保持Shader可 Early-z

不透明物体避免:

clip()
discard
alpha test

透明物体渲染原则

因为透明物体 = 无 Early-Z

  1. 少用透明物体

  2. 少用大面积透明

  3. 少用Alpha Blend

    优先:

    AlphaClip
    Dither
  4. 粒子减少屏幕覆盖

    粒子数量不是最大问题,屏幕覆盖率才是,1000小粒子可能比1个全屏Smoke便宜.

避免 TileFlush

Shader优化重点

  1. half 优先于 float

  2. 减少 texture sample

  3. 避免动态分支

    hlsl
    if(x > 0)

    移动 GPU SIMD divergence 成本高。

  4. 变体数量控制

VR特有优化

  1. Single Pass Instanced

  2. Fixed Foveated Rendering

  3. 降低 Fragment Density

    这是一个不得已的操作,为了保证玩家的实际体验,不建议开发者降低渲染分辨率,因为在VR中,分辨率的降低导致的锯齿与不真实感更容易被感知.

渲染路径的选择

Forward 或 Forward+ 渲染路径

如果场景实时灯光数量在2个或以下,使用Forward

如果场景实时灯光数量在2个以上,使用 Forward+

减少外部显存带宽

避免Grab Pass

在URP中 Grab Pass 就是

_CameraOpaqueTexture

本质上就是读取当前已经渲染好的屏幕颜色

正常TBDR的理想情况

GPU :

Tile A

在GMEM里完成所有绘制

最后一次性写显存

GrabPass 会发生什么

假设:

你渲染到一半:

现在需要读取屏幕颜色

但是当前颜色还在 Tile Memory 里

还没写回显存.

于是 GPU 被迫:

GMEM
↓ resolve/store
外部显存

然后:

shader 才能:

sample screen texture

这就是:

强制 Tile Resolve

避免Camera Stack

什么是 Camera Stack

URP :

Base Camera + Overlay Camera

Overlay Camera常用于

  • UI
  • 武器
  • Protal
  • 小地图

在 TBDR 上的问题

理想情况:

一次完整 RenderPass

GPU 可以:

Tile 保持在 GMEM

直到结束。


Camera Stack 会导致:

Camera A 结束

Store Tile

Camera B 开始

Reload Tile

为什么?

因为:

第二个 Camera:

需要:

读取前一个 Camera 结果

避免 Fullscreen RT 切换

示例

Render Scene → RT_A

Bloom → RT_B

Color Grade → RT_C

Final Blit

在 TBDR 上的问题

每次:

切 RenderTarget

都可能:

Store 当前 Tile
+
Load 新 Tile

为什么?

因为:

GPU Tile Memory:

一次只能工作在:

当前 RenderPass

实际代价

例如:

1080p:

1920×1080×4 bytes

一次 fullscreen RT:

就可能:

几十MB带宽

避免多MRT

MRT 是什么

Multiple Render Targets:

SV_Target0
SV_Target1
SV_Target2

一次 fragment:

输出多个 buffer.

为什么伤带宽

例如:

RGBA8 × 4 MRT

一次 pixel:

16 bytes+

MSAA 更糟

4x MSAA:

16 × 4
= 64 bytes/pixel

非常恐怖。

术语解释

**Tile Flush : **GPU 被迫把原本暂存在片上缓存里的 Tile 数据,提前写回显存.

GMEM : GPU块缓存