はじめに
どうも!けいてぃです!普段は日B白金でWebサービスを教えていますが,今回は久しぶりにUnityの技術投稿をしようかなと思いました!
この記事ではShaderに触れたことがない人でも,ToonShaderの概念,Shaderの基礎的な仕組みが理解できるように書いております!
ToonShaderとは?
ToonShaderとはオブジェクトの反射を抑え,アニメ調なやんわりとした雰囲気を与えてくれるシェーダーの一種です。
Unity製のゲームで例えると,原神などの有名アニメ調ゲームではToonShaderが使われています。
例えば,この画像のようなイラストでは
A: ハイライトカラー
B: ベースカラー
C: 中間影
D: 影
の4色が用いられています。段階的に色を落とすことで,リアル感をあえて無くすのがToonShaderの手法です。
UnityのデフォルトShaderではこのような表現がないため,アニメのモデルにデフォルトShaderを適用すると妙なリアル感が出てしまいます。(後述の動画参照)
今回作ったShader
さて,前置きが長くなりましたが,今回作ったToonShaderの紹介に入ります。
UnityのURPではShaderGraphという,Shaderをノードベースで書くツールが用意されています。
このShaderGraphを活かせば,コードを書かずにおしゃれなShaderを自作できます。
論理ゲートに近しい要素もあるため,大学などでブール代数を学んだことがある方は馴染みやすいのではないでしょか。
動作環境・使用するツール
- Unity バージョン
- Unity 2022.3.23f1
- プロジェクト
- URP
- アセット・ツール
- ShaderGraph
- UnityChan
ShaderGraph
まず初めに,BrightnessというSubshaderを画像のように作りました。SubShaderとは,複数のノードをまとめて一つのノードとしてまとめてくれる機能のことです。プログラミングで言うところの関数のようなものです。
後述するToonShaderのノード画面では,このBrightnessを多く使用するので,このようにひとまとめにした方が楽です。

SubShader Brightnessのノード画像
このBrightnessのOutputは光源と面の向きの関係を表す-1.0 から 1.0の数値が出力されます。具体的には,以下のような意味を持つ数値になります。
- 1.0 (白)
- 面が光源の方向を完全に正対している状態(一番明るい)
- 0.0 (黒)
- 光が真横から当たっている状態(明暗の境界)
- -1.0
- 光が裏側から当たっている状態(完全な影)
以下に各ノードの意味を数学的に解説します。飛ばしたい方は飛ばしてもらって構いません。
SubShaderの仕組み解説
このSubGraphでは,ベクトルの内積(Dot Product)を用いて明るさを計算しています。数式で表すと以下のようになり,出力は -1 ~ 1 の範囲をとります。
$$\text{Brightness} = \vec{N} \cdot \vec{L'} \quad (\text{Brightness} \in [-1, 1])$$
ここで $\vec{N}$はオブジェクトの法線ベクトル,$\vec{L}$は光源方向のベクトルです。 $\vec{N}$はNormalVectorノード,$\vec{L}$はMainLightDirectionノードで取得しています。
Unityの仕様上,$\vec{L}$は「光源 $\to$ 地面」の向きで取得されます。しかし,一般的なライティング計算では「地面 $\to$ 光源」の向き $\vec{L'}$ が必要なため,Multiply ノードで $(-1, -1, -1)$ を掛けてベクトルを反転させています。
この,$\vec{N}$,$\vec{L}$の内積を計算してくれるノードが,DotProductです。Shader Graphの仕様上,$\vec{N}$,$\vec{L}$は常に正規化(長さが1)されています。そのため,DotProductノードの出力結果は,なす角 $\theta$ のコサインと等しくなります。$$\vec{N} \cdot \vec{L'} = |\vec{N}| |\vec{L}| \cos \theta = \cos \theta$$これにより,光源に対して面が「正対している ($1$)」か,「垂直 ($0$)」か,「裏側を向いている ($-1$)」かを数値化しています。
分かりやすいように図で解説しました。

青文字がDotProductの出力結果です。ここで,太陽(光源)に向かっている面ではきれいに$\vec{N}$と$\vec{L}$が反対方向に向かっており,ここで,$\vec{L}$は-1掛けられるため,結果が1になることが分かると思います。
対照的に,光源とは真反対を向いている面では,値が-1.0となっています。
ToonShader
このBrightnessを用いて,ToonShaderを画像のように作成しました。

ToonShaderのノード画像(右クリックして新規タブで開いて拡大することをお勧めします)
このグラフでは,暗い色から順番に,明るい色を上書きしていくという手法で色を決定していきます。
ごちゃごちゃして分かりづらいですが,以下に丁寧に解説していきます。
ToonShaderの仕組み解説
ここからは,実際に色が決定される流れについて解説します。
トゥーンシェーダー特有の「パキッとした色の境界」を作るために,StepノードとLerpノードを組み合わせる手法を使っています。
Stepノードによる判定
まず,Brightness(先ほど作ったSubGraphの出力)が,指定したThreshold(しきい値)を超えているかをStepノードで判定します。
Stepノードは入力値によって以下のどちらかを出力します。
- しきい値を超えている $\rightarrow$ 1 (白)
- しきい値を超えていない $\rightarrow$ 0 (黒)
Lerpノードによる線形補間
次に,このStepノードの出力をLerp(Linear Interpolation:線形補間)ノードの T に接続します。
本来,Lerpは2つの値 $A, B$ の間を滑らかに補間するための計算で,数式では以下のように表されます。
$$
\text{Result} = A(1 - T) + B \cdot T
$$
分かりやすいように例を挙げると,Aに青色,Bに赤色を与えたうえで,$T$に0.5を与えると紫色が出力されます。
しかし,今回は $T$ に Stepノードの出力(0 か 1 のどちらか) を入力しています。
本来は滑らかに変化させるためのLerpノードに対し,あえて $0$ か $1$ しか渡さないことで,「塗るか塗らないか」という二択にしています。
4色の塗り分け
今回のToonShaderでは,以下の4つの色を定義してトゥーン表現を作ります。この4色は最初に紹介した4色の通りです。
- A: ハイライトカラー (HighLightColor) $\dots$光沢の色
- B: ベースカラー (BaseColor) $\dots$ 通常の色
- C: 中間影 (MidShadowColor) $\dots$ 少し暗い影
- D: 影 (ShadowColor) $\dots$ 最も暗い影
この4色をStepとLerpを使ってどんどん塗り分けていきます。
以下に詳細の手順を解説します。飛ばしたい方は「テクスチャの反映」まで飛ばすことをおすすめします。
大分機械的な説明になってしまったので,余力があればフローチャートで解説します。
-
初期状態 (土台):
まず,全体を最も暗い D (影) で塗りつぶした状態からスタートします。 -
第1段階 (D $\to$ C):
明るさがThreshold1を超えている場所を判定し,C (中間影) で上書きします。- 判定NGなら D のまま。
- 判定OKなら C になる。
-
第2段階 (C $\to$ B):
その結果に対して,明るさがThreshold2を超えている場所を B (ベースカラー) で上書きします。- 判定NGなら C (またはD) のまま。
- 判定OKなら B になる。
-
第3段階 (B $\to$ A):
さらにその結果に対して,明るさがThreshold3を超えている場所を A (ハイライトカラー) で上書きします。- 判定NGなら B (またはC, D) のまま。
- 判定OKなら A になる。
これで,綺麗な4段階のトゥーン表現になります。
テクスチャの反映
最後に,出来上がった4段階の色に対して,テクスチャを反映させます。
- Sample Texture 2D: テクスチャ画像を読み込むノード
-
Multiply (乗算): 光の色とテクスチャの色を掛け合わせるノード
これにより,「明るい場所ではテクスチャがそのままの色で」,「影の場所ではテクスチャが暗い色で」表示されるようになり,自然なトゥーンになります。
実際に適用してみる
実際にMaterialとして作成したShaderは以下のようになります。

しきい値,4色のカラーはプロパティで変更できるようにしました。
ハイライトカラーではHDRを属性を持たせて,若干光るようにしています。
あくまで目安ですが,しきい値,カラーは画像のようにしてあげると自然な表現が得られると感じました。
動画の通り,Toon表現が得られることが分かります。
UnityChanに適用してみる
Unityの代表的なモデルであるUnityChanにToonShaderを適用してみます。画像左がデフォルトのマテリアル,右が今回作成したToonShaderです。
左UnityChanの妙なリアル感とは対照に,右のUnityChanではToon表現が得られたことが分かりますね。アニメっぽい。おわりに
Toon Shaderの実装方法は数多く公開されていますが,本記事ではあえて内積,Step,Lerpという数学的な基礎知識だけで構成しました。
シェーダーの原理を理解する一助になれば幸いです。
参考資料
[1]
[ShaderGraphのドキュメント(英語)]
