为Unity3D构建程序化的Sprite材质系统 - Part
独立游戏《FISH 鱼》开发日志系列 - 为Unity3D构建程序化的Sprite材质系统 - Part 1
在过去的一周里,5.23-5.31,我所做的主要工作是,为FISH里的Sprite定制特殊的程序化的效果。FISH场景当中,石头是很重要的一部分,如何利用有限的资源,尽量少的存储空间,去实现最丰富的效果,是我们需要达到的目标。
(上图为13年版本的FISH第一个场景)FISH场景里的石头基本上采取的设计思路是:形式尽量相同,或者自相似,类似分形的逻辑;色彩上可以有多重变化,这样即便形状相似,也不会觉得太单调。在13年的FISH的版本中,所有的石头,都是直接在PS里绘制完成,石头的绘制,用了很多PS里的图层组混合模式(Blending Option)来完成,本身就是一种程序化的绘制方式。参见火星网的专访
(上图为PS里石头的涂层叠加属性绘制方法)
因此我们在想,既然石头本身就是程序化的方式绘制出来的,为何不把这套流程搬进Unity里,这样就能够减少2d材质的用量,做到精简游戏呢,相当于直接在Unity里画出来这些石头。当然,随便一想就知道,要把PS里的丰富的涂层混合选项(Blending Option)搬进Unity很定很费事儿,并且,偏离了我们做游戏的初衷,因此
先分析一下目前石头是怎么画的,然后看哪些特征是可以画在PS里的,哪些特征是需要大量改变的,区分出这些,比较有利于我们在unity里用最少的力气实现相应的功能。因此简化一下PS里的工作流,大致可以得到下面这个表。
所以基本上,我们能够方便的调整的就是,1. 石头的基本色,2. 石头的叠加的纹理材质,3.石头的混合颜色(Tint Color),4.以及Tint Color是如何和原本的石头进行过渡的。
因此在明确了这些需求之后,我们就就设计了Sprite的工具。基本的Inspector是长这个样子的。
FISH Sprite Inspector基本上就是2部分组成:Pattern Blend (纹理材质叠加)和Tint Blend (混合颜色叠加)。
GIF图show下:
1. Pattern Blend Mode(纹理材质混合模式)
这个功能可以让我们方便的切换纹理材质和基本的sprite 是如何混合的,和PS里图层混合模式是一个原理。
FISH Sprite - Pattern Blend Mode2. Pattern Blend Opacity (纹理材质叠加透明度)
这个功能可以方便的控制纹理材质的透明度,石头的纹理一般都是非常细腻的,往往透明度无法设的太高,需要调整到合适的值才能获得整体上比较好的效果。
FISH Sprite - Pattern Blend Opacity3. Pattern Blend TRS (纹理材质的平移旋转和缩放)
TRS Slider Scene handle4.Tint Color Blend Mode (混合颜色的混合模式)
Tint Color Blend Mode5. Tint Color Blend Texture TRS (混合颜色的混合材质,以及其平移旋转缩放)
Tint Color Blend Texture TRS基本上就是这些功能,没有用到太高级的东西。大概讲一下实现:
1. Shader部分主要是使用了一些多重编译关键字,来支持Sprite对应的Shader功能,Pattern Blend 开关,Tint Blend开关,以及二者对应的混合模式都是由不同的shader_feature 关键字定义的,可以方便的在对应的mono脚本中使用Material.EnableKeyword(string k)来实现相应功能的开关。另外需要注意的是,关键字是有数量限制的,每一组关键字组合都会被编译成一个独立的shader,如果用了太多的关键字,就会导致shader加载时间过长。这这个工具里,我使用了一个Shader文件,但是因为里面用了4组关键字,一共有2*4*2*4=64种组合,因此实际上这个shader最后需要被编译成64个不同的版本, 也因此,不同的功能组合实际上使用的是不同的material,因此无法batching在一起。而因为此,使用同一材质的Sprite,他们无法设置独立的 [是否使用Pattern] [Pattern Blend Mode] [是否使用Tint] [Tint Blend Mode] 这些选项。这点需要注意。
2. shader中的其他的参数的修改,例如TRS,透明度等是通过MaterialPropertyBlock来实现的。Sprite往往需要共享材质,在确定了上面所说的关键字组合后,往往同一类石头会使用同一个材质,而每个石头具体的材质参数是独立的。MaterialPropertyBlock可以通过SpriteRender的Renderer.GetPropertyBlock来获取。通过设置每个Renderer的MaterialPropertyBlock,能够方便的为使用同一个材质的sprite指定不同属性,同时获得较高的性能。
3. 由于MaterialPropertyBlock本身[不支持] 序列化,因此Mono脚本中需要把材质参数进行序列化,然后在脚本的Start()中,进行设置。
目前的问题是,由于使用了大量复杂的像素shader操作,因此可以想象在平板上的性能不会太好。由于这个是一个面向设计师的工具,sprite的特效在runtime基本不需要改变,因此未来可以考虑,将生成的特效渲染一次到材质上,然后再使用。动态的加载和释放这些rendertexture应该会更高效。当然这个功能略有些复杂,目前还是先暂时不考虑。
基本上暂时想到这么多,之后的DevLog 里,我会讲讲如何让这个系统变得更加易用,如何克服上面提到的无法独立设置shader feature 的问题,另外还会展示,如何用这个工具,结合关卡设计的考虑来进行创作。
如果您觉得这篇文章内容不错,请关注我们的微博