[Unity]崩壊スターレイルのキャラクターレンダリング再現

## はじめに
zhihuのNPR技術解説記事を色々読んで知見がたまってきたので練習として崩壊スターレイルのキャラクターレンダリング再現をしてみました

右がfbxを読み込んでtoon shaderを適用しただけの状態で左が調整後です

![ginro_ba.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440297/85b8ee2f-6ab9-c9fe-88c7-5cf244e6e8a0.png)


これは参考用に撮影してきたゲーム内の画像です
![StarRail_Image_1726870284.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440297/684e5748-a330-522f-66f0-3728d7dde3bb.png)

## 注意
- unity 2022.3.22f1 URP14 を使用しています
- 記事の内容は筆者が見様見真似で再現したものなので実際のゲーム内で使用されている技術とは異なります
- 筆者はUnityに関してある程度の知識(unityとshaderを実務で扱う程度)を持っていますが、アニメ調レンダリングに関しては初心者なので間違いを含む可能性があります。間違いを見つけたらコメントで指摘してくれると嬉しいです
- モデルの調整や各種Textureの追加やfovの調整、ポストプロセスなどでの見た目の調整を行っていますが、これらの基本的な事項は解説しません
- 中国語のサイトのリンクが多々あります

## モデルの用意
mihoyoが配布しているMMDモデルをダウンロードし、Blender の MMD Tools を使いfbxで書き出してUnityで扱えるようにしました。(今回は銀狼さんを用います)
ShaderはlilToonShaderを利用します。
インターネット上にたくさんの情報があるのでここでは解説しません。

## SDFを使った顔の影の制御
アニメのような顔の影を出すために使用される技術です。(レンブラントライティングのような見た目を出しやすい)
顔とライトの角度で影のマスクをいくつか作成しSDF化して、各SDFの補間結果を纏めたShadowMapを陰影制御に使う方式です。

![Timeline 1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440297/d060ed43-cc23-a940-63ab-92b67d0cd6e9.gif)

以下の記事に詳しい解説があります。

https://unrealengine.hatenablog.com/entry/2024/02/28/222220

この手法にはいくつか注意点があります
- ライトのY軸方向が結果に影響を与えない(顔が上や下を向いても結果が変わらない)
- 顔の方向を設定する必要がある

```hlsl
float sdf_face_shadow_mask(float2 uv, float3 l)
{
    float isSahdow = 0;
    half4 ilmTex = SAMPLE_TEXTURE2D(_FaceShadowMap, sampler_FaceShadowMap, uv);
    half4 r_ilmTex = SAMPLE_TEXTURE2D(_FaceShadowMap, sampler_FaceShadowMap, float2(1 - uv.x, uv.y));
    float2 Right = normalize(_FaceRight.xz);
    float2 Front = normalize(_FaceFront.xz);
    float2 LightDir = normalize(l.xz);
    float ctrl = 1.0 - (dot(Front, LightDir) * 0.5 + 0.5);
    float ilm = dot(LightDir, Right) < 0 ? ilmTex.r : r_ilmTex.r;
    isSahdow = step(ilm, ctrl);

    return 1.0 - isSahdow;
}
```

ShadowMapの作成は以下の記事を参考にしてCustomRenderTexture用のShaderを書き画像出力機能を使い作成しました。

https://zhuanlan.zhihu.com/p/389668800

実際の描画用の処理は以下の記事を参考にして、lilToonのカスタム機能で実装しました。
(追記: lilToon 1.8.0 でSDFの顔影の表現機能が追加されたようです)

https://zhuanlan.zhihu.com/p/361716315

https://lilxyzw.github.io/lilToon/#/ja-jp/dev/custom_shader



## 髪のセルフシャドウ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440297/d7e75369-de6a-21d2-f236-56bb24826ece.png)

処理内容は単純です。

- 顔の描画で任意の値のStencilRefを書き込む
- 髪のメッシュの描画で顔のStencilRefと同値の範囲を取得してクリップスペースで位置をずらす
- ↑を ColorMask 0 で色の描画はせずにStencil値だけを書き込む
- ↑がセルフシャドウの範囲になっているので顔のメッシュで影の描画をする

似た実装方法にlilToonFakeShadowを使ったものがあるようです。


以下の記事を参考にして実装しました。

https://zhuanlan.zhihu.com/p/416577141


## 浮き眉

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440297/815356e2-7b70-8498-01f7-32dfb03d5d6e.png)


髪に隠れている眉毛や睫毛を手前に表示する表現です。ステンシルを使用した手法が一般的なようですが、今回はShaderを使って髪より手前に半透明の眉を描画する手法を用いました。

```hlsl
v2f vert(a2v v)
{
    v2f o;

    VertexPositionInputs positionInputs = GetVertexPositionInputs(v.positionOS.xyz);
    o.positionCS = positionInputs.positionCS;

    float3 positionVS = positionInputs.positionVS;
    positionVS.z += _EyebrowOffsetZ;
    float4 positionCS = TransformWViewToHClip(positionVS);
    float depth = positionCS.z / positionCS.w;
    o.positionCS.z = depth * o.positionCS.w;

    o.uv = v.uv;
    return o;
}
```

以下の記事を参考にして実装しました。

https://zhuanlan.zhihu.com/p/696515379

## アウトライン

法線転写をしたメッシュやハードエッジのメッシュではアウトラインが綺麗に出ない対策として、アウトライン用の法線を接線空間に変換して頂点カラーやUVに設定し、描画時に利用する手法があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440297/ea7107f4-4253-27f1-5b85-2afd2e3b0734.png)


lilToonに頂点カラー焼きこみツールと頂点カラーの情報をアウトライン描画に利用する機能があったのでこちらを利用しました。

https://lilxyzw.github.io/lilToon/#/ja-jp/advanced/outline

<iframe width="560" height="315" src="https://www.youtube.com/embed/gPCtS33jx7Q?si=juR3MWWnNRJYhI5X" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## 終わりに

アニメ調レンダリングで用いられる手法は単純ながらも驚くような手法が多く、楽しく調査することができたと思います。
筆者の技術記事ではサンプルリポジトリを公開することが多いのですが、諸事情のため筆者の経済状況が安定した頃に作成しようと思います。


## 参考
本文中にいくつか参考リンクを貼りましたが、ほかにも参考にしたリンクをここに貼ります。

<iframe width="560" height="315" src="https://www.youtube.com/embed/T2iI9hbNqLI?si=RglqVAg1Y7Wt6OYz" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

https://zhuanlan.zhihu.com/p/411188212