【URP / iPhoneX】半透明描画の負荷 と カメラテクスチャブレンド負荷の比較
はじめに
オブジェクトを半透明で描画させたい時に、二つの方法があります。
- Blendingを利用して、半透明描画する
- カメラのカラーテクスチャをシェーダー内で合成する
どちらの方が負荷が高くなるのか気になったので、Instrumentsを利用して計測してみることにしました。
環境
- Unity2022.3.23f1
- URP14.0.10
シーンのセットアップ
計測のためにUnlit Colorシェーダーを適用したSphereを画面に配置したシーンを用意しました。
計測1 : 半透明ブレンド
カメラの正面にQuadを配置し、半透明ブレンドを行うシェーダーを適用します。
シェーダー
半透明ブレンドを行うシェーダーを使用します。
Blend SrcAlpha OneMinusSrcAlpha
シェーダーコード (Transparent.shader)
Shader "Test102/Transparent"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct appdata
{
float4 positionOS : POSITION;
};
struct v2f
{
float4 positionCS : SV_POSITION;
};
CBUFFER_START(UnityPerMaterial)
half4 _Color;
CBUFFER_END
v2f vert (appdata v)
{
v2f o;
o.positionCS = TransformObjectToHClip(v.positionOS);
return o;
}
half4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDHLSL
}
}
}
計測結果
iPhoneX上でのGPU負荷は以下のような値となりました。
fragment(ms) | vertertex(ms) |
---|---|
3.78 | 1.47 |
計測2 : CameraOpaqueTextureを合成する
CameraOpaqueTexture シェーダー内で合成することを考えます。
シェーダー
Blend One Zero
half4 frag (Varyings i) : SV_Target
{
const float2 screenPos = i.screenPosition.xy / i.screenPosition.w;
half4 color;
color.rgb = lerp(SampleSceneColor(screenPos).rgb, _Color.rgb, _Color.a);
color.a = 1;
return color;
}
シェーダーコード (BlendSceneColor.shader)
Shader "Test102/BlendSceneColor"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
Blend One Zero
ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"
struct appdata
{
float4 positionOS : POSITION;
};
struct v2f
{
float4 positionCS : SV_POSITION;
float4 screenPosition : TEXCOORD0;
};
CBUFFER_START(UnityPerMaterial)
half4 _Color;
CBUFFER_END
v2f vert (appdata v)
{
v2f o;
o.positionCS = TransformObjectToHClip(v.positionOS);
o.screenPosition = ComputeScreenPos(o.positionCS);
return o;
}
half4 frag (v2f i) : SV_Target
{
const float2 screenPos = i.screenPosition.xy / i.screenPosition.w;
half4 color;
color.rgb = lerp(SampleSceneColor(screenPos).rgb, _Color.rgb, _Color.a);
color.a = 1;
return color;
}
ENDHLSL
}
}
}
計測結果
CameraOpaqueTextureのDownSamplingを変えた時の負荷は以下のようになりました。
DownSampling | fragment(ms) | vertertex(ms) |
---|---|---|
None | 6.98 | 2.43 |
2x Bilinear | 6.39 | 2.31 |
4x Box | 6.34 | 2.59 |
4x Bilinear | 6.3 | 2.51 |
Downsampling
計測3 : CameraOpaqueTexture有効化 + Transparentシェーダー
Case2の計測結果には、CameraOpaqueTexture生成負荷が入っているため、
純粋にシェーダーのブレンディングの負荷の比較とはなりません。
そこで、CameraOpqaueTextureを有効にした状態で、Transparent.shaderを使用した時の負荷を計測してみます。
CameraOpqaueTextureは描画されますが、シェーダー内では使用されない状態です。
計測結果
DownSampling | fragment(ms) | vertertex(ms) |
---|---|---|
Downsampling : None | 7.01 | 2.51 |
Downsampling : 2x Bilinear | 6.55 | 2.3 |
Downsampling : 4x Box | 6.57 | 2.5 |
Downsampling : 4x Bilinear | 6.49 | 2.45 |
計測結果
計測結果を一つにまとめてみたものは以下になります。
fragment (ms) | vertex (ms) | fragment + vertex | |
---|---|---|---|
Opaque None | 6.98327 | 2.43479 | 9.41806 |
Opaque_2xBilinear | 6.38886 | 2.309 | 8.69786 |
Opaque_4xBox | 6.33768 | 2.59228 | 8.92996 |
Opaque_4xBilinear | 6.29805 | 2.51337 | 8.81142 |
Transparent_None | 7.01027 | 2.51285 | 9.52312 |
Transparent_2xBilinear | 6.55254 | 2.29557 | 8.84811 |
Transparent_4xBox | 6.56381 | 2.4936 | 9.05741 |
Transparent_4xBilinear | 6.48919 | 2.45124 | 8.94043 |
まとめ
- CameraOpaqueTextureを無効にして半透明描画するのが最も軽い
- CameraOpaqueTexutreを有効化すると、fragment負荷が2.7 ~ 3.3 ms ほど負荷が上がる
- CameraOpaqueTextureが有効な場合では、半透明描画より不透明描画の方が軽い
- 負荷の差分 : 0.03 ~ 0.19 ms
- 不透明描画かつ Downsampling を 2x Bilinear にしたケースが シェーダー負荷(Vertex+Fragment)が最も軽かった
おまけ : より詳細な比較
負荷のどこが増加しているのか気になったので、GPUカウンタを計測してみました。
GPUカウンタの値
fragment (ms) | vertex (ms) | fragment + vertex | F32 Utilization | F16 Utilization | Texture Sample Limiter | Texture Filtering Limiter | Texture Cache Limiter | Texture Write Limiter | Buffer Read Limiter | Buffer Write Limiter | Threadgroup/Imageblock Load Limiter | Threadgroup/Imageblock Store Limiter | Fragment Input Interpolation Limiter | GPU Last Level Cache Limiter | Vertex Occupancy | Fragment Occupancy | Compute Occupancy | GPU Read Bandwidth | GPU Write Bandwidth | MMU Limiter | MMU Utilization | Partial Renders Count | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Opaque_None | 6.98327 | 2.43479 | 9.41806 | 3.45764 | 0.03034 | 16.53634 | 27.14459 | 28.57466 | 52.41252 | 0.04555 | 0.00262 | 0.0184 | 18.70876 | 8.11922 | 64.08297 | 0.00761 | 45.52817 | 0 | 5.6927 | 8.97749 | 71.89764 | 35.44055 | 0 |
Opaque_2xBilinear | 6.38886 | 2.309 | 8.69786 | 3.46131 | 0.03433 | 16.22136 | 21.57864 | 20.8543 | 48.13993 | 0.0504 | 0.00289 | 0.02131 | 16.50318 | 7.57701 | 63.23955 | 0.00878 | 40.68932 | 0 | 4.68195 | 8.74706 | 63.46532 | 32.44388 | 0 |
Opaque_4xBox | 6.33768 | 2.59228 | 8.92996 | 4.33695 | 0.03358 | 17.98042 | 23.05799 | 21.64522 | 46.12128 | 0.05022 | 0.00276 | 0.02274 | 15.92012 | 7.06557 | 59.33887 | 0.00862 | 42.31889 | 0 | 4.79311 | 8.25396 | 60.02068 | 31.65525 | 0 |
Opaque_4xBilinear | 6.29805 | 2.51337 | 8.81142 | 3.64138 | 0.03351 | 17.39104 | 21.35605 | 21.34439 | 46.24221 | 0.04869 | 0.00278 | 0.02176 | 15.78942 | 7.9389 | 60.45347 | 0.00816 | 43.00796 | 0 | 4.7833 | 8.17352 | 59.63258 | 31.44311 | 0 |
Transparent_None | 7.01027 | 2.51285 | 9.52312 | 4.28611 | 0.76193 | 18.17287 | 25.89068 | 29.17183 | 53.42498 | 0.04531 | 0.00251 | 3.88581 | 22.52397 | 5.73857 | 63.56375 | 0.00746 | 52.8083 | 0 | 5.40715 | 9.04245 | 70.63753 | 34.83334 | 0 |
Transparent_2xBilinear | 6.55254 | 2.29557 | 8.84811 | 4.38791 | 0.78213 | 17.00159 | 20.95917 | 23.15491 | 47.81116 | 0.05001 | 0.00286 | 3.97362 | 20.30112 | 5.02193 | 64.80396 | 0.00868 | 48.6866 | 0 | 5.42256 | 8.58455 | 67.07069 | 33.84298 | 0 |
Transparent_4xBox | 6.56381 | 2.4936 | 9.05741 | 5.14535 | 0.82424 | 18.02937 | 21.69801 | 23.97855 | 46.37299 | 0.04924 | 0.00269 | 4.20084 | 20.15665 | 4.1413 | 63.34396 | 0.00842 | 49.61799 | 0 | 5.59458 | 8.3932 | 64.96178 | 33.75735 | 0 |
Transparent_4xBilinear | 6.48919 | 2.45124 | 8.94043 | 4.42764 | 0.78846 | 16.75304 | 19.82471 | 23.0775 | 46.75546 | 0.05016 | 0.00278 | 4.00472 | 19.77895 | 5.00538 | 64.83689 | 0.00868 | 48.28427 | 0 | 5.42299 | 8.41546 | 63.7453 | 33.41775 | 0 |
不透明描画かつ、Downsampling を 2x Bilinearにしたときが Vertex と Fragment を合わせた時の負荷が最も小さくなっていました。
グラフ(GPUカウンタ)
大きな変化が見られたGPUカウンタをグラフにまとめてみました。
メモリアクセス
半透明描画は、不透明描画と比べてタイルメモリへのアクセス量が増えていました
- Imageblock Load Limiter
- Imageblock Store Limite
- MMU Limiter
- MMU Utilization
テクスチャキャッシュ
半透明描画は、不透明と比べてテクスチャキャッシュの数値も増えていました
- Texture Cache Limiter
Texture read cache
Measures the time the GPU spends in the texture read cache.
https://developer.apple.com/documentation/xcode/reducing-shader-bottlenecks
グラフ(Bandwidth)
半透明描画は、Bandwidthも多く消費していました。
- GPU Read Bandwidth
- GPU Write Bandwidth
Downsampling無しの場合、不透明描画と半透明描画はBandwidthがほぼ同じですが、
テクスチャ解像度を下げると不透明描画の方のBandwidthが、半透明描画のものよりも低くなることが確認できます。
関連 (GPUカウンタ)
Discussion