simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:VRでも使えるMToon互換のURPシェーダを30分で作ろう

前書き

Unity でゲーム作っている人なら多くの方が知っている VRM - VR向け3Dアバターファイルフォーマット -
その VRM の標準シェーダーとして採用されている MToon を URP でも表示できるようにしたい!

作りましょう!
そのための情報集めから作業をこの記事で公開します。

そのあと、公開しました。
github.com

MToon のパラメータ一覧

新しく ShaderGraph - Unlit を作成して公開パラメータとして次のものを作成します。

f:id:simplestar_tech:20200216160442p:plain
MToon の入力

光源情報を Custom Function ノードで取得

github.com
ここにあるサブグラフ一式と .hlsl シェーダー実装をコピーしておきます。いつでも使えるように
内容はほんと、単に光源情報取ってきているだけです。

f:id:simplestar_tech:20200216160717p:plain
サブグラフ一式

Toon シェーディング for ShaderGraph

次の動画の説明欄にあるリンクから Toon 表現の肝となる Node を見つけてコピーします。

ほしいのはこの Node です。

f:id:simplestar_tech:20200216161217p:plain
Toon 表現

アウトライン Shader

そして MToon で外せないのが Outline です。
Shader Graph で実現する方法が紹介されている記事がこちら

Outline Shader

Normal が Alpha テストできない問題に最終的に当たるので、もろもろ削除して、結局 Depth を書き出すプロジェクトで次の CustomFunction を呼ぶだけにします。

最終的に Outline サブシェーダーを作りますが、そこに登場する Custom Function は三つ

下図のとおり、左上のオフセットが

Out = unity_StereoEyeIndex;

左下の係数が

Out = 1.0;
#if UNITY_SINGLE_PASS_STEREO
Out = 0.5;
#endif

右下の Custom Function がこちら

TEXTURE2D(_CameraDepthTexture);
SAMPLER(sampler_CameraDepthTexture);
float4 _CameraDepthTexture_TexelSize;


void OutlineObject_float(float2 UV, float OutlineThickness, float DepthSensitivity, float NormalsSensitivity, out float Out)
{
    float halfScaleFloor = floor(OutlineThickness * 0.5);
    float halfScaleCeil = ceil(OutlineThickness * 0.5);
    
    float2 uvSamples[4];
    float depthSamples[4];

    uvSamples[0] = UV - float2(_CameraDepthTexture_TexelSize.x, _CameraDepthTexture_TexelSize.y) * halfScaleFloor;
    uvSamples[1] = UV + float2(_CameraDepthTexture_TexelSize.x, _CameraDepthTexture_TexelSize.y) * halfScaleCeil;
    uvSamples[2] = UV + float2(_CameraDepthTexture_TexelSize.x * halfScaleCeil, -_CameraDepthTexture_TexelSize.y * halfScaleFloor);
    uvSamples[3] = UV + float2(-_CameraDepthTexture_TexelSize.x * halfScaleFloor, _CameraDepthTexture_TexelSize.y * halfScaleCeil);

    for(int i = 0; i < 4 ; i++)
    {
        depthSamples[i] = SAMPLE_TEXTURE2D(_CameraDepthTexture, sampler_CameraDepthTexture, uvSamples[i]).r;
    }

    // Depth
    float depthFiniteDifference0 = depthSamples[1] - depthSamples[0];
    float depthFiniteDifference1 = depthSamples[3] - depthSamples[2];
    float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2)) * 100;
    float depthThreshold = (1/DepthSensitivity) * depthSamples[0];
    Out = edgeDepth > depthThreshold ? 1 : 0;
}

f:id:simplestar_tech:20200216162116p:plain
Outline for URP VR

あとはつなぐだけ

Toon 表現と Outline がそろっているので、接続して MToon の説明をたよりに編みます
MToon の心臓となる処理は Main と Shade のテクスチャを陰影で利用すること。
なので、Toon 表現で白黒を出力して、それを Lerp する処理が書けたらほぼ完成。自分はさらにライト色をもらうように Multiplyしてます。

f:id:simplestar_tech:20200216162748p:plain
心臓部 Lerp

MToon の Add を少々

最後に塩コショウのように Sphere Add と Emission を足すと

f:id:simplestar_tech:20200216163046p:plain
Add するだけ

完成!

f:id:simplestar_tech:20200216163358p:plain
MToon互換 URP VR 結果

VR の Single Pass Stereo で覗いてもアウトラインが引けていました。
そこがポイント

色々と動的読み込みしても、今のところ困った絵にはならない様子
みんなも試してみてね!

f:id:simplestar_tech:20200216163948p:plain
オニャンコポンなつのすがた
f:id:simplestar_tech:20200216164103p:plain
童田明治ファンアートモデル