はじめに
Unityの2DのURPではLight2Dというものがあり、2Dゲームにいい感じのライティング設定をつけられます。
いい感じのライティングの例
公式の雑なスクショですが
- 光が電柱で遮られてそれっぽい影ができていたり(右上)
- NormalMapを使っていたりします(右下)
ならそもそもこのNormalMapはどうやって作ってどうやってUnityに取り込んで使用するのか気になったので作ってみました。
NormalMapを作成する
Spriteilluminatorというソフトを使用します。
こちらを使用すると取り込んだ画像にNormalMapを調整して入れることができます。
無料版と有料版があり、無料版はおそらく画像の出力ができません。今回は手元でパッと検証を行いたいだけなので無料トライアル版を使用します。これなら出力できました。
ちなみにソフトの使い方は公式サイトの動画を見てください。
なんとなく画面を見ていれば使い方がわかります。
LitTextureを押すと光源を動かしてどんな光り方がするのか確認できます。
遮蔽されててNormalが効いてていい感じなので出力します。
ツール上部のExportNormals
で出力できます。
これが完成したNormalMapです。
Unityに入れる
Unityにドラッグアンドドロップし、Textureの設定を行います。
NormalMapなのでSpriteModeでNormalMapを選択し、Applyします。
元のカラーテクスチャも入れておきます。
こっちは設定することは無いです。
Unityでライティングする
2DObjectからSquareを置きます。
SpriteRenderer
にはさっきのカラーテクスチャを入れます。
GlobalLight2D
を置きます。
光過ぎている場所にいくら光源を置いても明るくて変化がわからないのでLight2D
のIntensity
を下げておきます。
Sprite用の光源を設置します。
QualityをAccurateにし、Distanceを適当な値にします。0.2とかでいいです。
顔だけちょっと明るくなっていていい感じです。
ここに作ったNormalMapを設定します。
右下がSpriteのBorderやPivotを設定するモードからSecondaryTextureを設定する画面に切り替わります。
ここに_NormalMap
という名前で作ったNormalMapを入れます。
必ず_NormalMap
という命名でないといけないわけではないです。デフォルトのUnityのLight2DのShaderはNormalMapにこの命名でアクセスしているのでデフォルトのShaderを使用するならこの命名でないとアクセスされず、設定されないです。
逆に一からShaderを書く場合は無視して他の命名にしても問題ないです。
設定したらApplyします。
SpriteにShadowCaster2D
を付けます。(画像はEditor拡張されていて見た目が通常と異なります。)
EditShape
でSpriteの形を設定します(Editor拡張はポリゴンに沿ってこの形を設定するEditor拡張です)
ここにソースコードがあります。
Shaderを書いてみる
Normalの情報があればいろいろできます。
今回は適当にRimlightの計算を行います。
URPなのでSprite-Lit-DefaultのShaderをコピペし、それを拡張します。
また、Rimlightの計算方法はこれを使います。
inline float CalcRimlight(float3 V, float3 N, float width, float intensity)
{
float rim = pow(saturate(1. - dot(V, N) + width), intensity);
rim = saturate(rim);
return rim;
}
"LightMode" = "Universal2D"のPassの中に
#include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/NormalsRenderingShared.hlsl"
を追加
StructにいくつかNormal用の変数が足りないので追加を行い、
half3 normalWS : TEXCOORD3;
half3 tangentWS : TEXCOORD4;
half3 bitangentWS : TEXCOORD5;
Varyings CombinedShapeLightVertex(Attributes v)
の中にNormal計算に必要なものが足りなくなるので追加
o.normalWS = -GetViewForwardDir();
o.tangentWS = TransformObjectToWorldDir(v.tangent.xyz);
o.bitangentWS = cross(o.normalWS, o.tangentWS) * v.tangent.w;
CombinedShapeLightFragment
の中に
half3 normal = NormalsRenderingShared(main, normalTS, i.tangentWS.xyz, i.bitangentWS.xyz, i.normalWS.xyz);
half rim = 1.0 - CalcRimlight(float3(0.0, 0.0, 1.0), normal, _Width, _Intensity);
// 適当に赤
color.rgb += rim * half3(1.0, 0.0, 0.0);
を追加
これを行うことでNormalを利用してRimlightの計算ができます。
おわりに
ノーマルさえ用意できればあとは3Dでの処理とほぼ同じ書き方でいろいろ作れます。
NormalMapを用意するのがソフトを使おうとすると有料ですが簡単にベベルがかけれるのでお勧めです。
Shaderのソースコードはかなり雑ですがここにあります。
https://github.com/ayaha401/Sprite-Rimlight/blob/master/Sprite.shader