mcpField
M
mobile-shader-optimization

mobile-shader-optimization

github.com/adevra开发1651 次安装31 周前更新
代码仓库

关于 mobile-shader-optimization

mobile-shader-optimization 是面向 Unity 开发的技能,用于优化移动设备及其他 GPU 受限平台的着色器。内容涵盖精度选择、GPU 开销、渲染方案、LOD 和性能分析工具。

主要功能

  1. 01为计算选择精度类型
  2. 02减少分支与纹理采样
  3. 03参考移动 GPU 渲染特性
  4. 04设置着色器 LOD 与回退层级
  5. 05配置面向移动平台的 URP
  6. 06使用 GPU 分析工具进行性能分析

适用场景

  1. 01检查 Unity 着色器的移动端性能
  2. 02为 Android 或 iOS 调整着色器
  3. 03优化低端设备或 VR 的图形表现
  4. 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 时,也应触发此技能。


核心规则 — 硬性限制

编写移动端着色器时,必须始终遵守以下规则:

  1. 默认使用 half 精度。 仅在世界空间位置、需要高精度的 UV 坐标和深度计算中使用 float。在 Mali 和 Adreno GPU 上,half 会启用 16 位快速路径,速度提升 2 倍。

  2. 在 PowerVR 或 Apple GPU 上绝不使用 discard(clip/alpha-test)。 这会破坏 Early-Z/Hidden Surface Removal,迫使 GPU 处理本可跳过的片元。请改用 Alpha 混合,或将 discard 移到单独的 Pass 中。

  3. 移动端的 Shader Graph 节点数应少于 100。 每个节点都会编译为着色器指令。超过约 100 个节点可能导致超出移动 GPU 指令限制,并造成性能断崖式下降。

  4. 面向 OpenGL ES 2.0 / WebGL 1.0 的着色器中,绝不使用 while 或 do-while 循环。 只允许使用计数型 for 循环。即使在 ES 3.0 及更高版本中,也要避免动态循环边界,因为它们会阻止编译器展开循环。

  5. 避免在移动端使用动态分支。 移动 GPU 通常会执行两个分支中的全部内容。请使用 step()、lerp()、saturate() 等无分支替代方案。

  6. 先乘标量,再乘向量。 float3 result = scalar1 * scalar2 * vector 比 float3 result = vector * scalar1 * scalar2 更快,因为前者的第一次标量乘法只需 1 个 ALU 操作,而不是 3 个。

  7. 限制纹理采样次数。 每次采样都会消耗带宽。低端移动设备上,每个片元的纹理采样目标应为 ≤4 次。可使用通道打包来合并贴图(参见 texture-packing-variant-stripping 技能)。

  8. 使用 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

来源

评论

支持 Markdown