研发日志技术篇(上)——如何死磕GPU

2022-12-12
大家好,我们是国产科幻生存RTS《异星前哨》项目组成员,是一群热爱RTS的老炮儿,在游戏12月16日将于Steam等平台发售之际,我们决定把开发过程中遇到的一些经验积累分享给大家。希望给国内游戏的开发环境提供一些微末帮助。


鉴于我们的游戏类型是生存RTS,内容中包括人类与数量巨大的虫潮展开激烈对抗,所以多单位作战是我们要考虑的重要技术点之一。把它继续拆解后,多少单位作战,屏幕中可拥有多少单位等问题浮现在我们眼前。

最终,为了使《异星前哨》在市场上更有竞争力,制作人设定了“同屏十万只怪”这个看起来比较疯狂的目标。如果把这个S级的技术难点攻破,其它问题应该就也能攻破了。

十万只怪!技术组开了一礼拜的会,要达成这个目标,我们得面临CPU+GPU(图形处理器,显卡的核心)的双重压力。因为海量物体的绘制,需要GPU承担。这些物体的寻路、战斗、AI等行为又需要CPU的计算,这几乎是一个极限挑战。

尽管在公司的另一个项目《铁甲雄兵》已经实现了同屏数百人战斗的大战场,但同屏十万的难度跨越着实有点大,经过了对各类性能问题漫长的死磕,初步总结了一些方法,本篇主要从GPU角度的两个主要方案来谈一下。

一、GPU大批量动画优化渲染技术

由于游戏中存在海量怪物,每只怪物都需要播放各种动画,势必会对运行效率产生严重影响,所以需要一种新的动画渲染技术,能够承受海量的物体。


现代显卡的性能增长非常快,瓶颈逐步变成GPU和CPU之间的数据同步,每一次同步都需要互相等待,双方浪费大量时间。之前的很多游戏包括手游,都把减少drawcall作为一个重要优化手段,因为每一次drawcall都会导致一次CPU上传数据+GPU绘制的同步。这里要注意的是,并不是说drawcall本身效率很低,而是常规drawcall导致的数据同步效率极低,当然drawcall本身也是有开销的,能减少是最好。

因此这次的优化方向是,减少CPU与GPU的数据同步。怪物尽可能由GPU绘制,最大可能减少数据同步,以达到最高异步效率。知易行难,定了方向但是还有很多技术细节需要克服。

传统骨骼动画计算(CPU计算骨架+GPU计算蒙皮)


传统的做法是CPU按照动画序列计算当前各骨架位置(BoneMap),然后把BoneMap传递给GPU,GPU通过VertexShader,结合各mesh顶点数据,对应BoneMap数据以及骨骼权重等进行插值计算,得到新顶点位置。传统动画处理由于需要CPU+GPU两个步骤顺序进行计算,每个动画物体需要每次单独调用绘制,同时独立物体动画计算时需要在Drawcall时传递不同材质参数,不利于合批高效绘制,另外CPU与GPU之间频繁进行数据交互也会产生很大开销,所以我们必须换一种方式。

贴图骨骼动画


为了将计算挪入GPU,必须把相关数据存入显存供GPU读取,首先是美术输出的骨骼动画信息,我们选择将各骨骼运动的动画帧变化存到贴图RGBA Float格式中作为贴图骨骼动画文件,在VertexShader中通过函数tex2dlod采点获取贴图颜色数值,通过将颜色数值转换为变换矩阵,从而与本地mesh顶点等信息,产生动画变换。(注:在材质设置贴图采样模式时选用point,避免贴图采样受其他模式干扰)


其次,游戏运行时,每只怪物都有自己的当前动画信息,这些信息也需要同步给GPU,考虑后续的批量绘制等优化技术,我们还是选择贴图做为载体。先将各动画集如idle,run,attack转换为贴图动画纹理,在将这些贴图动画作为一个材质的各贴图通道。

另外由于批量绘制中各个角色播放的动画第几帧以及播放哪个动画集不确定,所以在调用draw call前,通过CPU计算各个单位动画系统得到一张全单位mask图,图上每个像素代表一个单位,一个像素RGBA信息记录,该单位播放哪个动画集以及播放第几帧。这样,一张贴图就包含了所有单位的实时状态信息,可以直接传输给GPU,只有一次数据同步。

总之,就是想办法把骨骼计算压力转给GPU,把CPU闲出来。

大批量实例化绘制

普通drawcall绘制函数一次只能绘制当前的一个mesh,而绘图系统还提供了批量绘制的接口,可以绘制一个mesh多次,出现在不同位置坐标。函数类型形如:

void DrawMeshInstanced(Mesh mesh, int submeshIndex, Material material, List<Matrix4x4> matrices);

这个函数一般只能拿来加速静态物体,比如场景里的石头之类。因为动态物体有动画,几个动态物体很难完全一致。


我们发现由于游戏里怪物数量非常多,总有一些怪物的动画帧当前相同,正好使用这个函数进行加速。举个例子,最常见的怪物进攻,假设移动动画长2秒,当前运行速度每秒60帧,无论多少只怪物,理论上最多调用60x2=120次就可以画完全部怪物。因为怪物最多只有120种状态。当然实际情况更复杂,因为每台机器帧率都不一样。

动画物体大批量实例化绘制

要使用动画物体大批量实例化绘制接口,首先要使多单位物体材质合批绘制,另外为了提高骨骼动画效率采用贴图动画技术,因此最终方法为:


效果展示:


二、大批量物体阴影优化渲染技术

阴影在游戏效果中能体现物体的更真实表现,在实际游戏中由于单位众多,每个单位所需阴影效果也是需要一种优化渲染技术。

传统阴影功能

在传统绘制阴影功能中,需要将产生阴影的物体额外绘制一遍ShadowMapDepth,最后将所生成的ShadowMap与画面中的接收阴影物体比较深度从而确定是否绘制阴影着色。

在多单位的情况下这里的额外绘制ShadowMapDepth处理成为影响效率的瓶颈,如何降低这个Pass绘制次数成为了关键问题所在。

批量化GPU阴影技术

首先按照屏幕大小创建一个RT,控制渲染次序在场景不透明物体渲染之后,获取当前场景绘制完的深度图,调用实例化绘制接口绘制多单位阴影,将深度图等信息传入所需大批量单位绘制ShadowCasterPass,从而在RT中获得当前屏幕内多单位最终阴影效果贴图,将该RT贴图与屏幕当前像素合并从而实现阴影最终效果。


进一步改进,通过在绘制单位中预留代理阴影体,在VertexShader阶段中做动态切换本体顶点或代理阴影体顶点,切换最终哪些顶点光栅化在pixel shader着色计算阴影提高效率。(调Shader可真是个体力活)

关于GPU的分享就到这里了,“同屏十万只怪”最终呈现效果如何,可关注《异星前哨》相关动态,游戏将于2022.12.16正式发售,售价68元,Steam与WeGame首周九折,一次买断!

关于游戏:

《异星前哨》是一款融合即时战略的国产科幻生存RTS,不同于传统RTS游戏,本作舍弃了PVP对战,将核心玩法聚焦于生存模拟。玩家将成为探索外星的领导者,建立基地并抵御外星虫潮的攻势。游戏首创英雄体系融入核心玩法,每个英雄拥有专属技能、兵种以及建筑单位等;数万虫潮同屏效果震撼,可暂停和随时存档。诚邀各位体验!


最新评论
暂无评论
参与评论