【译】GearVR 游戏优化之Darknet

发表于2016-01-31
评论0 1.7k浏览

原文地址:http://www.gamasutra.com/blogs/EMcNeill/20141021/228182/Optimizing_Darknet.php

原文作者未做版权声明,视为共享知识产权进入公共领域,自动获得授权


一个 Gear VR 游戏必须满足以下条件:

  • 运行在 1440p 下
  • 以立体 3D 形式运行
  • 保持稳定 60 帧
  • 运行在手机上

即便是针对 Darknet 这种图形简单的游戏,也非常非常难。当我最开始听到 GearVR 时,Darknet 在 PC 上运行到60fps都很难,这种登天般的差距带来的优化难度立刻就把我吓趴下了。不过,Oculus 还是给我展示了一些充分发挥移动 VR 潜力的演示,因此我知道以 Darknet 这种抽象规律的画面,理论上来说还是可以达到以上要求的。因此,我开工了。

几个月以后,Darknet 终于达成目标,能够满足以上这些苛刻的要求。不过,我并没有立刻宣布胜利,毕竟对于移动手机来说,不可能有“优化得太过分”这种事。如果再努力点,我还是能进一步减少电池消耗,降低发热,让表现更加流畅等等。但游戏已经非常可玩了,我觉得我可以分享一些我的经历经验。

假“发光”

Darknet 里面几乎所有东西都是发光的。在 PC 原版中,这个效果通过后期添加一个发光效果达成。基本上,发光物体会被重新渲染一次,然后模糊化再叠加到场景上面。这个处理方式比较简单,效果也不错,但是效率实在不敢恭维,基本上移动版就不要想了。

如果只是 2D 的 Sprites,解决方案非常简单:你只需要在放进游戏前给 Sprite 加上“发光”层就可以了,不过,对于 3D 游戏来说,这条路基本上走不通。因为你可以在物体的表面纹理上增加一些发光效果,但是这些光不会延伸到这个物体外,对于机器来说“物体外”这个逻辑是很不好定义的。又由于物体的剪影会随着摄像机角度不停变化,因此如果想靠添加假发光的 sprite 来达成特效你几乎需要给每个变化角度都对应添加一个,而这是不可能的。

不过还好的是,Darknet 里多数物体都基本上是圆形,因此这些物体的轮廓一直都是圆的!这也意味着我可以画一个 2D 发光圆圈叠加到物体上来达成假发光的效果。(由于圆圈的半径会随着摄像机位的距离发生变化,因此我需要通过一些高深的数学手段来即时调整圆圈的大小,不过对我来说问题不大)

而使用这个订制的 2D sprite 还有个额外的好处:现在这些圆形的物体都将有一个完美光滑的外表面,平常的话这种级别的外表面需要极其消耗性能的抗锯齿才能达成。 此外,这些“假冒”的发光效果也比之前的“真”发光效果要更加干净清晰。因此在这个特例上,我不仅让游戏更快了而且也更好看了!你suo我是不是天才

前后对比:

简化网格(Mesh)

在 Darknet 中最重要的 3D 物体是圆形的 “节点” 以及节点中间的 “连线”。这些节点都是不同种类的Goldberg 多面体,而连线都是动态生成的弯曲滤网。

一开始,这些连线都是光滑的连续曲线,但这样就需要太多多边形了。而针对移动版就没法这么奢侈,因此我调整了一下,让连线在几何形态上更加简单。由于它们是游戏开始后动态生成的,我可以在生成之前改变一个参数来达成效果。锯齿稍微多了点,但还好不是那么明显。

这些节点的处理其实比看上去麻烦。你可能以为,抽象的几何图形都比较简单,但多边形越复杂,就需要越多的顶点和三角形。其实很长的一段时间里我都抱着侥幸的心理以为机器能吃得消,但最终我放弃了,并开始考虑其它的解决方式。

我决定去试一个简单的圆形网孔(mesh),表面覆盖一层六边形纹理,这样能够将游戏的多边形数量降低一半。当然这不是一个理想的解决方案。我知道这样搞最后的效果看起来会有些奇怪,毕竟纯靠六边形来组成一个圆形是不可能的。圆形顶部和底部的六边形将无可避免地被拉伸变形,不过我希望通过将顶底朝向玩家来最小化这个问题。

但始料不及,六边形组成的圆形完全 OK。节点并不完美,但其缺陷非常微小。而且,由于边线轮廓如前文提及是单独绘制的,最终效果上这些圆形的边缘(锯齿其实稍微多了点)一点也不明显。

降低绘制调用(Draw Call)数量

其实谈及一个移动 Unity 游戏的优化,大家首先考虑的就是如何降低绘制调用数量。而这意味着,要么少绘制些图形物品(但谁想呢),要么去考虑如何巧妙地运用绘制调用批处理,也就是将一些物品捆绑成一组一起渲染。

批处理的概念非常简单,但使用起来就是另一回事了。Unity 有自动将物品智能批处理来降低绘制调用数量的功能,但毕竟是机器嘛,局限也非常多

  • Unity 没办法将使用不同渲染材料(Material)的物品分成一组来进行批处理。在 Darknet 的原型中,我写了些代码来给所有的物品的“renderer.material”属性进行自动调整,也就意味着所有物体的渲染材料属性都变得独特了,导致原版给场景中每一个物品都使用新的绘制命令,极大地增加了开销。不过,你可以使用“renderer.sharedMaterial” (否则调整顶点颜色而非材料颜色)来解决这个问题。

  • 我还通过使用纹理集合(texture atlasing)来降低独特材料的数量。为了简化问题,我就使用了一个2D 工具 来给所有 2D 物品自动创建集合,比如菜单和轮廓边线 sprites 等等。由于理论上来说他们使用了同样的纹理,因此可以放在一组进行批处理。

  • Unity 也不能对有太多顶点(貌似是300以上)的网格(mesh)进行批处理,因此我去整理了游戏内所有的网格确保它们都是可以进行批处理的。除了减少多边形数量,这也是致使我去给节点物体使用更简单网格的原因。

  • 不知道为啥,Unity 没法给不同比例的物体进行批处理。这意味着那些都是(1,1,1)比例的物品可以放在一组进行批处理,而那些不一致比例的物体之间也可以进行共组批处理,但不同比例之间比如(2,2,2)和(3,3,3)就不行。我只好改变它们的比例,比如给物体的比例增加一个(0,0,随机.值*0.05f)参数来统一比例。我觉得这个解决方案有点傻(为什么要让这个游戏更复杂才能优化它?),但这毕竟极大地降低了绘制调用数量。如果你有更好的解决方案,请告诉我!

  • 针对那些共享同一个渲染队列值的透明物体,Unity 会把它们按深度排列然后按顺序绘制。但有的时候这会有问题。想象下你有上百个 A 材料的物体在很远的地方,同时有上百个 B 材料的物体在很近的地方,Unity 会先用一个绘制调用来绘制 A 组物体,然后第二个绘制调用来绘制 B 组物体。但记住 Unity 会先按深度来排列然后才会按顺序绘制。如果这几百个 A B 材料的物体都随机分散在空间中而非都是近的或远的,Unity 会先绘制几个 A 组物体,然后几个 B 组物体,然后又是几个 A 组物体……这样导致消耗大量的绘制调用数量,因为不同材料的物体是不能被共组批处理的。我花了好长时间才意识到这个问题,但我是通过给不同材料物体以不同的渲染队列值或者通过纹理集合分类来解决这个问题的。

我计划在移动版的 Darknet 发布后也会继续优化它,毕竟其优化空间还是很大的。但尽管它离完美还有很远,我对于它目前的样子也非常满意。尽管我不是一个很有天赋或者很有经验的程序员,但对比目前的代码和最开始的原型,我愈发觉得我是个天才。



如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引