在阅读《Unity Shader入门精要》时,跟着此书实现了案例shader,今天把第十四章介绍的卡通风格渲染复现了一遍。这是一个简单但被广泛运用的效果。记录一下。感谢乐乐女神~
思路
渲染效果如下图所示:

书中给出的思路是:1.双通道描边;2.对高光反射进行处理,使得出现颜色统一的色块,而不是真实感渲染中的均匀高光反射区域,
描边
在Pass1中将所有顶点沿着外法线方向向外扩大一点,只渲染内表面。值得注意的是,书中源码在摄像机坐标系中扩大顶点。Pass1中将扩大后的内表面渲染成黑色。
在Pass2中正常渲染对象,结合先渲染的Pass1中的扩大版黑色内表面,以形成黑色描边。
Unity以三角形的顶点顺序区分正面还是反面(外面or内面),顺时针为外表面,逆时针为内表面。Cull Off就是正反面都看见,Cull Front就是只看见内表面,Cull Back只看见外表面(这个比较常见)
在Pass1中实现描边。实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| Pass { NAME "OUTLINE" Cull Front
CGPROGRAM #pragma vertex vert #pragma fragment frag
#include"UnityCG.cginc" struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; };
struct v2f { float4 pos : SV_POSITION; }; float _Outline; fixed4 _OutlineColor;
v2f vert (a2v v) { v2f o;
float4 pos = mul(UNITY_MATRIX_MV, v.vertex); float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); normal.z = -0.5; normal = normalize(normal);
pos = pos + float4(normal,0)* _Outline; o.pos = mul(UNITY_MATRIX_P, pos);
return o; }
fixed4 frag (v2f i) : SV_Target { fixed4 col = fixed4(_OutlineColor.rgb,1.0); return col; } ENDCG }
|
注意内置矩阵UNITY_MATRIX_MV,UNITY_MATRIX_P矩阵的使用。且法线的在坐标系之间的转换与点、向量的转换不一样,需要对矩阵进行逆、转置等变换。UNITY_MATRIX_IT_MV指的是modelview矩阵的逆转矩阵。且对于法线的坐标系间的变换,应使用33的矩阵,4*4的矩阵常用于对点进行变换。在摄像机坐标系中,固定法线的z值,可以使得外轮廓点朝着扁平的方向变化避免出现穿透。
均匀色块
漫反射项与高光项
在Pass2中实现卡通风格的色块,代替真实感渲染的漫反射与高光项。Pass2的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| Pass{ ... struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 tangent : TANGENT; }; struct v2f { float4 pos : POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; SHADOW_COORDS(3) };
v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos( v.vertex); o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; TRANSFER_SHADOW(o); return o; }
float4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); fixed4 c = tex2D (_MainTex, i.uv); fixed3 albedo = c.rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); fixed diff = dot(worldNormal, worldLightDir); diff = (diff * 0.5 + 0.5) * atten; fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb; fixed spec = dot(worldNormal, worldHalfDir); fixed w = fwidth(spec) * 2.0; fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
return fixed4(ambient + diffuse + specular, 1.0); }
... }
|
记录两个函数,step,smoothstep:

Unity中生成阴影
经典的生成阴影的方法是shadow map方法,Unity已经维护了屏幕空间的阴影映射纹理,作者学会利用Unity的技术的话,就可以非常迅速的得到阴影了……开启光源的shadow type,开启模型的Lighting选项中的阴影选项(receive shadow+cast shadow),注意渲染模型的shader里要能访问到ShadowCaster Pass(更新光源的阴影映射纹理从而更新屏幕的阴影映射纹理)。
在接收shadow的渲染对象的shader中,要想阴影显现,那要在fragment中,申明Unity和阴影相关的宏(如小节“漫反射项与高光项”中的代码)。
参考文献
- Unity Shader 入门精要
- Built-in shader variables - Unity官方文档
- GAMES101课件