mobile-shader-optimization について
mobile-shader-optimizationは、モバイルなどGPUリソースが限られた環境向けにUnityシェーダーを最適化する開発スキルです。精度、GPU負荷、レンダリング方式、LOD、プロファイリングツールを扱います。
主な機能
- 01計算に使う精度型を選択
- 02分岐とテクスチャサンプリングを削減
- 03モバイルGPUのレンダリング特性を考慮
- 04シェーダーLODとフォールバック層を設定
- 05モバイル向けにURPを構成
- 06GPU解析ツールでプロファイリング
活用シーン
- 01Unityシェーダーのモバイル性能を確認
- 02AndroidやiOS向けにシェーダーを調整
- 03低性能デバイスやVR向けに描画を最適化
- 04プロファイリングでGPUのボトルネックを調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ adevra/ unity-shader-agent-skills --skill mobile-shader-optimizationインストール後、Claude Code で/mobile-shader-optimizationを入力して読み込まれたか確認します。
詳細
Unity 向けモバイルシェーダーの最適化
トリガー
ユーザーがモバイルプラットフォーム(Android、iOS)や、GPU リソースに制約のある環境(Meta Quest VR、ローエンドデバイス)向けの Unity シェーダーを作成、レビュー、最適化している場合に、このスキルを使用してください。トリガーには、「mobile」、「Android」、「iOS」、「Mali」、「Adreno」、「PowerVR」、「performance」、「optimization」、「fillrate」、「overdraw」、「bandwidth」、「frame budget」、「half precision」、「LOD」、「baked lighting」に関する言及や、60fps 未満を対象としたシェーダー作業などがあります。また、ユーザーがシェーダーのプロファイリングや GPU アーキテクチャについて質問している場合、または URP で Simple Lit と Complex Lit を比較している場合にもトリガーしてください。
コア規則 — 厳守事項
モバイルシェーダーを記述する際は、常に以下の規則に従う必要があります。
-
デフォルトでは
half精度を使用してください。 ワールド空間の位置、高精度が必要な UV 座標、深度計算にのみfloatを使用します。Mali および Adreno GPU では、halfによって 16 ビットの高速処理パスが有効になり、2 倍高速になります。 -
PowerVR または Apple の GPU では、
discard(clip/アルファテスト)を絶対に使用しないでください。 Early-Z/Hidden Surface Removal が機能しなくなり、本来ならスキップされるフラグメントまで GPU が処理することになります。代わりにアルファブレンディングを使用するか、discard を別のパスに移してください。 -
モバイル向けの Shader Graph のノード数は 100 未満にしてください。 各ノードはシェーダー命令にコンパイルされます。約 100 ノードを超えると、モバイル GPU の命令数制限を超えるリスクがあり、パフォーマンスが急激に低下する原因になります。
-
OpenGL ES 2.0/WebGL 1.0 を対象とするシェーダーでは、
whileまたはdo-whileループを絶対に使用しないでください。 カウント付きのforループのみ使用できます。ES 3.0 以降でも、コンパイラーによるループのアンロールを妨げるため、動的なループ境界は避けてください。 -
モバイルでは動的分岐を避けてください。 モバイル GPU は両方の分岐を実行することがよくあります。分岐を使わない代替手段として、
step()、lerp()、saturate()を使用してください。 -
ベクトルより先にスカラーを乗算してください。
float3 result = scalar1 * scalar2 * vectorは、float3 result = vector * scalar1 * scalar2より高速です。最初のスカラー乗算は 3 ALU 演算ではなく 1 演算で済むためです。 -
テクスチャサンプルを制限してください。 サンプルごとに帯域幅を消費します。ローエンドのモバイルでは、フラグメントあたりのテクスチャサンプル数を 4 以下にすることを目標にしてください。マップを結合するには、チャンネルパッキングを使用してください(
texture-packing-variant-strippingスキルを参照)。 -
追加のピクセルごとのライトパスを省略するには、
noforwardaddサーフェスシェーダーディレクティブを使用してください。ライトパスが 1 つ増えるごとにオーバードローは 2 倍になります。
モバイル GPU アーキテクチャ
タイルベースレンダリングを理解することは、モバイルシェーダーの最適化に不可欠です。
タイルベース遅延レンダリング(TBDR)
モバイル GPU は画面を小さなタイル(Mali は 16×16、Adreno は可変)に分割し、メインメモリに書き込む前に各タイルをオンチップで完全に処理します。つまり、次のことを意味します。
- 主なボトルネックは演算ではなく帯域幅です。 テクスチャ読み取り、フレームバッファーへの書き込み、レンダーターゲットの切り替えはすべて、メインメモリの帯域幅を消費します。
- オーバードローは非常に高くつきます。 重なり合うフラグメントはそれぞれオンチップのタイルメモリを消費し、メインメモリへの書き込みが発生する可能性があります。
- タイルベース GPU では MSAA はほぼ無料です(オンチップで解決されます)。 デスクトップではフレームバッファーの帯域幅が増加しますが、タイルベース GPU ではその影響がありません。
- レンダーターゲットの切り替えにはコストがかかります。 切り替えのたびにタイルメモリがメインメモリへフラッシュされます。ポストプロセスのパスを最小限にしてください。
- 電力予算は 3~6W です(デスクトップは 300W)。 シェーダーが複雑すぎると、数分以内にサーマルスロットリングが発生し、クロック速度が低下します。
GPU ファミリーごとの注意点
- Mali(ARM):16×16 タイルです。Mali Offline Compiler でサイクル数を確認してください。遅延レンダリングよりフォワードレンダリングが適しています。
- Adreno(Qualcomm):タイルサイズは柔軟です(約 256K)。早期の棄却に LRZ(Low-Resolution Z)をサポートします。複雑なフラグメントシェーダーにもやや強い耐性があります。
- PowerVR(Apple/Imagination):真の TBDR で、HSR(Hidden Surface Removal)を備えています。アルファテスト/discard は HSR を機能させなくします。Apple GPU には専用の
halfハードウェアがあります。 - Apple A シリーズ:Metal と密接に統合されています。
halfは実際に 16 ビットで、処理性能は 2 倍です。floatは 32 ビットです。fixed型はありません。
精度型
// HLSL/ShaderLab precision mapping to GLSL:
// float → highp (32-bit) — positions, UVs, depth
// half → mediump (16-bit) — colors, normals, most calculations
// fixed → lowp (11-bit) — simple color ops only (deprecated on many GPUs)
// USE half FOR:
half4 color = tex2D(_MainTex, uv); // Texture samples
half3 normal = normalize(i.worldNormal); // Normals
half NdotL = saturate(dot(normal, lightDir)); // Lighting
half fresnel = pow(1.0h - NdotL, 4.0h); // Fresnel
// USE float FOR:
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // World position
float2 uv = TRANSFORM_TEX(v.texcoord, _MainTex); // UV transforms
float depth = Linear01Depth(rawDepth); // Depth
頂点とフラグメントの最適化
フィルレートがボトルネックとなるモバイル GPU では、頂点数はフラグメント数より桁違いに少なくなります。見た目に問題がない範囲で、計算を頂点シェーダーに移してください。
// BAD — per-pixel calculation (runs millions of times)
half4 frag(v2f i) : SV_Target {
half rim = 1.0h - saturate(dot(normalize(i.viewDir), normalize(i.worldNormal)));
half rimPower = pow(rim, _RimPower);
return _RimColor * rimPower;
}
// GOOD — per-vertex calculation (runs thousands of times)
v2f vert(appdata v) {
v2f o;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
float3 viewDir = normalize(GetWorldSpaceViewDir(TransformObjectToWorld(v.vertex.xyz)));
float3 worldNormal = TransformObjectToWorldNormal(v.normal);
o.rimFactor = pow(1.0 - saturate(dot(viewDir, worldNormal)), _RimPower);
return o;
}
half4 frag(v2f i) : SV_Target {
return _RimColor * i.rimFactor; // Just interpolated value
}
頂点シェーダーに移す計算:
- リム/フレネル計算
- 視線方向の正規化
- フォグ係数の計算
- シンプルな UV アニメーションのオフセット
- 光源方向との内積(フラット/ローポリスタイル向け)
フラグメントシェーダーに残す処理:
- テクスチャサンプリング
- 法線マップの参照
- 精密なスペキュラハイライト
- スクリーンスペースエフェクト
シェーダーの LOD とフォールバック戦略
LOD ティアを使用して、デバイスの性能に応じた複雑さのシェーダーを提供します。
Shader "Game/Character" {
// LOD 300: Full quality (desktop, high-end mobile)
SubShader {
LOD 300
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass {
// Normal map + specular + rim + emission
}
}
// LOD 150: Medium quality (mid-range mobile)
SubShader {
LOD 150
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass {
// Diffuse + simple lighting, no normal map
}
}
// LOD 100: Minimum quality (low-end mobile)
SubShader {
LOD 100
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass {
// Vertex-lit only, single texture
}
}
Fallback "Universal Render Pipeline/Simple Lit"
}
デバイスに応じてランタイムに設定します。
// In a device detection script:
if (SystemInfo.graphicsMemorySize < 1024)
Shader.globalMaximumLOD = 100;
else if (SystemInfo.graphicsMemorySize < 2048)
Shader.globalMaximumLOD = 150;
else
Shader.globalMaximumLOD = 300;
組み込みの LOD 値:Mobile/VertexLit = 100、Mobile/Diffuse = 150、Mobile/Bumped = 200。
モバイル向けベイク済みライティング
ライティングを完全にベイクすると、リアルタイムのシャドウマップが不要になり、フラグメントシェーダーの処理が単一のライトマップ取得に削減されます。ローエンドデバイスでは、これが 30 FPS と 60 FPS の差になることもよくあります。
ルール:
- モバイルでは、すべての静的ジオメトリにベイク済みライティングを使用します。 ライトマップのサンプリングは、リアルタイムのシャドウマッピングよりはるかに低コストです。
- リアルタイムライトは 1 つのディレクショナルライト(メインライト)に制限します。ライトを追加すると、ピクセル単位のパスが増えます。
- ディレクショナルライトが最も低コストで、ポイントライトが最も高コストです(リアルタイムの場合、6 方向のシャドウマップが必要です)。
- リアルタイムライトの代わりに、動的オブジェクトにはライトプローブを使用します。
- モバイルではディファードレンダリングは適していません。複数のレンダーターゲットが必要になるため、タイルメモリのフラッシュが複数回発生します。必ずフォワードレンダリングを使用してください。
モバイル向け URP アセット設定
モバイル向けに URP アセットを設定します。
- レンダリング:フォワードレンダリング、Depth Priming なし(プリパスが追加されます)、MSAA 2x または 4x(タイル型 GPU では低コストです)
- 品質:Bloom に必要な場合を除き、HDR を無効にします(HDR によってフレームバッファー帯域幅が倍増します)。メインのディレクショナルライトのみを使用する場合は、Additional Lights を無効にします。
- シャドウ:シャドウ解像度を下げます(モバイルでは 1024 または 512)。シャドウ距離を短くします。モバイルではシャドウカスケードを 1 つにします(最大でも 2 つ)。
- ポストプロセッシング:不要な場合は無効にします。必要な場合は、シングルパス方式を使用します(
mobile-post-processingスキルを参照)。
最適化チェックリスト
モバイルシェーダーをレビューする際は、以下を確認します。
- すべての色/法線計算で
half精度を使用している -
floatを使用しているのはワールド座標と UV のみ - iOS/PowerVR のターゲットで
discard/clip を使用していない(または別のパスに分離している) - 動的分岐がない(step/lerp/saturate に置き換えている)
- フラグメントあたりのテクスチャサンプル数が 4 以下
-
while/do-whileループがない - スカラー乗算をベクトル乗算より先に行っている
- 視点に依存しない計算を頂点シェーダーに移している
- SRP Batcher に対応している(プロパティが CBUFFER 内にある)
- 少なくとも 2 つのティア向けに LOD SubShaders を定義している
- サーフェスシェーダーで
noforwardaddを使用している(または URP で追加ライトを無効にしている) - Standard Shader を使用していない — Simple Lit またはカスタム Unlit を使用する
プロファイリングツール
- Unity Frame Debugger(Window > Analysis > Frame Debugger)— ドローコール、シェーダーパス、オーバードローを調査します
- Mali Offline Compiler — ARM Mali GPU の命令サイクル数を確認します
- Snapdragon Profiler — Adreno GPU のカウンターをプロファイリングします
- Arm Performance Studio (Streamline) — Mali GPU のパフォーマンスを詳細に分析します
- RenderDoc — フレームキャプチャとドローごとの分析を行います
- Xcode GPU Profiler — シェーダーコストのヒートマップを使用して Apple GPU をプロファイリングします
出典
- Unity Manual:シェーダーのパフォーマンス — https://docs.unity3d.com/Manual/SL-ShaderPerformance.html
- Unity Manual:モバイル向け最適化 — https://docs.unity3d.com/2019.4/Documentation/Manual/MobileOptimisation.html
- Unity Manual:データ型と精度 — https://docs.unity3d.com/2019.4/Documentation/Manual/SL-DataTypesAndPrecision.html
- Unity:シェーダー最適化のヒント — https://create.unity.com/shader-optimization
- Unity:モバイルアートの最適化 パート2 — https://unity.com/how-to/mobile-game-optimization-tips-part-2
- Meta Developer:GPU タイルレンダリング — https://developers.meta.com/horizon/documentation/unity/gpu-tiled/
- ARM Community:モバイルでのポストプロセス — https://community.arm.com/arm-community-blogs/b/graphics-gaming-and-vr-blog/posts/post-processing-effects-on-mobile-optimization-and-alternatives
- Quick Unity Tips:シェーダー最適化 2025 — https://quickunitytips.blogspot.com/2025/11/unity-shader-optimization-guide-2025.html
- Toxigon:モバイルシェーダーの最適化 — https://toxigon.com/optimizing-unity-shaders-for-mobile-performance
- UWA Blog:GPU 負荷 — https://blog.en.uwa4d.com/2022/12/28/unity-mobile-game-performance-optimization-the-balance-between-graphics-performance-and-gpu-pressure/
- Unity Forum:Unity 6 モバイル向けのヒント — https://discussions.unity.com/t/unity-6-mobile-graphics-optimization-tips-and-tricks/1593293
- Android Developer:URP アセット設定 — https://developer.android.com/develop/xr/unity/performance/urp-asset-settings
- TheGamedev.Guru:オーバードロー — https://thegamedev.guru/unity-gpu-performance/overdraw-optimization/
- ColinLeung-NiloCat:URP トゥーンシェーダー — https://github.com/ColinLeung-NiloCat/UnityURPToonLitShaderExample
- awesome-mobile-graphics — https://github.com/shihchinw/awesome-mobile-graphics