1.实现逻辑
描边效果Shader有多种实现方式,可以通过后处理和MatCap实现。
这次主要想展示的是通过两个Pass实现。
当Shader中有多个Pass时,渲染流程会安装顺序依次执行,于是后面的Pass效果会覆盖前面Pass所绘制的图像。
实现的逻辑是:
1.1
第一层,将模型的顶点沿着法线方向膨胀一段距离,膨胀的距离就是描边的宽度,膨胀的距离可以作为属性开放到材质面板上,方便调用。然后再为膨胀之后的模型指定一个纯色进行着色,着色不需要参与任何光照交互,膨胀之后的纯色就是描边的颜色。将颜色也作为属性开放到材质面板,方便后期更换颜色。
1.2
第二层,模型以正常效果进行显示。也就是Surface Shader,并使用Standard Specular光照模型。
1.3
在正常情况下,第一层膨胀后的模型会将第二层的模型完全笼罩,第二层的模型是无法通过深度测试的,最终只会显示第一层膨胀后的效果,解决这个就需要让第二次的模型通过深度测试。
1.4
办法就是关闭第一层的深度写入,这样就没有第一层模型的深度值,第二层模型自然就可以通过深度测试了。
1.5
这样会出现一种情况,当物体关闭深度写入之后,在膨胀着一层后面的物体就不知道自己是被遮挡的,本该被遮挡,因为没有深度写入,现在反而通过了速度测试,因此就会覆盖掉膨胀的这一层模型。
- 避免这一状况出现
需要使该物体在所有不透明物体绘制完成之后再进行绘制,因此更改Shader的渲染队列为Transparent,如此一来绘制的图像就不会被后面的物体覆盖了。
2.编写Shader
理解了实现逻辑,那么接下来按照逻辑思路开始编写Shader。
2.1.开放属性
正常效果相关属性:
[Header(Texture Group)] [Space(10)] _Albedo ("Albedo" , 2D) = "white" {} [NoScaleOffset]_Specular ("Specular (RGB - A)" , 2D) = "black" {} [NoScaleOffset]_Normal("Normal" , 2D) = "bump" {} [NoScaleOffset]_AO ("Ambient Occlusion" , 2D) = "white" {}
讲解:
Properties代码块开放了常规Specular工作流所需要的贴图有Albedo、Specular、Normal 和 AO,至于Smoothness属性,本案例参照Unity内置的Standard Specular Shader,将其合并到Specular贴图的alpha通道中。
通过Albedo纹理贴图的Tiling和Offset控制所有的纹理贴图,也就是使用Albedo的纹理坐标对所有纹理贴图进行采样,因此除了Albedo,其他所有纹理贴图的Tiling和Offset都会失效,如是在这些属性前添加[NoScaleOffset]指令以隐藏Tiling和Offset。
为了对属性进行类别区分,在这些属性之前添加[Header(Texture Group)]指令,从而在材质面板上显示“Texture Group”文字以起到分组提醒的作用。
描边相关属性:
[Header(Outline Properties)] [Space(10)] _OutlineColor (“OutlineColor” , Color) = (1,0,1,1) _OutlineWidth (“OutlineWidth” , Range(0,0.1)) = 0.01
讲解:
除了开放正常效果所需的贴图属性,还开放了两个用于控制描边效果的属性,分别为:描边颜色 _Outlinecolor 描边宽度_OutlineWidth。
为了对属性进行类别区分,同样在这些属性之前添加[Header(Outline Properties)]指令,从而在材质面板上显示“Outline Properties”文字以起到分组提醒的作用。
2.2.SubShader标签
代码:
Tags { "RenderType"="Opaque" "Queue"="Transparent" }
讲解:
属于不透明效果,因此渲染类型设置为“Opaque”。为了使物体在所有不透明物体之后再进行绘制,还需要将渲染队列设置为“Transparent”。
2.3.描边Pass
代码:
Pass { ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 vertex : SV_POSITION; }; fixed4 _OutlineColor; fixed _OutlineWidth; v2f vert (appdata_base v) { v2f o; v.vertex.xyz += v.normal * _OutlineWidth; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag(v2f i) : SV_Target { return _OutlineColor; } ENDCG }
讲解:
在描边效果的Pass中,为了不遮挡后面的Pass,需要将深度写入关闭。
在顶点着色器中,需要将顶点的法线向量乘以_OutlineWidth属性,用于控制法线向量的长度,乘积与顶点相加,使顶点沿着法线偏移,从而产生膨胀效果。
在片段着色器中不需要做任何计算,直接返回 _OutlineColor属性即可。
2.4.描边Pass
代码:
CGPROGRAM #pragma surface surf StandardSpecular fullforwardshadows struct Input { float2 uv_Albedo; }; sampler2D _Albedo; sampler2D _Specular; sampler2D _Normal; sampler2D _AO; void surf (Input IN , inout SurfaceOutputStandardSpecular o) { fixed4 c = tex2D (_Albedo , IN.uv_Albedo); o.Albedo = c.rgb; fixed4 specular = tex2D(_Specular , IN.uv_Albedo); o.Specular = specular.rgb; o.Smoothness = specular.a; o.Normal = UnpackNormal(tex2D (_Normal, IN.uv_Albedo)); o.Occlusion = tex2D(_AO, IN.uv_Albedo); }
讲解:
使用Surface Shader编写模型的正常效果。在编译指令中,定义光照模型为StandarSpecular,然后添加fullforwardshadows指令,使物体支持所有灯光类型的投影。
在表面着色器中使用Albedo的纹理坐标uv_Albedo 对所有的贴图进行采样,然后分别输出到对应的表面属性上。其中Smoothness被合并在Specular纹理的Alpha通道中,因此将specular.a输出到Smoothness。
下面是完整代码:
Shader "Sample/Outline" { Properties { [Header(Texture Group)] [Space(10)] _Albedo ("Albedo", 2D) = "white" {} [NoScaleOffset]_Specular ("Specular (RGB - A)" , 2D) = "black" {} [NoScaleOffset]_Normal("Normal" , 2D) = "bump" {} [NoScaleOffset]_AO ("Ambient Occlusion" , 2D) = "white" {} [Header(Outline Properties)] [Space(10)] _OutlineColor ("OutlineColor" , Color) = (1,0,1,1) _OutlineWidth ("OutlineWidth" , Range(0,1)) = 0.01 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Transparent" } LOD 100 Pass { ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 vertex : SV_POSITION; }; fixed4 _OutlineColor; fixed _OutlineWidth; v2f vert (appdata_base v) { v2f o; v.vertex.xyz += v.normal * _OutlineWidth; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag(v2f i) : SV_Target { return _OutlineColor; } ENDCG } //----------Regular layer---------- CGPROGRAM #pragma surface surf StandardSpecular fullforwardshadows struct Input { float2 uv_Albedo; }; sampler2D _Albedo; sampler2D _Specular; sampler2D _Normal; sampler2D _AO; void surf (Input IN , inout SurfaceOutputStandardSpecular o) { fixed4 c = tex2D (_Albedo , IN.uv_Albedo); o.Albedo = c.rgb; fixed4 specular = tex2D(_Specular , IN.uv_Albedo); o.Specular = specular.rgb; o.Smoothness = specular.a; o.Normal = UnpackNormal(tex2D (_Normal, IN.uv_Albedo)); o.Occlusion = tex2D(_AO, IN.uv_Albedo); } ENDCG } }