关于 mobile-shader-optimization
mobile-shader-optimization 是面向 Unity 开发的技能,用于优化移动设备及其他 GPU 受限平台的着色器。内容涵盖精度选择、GPU 开销、渲染方案、LOD 和性能分析工具。
主要功能
- 01为计算选择精度类型
- 02减少分支与纹理采样
- 03参考移动 GPU 渲染特性
- 04设置着色器 LOD 与回退层级
- 05配置面向移动平台的 URP
- 06使用 GPU 分析工具进行性能分析
适用场景
- 01检查 Unity 着色器的移动端性能
- 02为 Android 或 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”,以及任何面向低于 60 fps 场景的着色器工作。用户分析着色器性能、询问 GPU 架构,或比较 URP 中 Simple Lit 与 Complex Lit 时,也应触发此技能。
核心规则 — 硬性限制
编写移动端着色器时,必须始终遵守以下规则:
-
默认使用
half精度。 仅在世界空间位置、需要高精度的 UV 坐标和深度计算中使用float。在 Mali 和 Adreno GPU 上,half会启用 16 位快速路径,速度提升 2 倍。 -
在 PowerVR 或 Apple GPU 上绝不使用
discard(clip/alpha-test)。 这会破坏 Early-Z/Hidden Surface Removal,迫使 GPU 处理本可跳过的片元。请改用 Alpha 混合,或将 discard 移到单独的 Pass 中。 -
移动端的 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更快,因为前者的第一次标量乘法只需 1 个 ALU 操作,而不是 3 个。 -
限制纹理采样次数。 每次采样都会消耗带宽。低端移动设备上,每个片元的纹理采样目标应为 ≤4 次。可使用通道打包来合并贴图(参见
texture-packing-variant-stripping技能)。 -
使用
noforwardadd表面着色器指令,跳过额外的逐像素光照 Pass。每增加一个光照 Pass,过度绘制就会加倍。
移动 GPU 架构
了解基于瓦片的渲染对于优化移动端着色器至关重要。
基于瓦片的延迟渲染(TBDR)
移动 GPU 会将屏幕划分为小瓦片(Mali 为 16×16,Adreno 则可变),并在写入主存储器之前,完全在芯片上处理每个瓦片。这意味着:
- 带宽是主要瓶颈,而非计算。每次纹理读取、帧缓冲区写入和渲染目标切换都会消耗主存储器带宽。
- 过度绘制代价极高,因为每个重叠片元都会占用片上瓦片内存,还可能触发主存储器写入。
- 在基于瓦片的 GPU 上,MSAA 几乎没有额外成本(在片上解析),而桌面 GPU 上的 MSAA 会增加帧缓冲区带宽消耗。
- 渲染目标切换代价高昂——每次切换都会将瓦片内存刷新到主存储器。请尽量减少后期处理 Pass。
- 功耗预算为 3–6W(桌面 GPU 为 300W)。如果着色器过于复杂,几分钟内就会触发热降频,降低时钟频率。
各 GPU 系列说明
- Mali(ARM):16×16 瓦片。使用 Mali Offline Compiler 检查周期数。相比延迟渲染,优先使用前向渲染。
- Adreno(Qualcomm):瓦片大小灵活(约 256K)。支持 LRZ(Low-Resolution Z)以便提前剔除。对复杂片元着色器的容忍度略高。
- PowerVR(Apple/Imagination):真正的 TBDR,带有 HSR(Hidden Surface Removal)。Alpha 测试/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 资源:
- 渲染:使用前向渲染,不启用深度预通过(会增加一个预通道),MSAA 设为 2x 或 4x(在基于图块的 GPU 上开销较低)
- 质量:除非需要 Bloom,否则禁用 HDR(HDR 会使帧缓冲带宽翻倍)。若只使用主方向光,则禁用额外光源。
- 阴影:降低阴影分辨率(移动设备设为 1024 或 512)。缩短阴影距离。移动设备使用 1 级阴影级联(最多 2 级)。
- 后处理:不需要时禁用。需要时使用单通道方法(参见
mobile-post-processing技能)。
优化检查清单
检查移动着色器时,请确认:
- 所有颜色/法线计算均使用
half精度 - 只有世界坐标和 UV 使用
float - iOS/PowerVR 目标上没有
discard/clip(或将其隔离在单独的通道中) - 没有动态分支(改用 step/lerp/saturate)
- 每个片段的纹理采样次数 ≤ 4
- 没有
while/do-while循环 - 标量乘法在向量乘法之前执行
- 将与视角无关的计算移至顶点着色器
- 兼容 SRP Batcher(属性位于 CBUFFER 中)
- 至少为 2 个层级定义 LOD SubShader
- 曲面着色器使用
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 手册:着色器性能 — https://docs.unity3d.com/Manual/SL-ShaderPerformance.html
- Unity 手册:移动端优化 — https://docs.unity3d.com/2019.4/Documentation/Manual/MobileOptimisation.html
- Unity 手册:数据类型与精度 — 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