故事
在我们的游戏场景中,有一个场景物件是由三个半透明面片组成的,他们三个使用的是同一个材质球,但是夹在中间的这个片美术用插件对它做了uv动画,一旦启动游戏就会实例化成为一个独立的材质球。在进行DrawCall检查的时候发现中间这层片一旦隐藏,就会导致降低2个DrawCall。最开始以为是材质的问题,仔细看了Shader之后看不出什么端倪。把uv动画的物体换成内置Diffuse就好了,但是换成任何其它半透明的shader都会有问题。
就这样我跟这个问题鬼扯了一个多小时。直到某一次偶然手欠,挪了一下中间那个片片,DrawCall少了一个,我才一拍脑袋,恍然大悟。
欲知来龙去脉,且听我娓娓道来。
图元排序与批次合并
要渲染半透明物体,为了能够得到正确的渲染结果,对物体进行排序并且保证自后向前渲染是必须的。所以对于我的三个片片,引擎肯定是先渲染最后面的那个片,再渲染中间的,再渲染最前面的。
批次合并的本质其实是把材质完全相同的物体集中在一起进行渲染,这样在渲染这几个物体过程中就不再需要更换材质,节省掉几次更换材质的开销。
对于我例子中的情况,如果中间的片片不加uv动画,那它们就是三个材质完全相同的物体,可以毫无疑问的被合并成为一个批次。
为什么隐藏掉中间的物体会减少2个DrawCall?
现在,中间的片片加了一个uv动画,它有了属于自己的材质,我们的3个物体共有2个材质,似乎剩下两个片片没受影响,依然可以在同一批次渲染?但其实不能了,因为从渲染顺序上讲,当渲染ObjA后需要渲染ObjB,此时材质球已经被切换掉了,再渲染ObjC还要切换一次材质。
如果我把中间的片片移动一下位置,或者是隐藏掉它,让它不再成为夹在中间的物体,那么剩下两个物体又能够重新合并批次了。
为什么把Shader换成内置Diffuse,就好了呢?
因为内置的Diffuse Shader是一个不透明Shader。游戏引擎在处理不透明(或AlphaTest)物体时,总是会比半透明物体先渲染,并且因为不存在混合的问题,所以也不需要保证自后向前渲。所以它的位置虽然还夹在中间,但是渲染时机已经被移动到了第一个。
解决
首先想到的方式是简单的移动,让它不夹在中间。但这个方法有一个缺陷,如果有很多组这样的物件摆在同一个场景中,就肯定有带动画的物体夹在它们中间,批次还是会被打破。
所以我转而改变带动画物体材质的RenderQueue,原理和Diffuse Shader一样,这样所有其它物体依然能够合并批次。
无论是哪种方法,我都无法做到让带uv动画的那个物体看起来被夹在中间了,只是对于我目前的需求来讲,是可以接受的一个效果。
进一步完善
静态的修改材质球的RenderQueue可以让物体更早或者更晚渲染,而无视它的空间位置。
假设我把ObjB的RenderQueue增加了10,那么它就会看起来摆在所有其它RenderQueue未经修改的物体的最前面,无论你在正面看还是背面看。
如果你真的需要在两面看起来都是正确的,可以给ObjB添加一个组件,间隔固定的时间判断玩家摄像机和它的相对位置。根据玩家摄像机在它的前面还是后面,动态的决定把RenderQueue增加还是减少,就可以了。