ShaderGraphコンパイル後のアセンブリコードを読んでみる
はじめに
UnityのShaderGraphってどのくらい重いんだろうか?と気になったので、ShaderGraphの計算負荷を調べてみました。
ShaderGraphをシェーダーコンパイルした際のアセンブリコード(d3d asm)を読んでみようと思います。
シェーダーコンパイルはD3Dプラットフォームで行っています。
対象読者
・軽いシェーダーを書くことに興味がある人
・ShaderLabよりShaderGraphの方が重そうだと疑っている人
環境
- Unity2020.3.13f1
- Universal RP 10.5.1
比較したもの
- シェーダーPassの比較
- シェーダーコンパイル後のアセンブリコードを比較
シェーダーコンパイルの流れ
まずは、ShaderGraphやShaderLabがコンパイルされる流れについておさらいしてみましょう。
ShaderLab
ShaderLabは、シェーダーコンパイラによってコンパイルされ、プラットフォームごとのシェーダーへと変換されます。
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シェーダーコードに変換されます。
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)
をコピーするようなアセンブリになっています。
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コード
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のアセンブリ比較
ps_4_0
dcl_output o0.xyzw
0: mov o0.xyzw, l(0.100000,0.200000,0.300000,0.400000)
1: ret
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での頂点シェーダーのアセンブリは、ほぼ同じ処理になっています。
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
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