通用特效Shader

一、通用特效Shader介绍

1.1 什么是通用特效材质

Unity支持SRP Batcher后,使用UberShader的优势非常明显。所谓,UberShader,即一个超级Shader,覆盖一类功能,而不是多个分散的小Shader,比如一个通用特效Shader,整个项目内的所有特效都使用该Shader来制作所有的粒子特效或者模型特效,其它需求也类似,比如物体、地形等。

1.2 通用特效材质的意义

1.2.1 方便使用和沟通

整个项目只有一个特效Shader的情况下,美术很容易熟悉该Shader有哪些功能,能够尽可能复用该通用Shader的多个功能制作复杂特效;同时,避免美术与技术美术或者技术重复沟通类似的功能。

1.2.2 方便维护

对于技术美术或者开发同学来说,维护一个Shader的成本比维护多个的成本低很多。对于重复功能不需要再重复开发,优化性能时候也不用满世界扫描美术到底用的是什么Shader,只需要优化这一个Shader性能即可。

1.2.3 批次更低

由于支持SRP Batcher,Unity使用的是最终的变体级别合批。与多个分散小Shader相比,一个Shader质制作的特效更可能被SRP Batcher,因为使用的同一个变体的概率很高,而不同的Shader天然就是不同的变体,不可能被SRP Batcher。因此,只要复用率越高,被合并的批次越多。

1.2 通用特效材质主要模块

主要包括基础功能、Mask、Distortion(扭曲)、Dissolve(溶解)、Emission(自发光)、Reflection(反射)、其它等。每个大的模块都有变体开关,部分消耗大的小功能也需要有变体开关。这里只是列出本人项目中用到过的一些功能,基本上覆盖了常见的特效功能,但是不同项目会有不同的需求,因此除了常见的扭曲、溶解等还会有不少定制功能。这篇文章的目的,不在于介绍这些功能的实现细节,而是说明一个超级特效Shader在项目中使用的意义。

二、通用特效具体模块

2.1 Surface Options(基础设置)


Surface Type:不透明或者半透明
Render Face:Front(剔除背面)、Back(剔除前面)、Both(不剔除)
Custom ZWrite:是否覆盖深度写入
ZTest:深度测试
ColorMask:输出颜色通道
Alpha Clipping:Alpha裁剪
Enable Billboard:是否作为Billboard渲染(朝向相机)

2.2 Surface Inputs (基础输入)


Base Map:基础颜色贴图
Base Color:基础颜色
Enable BackFace Base Color:开启该选项,可以给背面单独制定基础颜色
Apply Vertex Color:开启该选项,顶点颜色应用到基础色上
BaseMap UvType:该设置有UV、ScreenUV、ReflectionUV三种类型。UV模式是使用模型UV0,ScreenUV是使用屏幕空间位置作为UV,ReflectionUV使用反射向量的两个分量比如xz作为UV。ScreenUV和ReflectionUV可以实现一些特殊效果。
BaseMap Rotation:UV模式下,对模型UV0旋转。
BaseMap U Speed:UV的U移动速度。
BaseMap V Speed:UV的V移动速度。
BaseMap Custom Speed (CustomData1.xy, UV0.zw):该功能是使用Unity粒子的CustomData1.xy来作为UV。如下所示的CustomData1.xy是曲线:

Normal Map:法线贴图,只在需要使用法线方向时候有意义,比如反射、Matcap等
Normal Scale:法线强度缩放
Normal U Speed:法线的U移动速度。
Normal V Speed:法线的V移动速度。

2.3 Mask Options


Mask贴图的作用是修改透明度,在半透明模式下可以只显示通过Mask的区域。

Mask Map (R):Mask贴图,默认使用R通道。
Mask Channel Mask (Default R):Mask贴图的通道掩码。
Mask Rotation:Mask贴图的UV旋转。
Mask U Speed:Mask贴图的U移动速度。
Mask V Speed:Mask贴图的V移动毒素。
Mask Custom Speed (CustomData1.zw, UV1.xy):使用Unity粒子的CustomData1.zw来作为Mask的UV。
Mask Intensity: Mask的强度缩放。
Mask Min:Mask的强度最小值。
Mask Max:Mask的强度最大值。最终的Mask强度会在该范围内SmoothStep。
下面是使用Mask的效果:

2.4 Distortion Options


Distortion的功能就是扭曲其它模块的采样UV,比如基础色、自发光、溶解等。
Distortion Map (RG) Mask(A):扭曲贴图,RG通道是扭曲强度,A通道是扭曲Mask。
Distortion Rotation:扭曲的UV旋转。
Distortion U Speed:扭曲的U移动速度。
Distortion V Speed:扭曲的V移动速度。
Distortion Intensity:扭曲的强度缩放。
下面是使用扭曲的效果:

2.5 Dissolve Options


Dissolve Map (R):溶解贴图,默认使用R通道。
Dissolve Channel Mask (Default R):溶解贴图的通道掩码。
Dissolve Rotation:溶解贴图的UV旋转。
Dissolve U Speed:溶解贴图的U移动速度。
Dissolve V Speed:溶解贴图的V移动速度。
Dissolve Intensity:溶解强度,注意不是溶解贴图的输入强度缩放。
Dissolve Width:溶解宽度。
Dissolve Edge Color:溶解边缘颜色。
Dissolve Edge Intensity:溶解边缘颜色强度缩放。
Dissolve Hard Edge:是否硬边溶解。
Dissolve Custom Intensity (CustomData2.x, UV1.z):使用粒子系统的CustomData2.x作为溶解强度。
Dissolve Custom Width (CustomData2.y, UV1.w):使用粒子系统的CustomData2.y作为溶解宽度。
溶解的功能比较复杂,最基本的思想是用一张贴图作为溶解强度输入,然后通过在溶解强度和宽度之间计算出溶解阈值,用这个阈值去修改透明度;溶解的边缘颜色则是将溶解阈值应用到单独是边缘颜色上再叠加到输出颜色上;至于硬边溶解是直接将溶解阈值取sign。
有兴趣还原的可以参考下面代码:

1
2
3
4
5
6
7
half disslove = dot(SAMPLE_TEXTURE2D(_DissolveMap, sampler_DissolveMap, input.uv3.xy + distortion), _DissolveChannelMask);
half dissloveIntensity = input.uv3.z;
half dissolveWidth = input.uv3.w;
half dissolveWithParticle = (dissloveIntensity * (1 + dissolveWidth) - dissolveWidth);
half dissolveAlpha = saturate(smoothstep(dissolveWithParticle, (dissolveWithParticle + dissolveWidth), disslove));
color.a *= _DissolveHardEdge ? sign(dissolveAlpha) : dissolveAlpha;
color.rgb += _DissolveEdgeColor.rgb * _DissolveEdgeColor.a * _DissolveEdgeIntensity * (_DissolveHardEdge ? sign(1 - dissolveAlpha) : (1 - dissolveAlpha));

下面是使用溶解的效果:

2.6 Emission Options


Emission Map:自发光贴图。
EmissionColor:自发光颜色。
Use WorldPos As UV:特殊需求,使用世界空间位置作为UV采样自发光贴图。
Emission U Speed:自发光的U移动速度。
Emission V Speed:自发光的V移动速度。
Emission Intensity:自发光的强度缩放。
自发光的原理比较简单,采样一张额外的自发光贴图叠加颜色,不再赘述。

2.7 RimLight Options


RimLight Color:边缘光颜色。
RimLight Width:边缘光宽度。
RimLight Smoothness:边缘光光滑度。
RimLight Intensity:边缘光强度缩放。
RimLight Min:边缘光最小强度。
RimLight Max:边缘光最大强度。
RimLight Reverse:是否反转强度。
边缘光是实际上是计算法线与视线的夹角来判断边缘,当夹角越大边缘光越强,最终将边缘光叠加回输出颜色上。具体代码如下:

1
2
3
4
5
half cosTheta = dot(normalWS, viewDirWS);
half rimLightStrength = pow(saturate(1 - 1 / _RimLightWidth * cosTheta), _RimLightSmoothness);
rimLightStrength = (_RimLightReverse ? 1 - rimLightStrength : rimLightStrength) * _RimLightIntensity;
rimLightStrength = smoothstep(_RimLightMinValue, _RimLightMaxValue, rimLightStrength);
color.rgb += _RimLightColor * rimLightStrength;

2.8 Light Options


Real Time Light Strength:实时光强度
Real Time Shadow Strength:实时光阴影强度
Real Time Shadow Color:实时光阴影颜色
Real Time Shadow Color Strength:实时光阴影颜色强度
强调一下:这里不是让特效Shader走完整的光照计算,而只是用光源的信息去修改最终的输出颜色。这样性能高效,看起来也在接受光源阴影。

2.9 其它功能

2.9.1 Depth Bias

深度偏移,特效有时候需要偏移深度来强制放在某些物体之前,这个功能比较有效果。

2.9.2 Reflection Options

使用反射方向采样Cubemap,将结果叠加到输出颜色上即可。

2.9.2 Matcap

参考上一篇文章的Matcap,对于特效的matcap可以简单实现即可。