[URP]実践、URPでシェーダー書いてみる(SimpleLitにRimライティングを付加

Unity2019以降はシェーダーを自作しようと思うと古来よりあるshader code直接書く方法とShaderGraphを使う方法があります。

体感的にはLighting(BRDF)に依存しないadhocで使い捨てな見た目を作りたいときにいShaderGraphを使うと楽かなと思ってます。今回はBRDFにRimライティング項を追加するのでshader code書こうと思います。

事前に↑こちらで勉強。ありがたい記事です。

SimpleLitRim.shader

Properties
    {
        ~略
        [HDR] _RimColor ("Rim Color", Color) = (0,0,0)
        [NoScaleOffset] _RimTexture ("Rim Texture", 2D) = "white" {}
        _RimLightingMix ("Rim Lighting Mix", Range(0, 1)) = 0
        [PowerSlider(4.0)] _RimFresnelPower ("Rim Fresnel Power", Range(0, 100)) = 1
        _RimLift ("Rim Lift", Range(0, 1)) = 0
    }
 SubShader
    {
       ~略
Pass
        {
            Name "ForwardLit"
            Tags { "LightMode" = "UniversalForward" }

            ~略
            #include "./MKFWToonInput.hlsl"
            #include "./MKFWToonForwardPass.hlsl"
            ENDHLSL
        }
  ~略
  CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.SimpleLitRimShader"
}

PropertiesはMToonからのコピペ、それ以外はSimpleLitを見ながら勘で書き換えました。

.shaderはマテリアル画面で設定するテクスチャとかのプロパティを宣言、また描画パイプラインの一連のPassを全て記述する箇所みたいですね。SimpleLitだとMeta(ライトマップベイク用)も含め5Passですね

SimpleLitRimInput.hlsl

シェーダーコードで使う変数をここで定義します

~略
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half4 _SpecColor;
half4 _EmissionColor;
half _Cutoff;

half4 _RimColor;

half _RimLightingMix;
half _RimFresnelPower;
half _RimLift;
CBUFFER_END

TEXTURE2D(_SpecGlossMap);       SAMPLER(sampler_SpecGlossMap);
TEXTURE2D(_RimTexture);            SAMPLER(sampler_RimTexture);
~略

CBufferというのがポイントですね。確かSRPBatcherが有効になるための条件だった気がします。CBufferって何の略でしょうね、constant bufferでしょうか?

SRP Batcher:レンダリングをスピードアップ
2018 年、Unity はスクリプタブルレンダーパイプライン(SRP)というカスタマイズ性に優れたレンダリングテクノロジーを導入しました。この中には、ローレベルエンジン向けの新しいレンダリングループ「SRP Batcher」が用意されています。SRP Batcher を使うと、CPU によるレンダリング処理の速度がシ...

SimpleLitRimForwardPass.hlsl

color値を取得する処理をここに書きます。一番大事な部分ですね。今回はvertex shader, fragment shader自体はShaderLitの関数そのまま使ってます。代わりにfragment shaderであるLitPassFragmentSimple関数内で呼んでいたUniversalFragmentBlinnPhong関数ではなく自作のSimpleLitRimFragmentを呼ぶようにしてます。この関数自体はLitpassFragmentSimpleの上に書きました(下に書くとコンパイルエラーです

BRDFなんでもっと気のきいた名前にすべきでえしたね

half4 SimpleLitRimFragment(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha)
{
    Light mainLight = GetMainLight(inputData.shadowCoord);
    MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));

    half3 attenuatedLightColor = mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
    half3 diffuseColor = inputData.bakedGI + LightingLambert(attenuatedLightColor, mainLight.direction, inputData.normalWS);
    half3 specularColor = LightingSpecular(attenuatedLightColor, mainLight.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);

#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
        half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
        diffuseColor += LightingLambert(attenuatedLightColor, light.direction, inputData.normalWS);
        specularColor += LightingSpecular(attenuatedLightColor, light.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
    }
#endif

#ifdef _ADDITIONAL_LIGHTS_VERTEX
    diffuseColor += inputData.vertexLighting;
#endif

    // parametric rim lighting
    half3 staticRimLighting = 1;
    half3 mixedRimLighting = diffuseColor;

    half3 rimLighting = lerp(staticRimLighting, mixedRimLighting, _RimLightingMix);
    half3 rim = pow(saturate(1.0 - dot(inputData.normalWS, inputData.viewDirectionWS) + _RimLift), _RimFresnelPower) * _RimColor.rgb;
    diffuseColor += rim * rimLighting;


    half3 finalColor = diffuseColor * diffuse + emission;

#if defined(_SPECGLOSSMAP) || defined(_SPECULAR_COLOR)
    finalColor += specularColor;
#endif

    return half4(finalColor, alpha);
}

書いてから思ったのですが、このBRDF部分は_RimColorといったシェーダー変数に依存すべきではないですね。だって他のshaderから呼び出されるかもしれないんですから。引数部分にrim系パラメータを渡す設計にすべきでした。

SimpleLitRimShader.cs

カスタムインスペクタです。なくても動きます。でもクールな見た目と入力ミスを防ぐという目的ではあった方がいいです。ここはSimpleLitShader.csをほぼ丸コピしたので省きます

SimpleLitRimGUI.cs

カスタムインスペクタ、OnGUIで描画する部分です。実際は継承されてるんで記述は最小限です

 public static void DoRimArea(SimpleLitRImProperties properties, MaterialEditor materialEditor, Material material)
        {
            BaseShaderGUI.TextureColorProps(materialEditor, Styles.rimMapText, properties.rimTexture, properties.rimColor, true);
            var rimLightingMix = properties.rimLightingMix.floatValue;
            rimLightingMix = EditorGUILayout.Slider(Styles.rimLightingMixText, rimLightingMix, 0f, 1f);
            properties.rimLightingMix.floatValue = rimLightingMix;

            var rimFresnelPower = properties.rimFresnelPower.floatValue;
            rimFresnelPower = EditorGUILayout.Slider(Styles.rimFresnelPowerText, rimFresnelPower, 0f, 100f);
            properties.rimFresnelPower.floatValue = rimFresnelPower;

            var rimLift = properties.rimLift.floatValue;
            rimLift = EditorGUILayout.Slider(Styles.rimLiftText, rimLift, 0f, 1f);
            properties.rimLift.floatValue = rimLift;

        }

DoSpecularArea関数をコピペして描きました。SimpleLitRimProperties構造体に色々追加とかStylesにヘルプテキスト追加とかありますがまぁ適当に

SRPBatcher対応を確認

not compatibleなら理由書かれてるんで対応します。僕の場合はDepthOnlyとかのPassの#includeがSimpleLitInput.hlslのままでした。

まとめ

MToonリファレンス [VirtualCast]

リムライティング計算部分はMToonを参考にしました。

URP(というかSRP)では昔あったSetPassでCPU負荷あがるよー問題はSRP Batcherのおかげでほぼ無視できるくらいになりました。これからは異なるShaderが点在する方が問題になりそうですね。なるべく統一されたuberシェーダーを書いて使いまわすのがよさそうです