Unityシェーダー:ToonShader

Unityで開発したシンプルなToonShader.
Toon Shading

Toon Shadingでは,フォトリアリスティックなライティング結果に減色されたカラーマップを適用してアニメ風のレンダリング結果を得ます.初めてこの技術が発表されたのは,Stylized rendering techniques for scalable real-time 3D animationでその後X-toonハイライトのアニメ表現等色々な拡張が行われています.

Toon Shadingのプロセス
今回は,Lambertシェーディングの拡散反射項$k_d (\mathbf{L} \cdot \mathbf{N})$,Phongの鏡面反射項$k_s (\mathbf{L} \cdot \mathbf{RV})^{\alpha}$をライティングモデルとして使用し,単純な閾値処理で動的にカラーマップを作成してToon Shadingを行います.

ToonShader.shader
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Pixel処理でシェーディング計算を行うToonShader
Shader "Custom/ToonShader" {
// ToonShaderのパラメータを宣言
Properties {
// 一番暗い色の指定
_Ambient ("Ambient Color", Color) = (0.1,0.1, 0.4,1)
// 拡散反射で制御される陰影領域(陰影の明るい部分)
// Border: 陰影領域の境界を制御
// BorderBlur: 陰影領域の境界のぼけ具合の制御
_Diffuse ("Diffuse Color", Color) = (0.3,0.3,1,1)
_DiffuseBorder ("Diffuse border", Range (0.01, 1)) = 0.2
_DiffuseBorderBlur ("Diffuse border blur", Range (0.01, 0.2)) = 0.01
// 鏡面反射で制御される陰影領域(ハイライト部分)
// Border: 陰影領域の境界を制御
// BorderBlur: 陰影領域の境界のぼけ具合の制御
_Specular ("Spec Color", Color) = (1,1,1,1)
_SpecularBorder ("Specular border", Range (0.01, 1)) = 0.5
_SpecularBorderBlur ("Specular border blur", Range (0.01, 0.2)) = 0.01
_Shininess ("Shininess", Range (0.01, 1)) = 0.7
}
SubShader {
pass{
// Unityのライトオブジェクトを使ったLightMode
Tags { "LightMode" = "ForwardBase" }
// Cgプログラムを使用する宣言
// 頂点処理とピクセル処理を行うことを宣言
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// Cgプログラムで使う変数
// Propertiesブロックと対応付ける
float4 _Ambient;
float4 _Diffuse;
float _DiffuseBorder;
float _DiffuseBorderBlur;
float4 _Specular;
float _SpecularBorder;
float _SpecularBorderBlur;
float _Shininess;
// 頂点からピクセルに転送されるデータ
struct vertexOutput {
float4 pos : SV_POSITION; // 座標変換後の位置
float3 L : TEXCOORD0; // ライトベクトル
float3 N : TEXCOORD1; // 法線ベクトル
float3 RV : TEXCOORD2; // 視線の正反射ベクトル
};
// 頂点毎の処理
// pos, L, N, RVのデータを計算する.
vertexOutput vert(appdata_base v) : POSITION
{
vertexOutput output;
// 座標変換後の位置
output.pos = mul (UNITY_MATRIX_MVP, v.vertex);
// 法線ベクトル
float3 N = v.normal;
output.N = N;
// ライトベクトル
output.L = ObjSpaceLightDir(v.vertex);
// 視線の正反射ベクトル
float3 V = ObjSpaceViewDir(v.vertex);
output.RV = reflect(-V, N);
return output;
}
// ピクセル毎の処理
// 1. 通常の物理的なライティング計算を行う
// 2. 照明結果を基に減色処理を行う
float4 frag(vertexOutput input) : COLOR
{
// 頂点処理で計算したベクトルデータを正規化して取り出す
float3 L = normalize( input.L );
float3 N = normalize( input.N );
float3 RV = normalize( input.RV );
// 拡散反射の度合I_dを計算
float LdN = clamp( dot(L, N), 0, 1 );
float I_d = LdN;
// 鏡面反射の度合I_sを計算
float LdRV = clamp( dot(L, RV), 0, 1 );
float shininess = pow(500.0, _Shininess);
float I_s = pow( LdRV, shininess);
// 得られたI_d, I_sを基に減色処理を行う
// 一番暗い色からスタートする
float4 c_a = _Ambient;
// 拡散反射の度合I_dを基に閾値処理を行い,
// I_d > _DiffuseBorderであれば,_Diffuseの色で塗る
// _DiffuseBorderBlurにより,境界部分のぼけ具合を制御している
float t_d = smoothstep( _DiffuseBorder - _DiffuseBorderBlur, _DiffuseBorder + _DiffuseBorderBlur, I_d);
float4 c_d = lerp(c_a, _Diffuse, t_d);
// 拡散反射と同様に,鏡面反射についても閾値処理を行う
float t_s = smoothstep(_SpecularBorder - _SpecularBorderBlur, _SpecularBorder + _SpecularBorderBlur, I_s);
float4 c = lerp(c_d, _Specular, t_s);
return c;
}
ENDCG
}
}
FallBack "Diffuse"
}

陰影の色は,暗い順に_Ambient,_Diffuse,_Specularで指定されます.
_DiffuseBorder,_SpecularBorderにより陰影色の境界を動かすことができます.
追加の機能として,_DiffuseBorderBlur,_SpecularBorderBlurでぼけ具合を調整して少し柔らかい質感を表現可能です.

参考文献

[1] Stylized rendering techniques for scalable real-time 3D animation: http://dl.acm.org/citation.cfm?id=340918
[2] X-toon: an extended toon shader: http://maverick.inria.fr/Publications/2006/BTM06a/
[3] アニメ表現のためのハイライトと陰影の直感的演出法 - OLM Digital R&D: http://www.olm.co.jp/rd/tweakable-light-and-shade-for-cartoon-animation/