Let's Enjoy Unreal Engine

Unreal Engineを使って遊んでみましょう

UE4 誰でもわかるセルシェーダー入門

この記事はUnreal Engine 4(UE4) Advent Calendar 2019の1日目の投稿記事です。

qiita.com

さて、今年のAdvent Calendar 1発目のネタはみんな大好きなセルシェーダーについてです。と、言っても今更なネタでもありますので、普段仕事でも散々NPR系のシェーダーを作っており、本当は高度なネタも沢山知っていますが、今回はシェーダー初心者に向けたセルシェーダーの原理についての解説です。タイトルにもある通り誰でもわかる程度の内容を想定しています。

もう散々解説されているネタではありますが、意外なくらい原理を解説しているくらい記事は見かけないので、今回はあくまでもその原理を解説するという趣旨です。

セルシェーダー実装の種類

セルシェーダーの実装には種類があり、大きく分けて以下の2タイプです。

1. ポストプロセスマテリアルによるG-Bufferを使った加工処理
2. サーフェスマテリアルによる擬似的な陰影計算処理

1のポストプロセスマテリアルによるセルシェーダー実装は私自身が以前に開発した、PPCelShaderというものがあり、こちらは既にGitHub上で誰でも使える形で公開しています。

github.com

ポストプロセスによるセルシェーダーはとにかく使い方が楽で、ほとんど調整の必要がありません、しかしモデルに対して細かい調整ができないので、細かい制御が必要な場合には向いていません。

それに対して、サーフェスマテリアル、つまり通常のマテリアルに対してのセルシェーダーは柔軟にカスタマイズが可能なので個別に調整を繰り返したい場合には必然とサーフェスマテリアルを利用して作ることになります。今回はこちらを使った解説ということになります。

セルシェーダーと内積

セルシェーダーと線形代数学に出てくる内積は切っても切れない関係です。内積ってわかりますか?おそらくそんなに難しい概念ではないのですが、内積の解説を探しにいくと、どこを見ても難しそうな数式の話がでてくるので、わかりやすく画像で解説します。

f:id:alwei:20191130225609p:plain

画像内にある、光源方向と、物体のもつ法線を計算し、そのなす角度が内積でθ(シータ)として求められます。θが1~0の場合、光に当たっていることとなり、-1~0の場合には光が当たっていないこととなるので、この境界を利用することで陰影をハッキリとさせてメリハリをつけることでセルシェーダーが可能となります。

基本的なセルシェーダーはこれが全てと言っても過言ではありませんが、世の中の解説はこれを難しく解説しすぎているので、数学が苦手な人にとっては理解する気も起きないわけです。ではこれをUE4で再現すると、どうなるのでしょうか?

事前準備

実際にセルシェーダーを作成する前に、モデルやライト用のBP、パラメーター格納用のマテリアルパラメーターコレクションを用意します。

今回モデルはバンダイナムコスタジオさんが配布している公式モデルのミライ小町を利用させていただきました。モデルのインポート方法は全く別の話となるので、ここでは解説していません。

www.bandainamcostudios.com

次にライトベクトルを得るためのブループリントを作成します。ライトベクトルに関しては後ほど解説します。新規BP作成を行い、親クラスに"DirectionalLight"を選択します。名前は"BP_DirectionalLight"にでもしておきましょう。

f:id:alwei:20191130230352p:plain

そして次にライトベクトルを格納する新規のマテリアルパラメーターコレクションを作成します。名前は"MPC_CelShader"にしておきます。"Vector Parameters"に"LightVector"という名前のパラメーターを新規で追加します。

f:id:alwei:20191130230705p:plain

最後に、"BP_DirectionalLight"にノードを追加します。"Construction Script"と"Event Graph"の"Event Tick"に以下のようにノードを作成します。これにより、ディレクショナルライトから直接光源方向を得ることができるようになります。

f:id:alwei:20191130230938p:plain

最後に既に配置してあるディレクショナルライトがあれば、それを"BP_DirectionalLight"と差し替えを行っておきましょう。レベルに配置済みのディレクショナルライトがあればそれを右クリックすることで、コンテンツブラウザ上で選択している"BP_DirectionalLight"をそのまま置換することも可能です。

f:id:alwei:20191130231319p:plain

これで事前準備が全て整いました!

セルシェーダー用マテリアル作成

ここからやっとセルシェーダー用のマテリアル作成に入ります。まずは通常のマテリアルを作成し、そのマテリアルをメッシュ上に全て割り当てておきましょう。このマテリアルを"M_CelShader"という名前にしておきます。

f:id:alwei:20191130231904p:plain

当然何も作っていないので真っ黒な状態でスタートになります。そして以下のようなノードを作成します。

f:id:alwei:20191130232204p:plain

まずは左のノードから解説していきます。マテリアルパラメーターコレクションからライトベクトルを抽出します。"Collection Parameter"というノードで、詳細の中から"MPC_CelShader"とParameter Nameで"LightVector"を選択します。このライトベクトルが正しく光源方向となり、光の向きとなります。これを"Component Mask"ノードで"RGB"だけ抽出します。本来はなくてもいいのですが、"Collection Parameter"ノードは4 Vectorを返すノードなので、ここでMaskして 3 Vectorに変換しておく必要があります。

そして遂に出てくるのが内積です。内積は"Dot"という名前のノードとなっており、これを"VertexNormalWS"ノードでメッシュのワールド空間上の頂点法線を取得し、ライトベクトルと法線方向を内積として計算させます。これの結果が以下です。

f:id:alwei:20191130232835p:plain

何か怖い結果ですね。ちなみにややこしいですが、白い部分が陰影となる部分となっています。なぜ白い部分が陰影となるのかは、また後程解説します。このまま次に以下のようにノードを組みます。

f:id:alwei:20191130233200p:plain

"Multiply"ノードで100をかけています。それを"Clamp"ノードで0~1に収まるようにしています。これにより、陰影となる部分の境界をパキっとさせることができます。もしグラデーションをかけたい場合にはMultiplyの値を小さくすることで、多少陰影のグラデーションを残すこともできます。

f:id:alwei:20191130233400p:plain

次にテクスチャーと影となる部分の色を決めます。更に以下のようにノードを組みます。

f:id:alwei:20191130233715p:plain

Clampした結果は常に0~1の結果になるので、これを"Lerp"ノードの"Alpha"に渡すことで、2つの結果を合成することができます。ここで、陰影が白くなっていた理由がわかります。LerpのAにはベースとなるテクスチャーの色がそのまま入り、LerpのBにはテクスチャーの色と陰影の色をかけ算した結果が入ります。つまり、陰影が白い部分はBの結果を得ることができるので、陰影色をそのまま受け取ることができるようになるのです!

f:id:alwei:20191130234038p:plain

おお!なんかそれっぽい感じになりましたね。これをもう少し改造すると、ベースカラーの色も変更できるようになります。

f:id:alwei:20191130234411p:plain

ベースカラーを弄って、自由自在に色を変更できるように。

f:id:alwei:20191130234514p:plain

では、全てマテリアルインスタンス化して、それぞれのテクスチャーをパラメーターで設定してみましょう!

f:id:alwei:20191130234807p:plain

うん、ばっちりですね!ひとまずセルシェーダーとして必要最低限の情報が揃いました。意外と簡単だなということがわかったと思います!

f:id:alwei:20191130235033p:plain

もう少しアニメっぽく加工する

もう少しそれっぽくするために、MetalicやRoughnessはあえて、0と1に設定しておきます。これで変にテカるようなこともなくなります。代わりに自分でスペキュラやハイライト計算をしなくてはなりませんが、それはまた別の機会に。

f:id:alwei:20191130235530p:plain

個別のベースカラーとシャドウカラーの調整とポストプロセスによるカラーグレーディングで彩度を高めに設定して、アニメっぽい加工を行うと、より良い感じに。

f:id:alwei:20191201000012p:plain

そしてグレイマンも一緒に並ばせてみたり。

f:id:alwei:20191201000152p:plain

よいよい。

まだ基本的な機能のみ

いかがでしたか。今回は基本的な機能のみでしたが、UE4におけるセルシェーダーの簡単さは伝わったと思います。ここから更にハイライトや陰影位置をもっと細かく制御したり、ディレクショナルライト以外の影響も受けたいとなると、色々なシェーダーが必要になってきます。

しかしUE4のセルシェーダーやいわゆるNPRのシェーダーは決してハードルが高いわけではありませんので、皆さんもぜひ挑戦してみてください!

明日は takezoh さんによる、「ガルガンチュア on Oculus Quest - 72FPS への挑戦 -」とのことで、非常に楽しみです。よろしくお願いします!