前言:最近有必要系统的学习下shader了,虽然平时也用着,原理什么的都懂,而且觉得应用层的shader特别简单,真的难的是隐藏在shader后面的算法,这里准备了一个系列,这个系列是对unity 标准库shader的学习,从而对shader有更深的认识,同时也希望能认识更多搞图形学的朋友一起交流。
游戏shader中最常见的Toon shader(卡通shader)
. Toon 效果图
本文shader源码所在目录:Standard Assets/Effects/ToonShading
ToonBasic 的原理与写法
Basic的原理是通过一个Cubemap类型的贴图和当前的shader进行叠加相乘得到的(两个像素相乘是改变对应像素的亮度或者色值,具体可以查阅下资料像素相乘和相加的意义)。这个时候你就会看到模型的脸,腿的部分很亮,其他部分就暗色些。
具体的写法如下:
Shader "Toon/Basic" {
Properties {
_Color ("Main Color", Color) = (.5,.5,.5,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { }
}
SubShader {
Tags { "RenderType"="Opaque" } //渲染不透明物体
Pass {
Name "BASE" //pass的名字,这个后续的shader会用到
Cull Off //双面渲染
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog //①编译多种雾效的类型
#include "UnityCG.cginc"
sampler2D _MainTex;
samplerCUBE _ToonShade;
float4 _MainTex_ST;
float4 _Color;
struct appdata {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float2 texcoord : TEXCOORD0;
float3 cubenormal : TEXCOORD1;
UNITY_FOG_COORDS(2) //②获取fog的坐标
};
v2f vert (appdata v)
{
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); //③获取2d纹理坐标
o.cubenormal = mul (UNITY_MATRIX_MV, float4(v.normal,0));
UNITY_TRANSFER_FOG(o,o.pos); //④输出雾效的数据
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = _Color * tex2D(_MainTex, i.texcoord);
fixed4 cube = texCUBE(_ToonShade, i.cubenormal);
fixed4 c = fixed4(2.0f * cube.rgb * col.rgb, col.a);
UNITY_APPLY_FOG(i.fogCoord, c); //⑤i.fogcoord是从顶点数据取出来的一个2维的纹理坐标
return c;
}
ENDCG
}
}
Fallback "VertexLit"
}
补充说明下:这里有几条unitycg.cginc里面定义的指令
①:是编译多种类型的雾的变体,你可以在fog设置的地方看到fog mode的设置情况,这句话就表明了在不同mode 的时候对对本shader的编译
②:fog的顶点数据,为后面作色做准备
③:获取2d纹理的坐标数据
④:获取无效数据
⑤:对雾的颜色值和当前的像素进行插值,如果渲染的模式是renderpath的那么末日的作色是黑色
ToonBasicOutLine的原理与写法
outline的原理:沿着视角垂直的地方向外拉升像素,用于作色使用(这个应该是目前游戏中使用最多的算法了)
具体的写法如下:
Shader "Toon/Basic Outline" {
Properties {
_Color ("Main Color", Color) = (.5,.5,.5,1)
_OutlineColor ("Outline Color", Color) = (0,0,0,1)
_Outline ("Outline width", Range (.002, 0.03)) = .005
_MainTex ("Base (RGB)", 2D) = "white" { }
_ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { }
}
CGINCLUDE
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
UNITY_FOG_COORDS(0)
fixed4 color : COLOR;
};
uniform float _Outline;
uniform float4 _OutlineColor;
v2f vert(appdata v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
float3 norm = normalize(mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal));
float2 offset = TransformViewToProjection(norm.xy);
#ifdef UNITY_Z_0_FAR_FROM_CLIPSPACE //to handle recent standard asset package on older version of unity (before 5.5)
o.pos.xy += offset * UNITY_Z_0_FAR_FROM_CLIPSPACE(o.pos.z) * _Outline;
#else
o.pos.xy += offset * o.pos.z * _Outline; //核心地方:在处理顶点的时候沿着视线垂直的地方进行向外拉升
#endif
o.color = _OutlineColor;
UNITY_TRANSFER_FOG(o,o.pos);
return o;
}
ENDCG
SubShader {
Tags { "RenderType"="Opaque" }
UsePass "Toon/Basic/BASE" //引用之前的base pass也就是先作色人物
Pass {
Name "OUTLINE" //命名为outline
Tags { "LightMode" = "Always" }
Cull front //剔除正面
ZWrite On //打开写缓存
ColorMask rgb //对rgb颜色值进行蒙板
Blend SrcAlpha OneMinusSrcAlpha //混合
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
fixed4 frag(v2f i) : SV_Target
{
UNITY_APPLY_FOG(i.fogCoord, i.color);
return i.color;
}
ENDCG
}
}
Fallback "Toon/Basic"
}
发现这篇文章很长了,而后面的两个带光的shader其实和这两个shader没啥区别,唯一不同的是使用了自己写的光照模型。所以这里不再做介绍,有兴趣的同学可以自己研究下