【Unity】lilToonのTipsなど

先日私がリリースいたしましたUnity向けシェーダー「lilToon」についての話です。
公開直後ながら既に多くのご支援をいただいており、心より感謝申し上げます。
今回の記事はlilToon向けの話になりますが、BlendingやステンシルやVR向けのシェーダー処理の解説も含まれており、他シェーダーにも応用できる内容も含まれておりますので是非ご活用ください。

# 目次
- [Blending関連](#blending関連)
    - [乗算合成](#乗算合成)
- [ステンシル関連](#ステンシル関連)
    - [照れシェイプキーや前髪の影を肌の上だけに出す](#照れシェイプキーや前髪の影を肌の上だけに出す)
    - [オブジェクトの縁だけにアウトラインを出す](#オブジェクトの縁だけにアウトラインを出す)
    - [【2021/07/20追記】ステンシルを半透明にする](#20210720追記ステンシルを半透明にする)
- [その他](#その他)
    - [環境光を無視して常に影を出す](#環境光を無視して常に影を出す)
    - [輪郭線の太さを一定にする](#輪郭線の太さを一定にする)
    - [ワールドに関係なくガチ恋距離](#ワールドに関係なくガチ恋距離)
    - [アルファマスクを使いたい](#アルファマスクを使いたい)
    - [顔だけ明るかったり暗かったりする](#顔だけ明るかったり暗かったりする)

# Blending関連
## 乗算合成
1. `透過モード`を`半透明`にする
2. 詳細設定から`基本設定`を開く
3. `Unlit化`を1にする
4. 詳細設定から`レンダリング設定`を開く
5. `Forward`を開き`SrcBlend RGB`を`Zero`に、`DstBlend RGB`を`SrcColor`にする
6. `ForwardAdd`を開き`SrcBlend RGB`を`Zero`に、`DstBlend RGB`を`One`にする

（解説）
まず、二重にライティングされて暗くなるのを防ぐためにUnlitのシェーダーを用いる必要があったのでUnlit化しています。
そしてBlendについてですが、Srcがシェーダーで計算した色、Dstが既に描画されている色を表しています。
『`SrcBlend RGB`を`Zero`に、`DstBlend RGB`を`SrcColor`にする』という操作で、`Src * Zero + Dst * SrcColor = Dst * SrcColor`となり乗算合成となるわけです。

# ステンシル関連
## 照れシェイプキーや前髪の影を肌の上だけに出す
1. 肌のマテリアルを選択
2. 詳細設定から`ステンシル設定`を開く
3. `Ref`の数値を0以外にし、`Pass`を`Replace`にする
4. 肌の上に描画したい物のマテリアルを選択
5. `Ref`の数値を肌で設定したものに合わせ、`Comp`を`Equal`にする
6. `レンダリング設定`からRenderQueueの数値を肌のマテリアルより大きい数値にする

## オブジェクトの縁だけにアウトラインを出す
1. 詳細設定から`ステンシル設定`を開く
2. `Ref`の数値を２つとも0以外にする（数値は合わせる）
3. 上にある本体側の`Pass`を`Replace`にする
4. 下にある輪郭線の`Comp`を`NotEqual`にする

## 【2021/07/20追記】ステンシルを半透明にする
ここでは眉毛を髪の毛の上に描画する場合の手順について解説します。
**1～6までは普通のステンシル設定**なのでわかる方は読み飛ばしてください。
1. 眉毛のマテリアルを選択
2. 詳細設定から`ステンシル設定`を開く
3. `Ref`の数値を0以外にし、`Pass`を`Replace`にする
4. 髪のマテリアルを選択
5. `Ref`の数値を眉で設定したものに合わせ、`Comp`を`NotEqual`にする
6. `レンダリング設定`からRenderQueueの数値を肌のマテリアルより大きい数値にする
7. 髪のマテリアルを複製
8. 髪のメッシュの`Materials`の`Size`を2に増やし、`Element 1`の方に複製したマテリアルを割り当てる
9. 複製したマテリアルを選択
10. 透過モードを半透明にする
11. シェーダーを`_lil/[Optional] lilToonOverlay`に変更
12. `Ref`の数値を眉で設定したものに合わせ、`Comp`を`Equal`にする
13. 透明度を調整する

（解説）
ステンシルというのはスクリーン上で行えるマスク操作のようなものです。
オブジェクトを描画する際に色や奥行き情報が書き出されますが、一緒にステンシルの数値を書き出すことで様々なマスク操作を行うことができます。
`Pass`というのは数値をどのように書き換えるかの設定で、例えば`Keep`ならもともとあった数値をキープしますし、`Replace`なら`Ref`の数値に置き換えます。
`Comp`はどのような条件でオブジェクトを描画するかの設定で、`Always`ならオールウェイズ描画してくれますし、`NotEqual`なら`Ref`とイコールじゃないときに描画します。
照れシェイプキーや髪影の設定はアーティスト視点から見ても結構気になる部分なので、クリスタのクリッピング感覚でマスク処理できるというのは知っておくだけで役に立つと思います。
【2021/07/20追記】
ステンシルを半透明にする方法を追記しました。
シェーダーのパス数を増やせば同様のことを行えますが、URPではパイプラインの編集が必要になるためマテリアルを分けています。
今回使用したlilToonOverlayはメインパスのみ使用するシェーダーになっているため普通のシェーダーを利用する場合より負荷を抑えられます。

# その他
## 環境光を無視して常に影を出す
1. 詳細設定から`影設定`を開く
2. `環境光の強さ`を下げる

（解説）
この環境光の強さは影の色だけに影響し、アバター自体の明るさに影響はありません。
lilToonではStandardShaderに近づけるために`Environment Lighting`（環境光）を取得して影の色を補正するようになっています。
なので、StandardShaderが明るくなる状況ではlilToonも明るくなって影が薄くなります。

```hlsl
indirectCol = lerp(indirectCol, albedo, environmentLighting * _ShadowEnvStrength);
```

## 輪郭線の太さを一定にする
1. 詳細設定から`輪郭線設定`を開く
2. `太さを補正`のチェックを外す

（解説）
距離に応じた太さの補正が無効化されます。
カメラがキャラクターに近づいたときに線が太くなり気になることがありますが、ワールド空間でのカメラ座標と頂点座標の距離を元に太さを補正することでこの問題を緩和できるようになっています。

```hlsl
if(_OutlineFixWidth) outlineWidth *= saturate(length(_WorldSpaceCameraPos.xyz - positionWS.xyz));
```

## ワールドに関係なくガチ恋距離
1. 詳細設定から`シェーダー設定`を開く
2. `距離クリッピングキャンセラー`をオンにしてApply

（解説）
普通にゲームや映像を作っている分にはクリッピング距離はそこまで気になりませんが、VRでは「顔を近づけて物を見る」という場面が出てくるので近づくと消えてしまうのは没入感を下げてしまう要因になるかと思います。
カメラ側でクリッピング距離を小さくしてあげるのがベストですが、クリッピング距離近くなったときにメッシュを圧縮するような処理を加えることでも回避できます。

```hlsl
#if defined(UNITY_REVERSED_Z)
    // DirectX
    if(output.positionCS.w < _ProjectionParams.y * 1.01 && output.positionCS.w > 0) output.positionCS.z = output.positionCS.z * 0.0001 + output.positionCS.w * 0.999;
#else
    // OpenGL
    if(output.positionCS.w < _ProjectionParams.y * 1.01 && output.positionCS.w > 0) output.positionCS.z = output.positionCS.z * 0.0001 - output.positionCS.w * 0.999;
#endif
```

## アルファマスクを使いたい
焼き込みのみの対応となっていたため、次バージョンで追加いたします。
焼き込みは透過マテリアルにした際にメインテクスチャの下に現れる`アルファマスクを焼き込み`から行えます。
【2021/07/16追記】
lilToon v1.1にてアルファマスクに対応いたしました。
アルファマスクを割り当て後にスライダーで透明度の調整、調整したテクスチャの焼き込みが行えます。

## 顔だけ明るかったり暗かったりする
開発中から気になっていたのですが、これはNot Importantなスポットライト（頂点ライト）では形状を考慮できないというUnityの仕様によるものです。
次バージョンで頂点ライティングを無効化するプロパティを追加して、Avatar3.0のメニューなどから状況に応じてオンオフできるようにします。
回避策が見つかり次第修正したいところですが、ポイントライト・スポットライトを区別できる変数はなさそう……？
【2021/07/16追記】
lilToon v1.1にて頂点ライトの強度が調整可能になりました。
基本設定より`頂点ライトの強度`を0にすることでこの問題の修正ができます。