呜,其实很早就看到了这类Shader,实现方法很多,效果也有些许不一样。从这篇开始,陆续学习一下接触到的卡通类型Shader的编写。
本篇的最后效果如下(只有怪物和苹果部分):
本篇文章里指的卡通效果具有如下特点:
我们再来回顾一下Unity Surface Shader的pipeline。(来源:Unity Gems)
由上图可以看出,我们一共有4个可修改渲染结果的机会(绿色方框中的代码)。在理解这个的基础上,我们来真正学习如何实现上述效果。
在第一步中,我们只实现一个最常见的Bump Diffuse Shader,在这个基础上添加一点其他的技巧来实现简化颜色的目的。Unity的内置Shader也包含了Bump Diffuse Shader,它的作用很简单,就是用一张贴图(也叫法线贴图)记录了模型上的凹凸情况,以此来作为顶点的法线信息,渲染出来的模型也就有了凹凸不平的感觉(详情可见Unity官网)。
基本的Bump Diffuse Shader代码如下:
Shader "Example/Diffuse Bump" { Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv_BumpMap; }; sampler2D _MainTex; sampler2D _BumpMap; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); } ENDCG } Fallback "Diffuse" }
效果如下:
接下来,我们进行以下步骤:
_Tooniness ("Tooniness", Range(0.1,20)) = 4
float _Tooniness;
#pragma surface surf Lambert finalcolor:final
void final(Input IN, SurfaceOutput o, inout fixed4 color) { color = floor(color * _Tooniness)/_Tooniness; }
Shader "Custom/Toon" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _Bump ("Bump", 2D) = "bump" {} _Tooniness ("Tooniness", Range(0.1,20)) = 4 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert finalcolor:final sampler2D _MainTex; sampler2D _Bump; float _Tooniness; struct Input { float2 uv_MainTex; float2 uv_Bump; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal( tex2D(_Bump, IN.uv_Bump)); o.Albedo = c.rgb; o.Alpha = c.a; } void final(Input IN, SurfaceOutput o, inout fixed4 color) { color = floor(color * _Tooniness)/_Tooniness; } ENDCG } FallBack "Diffuse" }
除了上述使用取整的方法简化颜色,更常见的是使用一张渐变贴图(ramp texture)来模拟卡通光照达到目的。下图是我们为怪兽使用的渐变贴图(PS里面画的):
这张图的特点就是边界明显,而不像其他渐变图那样是缓慢渐变的。正如卡通风格里面经常有分界明显的明暗变化一样。
我们按如下步骤添加光照函数:
_Ramp ("Ramp Texture", 2D) = "white" {}
sampler2D _Ramp;
#pragma surface surf Toon
void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal( tex2D(_Bump, IN.uv_Bump)); o.Albedo = (floor(c.rgb * _Tooniness)/_Tooniness); o.Alpha = c.a; }
half4 LightingToon(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) { float difLight = max(0, dot (s.Normal, lightDir)); float dif_hLambert = difLight * 0.5 + 0.5; float rimLight = max(0, dot (s.Normal, viewDir)); float rim_hLambert = rimLight * 0.5 + 0.5; float3 ramp = tex2D(_Ramp, float2(rim_hLambert, dif_hLambert)).rgb; float4 c; c.rgb = s.Albedo * _LightColor0.rgb * ramp; c.a = s.Alpha; return c; }
Shader "Custom/Toon" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _Bump ("Bump", 2D) = "bump" {} _Ramp ("Ramp Texture", 2D) = "white" {} _Tooniness ("Tooniness", Range(0.1,20)) = 4 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Toon sampler2D _MainTex; sampler2D _Bump; sampler2D _Ramp; float _Tooniness; float _Outline; struct Input { float2 uv_MainTex; float2 uv_Bump; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal( tex2D(_Bump, IN.uv_Bump)); o.Albedo = (floor(c.rgb * _Tooniness)/_Tooniness); o.Alpha = c.a; } half4 LightingToon(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) { float difLight = max(0, dot (s.Normal, lightDir)); float dif_hLambert = difLight * 0.5 + 0.5; float rimLight = max(0, dot (s.Normal, viewDir)); float rim_hLambert = rimLight * 0.5 + 0.5; float3 ramp = tex2D(_Ramp, float2(rim_hLambert, dif_hLambert)).rgb; float4 c; c.rgb = s.Albedo * _LightColor0.rgb * ramp; c.a = s.Alpha; return c; } ENDCG } FallBack "Diffuse" }
最后,我们给模型添加描边效果。这是通过边缘光照(rim lighting)来实现的,在本例中我们将边缘渲染成黑色来实现描边。边缘光照找到那些和观察方向接近90°的像素,再把他们变成黑色。你大概也想到了边缘光照使用的方法了:点乘。
我们按如下步骤实现:
_Outline ("Outline", Range(0,1)) = 0.4
float _Outline;
struct Input { float2 uv_MainTex; float2 uv_Bump; float3 viewDir; };
half edge = saturate(dot (o.Normal, normalize(IN.viewDir))); edge = edge < _Outline ? edge/4 : 1; o.Albedo = (floor(c.rgb * _Tooniness)/_Tooniness) * edge;
Shader "Custom/Toon" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _Bump ("Bump", 2D) = "bump" {} _Ramp ("Ramp Texture", 2D) = "white" {} _Tooniness ("Tooniness", Range(0.1,20)) = 4 _Outline ("Outline", Range(0,1)) = 0.4 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Toon sampler2D _MainTex; sampler2D _Bump; sampler2D _Ramp; float _Tooniness; float _Outline; struct Input { float2 uv_MainTex; float2 uv_Bump; float3 viewDir; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal( tex2D(_Bump, IN.uv_Bump)); half edge = saturate(dot (o.Normal, normalize(IN.viewDir))); edge = edge < _Outline ? edge/4 : 1; o.Albedo = (floor(c.rgb * _Tooniness)/_Tooniness) * edge; o.Alpha = c.a; } half4 LightingToon(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) { float difLight = max(0, dot (s.Normal, lightDir)); float dif_hLambert = difLight * 0.5 + 0.5; float rimLight = max(0, dot (s.Normal, viewDir)); float rim_hLambert = rimLight * 0.5 + 0.5; float3 ramp = tex2D(_Ramp, float2(rim_hLambert, dif_hLambert)).rgb; float4 c; c.rgb = s.Albedo * _LightColor0.rgb * ramp; c.a = s.Alpha; return c; } ENDCG } FallBack "Diffuse" }
本篇一开始是参考了Unity Gems的一篇文章,但在学习过程中发现了一面一些错误和改善的地方,例如里面对光照函数的解释,以及渐变贴图的实现。以后的学习还是要多思考,去其糟粕取其精华啊。
在后面的卡通Shader系列,我会首先学习Unity Gems里面用Fragment Shader实现的方法,最后,再学习一下Unity一个资源包里面的卡通效果实现方法。
欢迎交流和指教!
【Shader实战】卡通风格的Shader(一),布布扣,bubuko.com
原文:http://blog.csdn.net/candycat1992/article/details/37882425