ShaderGraphコンパイル後のアセンブリコードを読んでみる

2021/12/01に公開

はじめに

UnityのShaderGraphってどのくらい重いんだろうか?と気になったので、ShaderGraphの計算負荷を調べてみました。

ShaderGraphをシェーダーコンパイルした際のアセンブリコード(d3d asm)を読んでみようと思います。

シェーダーコンパイルはD3Dプラットフォームで行っています。

対象読者

・軽いシェーダーを書くことに興味がある人
・ShaderLabよりShaderGraphの方が重そうだと疑っている人

環境

  • Unity2020.3.13f1
  • Universal RP 10.5.1

比較したもの

  1. シェーダーPassの比較
  2. シェーダーコンパイル後のアセンブリコードを比較

シェーダーコンパイルの流れ

まずは、ShaderGraphやShaderLabがコンパイルされる流れについておさらいしてみましょう。

ShaderLab

ShaderLabは、シェーダーコンパイラによってコンパイルされ、プラットフォームごとのシェーダーへと変換されます。

https://docs.unity3d.com/ja/2018.4/Manual/SL-ShadingLanguage.html

ShaderGraph

ShaderGraphはHLSL(ShaderLab)コードに変換され、シェーダーコンパイラによってコンパイルされます。

シェーダーコンパイルコードの確認方法

Compile and show code を選択します。

D3D向けにコンパイルするには、D3DのチェックボックスをONにします。

ShaderGraph vs ShaderLab

Unity上の右クリックメニューから作成した直後のShaderLab・ShaderGraphの主な違いを表にまとめてみました。

機能 ShaderGraph ShaderLab 対応させるには
GPU Instancing × #pragma multi_compile_instancing 有効
SRP Batcher CBUFFER を利用する
Hybrid Renderer V2 × #pragma multi_compile _ DOTS_INSTANCING_ON 有効
コンパイルターゲット 2.0と4.5 2.0 #pragma target 4.5有効

比較01 : Passの比較

シェーダーのPassを比べてみます。
ShaderLabにはPassが1つ、ShaderGraphにはPassが3つ含まれます。

種類 Pass
Unlit ShaderLab Pass
Unlit ShaderGraph Pass, ShadowCaster, DepthOnly

ShaderLab

Unityで作成直後のUnlitシェーダーには、SubShaderとPassが1つだけ含まれています。

作成直後のUnlitシェーダー(.shader)
Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

ShaderGraph

以下のようなShaderGraphを作成し、View Generated Shaderで生成されるHLSLコードを確認してみます。

UnlitのShaderGraph

ShaderGraphから生成されるShaderLabを確認する

HLSLコードへの変換

ShaderGraphのInspectorから、View Generated Shaderを実行すると、ShaderGraphを変換したHLSLコードを見ることができます。

ShaderGraphは複数のSubShader、複数のPass、複数のkeywordが定義されたHLSLシェーダーコードに変換されます。

GeneratedFromGraph-NewShaderGraph.shader
Shader "New Shader Graph"
{
    Properties
    {
        (省略)
    }
    SubShader
    {
        Tags
        {
            (省略)
        }
        Pass
        {
            Name "Pass"
            (省略)
            #pragma target 4.5
            #pragma exclude_renderers gles gles3 glcore
            #pragma multi_compile_instancing
            #pragma multi_compile _ DOTS_INSTANCING_ON
            #pragma vertex vert
            #pragma fragment frag
            (省略)
        }

        Pass
        {
            Name "ShadowCaster"
            (省略)
        }
        
        Pass
        {
            Name "DepthOnly"
            (省略)
        }
    }
    
    SubShader
    {
        Tags
        {
            (省略)
        }
        Pass
        {
            Name "Pass"
            (省略)
            #pragma target 2.0
            #pragma only_renderers gles gles3 glcore d3d11
            #pragma multi_compile_instancing
            #pragma multi_compile_fog
            #pragma vertex vert
            #pragma fragment frag
            (省略)
        }

        Pass
        {
            Name "ShadowCaster"
            (省略)
        }
        
        Pass
        {
            Name "DepthOnly"
            (省略)
        }
    }
    FallBack "Hidden/Shader Graph/FallbackError"
}

HLSLコードを確認すると、SubShaderが2つ、Passが3つ含まれることが確認できます。
(Pass, ShadowCaster, DepthOnly)

デプスを利用した表現を行わない場合、DepthOnlyパスは余計な処理です。
落ち影が不要な場合は ShadowCasterパスも不要です。

比較02 : Fragmentシェーダーの比較

シェーダーコンパイル後のアセンブリコードを見ることで、
Fragmentシェーダー(ピクセルシェーダー)がどの程度の量の計算を行っているのかが見えるようになります。

ShaderLabをコンパイルしてみる

ShaderLabをコンパイルしてみます。

float4 frag (v2f i) : SV_Target
{
    return float4(0.1, 0.2, 0.3, 0.4);
}

以下のようなアセンブリに変換されます。

      ps_4_0
      dcl_output o0.xyzw
   0: mov o0.xyzw, l(0.100000,0.200000,0.300000,0.400000)
   1: ret 

フラグメントシェーダーの出力レジスタとなるような o0.xyzw に対して、
ベクトル値 l(0.100000,0.200000,0.300000,0.400000) をコピーするようなアセンブリになっています。

https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/mov--sm4---asm-

ps_4_0とps_5_0

ShaderLabにて#pragma target 2.0 と指定したPassのfrag関数は ShaderModel 4.0の Pixel Shader(ps_4_0)のアセンブリに変換されます。
#pragma target 4.5 と指定したPassのfrag関数は ShaderModel 5.0の Pixel Shader(ps_5_0)のアセンブリに変換されます。

ShaderGraphをコンパイルしてみる

次に、ShaderGraphをコンパイルします。

3Dオブジェクト描画時に実行されるfrag関数は、UnlitPass.hlslの中に定義されています。

com.unity.render-pipelines.universal@10.5.1\Editor\ShaderGraph\Includes\UnlitPass.hlsl

frag関数のHLSLコード
UnlitPass.hlsl
half4 frag(PackedVaryings packedInput) : SV_TARGET 
{    
    Varyings unpacked = UnpackVaryings(packedInput);
    UNITY_SETUP_INSTANCE_ID(unpacked);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(unpacked);

    SurfaceDescriptionInputs surfaceDescriptionInputs = BuildSurfaceDescriptionInputs(unpacked);
    SurfaceDescription surfaceDescription = SurfaceDescriptionFunction(surfaceDescriptionInputs);

    #if _AlphaClip
        half alpha = surfaceDescription.Alpha;
        clip(alpha - surfaceDescription.AlphaClipThreshold);
    #elif _SURFACE_TYPE_TRANSPARENT
        half alpha = surfaceDescription.Alpha;
    #else
        half alpha = 1;
    #endif

#ifdef _ALPHAPREMULTIPLY_ON
    surfaceDescription.BaseColor *= surfaceDescription.Alpha;
#endif

    return half4(surfaceDescription.BaseColor, alpha);
}

frag関数は複雑な処理に見えますが、
これをコンパイルすると、以下のような短いシェーダーアセンブリになります。

      ps_4_0
      dcl_output o0.xyzw
   0: mov o0.xyzw, l(0.100000,0.200000,0.300000,1.000000)
   1: ret 

ShaderLabとShaderGraphのアセンブリ比較

ShaderLabのfrag関数
      ps_4_0
      dcl_output o0.xyzw
   0: mov o0.xyzw, l(0.100000,0.200000,0.300000,0.400000)
   1: ret 
ShaderGraphのUnlitPassのfrag関数
      ps_4_0
      dcl_output o0.xyzw
   0: mov o0.xyzw, l(0.100000,0.200000,0.300000,1.000000)
   1: ret 

ShaderGraphとShaderLabのfrag関数は、シェーダーコンパイル後に同じ処理になっていると読み取れます。

比較03 : 頂点シェーダーの比較

ShaderLab

以下のような頂点シェーダーをコンパイルしてみます。

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
}

コンパイル後は、以下のようになります。

      vs_4_0
      dcl_constantbuffer CB0[4], immediateIndexed
      dcl_constantbuffer CB1[21], immediateIndexed
      dcl_input v0.xyz
      dcl_output_siv o1.xyzw, position
      dcl_temps 2
   0: mul r0.xyzw, v0.yyyy, cb0[1].xyzw
   1: mad r0.xyzw, cb0[0].xyzw, v0.xxxx, r0.xyzw
   2: mad r0.xyzw, cb0[2].xyzw, v0.zzzz, r0.xyzw
   3: add r0.xyzw, r0.xyzw, cb0[3].xyzw
   4: mul r1.xyzw, r0.yyyy, cb1[18].xyzw
   5: mad r1.xyzw, cb1[17].xyzw, r0.xxxx, r1.xyzw
   6: mad r1.xyzw, cb1[19].xyzw, r0.zzzz, r1.xyzw
   7: mad o1.xyzw, cb1[20].xyzw, r0.wwww, r1.xyzw
   8: ret 

ShaderGraph

次は、以下のShaderGraphをコンパイルしてみます。

以下のようなアセンブリになります。

      vs_4_0
      dcl_constantbuffer CB0[73], immediateIndexed
      dcl_constantbuffer CB1[4], immediateIndexed
      dcl_input v0.xyz
      dcl_output_siv o0.xyzw, position
      dcl_temps 2
   0: mul r0.xyz, v0.yyyy, cb1[1].xyzx
   1: mad r0.xyz, cb1[0].xyzx, v0.xxxx, r0.xyzx
   2: mad r0.xyz, cb1[2].xyzx, v0.zzzz, r0.xyzx
   3: add r0.xyz, r0.xyzx, cb1[3].xyzx
   4: mul r1.xyzw, r0.yyyy, cb0[70].xyzw
   5: mad r1.xyzw, cb0[69].xyzw, r0.xxxx, r1.xyzw
   6: mad r0.xyzw, cb0[71].xyzw, r0.zzzz, r1.xyzw
   7: add o0.xyzw, r0.xyzw, cb0[72].xyzw
   8: ret 

ShaderGraphとShaderLabの比較

ShaderGraphとShaderLabでの頂点シェーダーのアセンブリは、ほぼ同じ処理になっています。

ShaderLabのvert関数
      vs_4_0
      dcl_constantbuffer CB0[4], immediateIndexed
      dcl_constantbuffer CB1[21], immediateIndexed
      dcl_input v0.xyz
      dcl_output_siv o1.xyzw, position
      dcl_temps 2
   0: mul r0.xyzw, v0.yyyy, cb0[1].xyzw
   1: mad r0.xyzw, cb0[0].xyzw, v0.xxxx, r0.xyzw
   2: mad r0.xyzw, cb0[2].xyzw, v0.zzzz, r0.xyzw
   3: add r0.xyzw, r0.xyzw, cb0[3].xyzw
   4: mul r1.xyzw, r0.yyyy, cb1[18].xyzw
   5: mad r1.xyzw, cb1[17].xyzw, r0.xxxx, r1.xyzw
   6: mad r1.xyzw, cb1[19].xyzw, r0.zzzz, r1.xyzw
   7: mad o1.xyzw, cb1[20].xyzw, r0.wwww, r1.xyzw
   8: ret 
ShaderGraphのvert関数
      vs_4_0
      dcl_constantbuffer CB0[73], immediateIndexed
      dcl_constantbuffer CB1[4], immediateIndexed
      dcl_input v0.xyz
      dcl_output_siv o0.xyzw, position
      dcl_temps 2
   0: mul r0.xyz, v0.yyyy, cb1[1].xyzx
   1: mad r0.xyz, cb1[0].xyzx, v0.xxxx, r0.xyzx
   2: mad r0.xyz, cb1[2].xyzx, v0.zzzz, r0.xyzx
   3: add r0.xyz, r0.xyzx, cb1[3].xyzx
   4: mul r1.xyzw, r0.yyyy, cb0[70].xyzw
   5: mad r1.xyzw, cb0[69].xyzw, r0.xxxx, r1.xyzw
   6: mad r0.xyzw, cb0[71].xyzw, r0.zzzz, r1.xyzw
   7: add o0.xyzw, r0.xyzw, cb0[72].xyzw
   8: ret 
// Approximately 0 instruct

UVの計算処理を追加した場合の頂点シェーダー

ShaderLab

uvの計算処理を追加してみます。

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 追加
    return o;
}

頂点シェーダーの処理の行数が8から9へ増えます

      vs_4_0
      dcl_constantbuffer CB0[3], immediateIndexed
      dcl_constantbuffer CB1[4], immediateIndexed
      dcl_constantbuffer CB2[21], immediateIndexed
      dcl_input v0.xyz
      dcl_input v1.xy
      dcl_output o0.xy
      dcl_output_siv o1.xyzw, position
      dcl_temps 2
   0: mad o0.xy, v1.xyxx, cb0[2].xyxx, cb0[2].zwzz
   1: mul r0.xyzw, v0.yyyy, cb1[1].xyzw
   2: mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw
   3: mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw
   4: add r0.xyzw, r0.xyzw, cb1[3].xyzw
   5: mul r1.xyzw, r0.yyyy, cb2[18].xyzw
   6: mad r1.xyzw, cb2[17].xyzw, r0.xxxx, r1.xyzw
   7: mad r1.xyzw, cb2[19].xyzw, r0.zzzz, r1.xyzw
   8: mad o1.xyzw, cb2[20].xyzw, r0.wwww, r1.xyzw
   9: ret 

ShaderGraph

テクスチャ座標をFragment側で参照するようなShaderGraphを組み、コンパイルしてみます。

こちらも頂点シェーダーの処理の行数が8から9へ増えました。
Fragment側でUVを参照すると、頂点シェーダー側にも処理が追加されるようです。

      vs_4_0
      dcl_constantbuffer CB0[73], immediateIndexed
      dcl_constantbuffer CB1[4], immediateIndexed
      dcl_input v0.xyz
      dcl_input v3.xyzw
      dcl_output_siv o0.xyzw, position
      dcl_output o1.xyzw
      dcl_temps 2
   0: mul r0.xyz, v0.yyyy, cb1[1].xyzx
   1: mad r0.xyz, cb1[0].xyzx, v0.xxxx, r0.xyzx
   2: mad r0.xyz, cb1[2].xyzx, v0.zzzz, r0.xyzx
   3: add r0.xyz, r0.xyzx, cb1[3].xyzx
   4: mul r1.xyzw, r0.yyyy, cb0[70].xyzw
   5: mad r1.xyzw, cb0[69].xyzw, r0.xxxx, r1.xyzw
   6: mad r0.xyzw, cb0[71].xyzw, r0.zzzz, r1.xyzw
   7: add o0.xyzw, r0.xyzw, cb0[72].xyzw
   8: mov o1.xyzw, v3.xyzw
   9: ret 

ShaderLabでは、
vertにUVの計算処理を追加した場合、frag側でUVを使わなかったとしてもvert側の計算負荷が増加します。

対してShaderGraphでは、
Fragment側でUVを利用しない場合は、Vertex側にUV計算が入りません。

まとめ

  • Pass単体に着目したとき、ShaderGraphとShaderLabの計算量は同じ (ShaderLab実装時に無駄な処理を書くと、ShaderGraphより重くなる)
  • ShaderLabで実装した場合、必要なPassだけ含めることができる
  • ShaderGraphには余計なPassが含まれてしまう
  • ShaderGraphのFragmentでUVを利用すると、Vertex処理にもUV計算処理が入る

ShaderGraphの最適化いろいろ

FloatプロパティとVectorプロパティ

Floatプロパティ3つを使う場合と、Vectorプロパティ1つを使う場合は、コンパイル後に同じアセンブリコードになります。


      ps_4_0
      dcl_constantbuffer CB0[1], immediateIndexed
      dcl_output o0.xyzw
   0: mov o0.xyz, cb0[0].xyzx
   1: mov o0.w, l(1.000000)
   2: ret 

Vector2プロパティ2つとVector4プロパティ1つ

Vector2プロパティ2つを使った場合と、Vector4プロパティ1つを使った場合、コンパイル後に同じアセンブリコードになります。

      ps_4_0
      dcl_constantbuffer CB0[1], immediateIndexed
      dcl_input_ps linear v1.xy
      dcl_output o0.xyzw
   0: mad o0.xy, v1.xyxx, cb0[0].xyxx, cb0[0].zwzz
   1: mov o0.zw, l(0,0,0,1.000000)
   2: ret 

Sample Texture 2D の最適化

以下の二つは、コンパイル後に同じアセンブリコードになります。

      ps_4_0
      dcl_sampler s0, mode_default
      dcl_resource_texture2d (float,float,float,float) t0
      dcl_input_ps linear v1.xy
      dcl_output o0.xyzw
      dcl_temps 1
   0: sample r0.xyzw, v1.xyxx, t0.xyzw, s0
   1: mov o0.xy, r0.xxxx
   2: mov o0.zw, l(0,0,0,1.000000)
   3: ret 

Remap の最適化

範囲[0,1]から範囲[0,2]への変換と、2の乗算は、コンパイル後に同じアセンブリコードになります。

      ps_4_0
      dcl_constantbuffer CB0[1], immediateIndexed
      dcl_output o0.xyzw
   0: mul o0.x, cb0[0].x, l(2.000000)
   1: mov o0.yzw, l(0,0,0,1.000000)
   2: ret 

Add の最適化

足し算A+Aと、掛け算A×2は、コンパイル後に同じアセンブリコードになります。

      ps_4_0
      dcl_constantbuffer CB0[1], immediateIndexed
      dcl_output o0.xyzw
   0: mul o0.x, cb0[0].x, l(2.000000)
   1: mov o0.yzw, l(0,0,0,1.000000)
   2: ret 

ShaderGraphの重いノード

SampleGradientは重い

SampleGradientは重いので、使うのは避けたほうが良いでしょう。
行数: 3 ~ 20くらい

      ps_4_0
      dcl_input_ps linear v1.x
      dcl_output o0.xyzw
      dcl_temps 3
   0: mul_sat r0.x, v1.x, l(3.551262)
   1: mul r0.yzw, r0.xxxx, l(0.000000, 0.164706, 0.000000, 0.500000)
   2: mad r1.xyz, -r0.xxxx, l(0.164706, 0.000000, 0.500000, 0.000000), l(1.000000, 0.015686, 0.296918, 0.000000)
   3: add r2.xy, v1.xxxx, l(-0.281590, -0.582956, 0.000000, 0.000000)
   4: mul_sat r2.xy, r2.xyxx, l(3.318228, 2.670756, 0.000000, 0.000000)
   5: mad r0.xyz, r2.xxxx, r1.xyzx, r0.yzwy
   6: add r1.xyz, -r0.xyzx, l(0.992982, 1.000000, 0.344340, 0.000000)
   7: mad o0.xyz, r2.yyyy, r1.xyzx, r0.xyzx
   8: mov o0.w, l(1.000000)
   9: ret 

Gradientのキーフレームが少ない場合はピクセルシェーダーのアセンブリも短くなるようです。

      ps_4_0
      dcl_input_ps linear v1.x
      dcl_output o0.xyzw
   0: mov_sat o0.xyz, v1.xxxx
   1: mov o0.w, l(1.000000)
   2: ret 

キーが増えてくると、Fragmentの処理も重くなってきます。

      ps_4_0
      dcl_input_ps linear v1.x
      dcl_output o0.xyzw
      dcl_temps 1
   0: add r0.x, v1.x, l(-0.500008)
   1: mul_sat r0.x, r0.x, l(2.000031)
   2: mul_sat r0.y, v1.x, l(1.999969)
   3: mad o0.xyz, r0.xxxx, -r0.yyyy, r0.yyyy
   4: mov o0.w, l(1.000000)
   5: ret 

      ps_4_0
      dcl_input_ps linear v1.x
      dcl_output o0.xyzw
      dcl_temps 3
   0: add r0.xyzw, v1.xxxx, l(-0.098543, -0.210498, -0.344457, -0.513039)
   1: mul_sat r0.xyzw, r0.xyzw, l(8.932125, 7.464977, 5.931841, 7.104065)
   2: mad r1.xyz, r0.xxxx, l(-0.908894, -0.756385, -0.650943, 0.000000), l(1.000000, 1.000000, 1.000000, 0.000000)
   3: add r2.xyz, -r1.xyzx, l(0.000000, 1.000000, 0.765280, 0.000000)
   4: mad r1.xyz, r0.yyyy, r2.xyzx, r1.xyzx
   5: add r2.xyz, -r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
   6: mad r0.xyz, r0.zzzz, r2.xyzx, r1.xyzx
   7: mad r0.xyz, r0.wwww, -r0.xyzx, r0.xyzx
   8: add r1.xyz, -r0.xyzx, l(0.000000, 0.713726, 1.000000, 0.000000)
   9: add r2.xyz, v1.xxxx, l(-0.653803, -0.788327, -0.903822, 0.000000)
  10: mul_sat r2.xyz, r2.xyzx, l(7.433638, 8.658346, 10.397434, 0.000000)
  11: mad r0.xyz, r2.xxxx, r1.xyzx, r0.xyzx
  12: add r1.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
  13: mad r0.xyz, r2.yyyy, r1.xyzx, r0.xyzx
  14: add r1.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
  15: mad o0.xyz, r2.zzzz, r1.xyzx, r0.xyzx
  16: mov o0.w, l(1.000000)
  17: ret 

SimpleNoiseは重い

SimpleNoiseは重いので、Fragment処理では使わないほうが良いでしょう。
行数は70近くあり、sincos命令も使われています。
行数: 73

      ps_4_0
      dcl_input_ps linear v1.xy
      dcl_output o0.xyzw
      dcl_temps 5
   0: mul r0.xyzw, v1.xyxy, l(500.000000, 500.000000, 250.000000, 250.000000)
   1: round_ni r1.xyzw, r0.xyzw
   2: frc r0.xyzw, r0.xyzw
   3: add r2.xyzw, r1.zwzw, l(0.000000, 1.000000, 1.000000, 1.000000)
   4: dp2 r2.x, r2.xyxx, l(12.989800, 78.233002, 0.000000, 0.000000)
   5: dp2 r2.y, r2.zwzz, l(12.989800, 78.233002, 0.000000, 0.000000)
   6: sincos r2.xy, null, r2.xyxx
   7: mul r2.xy, r2.xyxx, l(43758.546875, 43758.546875, 0.000000, 0.000000)
   8: frc r2.xy, r2.xyxx
   9: mul r3.xyzw, r0.xyzw, r0.xyzw
  10: mad r0.xyzw, -r0.xyzw, l(2.000000, 2.000000, 2.000000, 2.000000), l(3.000000, 3.000000, 3.000000, 3.000000)
  11: mul r4.xyzw, r0.xyzw, r3.xyzw
  12: mad r0.xyzw, -r3.xyzw, r0.xyzw, l(1.000000, 1.000000, 1.000000, 1.000000)
  13: mul r2.y, r2.y, r4.z
  14: mad r2.x, r0.z, r2.x, r2.y
  15: mul r2.x, r2.x, r4.w
  16: dp2 r2.y, r1.zwzz, l(12.989800, 78.233002, 0.000000, 0.000000)
  17: sincos r2.y, null, r2.y
  18: mul r2.y, r2.y, l(43758.546875)
  19: frc r2.y, r2.y
  20: add r3.xyzw, r1.xyzw, l(1.000000, 1.000000, 1.000000, 0.000000)
  21: dp2 r1.z, r3.zwzz, l(12.989800, 78.233002, 0.000000, 0.000000)
  22: dp2 r1.w, r3.xyxx, l(12.989800, 78.233002, 0.000000, 0.000000)
  23: sincos r1.zw, null, r1.zzzw
  24: mul r1.zw, r1.zzzw, l(0.000000, 0.000000, 43758.546875, 43758.546875)
  25: frc r1.zw, r1.zzzw
  26: mul r1.zw, r1.zzzw, r4.zzzx
  27: mad r0.z, r0.z, r2.y, r1.z
  28: mad r0.z, r0.w, r0.z, r2.x
  29: dp2 r0.w, r1.xyxx, l(12.989800, 78.233002, 0.000000, 0.000000)
  30: add r2.xyzw, r1.xyxy, l(1.000000, 0.000000, 0.000000, 1.000000)
  31: sincos r0.w, null, r0.w
  32: mul r0.zw, r0.zzzw, l(0.000000, 0.000000, 0.250000, 43758.546875)
  33: frc r0.w, r0.w
  34: dp2 r1.x, r2.xyxx, l(12.989800, 78.233002, 0.000000, 0.000000)
  35: dp2 r1.y, r2.zwzz, l(12.989800, 78.233002, 0.000000, 0.000000)
  36: sincos r1.xy, null, r1.xyxx
  37: mul r1.xy, r1.xyxx, l(43758.546875, 43758.546875, 0.000000, 0.000000)
  38: frc r1.xy, r1.xyxx
  39: mad r1.y, r0.x, r1.y, r1.w
  40: mul r1.xy, r1.xyxx, r4.xyxx
  41: mad r0.x, r0.x, r0.w, r1.x
  42: mad r0.x, r0.y, r0.x, r1.y
  43: mad r0.x, r0.x, l(0.125000), r0.z
  44: mul r0.yz, v1.xxyx, l(0.000000, 125.000000, 125.000000, 0.000000)
  45: round_ni r1.xy, r0.yzyy
  46: add r1.zw, r1.xxxy, l(0.000000, 0.000000, 1.000000, 1.000000)
  47: dp2 r0.w, r1.zwzz, l(12.989800, 78.233002, 0.000000, 0.000000)
  48: sincos r0.w, null, r0.w
  49: mul r0.w, r0.w, l(43758.546875)
  50: frc r0.yzw, r0.yyzw
  51: mul r1.zw, r0.yyyz, r0.yyyz
  52: mad r0.yz, -r0.yyzy, l(0.000000, 2.000000, 2.000000, 0.000000), l(0.000000, 3.000000, 3.000000, 0.000000)
  53: mul r2.xy, r0.yzyy, r1.zwzz
  54: mad r0.yz, -r1.zzwz, r0.yyzy, l(0.000000, 1.000000, 1.000000, 0.000000)
  55: mul r0.w, r0.w, r2.x
  56: add r3.xyzw, r1.xyxy, l(1.000000, 0.000000, 0.000000, 1.000000)
  57: dp2 r1.x, r1.xyxx, l(12.989800, 78.233002, 0.000000, 0.000000)
  58: sincos r1.x, null, r1.x
  59: mul r1.x, r1.x, l(43758.546875)
  60: dp2 r1.y, r3.zwzz, l(12.989800, 78.233002, 0.000000, 0.000000)
  61: dp2 r1.z, r3.xyxx, l(12.989800, 78.233002, 0.000000, 0.000000)
  62: sincos r1.yz, null, r1.yyzy
  63: mul r1.yz, r1.yyzy, l(0.000000, 43758.546875, 43758.546875, 0.000000)
  64: frc r1.xyz, r1.xyzx
  65: mul r1.z, r1.z, r2.x
  66: mad r1.x, r0.y, r1.x, r1.z
  67: mad r0.y, r0.y, r1.y, r0.w
  68: mul r0.y, r0.y, r2.y
  69: mad r0.y, r0.z, r1.x, r0.y
  70: mad o0.xyz, r0.yyyy, l(0.500000, 0.500000, 0.500000, 0.000000), r0.xxxx
  71: mov o0.w, l(1.000000)
  72: ret 
  // Approximately 0 instruction slots used

Blendノードは重い

Blendノードはそこそこ重いので、使うのは避けたほうが良いかもしれません。

Overlayブレンドの場合
行数 : 16

      ps_4_0
      dcl_constantbuffer CB0[3], immediateIndexed
      dcl_sampler s0, mode_default
      dcl_sampler s1, mode_default
      dcl_resource_texture2d (float,float,float,float) t0
      dcl_resource_texture2d (float,float,float,float) t1
      dcl_input_ps linear v1.xy
      dcl_output o0.xyzw
      dcl_temps 5
   0: sample r0.xyzw, v1.xyxx, t1.xyzw, s1
   1: add r1.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
   2: sample r2.xyzw, v1.xyxx, t0.xyzw, s0
   3: add r3.xyz, -r2.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
   4: add r3.xyz, r3.xyzx, r3.xyzx
   5: mad r1.xyz, -r3.xyzx, r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
   6: ge r3.xyz, l(0.500000, 0.500000, 0.500000, 0.000000), r2.xyzx
   7: movc r4.xyz, r3.xyzx, l(0,0,0,0), l(1.000000,1.000000,1.000000,0)
   8: and r3.xyz, r3.xyzx, l(0x3f800000, 0x3f800000, 0x3f800000, 0)
   9: mul r1.xyz, r1.xyzx, r4.xyzx
  10: mul r0.xyz, r0.xyzx, r2.xyzx
  11: add r0.xyz, r0.xyzx, r0.xyzx
  12: mad r0.xyz, r0.xyzx, r3.xyzx, r1.xyzx
  13: add r0.xyz, -r2.xyzx, r0.xyzx
  14: mad o0.xyz, cb0[2].xxxx, r0.xyzx, r2.xyzx
  15: mov o0.w, l(1.000000)
  16: ret 

Burnブレンドの場合
行数 : 9

      ps_4_0
      dcl_constantbuffer CB0[3], immediateIndexed
      dcl_sampler s0, mode_default
      dcl_sampler s1, mode_default
      dcl_resource_texture2d (float,float,float,float) t0
      dcl_resource_texture2d (float,float,float,float) t1
      dcl_input_ps linear v1.xy
      dcl_output o0.xyzw
      dcl_temps 3
   0: sample r0.xyzw, v1.xyxx, t1.xyzw, s1
   1: add r0.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
   2: sample r1.xyzw, v1.xyxx, t0.xyzw, s0
   3: add r2.xyz, r1.xyzx, l(0.000000, 0.000000, 0.000000, 0.000000)
   4: div r0.xyz, r0.xyzx, r2.xyzx
   5: add r0.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000)
   6: add r0.xyz, -r1.xyzx, r0.xyzx
   7: mad o0.xyz, cb0[2].xxxx, r0.xyzx, r1.xyzx
   8: mov o0.w, l(1.000000)
   9: ret 

Smoothstepノードはちょっと重い

行数 : 7

      ps_4_0
      dcl_constantbuffer CB0[1], immediateIndexed
      dcl_output o0.xyzw
      dcl_temps 1
   0: add r0.xy, -cb0[0].xxxx, cb0[0].yzyy
   1: div r0.x, l(1.000000, 1.000000, 1.000000, 1.000000), r0.x
   2: mul_sat r0.x, r0.x, r0.y
   3: mad r0.y, r0.x, l(-2.000000), l(3.000000)
   4: mul r0.x, r0.x, r0.x
   5: mul o0.xyz, r0.xxxx, r0.yyyy
   6: mov o0.w, l(1.000000)
   7: ret 

Rotateノードはちょっと重い

sincos命令があるので、コストは高いと考えられます。
行数 : 7

      ps_4_0
      dcl_constantbuffer CB0[2], immediateIndexed
      dcl_input_ps linear v1.xy
      dcl_output o0.xyzw
      dcl_temps 3
   0: sincos r0.x, r1.x, cb0[1].x
   1: mov r1.yz, r0.xxxx
   2: mad r0.xyz, r1.xyzx, l(0.500000, -0.500000, 0.500000, 0.000000), l(0.500000, 0.500000, 0.500000, 0.000000)
   3: mad r0.xyz, r0.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000)
   4: add r1.xy, v1.xyxx, l(-0.500000, -0.500000, 0.000000, 0.000000)
   5: dp2 r2.x, r1.xyxx, r0.xzxx
   6: dp2 r2.y, r1.yxyy, r0.xyxx
   7: add o0.xy, r2.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000)
   8: mov o0.zw, l(0,0,0,1.000000)
   9: ret 

余談 : ShadowCasterパスの頂点処理は重い

Unlit ShaderGraphはShadowCasterパスを持っています。
ShadowCasterパスの頂点処理のアセンブリを見ると、
通常の3Dモデル描画時より多くの計算が実行されていることが読み取れます。

      vs_4_0
      dcl_constantbuffer CB0[123], immediateIndexed
      dcl_constantbuffer CB1[7], immediateIndexed
      dcl_input v0.xyz
      dcl_input v1.xyz
      dcl_output_siv o0.xyzw, position
      dcl_temps 2
   0: mul r0.xyz, v0.yyyy, cb1[1].xyzx
   1: mad r0.xyz, cb1[0].xyzx, v0.xxxx, r0.xyzx
   2: mad r0.xyz, cb1[2].xyzx, v0.zzzz, r0.xyzx
   3: add r0.xyz, r0.xyzx, cb1[3].xyzx
   4: mad r0.xyz, cb0[122].xyzx, cb0[121].xxxx, r0.xyzx
   5: dp3 r1.x, v1.xyzx, cb1[4].xyzx
   6: dp3 r1.y, v1.xyzx, cb1[5].xyzx
   7: dp3 r1.z, v1.xyzx, cb1[6].xyzx
   8: dp3 r0.w, r1.xyzx, r1.xyzx
   9: max r0.w, r0.w, l(0.000000)
  10: rsq r0.w, r0.w
  11: mul r1.xyz, r0.wwww, r1.xyzx
  12: dp3_sat r0.w, cb0[122].xyzx, r1.xyzx
  13: add r0.w, -r0.w, l(1.000000)
  14: mul r0.w, r0.w, cb0[121].y
  15: mad r0.xyz, r1.xyzx, r0.wwww, r0.xyzx
  16: mul r1.xyzw, r0.yyyy, cb0[70].xyzw
  17: mad r1.xyzw, cb0[69].xyzw, r0.xxxx, r1.xyzw
  18: mad r0.xyzw, cb0[71].xyzw, r0.zzzz, r1.xyzw
  19: add r0.xyzw, r0.xyzw, cb0[72].xyzw
  20: min o0.z, r0.w, r0.z
  21: mov o0.xyw, r0.xyxw
  22: ret 

Discussion