[UFSH2023]《三角洲行动》端手一体地形渲染方案
《三角洲行动》端手一体地形渲染方案
项目背景与目标
项目定位
- 三角洲行动 是一款 写实风格大世界 FPS 游戏,由腾讯天美 Y1 工作室(原金山工作室)研发
- 核心理念:端手一体(Cross-Platform)——不同于"端游出 HD 版手游"的做法,而是:
- 端游 对标 3A 品质
- 手游 追求次世代品质
- 使用 同一个引擎(UE4),同一套关卡和美术资产,同时进行双端开发
- 目标:一次生产,端手同步,美术无需为端游和手游独立制作资源
地形技术目标
一句话总结:让手游拥有端游级的品质
- 基本逻辑:先把品质追到最高,再用各种技术突破让它落地到手游
- 同时完善美术工具链,使美术可以稳定产出场景内容
手游地形已达成的成果
| 指标 | 具体数据 |
|---|---|
| 大世界规模 | 10km 级别 |
| 材质层数 | 全场景支持 32 种材质(可扩展至 256 种,性能不受影响) |
| 材质精度 | 每米 500+ texels(对比 CODM 的 256 texels/米,高出一倍) |
| 特色功能 | 峭壁优化、海量贴花、移动端 Tessellation、湿度/颜色等生态信息变化 |
| 性能表现 | 三角洲高配在帧率、发热、带宽上甚至优于 CODM 低配 |
- 以上多项特性在手游上均属 首创
基本方案:地形材质与混合
地形渲染的核心问题归结为两块:材质(Material)和 混合(Blending)。
原生方案:传统权重图方案(Splat Map)
工作原理
- 每层材质对应一个 权重通道(Weight Channel)
- 多层材质的权重存储在 权重图(Splat Map)中,例如 2 层材质使用一张纹理的 RG 通道
- 在 Pixel Shader 中采样权重图,获取每层材质权重,按权重混合各层纹理
优点
- 四层以下材质时计算简单,效果可控
问题(以 8 层材质为例)
- 时间开销:每个像素需要 18 次纹理采样,非常昂贵
- 空间开销:需要 两张 RGBA 权重图,即使压缩后内存占用依然很大
- 可扩展性极差:材质数量增加时,时间和空间开销 成倍增长
- 项目用了 32 层 → 开销是 8 层的 4 倍
UE4 原生优化方式
- 按 地块(Component) 划分材质变体,只采样该地块实际用到的材质
- 但存在问题:
- 不同地块材质不同 → 影响合批(Batch)
- 制作不可控、灵活度不够 → 限制美术发挥,影响最终品质
核心矛盾
真正的需求是 整个大世界支持更多材质,而不是每一个像素都需要那么多材质层。这就是新方案的突破口。
业界参考方案:Ghost Recon(幽灵行动)的 Material ID 方案
工作原理
- 用一张 Texture Array 存放所有材质的纹理
- 用一张 Material ID 图 覆盖整个地形,每个元素对应 1 米 的地形格子,存储该区域使用的 材质 ID(即 Texture Array Index)
- 在 PS 中以 正方形 为单位,采样周围 4 个 ID,再从 Texture Array 采样对应纹理
- 根据 距离 将四种材质做 线性混合
问题
- 没有权重信息:权重是 非零即一 的,只能标记某 1 米区域是否有某材质,美术 无法控制混合程度
- Block 问题(方格化伪影):材质边缘出现明显的 锯齿感 / 阶梯感,尤其在曲线边界处非常明显
三角洲行动的自研方案:ID 图 + 权重 + 三角形混合
三大核心改进
| 改进点 | 具体做法 |
|---|---|
| ① 保留权重 | 同样使用 ID 图,但 保留了权重信息,美术可以精确控制混合程度 |
| ② 三角形单位 | 以 三角形(而非正方形)为混合单位 → 减少采样次数 + 大幅缓解 Block 问题 |
| ③ 每米最多 3 种材质 | 全场景支持 32 种材质,但保证 每米局部最多使用 3 种材质 |
PS 中的工作流程
- 采样 3 个 Texel(三角形的 3 个顶点)
- 得到 6 个材质 ID
- 筛选出 3 个 最终参与混合的材质
- Texture Array 最多只需采样 3 次
性能优势
- 由于每米最多 3 种材质,Texture Array 采样次数上限为 3 次(对比原生方案的 18 次采样)
- 每米 3 种材质对美术来说也 完全够用
与主流方案的对比
| 维度 | 原生 Splat Map | Ghost Recon ID 图 | 三角洲自研方案 |
|---|---|---|---|
| 性能 | 差(采样多) | 中等 | 好(最多 3 次采样) |
| 内存 | 大(多张权重图) | 较小 | 小 |
| 效果 | 好(有权重) | 差(无权重 + Block) | 好(有权重 + 三角形混合) |
| 可扩展性 | 极差(线性增长) | 中等 | 极好(材质数翻倍无额外开销) |
一句话总结:以更高的性能,实现了更好的效果。
关键概念速查
- Splat Map(权重图):传统地形方案中存储每层材质权重的纹理,每个通道对应一层材质
- Texture Array:将多张同尺寸纹理打包成数组,通过索引访问,避免绑定多张独立纹理的开销
- Material ID 图:每个 texel 存储对应地形区域使用的材质索引,而非权重值
- Block 问题(方格化伪影):以正方形网格为单位切换材质导致的边缘阶梯状锯齿
- 每米 Texel 数:衡量地形材质精度的指标,数值越高细节越丰富(三角洲行动手游达到 500+ texels/m)
Adaptive Dynamic Texture Array:32 层材质的内存优化
问题:32 层材质的内存爆炸
- 前面已实现 32 层以上材质方案,但 32 层纹理 × 2(至少 BaseColor + Normal)= 内存爆炸
- 如果削减材质数量 → 品质降低(一般手游只有 4~8 层)
- 传统的基于 Mipmap 的 Texture Streaming 技术 无法用于 Texture Array(Texture Array 是整体对象,不能对单层做独立 Streaming)
解决方案:Adaptive Dynamic Texture Array
核心思路
同一时间,屏幕上只会用到 32 种材质中的一部分
- 在 GPU 内存中 只存放当前视野内用到的材质纹理
- 当玩家移动到新区域 → 需要的材质发生变化 → 动态更新 Texture Array 内容
- Texture Array 也会自动 Resize → 所以叫 "Adaptive"
内存优化效果
| 对比项 | 数据 |
|---|---|
| 原始 32 层全加载 | ~85 MB |
| 优化后(动态 8 层) | ~21 MB |
| 压缩比 | 仅原来的 25% |
如何控制每个区域的材质层数
- 使用 PCG(程序化内容生成) 整体规划不同区域用到的材质种类和数量
- 提供 自动化 + 可视化的层数检查工具
- 在 过渡区 / 野外非 POI 区域,内存压力较小 → 适当放开限制,提高野外品质
技术落地的工程思路
不只是技术实现,而是一个 完整的规划链条:
- 方案设计 → 确定技术路线
- 与美术 / TA 协同 → 规划不同生态、不同区域的材质层数
- 与客户端评估 → 评估不同区域的内存压力
- 技术实现 → 最终找到 内存与品质的最佳平衡点
- 例如"为什么是 8 层而非 7 层或 9 层",都是 前期充分评估 的结果
材质混合算法:改进的高度混合
传统混合方式对比
权重混合(Weight Blending)
- 最基础的方式:根据 权重值线性插值 两种材质
- 效果较 平淡,过渡区看起来"糊"在一起
高度混合(Height Blending)
- 根据材质纹理的 高度信息(Height Map)决定混合方式
- 效果更加 物理真实:例如泥土和石块的混合,石块会自然地"凸出来"
- 缺点:传统高度混合算法 计算复杂(需要获取每层高度 → 求和 → 算平均高度等)
三角洲行动的新高度混合算法
核心优势
- 计算极其简单:只需要几次乘法操作
- 支持权重控制:不同权重下都能产生良好的高度混合效果
- 两个美术可调参数:
- Scale :控制高度混合的幅度
- Sharpness :控制混合边界的锐利程度
- 32 层材质中每一层的这两个参数都可以独立设置,自由度极高
远近自适应:统一的混合公式
问题
- 高度混合在 近景 效果优秀
- 但在 远景 会出现 方格化伪影(Block Artifact),此时权重混合反而更好
解决方案
- 在公式中引入 系数
- 当 ,且 , 时 → 公式 完全退化为权重混合
- 通过 距离 控制 的值:
- 近处 较大 → 高度混合
- 远处 趋近于 0 → 权重混合
- 过渡 非常平滑,无需额外的分支或判断
一个统一的公式,同时实现近处高度混合 + 远处权重混合 + 平滑过渡,且消耗极低。
CDLOD + VT Render 系统:地形渲染管线重构
Virtual Texture(VT)基础回顾
VT 的出发点
- 假设有一张 超大超高分辨率纹理 平铺在整个地形上
- 所有材质的混合结果都 预先烘焙 在这张纹理中
- 在 Base Pass 中只需 一次采样 即可 → 无需实时做材质混合
- 但这张纹理可能有 上百 TB,无法直接存储
运行时生成 + 缓存
- 运行时按需生成 这张超大纹理中 当前用到的部分
- 这张超大纹理 = 虚拟纹理(Virtual Texture, VT)
- 生成的基本单元 = Page(页)
- 在 Base Pass 之前有一个 VT Pass,负责生成 VT Page
VT 的核心优势
- 将逐帧的采样和混合计算,均摊到多帧中执行
- 结果缓存后,Base Pass 只需 一次采样
- 大幅优化 性能、带宽、发热
UE4 原生地形方案的问题
Landscape Proxy 架构
UE4 原生地形使用 Landscape Proxy 作为核心数据结构:
- 地形被网格切割(如 100m × 100m 一格),每格 = 一个 Proxy
- 10km 大世界 → 上千个 Proxy,对应上千个关卡(Level)
具体问题
| 问题类别 | 具体描述 |
|---|---|
| 内存浪费 | Proxy 存储了大量冗余数据(草权重图、高度图等),在自研方案下用不到 |
| 加载缓慢 | Proxy 体积大 → 加载慢 → 上千个 Proxy 即使远处用 Mesh 替代也会 严重卡顿 |
| Draw Call 爆炸 | Base Pass 中产生 大量 Draw Call,且 无法 Instance |
| 数据管理不便 | 物理数据和渲染数据耦合在一起:服务端不需要渲染数据、客户端希望统一管理物理(不仅限地形)、物理和渲染的加载距离不同…… |
自研方案:CDLOD + VT Renderer
整体架构对比
| Pass | UE4 原生 | 三角洲行动自研 |
|---|---|---|
| Base Pass | Landscape Proxy | CDLOD |
| VT Pass | Landscape Proxy | VT Renderer |
两个 Pass 都完全抛弃了 Landscape Proxy,从零开发。
CDLOD(负责 Base Pass)
CDLOD (Continuous Distance-Dependent Level of Detail):
- 用 1~2 个 Draw Call 即可绘制 整个地形 → 解决 Draw Call 爆炸问题
- LOD 切换无接缝
- 可以 非常自由地控制面数,在高 / 中 / 低配下都能达到性能与效果的平衡
VT Renderer(负责 VT Pass)
- VT Renderer 是一个极度轻量化的数据结构
- 只包含两样东西:
- Material ID 图(前面自研的材质 ID 纹理)
- 一个极简 Mesh(只有 2 个三角形的平面片)
- 它 只做一件事:在 VT Pass 的 Fragment Shader 中生成 VT Page
收益总结
- 一举解决 内存浪费和加载卡顿问题
- 拥有自己的数据结构 → 为后续 大刀阔斧的优化和修改打下基础
- 感谢 UE4 打下的强大基础架构,使得自研方案能够方便地落地
地形品质进阶:四大提升手段
生态变化(Ecological Variation)——不增加材质数量的丰富度提升
核心思路
- 已有 32 种材质,在手游上已是极限数量
- 目标:不增加材质数量,但继续提升场景丰富度
- 答案是 "变化":
- 颜色变化(Color Tint)
- 湿度变化(Wetness)
- 明暗变化(AO / Brightness)
- 例如:同一种地面材质,通过 染色 给出不同区域的颜色差异,美术自由度极高
实现方式:全场景信息图
- 将变化信息存储在 一张覆盖全场景的纹理 上(如颜色图、湿度图)
- 精度要求:每米一个信息(1 texel/m)
- 问题:10km 大世界 → 10K × 10K 纹理 → 压缩后仍需 ~133 MB,手游不可接受
技术突破:移动端 Clip Map 技术
核心观察
超大纹理上,同时用到的区域可能连 1% 都不到——越高精度的 Mip Level,只有玩家附近很小范围才用得到
工作原理
- 类似 Mipmap 的空间局部性 思想
- 原始大纹理的每个 Mip Level,只在内存中存放当前可见的区域(绿色区域)
- 玩家移动时 → 滚动更新(Rolling Update)对应区域的数据
内存优化效果
| 项目 | 数据 |
|---|---|
| 原始 10K 染色图 | ~133 MB |
| 使用 Clip Map 后 | < 2 MB |
| 压缩比 | 降至约 1% |
已落地的生态变化效果
| 功能 | 具体表现 |
|---|---|
| 水坑 | 读取全场景 湿度图,结合地形纹理的 高度信息 做 高度混合(Height Blending),水坑边缘自然 |
| 河边 / 水下湿度 | 大范围湿度渐变 |
| 湖泊 / 积雪染色 | 湖泊区域染色(上方再叠加湖面模型) |
| 河道 / 峭壁变色 | 峭壁区域的特殊颜色处理 |
性能分析
- 染色采样 在 VT Pass 中进行(烘焙阶段)
- 额外开销:几个 ALU + 一次纹理采样 → 性能几乎无影响
- 如果放在 Base Pass 中做 → 代价较大(不推荐)
Clip Map 的通用性——不仅仅用于地形
Clip Map 本质上是一个通用的纹理 Streaming 方案
| 应用对象 | 具体效果 |
|---|---|
| 草地 | 同一种草(同一个 Instance 绘制)通过 Clip Map 获取不同颜色变化;草与地面衔接处通过 Clip Map 存储 AO 遮蔽 信息 |
| 树木 | 模拟 生长年龄、树叶健康度、四季颜色变化 等,不增加 Draw Call |
| 岩石、植被、河流 | 均可使用 |
额外优化:Material ID 图也改用 Clip Map
- 此前 VT Render 中的 Material ID 图 需要被 Streaming
- 将其改为 Clip Map 形式 → 变成一张 覆盖整个场景的常驻数据
- 好处:进一步优化卡顿(减少 Streaming 导致的帧率波动)
峭壁渲染优化(Cliff Rendering)
问题分析
表现
- 地形峭壁(陡峭面)出现 纹理拉伸失真
- 原因:地形默认使用 从上往下的投影(Z 轴投影)计算 UV
- 对于近乎垂直的面 → UV 被严重拉伸 → 采样出来的纹理也被拉伸(相当于把正方形贴图拉成了长方形)
后果
- 美术 不敢制作陡峭山体 → 手游的山普遍是 低精度的缓坡曲线
- 但 3A 端游的野外常有 高山峭壁,做好峭壁品质会有 质的提升
- 三角洲行动的美术设计和原画中有 大量峭壁需求
解决思路
核心原理
- 拉伸发生在 Base Pass 的 UV 上
- 用拉伸的 UV 去采样 未变化的 VT → 结果拉伸
- 最直接的解决方案:让 VT 内容也跟着 UV 一起变化 → 用拉伸 UV 采样变化后的 VT → 结果正确
本质技术:Triplanar Mapping
- 传统方式:只从 Z 轴(上往下)做投影算 UV
- Triplanar Mapping:分别从 X 轴、Y 轴、Z 轴 三个方向投影,计算三组 UV,采样三次纹理,再按 法线方向权重 混合
关键改进:将 Triplanar Mapping 做进 VT 中(在 VT 烘焙阶段处理),而不是像 Farcry 那样在 Base Pass 中单独绘制峭壁(那样很贵)
三种优化方案对比
| 方案 | 原理 | 采样次数 | 效果 |
|---|---|---|---|
| Lazy Triplanar | 三个方向中 选权重最大的一个 | 不增加采样 | 有 明显接缝 |
| Biplanar(二方向混合) | 选 法线分量最大的两个方向,用法线分量做权重混合 | 多一倍采样 | 接近 Triplanar 效果,但采样翻倍 |
| Stochastic Blending(最终方案) | 类似 Farcry 的随机混合思路,通过 控制概率分布 + Alpha Test 模拟 Alpha Blending | 不增加采样 | 类似 Dithering 效果,近距离贴着看才能发现瑕疵 |
最终选择:Stochastic Blending
- 不增加采样数——这对手游至关重要
- 原理:不同像素按概率选择不同投影方向,利用 Alpha Test 替代 Alpha Blend
- 视觉效果:非零距离贴着看,完全看不出瑕疵
- 设计哲学:
手游需要的正是这种"牺牲 1% 来换取 99% 提升"的技术
- 这种 Stochastic 思路后续还会被复用(在其他功能中也会使用类似方法)
关键技术总结
| 技术 | 核心手段 | 关键收益 |
|---|---|---|
| 生态变化 | Clip Map + 全场景信息图 | 不增加材质数,大幅提升丰富度,133MB → <2MB |
| 峭壁优化 | Triplanar in VT + Stochastic Blending | 零额外采样开销,消除峭壁拉伸 |
| Clip Map 通用化 | Material ID 图、草地、树木均可使用 | 减少 Streaming 卡顿,统一框架 |
远景细节优化:SVT 法线烘焙方案
问题:远景几何细节严重丢失
现象
- PCG 生成的 沟壑、侵蚀 等地形细节在远景中被 完全抹平
- 近处看地形丰富多变,远处看一片光滑
根本原因
- 编辑器中地形有 38 万面
- 手游实际运行时(经过 LOD 简化)只剩 几万面("一个零头")
- 越远网格密度越低 → 没有顶点 → 自然没有几何细节
- 这是手游的硬性限制,必须降面数
VT 的构成:SVT + RVT
插入一个关键概念,理解后续方案的前提
- 三角洲行动的 VT(Virtual Texture) 由两部分组成:
| 类型 | 全称 | 说明 |
|---|---|---|
| RVT | Runtime Virtual Texture | 运行时动态生成,覆盖近处高精度 Mip Level(如前 3 层) |
| SVT | Streaming Virtual Texture | 离线预烘焙,覆盖远景低精度 Mip Level |
- 例如一个 16K 的 VT:
- 前 3 层 Mip(高精度):由 RVT 运行时渲染生成
- 后续 Mip(低精度):由 SVT 离线烘焙,运行时 Streaming 加载
- SVT 被加载后 与 RVT 共享同一个 Page Pool → 不增加额外内存开销
解决方案:将顶点法线烘焙进 SVT
思路推导
-
暴力方案:把所有顶点法线存在一张纹理中,在 PS 中采样 → 理论上能恢复几何细节,且精度是 逐像素 的(比网格精度更高)
- 问题:全场景法线纹理 → 内存、带宽、采样数全部爆炸
-
关键发现:已经有一张 覆盖全场景的纹理 → SVT!
- SVT 本身就有 法线通道
- SVT 本来就会被 Streaming 进来
-
最终方案:
在离线烘焙 SVT 时,将高模的顶点法线(几何法线)混入 SVT 的法线通道
- 当 SVT Streaming 进来时 → 自然带上了 逐像素精度的几何法线
- 无需额外纹理、无需额外采样
效果
| 对比项 | 数据 |
|---|---|
| 原始编辑器面数 | 38 万面 |
| 实际运行面数 | ~8 万面 |
| 视觉效果 | 以 8 万面的性能开销,实现了 38 万面的细节 |
| 额外性能开销 | 零 |
额外优势
- 美术可以 手动编辑 SVT,对远景进行进一步精修优化
- 完全无侵入式方案,不改变渲染管线
近景立体贴花(3D Decal)
贴花的重要性
端游 vs 手游的差距
- 传统手游场景 "干净得不真实",根源是 缺少细节
- 端游大量使用 贴花(Decal) 来丰富场景:
- 道路破损、水坑、碎石、裂缝、污渍……
- 这些小细节让场景 看起来像真实世界
- 不仅仅是地形问题,贴花影响整个场景的真实感
手游上的贴花困境
- 传统贴花(Deferred Decal)存在 Draw Call 和 Overdraw 问题
- 手游上无法大量使用
VT 贴花的优势
- VT 贴花没有 Overdraw 问题(烘焙进 VT 后,运行时采样成本固定)
- 三角洲行动使用 大量 VT 贴花:道路、水坑、破损、石块等
- 效果:极大提升地形丰富度和细节感
立体贴花(3D Decal)——移动端低成本 Tessellation
灵感来源
- 灵感来自 Fortnite(堡垒之夜) 的分享
- 目标:在移动端以 低成本 实现类似 Tessellation(曲面细分) 的立体效果
工作原理
制作流程
- 对一个普通的 VT 平面贴花(如地面石块纹理)
- 制作一个 对应的 Mesh(网格模型):
- 四周是平的 → 与地形 无缝衔接
- 中间有凸起 → 模拟贴花对应的立体形状
- 该 Mesh 在 Base Pass 中渲染,采样 VT 获取纹理
- 简单来说:把地形局部"顶起来"
加载与过渡机制
| 距离 | 表现 |
|---|---|
| 远处(刚加载) | Z 轴 Scale = 0 → 完全平坦,看到的是普通 VT 贴花 |
| 逐渐靠近 | Z 轴 Scale 从 0 渐变到 1 → 贴花逐渐"鼓起来" |
| 近处 | Z 轴 Scale = 1 → 完整的立体石块 / 凸起效果 |
- 视觉效果:远处是平面贴花,走近后自然变成立体几何,类似 Tessellation
性能分析
- 可视范围和加载范围都很小 → 同屏可能只有 几个
- 使用了 Dynamic Instancing → Draw Call 开销极低
- 非常划算:少量开销 → 显著拔高品质上限 → 进一步缩小端手差距
性能优化:基于变体与 VT Pass 的 GPU 优化
基于 Shader 变体的优化
问题:远近混合的采样浪费
背景
- 地形渲染常需要 远近混合(Distance Blending):
- 近处:使用正常 Tiling 采样
- 远处:使用 更大 Tiling 采样(避免远处纹理重复感)
- 过渡区:两者都采样,然后混合
传统方案的问题
| 方案 | 做法 | 问题 |
|---|---|---|
| 方案 A | 所有像素都做双倍采样(近 + 远) | 采样翻倍,浪费严重(大部分像素不在过渡区) |
| 方案 B | 近处 / 过渡区 / 远处分成不同 Draw Call | Draw Call 翻倍 |
三角洲行动的方案:利用 VT 天然的多 Draw Call
关键发现
VT 本身就需要 多个 Draw Call 来绘制不同 Page 的内容,而 VT 的不同 Page 天然对应不同距离
做法
- 在 VT Render 的各个 Draw Call 中,根据对应的 距离 选择 不同的 Shader 变体:
- 近处 Page:只采样近处 Tiling → 单次采样
- 过渡区 Page:打开远近混合 → 双倍采样
- 远处 Page:只采样远处 Tiling → 单次采样
- 同样可以用来 区分峭壁区域(峭壁只在特定区域启用 Tri-planar Mapping)
- 变体控制权交给 VT Render → 统一管理
效果
不损失效果,大幅减少不必要的采样开销
极致优化:按区域材质数选择 Shader 变体
数据分析
- 以最复杂的 POI 区域 为例,分析每 1 米地形实际使用的材质数量分布
- 结论:只有 13% 的区域用满了 3 种材质,但 100% 的区域都在运行 3 材质的 Shader
优化方案
- 离线计算 每个区域(每 1 米)实际使用的材质数量
- VT 渲染时,根据区域选择 不同材质数的 Shader 变体:
| 变体 | 采样次数 | 混合计算 |
|---|---|---|
| 单材质 Shader | 1 次 | 无需混合 |
| 双材质 Shader | 2 次 | 1 次混合 |
| 三材质 Shader | 3 次 | 2 次混合 |
关键优势
- 传统方案做不到:传统 Base Pass 地形无法精细区分每个像素的材质数量
- VT 方案天然支持 → 灵活性不降低、效果不损失、性能最大化
这是 VT 架构带来的 独特优势:在 VT Pass 阶段可以按区域极致优化 Shader 复杂度,而最终 Base Pass 只需要统一采样 VT 即可。
VT Pass GPU 性能优化
原生 VT Pass 流程分析
原始流程
- VT Pass 分为两个阶段:
- 绘制阶段(Render):将地形材质混合结果绘制到 Page 上
- 压缩阶段(Compress):对绘制好的 Page 进行压缩
原生方案的问题
- 绘制阶段需要支持 半透明(Alpha Blend) → Alpha 通道被占用(存放透明度)
- 实际数据只能放在 RGB 通道
- 一个最常用的 8 通道 VT(如 BaseColor RGB + Normal XY + Roughness + Metallic + AO)→ 需要 3 张 Render Target(RT)
- 后果:内存大、带宽高、写入次数多
优化方案:Combine Pass
核心改动
- 新增一个 Combine Pass 替代原始的半透混合方式
- 释放 Alpha 通道 用于存储实际数据 → 每张 RT 存 4 个通道(RGBA)
各阶段优化效果
| 阶段 | 原始方案 | 优化后 | 收益 |
|---|---|---|---|
| 绘制阶段 | 3 张 RT | 2 张 RT | 降低内存、减少写入次数和带宽 |
| 压缩阶段 | 读取 3 张 RT → 3 次采样 | 读取 2 张 RT → 2 次采样 | 降低读取带宽和采样次数 |
代价
- 只能支持 Opaque(不透明) 和 Alpha Mask
- 失去了半透明支持
用 Alpha Mask 模拟半透明
问题
- 很多 贴花是半透明的(如道路边缘渐变、水渍扩散等),不能直接放弃半透支持
解决方案
与之前峭壁 Tri-planar 过渡区类似的思路
- 使用 控制 0/1 分布密度的 Alpha Mask 来 模拟 Alpha Blend
- 原理:
- 更透明的区域 → 0 的密度更高(更多像素被丢弃)
- 更不透明的区域 → 1 的密度更高
- 例如:原始 Alpha Blend 中透明度较高的 texel,在 Alpha Mask 中对应区域 0 的分布更密集
为什么可行
- 这是在 纹理空间(Texture Space) 上做的 Alpha Mask 混合
- VT 的 Page 分辨率足够高 → 肉眼完全看不出差别
- 在 VT 上 几乎可以完全替代半透明
保底方案:优化后的原始半透方案
- 对于极少数确实需要半透的情况,仍保留优化版原始方案:
- 利用移动端低成本的 On-Chip Fetch(片上读取,不走外部带宽)
- 将 3 张 RT 合成 2 张 RT 后再送入压缩阶段
- 压缩阶段读取带宽仍然 降低 33%
最终整体流程
绝大部分内容 → Combine Pass(2 张 RT,Alpha Mask 模拟半透)
少数特殊需求 → 优化后的原始方案(On-Chip Fetch + 2 RT 压缩)
Overdraw 优化
问题分析
- 单个 VT Page 覆盖范围只有 0.5 米
- 贴花经常完全覆盖地形(如道路区域)→ 发生 Overdraw
- 贴花数量多 → Overdraw 概率高,近处尤为严重(Page 覆盖范围小 + 更新最频繁 = 性能大头)
- 地形材质比贴花 复杂得多 → 一旦 Overdraw,浪费的性能非常可观
优化策略
| 策略 | 具体做法 |
|---|---|
| 引入 Depth Buffer | 在 VT Pass 中增加一个 Depth Buffer |
| 调整绘制顺序 | 先画贴花,再画地形 |
| Early-Z 剔除 | 利用 Early-Z 自动剔除被贴花完全覆盖的地形像素 → 跳过复杂的地形着色 |
| CPU 端整体剔除 | 如果一个不透明贴花(如道路中间区域)完全覆盖了某个 Page → 直接在 CPU 端 跳过该 Page 的地形 Draw Call |
| 无贴花区域跳过 Depth | 如果某个 Page 范围内 没有任何贴花 → 可以 不写入 Depth Buffer,节省开销 |
- 整体非常 灵活,根据每个 Page 的实际情况做不同级别的优化
性能对比测试:三角洲行动 vs CODM
测试方法论(严格控制变量)
测试环境
| 项目 | 设置 |
|---|---|
| 测试机型 | iQOO 17 Pro,骁龙 855 |
| 屏幕亮度 | 关闭自动亮度,调至 最低 |
| 温度控制 | 每次测试前关闭 APP,等待 CPU 温度恢复至 稳定 39°C,散掉机身热量 |
| 帧率锁定 | 除帧率测试外,统一锁定 60 FPS |
测试场景控制
- 三角洲行动选择 材质变化最多的 POI 区域
- CODM 同样使用 CDLOD 方案的对应区域
- 为排除 Texture Cache Miss 影响,专门编写了 权重图转换工具
- 确保 材质分布一致,只有渲染方案本身不同
测试维度
- 不同 画质档位(高配 / 低配)
- 静止 vs 运动(运动时 VT 需要更新,压力更大)
测试结果
蓝色 = 三角洲行动,黄色 = CODM
帧率
| 场景 | 结果 |
|---|---|
| 高画质 静止 | 三角洲 更优 |
| 低画质 静止 | 三角洲 更优 |
| 运动状态 | 三角洲虽因 VT 更新略有下降,但仍比 CODM 高出 50% 帧率 |
功耗(发热)
- 三角洲行动在 所有情况下 均 大幅领先
- 即使运动状态下也优于 CODM
- 三角洲高配的功耗 < CODM 低配的功耗 ← 极其亮眼的结果
带宽
- 由于材质方案的天然优势(ID 图 + Texture Array vs 传统权重图)
- 画质越高、层数越多、采样越多(有无法线等),三角洲的领先幅度越大
GPU 耗时
- 静止状态下测试 → 大幅度领先
- 从帧率数据也可间接验证运动状态下同样优秀
核心结论
三角洲行动高配的性能,甚至大幅优于 CODM 低配 同时三角洲还支持 更多材质层数 + 更好的混合品质 → 这就是技术方案选择的力量
实际落地验证
- 已经历 多轮大规模测试
- 日常有 每日性能自动化跑车(Performance Profiling) → 及时发现性能回退
- 示例:小米 11 跑图 11 分钟 的稳定性测试结果良好
总结:端手一体地形方案全景
技术突破全景
| 维度 | 技术突破 |
|---|---|
| 底层方案 | ID 图 + 权重 + 三角形混合、Adaptive Dynamic Texture Array |
| 材质混合 | 新高度混合算法(统一公式,近处高度混合 / 远处权重混合) |
| VT 渲染管线 | CDLOD + VT Render(SVT + RVT 协同) |
| 生态变化 | Clip Map 技术(颜色、湿度、明暗信息图,133 MB → 2 MB) |
| 峭壁 | VT 内 Tri-planar Mapping(无额外 Base Pass 开销) |
| 远景 | SVT 法线烘焙(8 万面实现 38 万面细节,零额外开销) |
| 近景 | 立体贴花(低成本移动端 Tessellation 替代) |
| 性能优化 | Shader 变体 + VT Pass GPU 优化(Combine Pass、Alpha Mask 模拟半透、Overdraw Early-Z 剔除) |
方法论总结
项目最终品质,不是单纯堆技术就行的,落地才是最难的。
- 每个方案在 设计阶段 就已经与 客户端、TA、美术 充分讨论了落地需要做的事情
- 技术 + 工具链 + 美术规划 + 性能评估 四位一体
- 先讨论清楚,再动工 → 避免做完发现落不了地