3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UnityでToonShaderを自作してみる

Last updated at Posted at 2025-12-16

はじめに

どうも!けいてぃです!普段は日B白金でWebサービスを教えていますが,今回は久しぶりにUnityの技術投稿をしようかなと思いました!
この記事ではShaderに触れたことがない人でも,ToonShaderの概念,Shaderの基礎的な仕組みが理解できるように書いております!

ToonShaderとは?

ToonShaderとはオブジェクトの反射を抑え,アニメ調なやんわりとした雰囲気を与えてくれるシェーダーの一種です。
Unity製のゲームで例えると,原神などの有名アニメ調ゲームではToonShaderが使われています。

image.png
画像出典: UnityShaderの概要 [1]

例えば,この画像のようなイラストでは
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を多く使用するので,このようにひとまとめにした方が楽です。
image.png
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$)」かを数値化しています。

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

ToonShader

このBrightnessを用いて,ToonShaderを画像のように作成しました。
image.png
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を使ってどんどん塗り分けていきます。
以下に詳細の手順を解説します。飛ばしたい方は「テクスチャの反映」まで飛ばすことをおすすめします。

大分機械的な説明になってしまったので,余力があればフローチャートで解説します。

  1. 初期状態 (土台):
    まず,全体を最も暗い D (影) で塗りつぶした状態からスタートします。
  2. 第1段階 (D $\to$ C):
    明るさが Threshold1 を超えている場所を判定し,C (中間影) で上書きします。
    • 判定NGなら D のまま。
    • 判定OKなら C になる。
  3. 第2段階 (C $\to$ B):
    その結果に対して,明るさが Threshold2 を超えている場所を B (ベースカラー) で上書きします。
    • 判定NGなら C (またはD) のまま。
    • 判定OKなら B になる。
  4. 第3段階 (B $\to$ A):
    さらにその結果に対して,明るさが Threshold3 を超えている場所を A (ハイライトカラー) で上書きします。
    • 判定NGなら B (またはC, D) のまま。
    • 判定OKなら A になる。

これで,綺麗な4段階のトゥーン表現になります。

テクスチャの反映

最後に,出来上がった4段階の色に対して,テクスチャを反映させます。

  • Sample Texture 2D: テクスチャ画像を読み込むノード
  • Multiply (乗算): 光の色とテクスチャの色を掛け合わせるノード
    これにより,「明るい場所ではテクスチャがそのままの色で」,「影の場所ではテクスチャが暗い色で」表示されるようになり,自然なトゥーンになります。

実際に適用してみる

実際にMaterialとして作成したShaderは以下のようになります。
image.png
しきい値,4色のカラーはプロパティで変更できるようにしました。
ハイライトカラーではHDRを属性を持たせて,若干光るようにしています。
あくまで目安ですが,しきい値,カラーは画像のようにしてあげると自然な表現が得られると感じました。

動画の通り,Toon表現が得られることが分かります。

UnityChanに適用してみる

Unityの代表的なモデルであるUnityChanにToonShaderを適用してみます。画像左がデフォルトのマテリアル,右が今回作成したToonShaderです。

左UnityChanの妙なリアル感とは対照に,右のUnityChanではToon表現が得られたことが分かりますね。アニメっぽい。

おわりに

Toon Shaderの実装方法は数多く公開されていますが,本記事ではあえて内積,Step,Lerpという数学的な基礎知識だけで構成しました。

シェーダーの原理を理解する一助になれば幸いです。

参考資料

[1]

[ShaderGraphのドキュメント(英語)]

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?