|
文/刘源
Turn 10是微软游戏的一个内部工作室,专注于赛车游戏。在 Froza III 中,一条赛道可以长达13英里。开发者希望有良好的加载性能,赛车从静止到200英里/时的高速移动都保持一致的高画质,没有物件突变,同时保持60fps。
本讲座首先介绍了游戏的5层存储层级,阐述资源如何从dvd达到处理器,如何优化Disk IO与CPU缓存。接下来,解释离线计算的遮挡剔除算法,以及其流式加载的工作集管理。
video https://www.gdcvault.com/play/1012335/Streaming-Massive-Environments-from-0
ppt https://www.gdcvault.com/play/1012656/Streaming-Massive-Environments-from-0
Turn 10的全职开发者约70人,会随着开发周期人员增加(主要是美术)。
Forza III 是一个xbox360游戏,赛道数据量极大,无法全部装入xbox360的512m内存。游戏的哲学是,动态加载越多越好,“除了加载本身,其他都是动态加载的”(按:实际上还是做了个别权衡,见后文)。
游戏中典型的赛道Le Mans有约6000模型和3000贴图,赛车奔驰7分钟行驶1.6圈,总加载量为0.98G,远超赛道的总数据量。具体来说为:
Steaming的存储层次结构
上图是加载的层次结构,数据从发布介质——硬盘或者光盘,逐级向上达到处理器。我们逐层解释细节。
1.Disk
首先,赛道以zip格式压缩,使用微软自有的 lzx 算法。xbox的专用lzx库提供 20M/s 解压速度,是开发者找到的最快算法。单赛道压缩后90~300MB,压缩率50%。
2.Compressed Cache, 56M
xbox360使用的双层dvd 有 10M/s 的平均读取速度,但是单次 seek 可耗时100ms。
所以这里使用一个缓存,隐藏 IO。每次从zip中连续读出一个固定大小数据块,可包含上百个文件,不解压。
- 数据块 1/4M~ 1M(默认)
- 由赛道配置,仅在性能不佳时微调
- 读取触发,LRU换出
在访谈最后的磁盘文件排序部分,我们将看到文件如何尽量聚集。
3.Decompressed Heap,194M
这一步从缓存解压到内存。解压后资源内存地址连续、对齐,可以直接由CPU、GPU使用。
内存碎片
内存碎片是长期运行堆的一个常见问题。
- 堆分配算法为地址排序的First-Fit,这是一种公认的碎片极少的算法。
- 资源按zone加载,每批置换2~20M不等。zone切换时,为减少碎片,要注意先一次性卸载所有待删除资源,再进行加载。
- LOD管理
和一般游戏不同,物体的各LOD拆散存储,可以只加载所需的LOD
mip分了三档。 后面的工作集管理会提到具体用法。
- 高:Mip0
- 中:Mip1~1*1
- 低:32*32~1*1
- xbox硬件允许mip链引使用多块不同内存,所以可以免费分开加载。
注意这里的内存预算。层次2加上层次3共250M,为xbox360总内存的1/2。56 : 194 这个比例是长期试验后微调出来的。
4.GPU/CPUCache
主机游戏编程中,很多项目都会关注处理器缓存命中率。密集计算,需要使用缓存友好的算法。
以游戏的视椎体裁剪为例:每个项根据cache大小设计,数据组织为线性,线性遍历,所以缓存命中率非常高。
另外,游戏大量使用了Command Buffer,包括所有的道路、很多渲染子系统。这样只需要提交渲染数据,计算过程全部在GPU上。(按:这个为啥在cache这一节)
5.GPU/CPU
减少不必要的贴图和顶点的传输。(按:细节信息量少,不录了)
预计算遮挡剔除
作者认为,实时的遮挡剔除算法不够好。他们往往使用近似的保守算法,返回偏多的待渲染物体;同时,需要简化的几何数据,带来巨大的数据量压力。所以本作遮挡剔除是离线预计算的。
- 直接返回LOD
- 基于贡献的剔除(Contribution Rejection),可见像素过少也被剔除
基于贡献度的剔除
遮挡剔除分为下面几个步骤:
1.采样,得到zone的可见表
- 美术用spline标记路的内外边界,构成线性路线
- 等分成zone,等间隔采样,如图
计算可见性:每个采样点,按赛道当前朝向(例如上坡),先以90°视角渲染4张深度图,然后重新渲染一遍,统计各物体像素数。
随后,可见性向外扩散1格,以便消除临近点的跳变。(按:个人感觉,本步扩散无意义)
累加得到:
可见物体表、各物体的最大像素数
模型、贴图表
注意这里过大的模型,会自动切分为小模型,增加剔除概率。
2. 计算工作集
前面提到了贴图的mip分为高、中、低三档视图,高为mip0,中为mip1~1*1,低为32*32~1*1。赛道所有低档(32*32以下)mip常驻内存,总在工作集中,20~60M不等。这是一个权衡,优点是任何模型加载好就可以立即显示。
然后我们计算单个zone的工作集。相邻3个zone可见物体合并,这就是理论上需要加载的物体。下一步通过丢弃贴图的方式,得到合适的工作集。由于贴图数据大概占85%,有很多丢弃空间。算法:
- 可见像素小于32*32的话,丢弃最高和中间级贴图。
- 内存限制:按可见像素数从小到大,依次丢弃最高级贴图、中间级贴图,直到工作集可以在Decompressed Heap(194M)中放下。
- 带宽限制:按最高移动速度计算出 zone n-1 到本zone的时间,乘估计的加载带宽,算出最大加载量。继续丢弃贴图,直到两工作集之加载差小于带宽限制。
最终,zone实际工作集差 2~20M,其中贴图占大概 85%。直路工作集变化很小,最大的变化会发生在转弯处。
3.磁盘文件排序
为了提高磁盘加载的缓存(56M)命中率,需要让数据尽量集中。所以整个地图的模型和贴图,按看见的顺序排序。效果会是:
(按:所以同一赛道,同一资源只有一份;不同赛道之间,资源会有重复)
性能手工微调
最后,测试中仍然会有出现跳变。跳变主要为下面两种情况,但是他们相互冲突:
- 带宽不足,加载晚了 —— 需要减少加载量
- 没加载可见物 —— 需要增加加载量
主要解决方案:
- 提供工具,人工对模型和贴图做加减权(bias)
- 如果无效的话,美术需要减少场景复杂度
演示效果和其他
整个流式加载演示过程见54:05。从视频来看:
- 实际上实现了基于zone的、考虑了遮挡的流式加载。
- 遮挡剔除完全是依赛道的赛车视角,相当狠。由于赛车很低,所以中距离的个别低矮的田地都会被遮挡不加载。如果镜头抬高,可能这算法不太够用。
- 其他我觉得有用的细节
- 自动修正美术的资源冗余
- 贴图像素比较去重:有的美术就是会复制贴图
- 逐级比较mip差异,自动缩小贴图:优化各种纯色和渐变贴图
- 由于用了Instancing,所以认为合并 mesh 的价值不大
- 资源导出时,会预检查model的结构,拆出公用子模型,保证Command Buffer的draw尽量少。(说得不是太清楚)
- GPU Instancing的时候附带一个自定数值(颜色;风力之类),让美术用同样的资源产生多样性
来源:GDC2010
知乎专栏:https://zhuanlan.zhihu.com/p/37500153
|
|