こんにちは。
株式会社スパーククリエイティブのクライアントエンジニア、中島です。
第2回では、前回の内容を踏まえた上で、セルシェーディングで用いられる様々な表現をUEで実際につくっていきます。
今回紹介する3つの表現
今回はこちらの3つの表現についてまとめていきます。
・アウトライン
・天使の輪
・法線転写
聞き馴染みのあるものやあまり聞いたことがないものがあると思いますが、どれもよりオシャレなセルシェーディングをする上で大事な表現だと思います。
アウトライン
アウトラインの表現は、法線反転したメッシュを利用します。そのため、同じモデルのメッシュを複製して法線反転させるためのメッシュを用意する必要があります。
今回はUE内でメッシュの複製を行なっていきますが、モデルデータにアウトライン用のメッシュを事前に用意しても大丈夫です。メッシュの複製は、レベルに配置したモデルを選択し、詳細タブからSkeletalMeshComponentを右クリックして[複製]を選択することで行えます。

名前はわかりやすく、SkeletalMeshComponent_Outlineにしました。これで前準備の完了です。

メッシュの複製ができたら、法線反転の処理を行うアウトライン用のマテリアルを作成します。
まずは、マテリアルの詳細パネルを下図のように変更してください。

主な変更箇所は、Blend Mode、Shading Model、Two Sidedの3箇所です。
Two Sidedは両面描画の設定項目です。通常はメッシュの表面のみ描画されますが、Two SidedをONにすることで裏面も描画されます。確認用の一面を外した箱メッシュでTwo Sidedの有無の差分を確認するとこんな感じです。

Two SidedをONにしたことで裏面が描画されるようになり、箱の内側が描画されていることがわかると思います。
裏面が描画されたことを確認できたら、TwoSidedSignノードを利用して裏面を取得します。
このノードはメッシュの表面だと1、裏面だと-1を返すノードです。TwoSidedSignの値が1の場合に赤、-1の場合に青になるマテリアルを確認用の箱メッシュに適用し、描画結果を確認してみます。

Two SidedをONにしたことで描画された裏面が青色に、表面が赤色になりました。
法線反転では、裏面のみを描画している状態にする必要があります。この箱メッシュの場合では、青色の箇所のみを描画している状態にならなければいけないということです。
ここで、Blend ModeをMaskedにしたことが活かされてきます。Maskedにするとオパシティマスクのピンが有効化されます。オパシティマスクはOpacity Mask Clip Valueに設定された値未満の値が入力されると描画を行いません。
Opacity Mask Clip Valueはマテリアルの詳細パネルから確認することができ、デフォルトは0.3333です。

ここに、TwoSidedSignノードに-1を乗算したものを入力すると、表面は-1、裏面は1となるため裏面のみを描画するようになります。
オパシティマスクに入力するノードは下図のようになっています。

この処理を確認用の箱メッシュのマテリアルに適用してみます。

箱メッシュの裏面である青色の箇所のみが描画されている状態になり、メッシュを法線反転させることができました。
しかし、このままではアウトラインとしてはまだ機能していません。
この確認用の箱メッシュをアウトライン用として複製したメッシュと想定し、通常の箱メッシュと重ねてみます。

ぴったりと重なり、確認用の箱メッシュが全く視認できない状態になってしまいます。
アウトラインとして機能させるために、VertexNormalWSで法線方向を取得し、ワールド位置オフセットに入力します。

ワールド位置オフセットはマテリアルを適用しているメッシュの頂点を入力したベクトル分ずらすため、法線方向を入力することでメッシュが膨らむような挙動になります。
この処理を確認用の箱メッシュのマテリアルに適用した状態で、再度通常の箱メッシュと重ねてみます。

少し見づらいですが、箱の輪郭に青い線が描画されていることがわかりますでしょうか?
これでアウトライン用マテリアルの基礎となる処理が完成です。
現時点でのノードの全体は下図の通りです(確認用マテリアルの処理は含まれていません)。

完成したマテリアルを、モデルを複製したメッシュにセットしていきます。マテリアルをセットしたら見た目を確認してみましょう。

だいぶアウトラインが太いため、調整する必要がありそうです。ワールド位置オフセットに入力するVertexNormalWSに適当な数値を乗算することで調整できます。

太さ調整用として、OutlineWidthという名前でパラメータを追加しました。体のOutlineWidthを0.1、顔を0.0にすると、いい感じに細くなります。

最後に、アウトライン用マテリアルの詳細パネルからShadingModelを忘れずUnlitにしておきましょう。

アウトライン用マテリアルノードの全体は以下の通りです。

アウトラインの色はデフォルトでは黒ですが、調整用にパラメータを追加してみました。これで、アウトライン用マテリアルは完成となります。
※ UE5.1 以降では OverlayMaterial を使って簡単にアウトラインを表現することができます。ただしこちらはマテリアルスロットが1つのため、髪と衣装でアウトラインをわけたいといった対応をするには少し難しいかもしれません。
天使の輪
よくアニメ的な表現で、キャラクターの髪に下図の赤丸の箇所のようなハイライトが入っていることがあるかと思います。

これは「天使の輪」と呼ばれており、よりアニメらしい表現をするためには利用していきたい要素です。
ハーフベクトルと従法線(binormal)の内積値を利用して、天使の輪を再現することができます。
ハーフベクトルは、カメラベクトルとライトベクトルを加算したものを正規化することで求められ、従法線は接線ベクトル(tangent)と法線ベクトルの外積で得たベクトルを正規化することで求められます。内積はdotノード、外積はcrossノードをそれぞれ利用し、必要なベクトルを求めていきます。

ハーフベクトルと従法線の内積値は下図のような見た目になります。

この状態だとわかりづらいですが、黒の部分には0〜-1の値が入っているため、絶対値を求めると下図のようになります。

絶対値はabsノードを使うことで求めることが可能です。

OneMinusノードによって、先ほど求めた値を1から減算し、適当な値で累乗すると天使の輪に利用する基礎の部分の完成です。


出来上がったものを、陰を作成したときと同じようにLerpでつなげると、綺麗にモデルに馴染みます。

法線転写
モデルの顔に陰やハイライトなどを描画する際に、顔の造形やUVの関係上想定していたものと異なる見た目になってしまう場合があります。そういった場合には、「法線転写」を行うことで綺麗に描画できる可能性があります。
法線転写とは仮想のオブジェクト(球形など単純なもの)の法線を用意し、実際に使用するモデルの法線を扱う計算で差し替える技法のことです。
今回は、モデルの顔に対して陰を描画する処理に法線転写を利用します。まず、仮想のオブジェクトの法線を用意します。今回用意する法線は球形のものとして進めていきます。球形の法線は下図のように、[頂点座標 - オブジェクト座標]のベクトルを正規化することで求めることができます。

ノードで作成すると下図のようになります。

次に、用意した法線をモデルの法線と差し替えます。モデルの法線を計算に利用している箇所を用意した法線に変えることで、法線転写の完了です。

完全に差し替えても問題はありませんが、モデルの法線と球形の法線で、計算に利用する法線を調整できるようにLerpに繋げてみました。さらに、法線転写の位置を調整できるように位置オフセット用のパラメータを用意しました。
法線転写を利用したものとしていないものの見た目の差はこのようになります。

顔の陰以外にも、天使の輪が上手く表現できなかった場合にも有効な手段となります。
最後に
今回の3つの表現を組み込んだ結果は、以下のようになりました。

Unlitシェーダを用いても様々な手法を使うことで、より可愛い見た目を表現することができました。しかしUnlitシェーダの場合「落ち影」を表現するには疑似的なシャドウマップを用意しなければならないため、難易度はかなり高いです。
次回は、第1回にもお伝えしたとおり、趣向を凝らしてポストエフェクトを使った表現を紹介できればと思います。

中島龍清/Ryusei Nakashima
2019年、SPARK CREATIVE入社。Unity・UEを用いた開発、フロントエンド開発のほか、グラフィック開発やエンジン機能拡張などを担当。
●SPARKグループ公式サイト
spark-group.jp
●SPARK CREATIVE Techブログ
tech.spark-creative.co.jp
TEXT_中島龍清 / Ryusei Nakashima(SPARK CREATIVE)
EDIT_小村仁美 / Hitomi Komura(CGWORLD)