在Unity中对森林植被进⾏优化
创建3D森林的时候不仅需要关注艺术技巧,更需要了解拥有什么资源以及如何进⾏放置。其中最重要⼀项需要考虑是:⼀个茂密森林的密度以及随之⽽来的性能优化。
问题
解决森林优化的⽅法有很多,但是关于如何创建资源的通⽤指南却不多。不过关于森林优化有⼀样事情是共通的,即头号敌⼈都会是绘制调⽤(Draw Call)。
虽然多边形数量也很重要,但是问题并没有那么复杂。你只需要知道⼀个合理的⽬标值。
下⾯是⼀些我所制作的植物资源以及它们的多边形数量,可作为对应合理数字的参考。如果发现需要⼤量的Alpha Card才能获得所需的树冠丰茂程度,你可以在纹理中增加树叶的覆盖量。
还有过度绘制(Overdraw),当在Alpha Card和对象之间存在⼤量重叠时会发⽣过度绘制。我没有考虑或关注过它,我不打算修改树的轮廓以减少重叠。实际上,我不会为除了使它们看上去更美之外的任何原因去修改树⽊的轮廓。我也不打算修改森林的布局,以避免植物重叠。让森林真实⽽⼜不必操⼼过度绘制太难了。所以我把精⼒主要集中到了降低多边形数量和绘制调⽤上。佛山电脑培训班
计划
要解决这个问题,我们需要⼀个计划。⼤多数的计划要求在开始创建森林的时候,就考虑到优化策略。如果森林由来⾃不同来源的资源组成,可能很难做到这⼀点。减少绘制调⽤的策略有很多,在本⽂中我将分享我所使⽤的策略。
我的⽬标是将LOD1⽹格(第⼆个LOD阶段)组合为巨型⽹格,这样当玩家移动到远处时就可以整合绘制调⽤。我计划中的第⼀步是让森林⾥的所有东西,或者可能接近的东西都使⽤同⼀个材质。
森林中所使⽤到的资源都共享同⼀种材质。因此我需要所有的植被从同⼀个材质采样。
选择这样做的原因,是因为可以确保任何⼀簇的植被资源在合并时,都能成功整合成⼀个绘制调⽤。如果每棵树或草都使⽤唯⼀的材质,合并后的⽹格可能会拥有许多⼦⽹格,从⽽产⽣许多绘制调⽤。
我为需要的资源准备了⼀个2048x2048的区域,然后在创作的过程不停地将它们添加进去。这个过程是可以⾃动化,需要通过脚本修改所有植被资源的UV,不过有时候⼿⼯的⽅式效率更⾼。
三年级英语教学计划
struck分组
最终的⽬标是合并,但⾸先需要确定合并的⽅式。⽣成⼀个超级⼤的⽹格不是什么好主意,这有⼆个原因:
Unity中的⽹格索引缓冲区是16位的,这意味着每个⽹格最多只能有64k顶点。
请注意:Unity2017.3及以上版本已经正式⽀持32位的索引缓冲,所以这已经不是问题。
⽆法对单独的分组使⽤LOD或从视锥体和遮挡剔除中获益,因为整个森林将是⼀整个⽹格。绘制调⽤将会很好很少,但是三⾓形数量可能多得吓⼈。我们还是可以⽤⼀些额外的绘制调⽤去拯救⼤堆的三⾓形的。
进⼊六边形⽹格。我编写了⼀个脚本,把所有的植物⾃动分到⼀个个六边形⽹格中。我选择了六边形⽽不是正⽅形,是因为六边形更接近圆,在进⾏玩家和每组之间的距离检测时可以更加精确。如果使⽤正⽅形,则会有⼀些⾓落离玩家太近。
六边形分组随后会被组成⼀个个超级六边形。在⼀个特定超级六边形中的每个常规六边形都是处于LOD2状态,超级六边形会切换到⼀个合并版本的LOD2六边形,进⼀步整合绘制调⽤。
为什么不简单的使第⼀个六边形⽹格中拥有更⼤的六边形呢?因为更⼩的六边形可以在过渡到远的LOD时,能拥有更好的粒度控制,以及更精确的剔除。当物体⾜够远,多边形够低时,它们可以被合并成更⼤的六边形。
baritone
合并
在最初构建系统的时候,我合并了LOD0以及LOD1,但这样占⽤的内存太多。它会使⾼多边形版本植
被资源的顶点都拥有唯⼀的内存占⽤,因为每⼀个合并的⽹格都是唯⼀的。此外⽹格越⼤,视锥体剔除效果就越差,最后导致更多额外的三⾓形被绘制。很可能会离LOD0六边形很近,或站在它们之上,因此视锥体剔除不精确的问题会更加突出。
我发现仅合并LOD1可以达到最佳平衡。LOD1多边形⾜够低,占⽤内存少,但节约的资源最多,所以我加载的世界其⼤部分将是处于
LOD1状态的。
在Unity中进⾏⽹格合并没那么轻松。我必须要⾃⼰编写脚本,因为资源商店⾥的那些脚本⽆法处理⼦⽹格或顶点颜⾊。我选择不使⽤内置的LOD组件,因为它也仅是⼀个数据容器⽽已,六边形分组会处理LOD切换。我简单的使⽤了⼀个MonoBehaviour,并引⽤了处于未激活状态的⼦游戏对象,这样可以轻松的从渲染器获得材质与⼦⽹格了。
走路去上学使⽤六边形组进⾏LOD化和剔除
有件重要的事情要了解:Unity内置的LOD组件性能不佳。特别是当所有的草丛上都有这个组件的时候,在CPU上会进⾏有许多的距离检测。将世界划分为整洁的六边形组有很多好处,其中之⼀就是可以对每⼀个组进⾏距离检测,⽽⾮每⼀个⼦对象。然后将整个组的LOD都设成⼀致。我甚⾄还有⼀个⽤了着⾊器中的Alpha cutoff属性的动画过渡。
分组的另⼀个⽤途是作为⼀种动态遮挡剔除来使⽤。我不使⽤Unity的内置遮挡剔除,因为觉得它与多场景兼容的不是太好。分组数量不多,在运⾏时完全可以对它们进⾏⼏个射线投射,确定六边形组是不是在视图中被完全遮挡。
我没有在每⼀帧进⾏这些射线投射,我仅仅在翻过⼭丘和拐弯时会提前留些余量做检测。不可能对所有对象这么做,成本太⾼。但对⼀个由20-50个对象组成的组做⼀些射线投射,还是可⾏的,特别是这些检测隔⼏帧才做⼀次。我只对地形进⾏遮挡检测,因为⼀个森林⾥没有什么东西能够确保是被完全遮挡的。
创作平滑LOD过渡
⼀个平滑的LOD0到LOD1的过渡通常要⽐⼀个基于LOD0的低多边形要好。如果过渡够漂亮,就可以把LOD距离移得更近些,从⽽减少屏幕上的总多边形数量。
我特别为树⽊做的⼀件事就是按照LOD1的样⼦进⾏创作。我使⽤3D包中的树枝实例来构建树。这使我可以为⼀些单独的树枝做⼀个LOD 模型,然后通过将所有实例更新为更低多边形的版本,创造完整的LOD1。
地形
地形有点超出了本⽂讨论范围,请注意:我没有使⽤Unity的内置地形系统,因此我的所有植物都是常规的游戏对象。
我选择使⽤常规⽹格⽽⾮地形系统,有三个原因:
默认的Unity地形系统性能不佳,它会产⽣数百个额外的绘制调⽤和上千个分布糟糕的多边形。使⽤常规⽹格可以按照需求来控制多边形分布。
为默认地形系统编写着⾊器很受限制。
我有许多的洞和悬垂物。我使⽤的着⾊器⾮常简单,它是⼀个带宏覆盖和法线的三通道Vertex Splat。
image
底纹
使植物着⾊器的⼀般性能,⼜称为像素填充率合理优化⼗分重要。
如果使⽤的是延迟渲染路径,那么将植物着⾊器完全延迟可以节省⼤量的渲染时间。我曾使⽤的是正向渲染,当我了解如何让相同的着⾊器使⽤延迟渲染之后,节省了30%的渲染时间。
经营时装店创建具有半透明度的完全延迟着⾊器并不简单,因为需要访问通常在延迟着⾊器程序⾥⽆法访问的光线衰减。我使⽤了⼀个带⾃定义光照模型的表⾯着⾊器,它把⼀个⾮常低保真度半透明遮罩写⼊到G-Buffer中未使⽤的2位 (RT2的alpha)。然后我在Internal-DeferredShading.shader⾥添加了半透明函数。
光照烘焙
为森林中的所有植物烘焙光照将会产⽣很⼤的光照贴图内存使⽤量,在⽣产阶段难以接受的烘焙时间,⽽且最后结果看起来也并不是那么好,因为Alpha Card通常不会⽣成太好的烘焙效果。我将光照探头⽤于⽐建筑物⼩的任何东西。我为树使⽤光照探头代理体,这样树冠部分就能有⼀个到更浅颜⾊的漂亮渐变。
由于树不是静态对象,因此⽆法被光线映射器所见。我需要⼀种可以⼿动将探头包围区域变暗的⽅式。我编写了⼀个简单的脚本,将某个给定代理体内所有探头按所选的颜⾊进⾏染⾊。
image
其它技巧
对树的上半部分使⽤LOD1: 有些树⾜够的⾼,你可以让树冠部分保持低多边形。
枯树,或没有树冠的树⼲:想要达到想要的树冠密度,在没有树冠的情况下增加树⼲是⼀种可以使森林看起来更厚的节约做法。
巨量⾯⽚资源 :在地图中较平坦的部分,可以将⼤量草的⾯⽚合成单个对象,从⽽减少绘制调⽤,即使⽬标对象还处于LOD0/未合并状态。
总结
下⾯是对茂盛森林与植被进⾏的性能优化的总结。
绘制调⽤很可能会成为最⼤的问题。你需要有⼀个计划去减少它们。
多边形数是相对简单的问题,只要确保每个资源的三⾓形数量合理。
我忽略了对于过渡绘制的考虑,因为⽆法在不破坏美观的前提下做到它。
所有的植被纹理都图集化到了⼀个材质,确保合并⽹格能成为单个绘制调⽤。
reader是什么意思我使⽤分组系统,将所有⽹格的LOD1都合并到⼀个组⾥。
我没有合并LOD0,因为它们的多边形太⾼,占⽤太多内存。
sometimes when we touch
分组不能太⼤,否则⽆法从视锥体或遮挡剔除中获益。
我使⽤⾃定义的LOD化脚本,因为我是根据分组⽽⾮对象进⾏LOD切换。
我使⽤⾃定义的⽹格合并脚本,处理顶点颜⾊和⼦⽹格。
我⼿⼯制作的植被资源,经常在提前做好LOD1的计划。
可以将Alpha cutoff动画化,制作更平滑的过渡。
我使⽤常规⽹格,⽽⾮Unity的内置地形系统。
我编写了⼀个完全延迟的植物着⾊器,以保持低像素填充率。
我使⽤光照探头来为森林提供照明,并为森林中的包围区域使⽤⾃定义染⾊体。
以上内容就是在经过多次试错后形成的结果,我将它⽤于世界中的许多对象,并不仅仅是针对植被。当有许多同类对象时,例如:⽊桶或岩⽯,它也能⼯作得很好。最好的情况下,⼀个对象的多个实例会被整合到单个绘制调⽤,⽽最坏的情况下,也仅是跟原先保持相同数量的绘制调⽤⽽已,不过会消耗更多的内存。
选择题技巧>奥巴马竞选演讲
作者:何三思
链接:/p/f6dad566746a
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。