不同平台的 GPU 性能差异显著(如高端 PC GPU 与低端移动 GPU),为确保游戏在各类设备上流畅运行,需针对性优化着色器,减少计算量和纹理读取。以下是关键优化策略及实践建议:
一、按需计算,避免冗余操作
1. 精简计算逻辑
- 移除无效参数:若材质属性(如颜色)始终为固定值,直接在着色器中硬编码,避免动态计算。
hlsl
// 反例:始终使用白色,仍从材质获取颜色 fixed4 color = _MainColor; // 优化:直接赋值 fixed4 color = fixed4(1, 1, 1, 1);
- 减少计算频率:将可在顶点阶段完成的计算(如世界坐标转换)移至顶点着色器,避免片元着色器重复计算。
hlsl
// 顶点着色器计算世界坐标并插值 void vert(inout appdata v, out v2f o) { o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 仅计算一次 // ... } // 片元着色器直接使用插值结果 float3 worldPos = i.worldPos;
2. 优先顶点着色器计算
- 适用场景:光照衰减、雾效参数等可通过顶点插值的计算。
hlsl
// 顶点着色器计算雾效因子 void vert(...) { float fogFactor = saturate((_FogStart - i.worldPos.y) / _FogRange); o.fogFactor = fogFactor; // 插值到片元着色器 } // 片元着色器直接使用插值后的雾效因子 float3 color = lerp(color, _FogColor, i.fogFactor);
二、合理选择数据精度
1. 精度类型与平台适配
类型 | 精度 | 适用场景 | 平台特性 |
---|---|---|---|
fixed | 12 位 | 简单颜色运算、布尔值 | 旧版移动 GPU(如 OpenGL ES 2.0) |
half | 16 位 | 纹理坐标、向量运算 | 现代移动 GPU(Metal/OpenGL ES 3.0+) |
float | 32 位 | 世界坐标、矩阵运算 | PC / 主机 GPU,或需要高精度的移动场景 |
2. 优化示例
- 纹理坐标:使用
half2
替代float2
(移动端)。hlsl
half2 uv = TRANSFORM_TEX(v.texcoord, _MainTex); // 半精度足够
- 颜色值:非 HDR 场景使用
fixed4
或half4
。hlsl
fixed4 color = tex2D(_MainTex, uv); // 固定精度存储颜色
三、规避高成本数学运算
1. 替代 transcendental 函数
- 避免使用:
pow
、sin
、sqrt
等函数在移动 GPU 上开销较大。 - 优化方案:
- 用
lerp
替代pow
实现简单插值(如边缘光)。 - 使用查找纹理(Lookup Texture)预处理复杂函数结果(如噪声纹理替代
sin
计算)。
- 用
2. 优先内置函数
- 使用 Unity 内置函数(如
dot
、normalize
)而非手动实现,编译器会自动生成高效代码。hlsl
// 推荐:内置归一化函数 float3 normal = normalize(v.normal); // 避免:手动计算归一化(需平方根) float3 normal = v.normal / length(v.normal);
3. 减少分支判断
- 问题:
if-else
分支会导致 GPU 管线串行化,尤其在片元着色器中影响显著。 - 优化:用数学运算替代分支(如
max
、step
函数)。hlsl
// 反例:分支判断 float intensity = lightDir > 0 ? lightDir : 0; // 优化:使用saturate float intensity = saturate(lightDir);
四、Surface Shader 专项优化
1. 指令优化
approxview
:对依赖视角的着色器(如高光),在顶点阶段归一化视角方向,减少片元计算。hlsl
#pragma surface surf BlinnPhong approxview // 顶点级视角方向
halfasview
:将半角向量(Half Vector)计算移至顶点阶段,进一步提升高光性能。hlsl
#pragma surface surf BlinnPhong halfasview // 顶点级半角向量
noforwardadd
:禁用前向渲染中的额外光源通道,仅支持 1 个主光源(其余为顶点光或 SH 光照)。hlsl
#pragma surface surf Lambert noforwardadd // 简化前向渲染路径
2. 禁用不必要特性
noambient
:关闭环境光和球面调和光照(SH),适用于无全局光照的场景。hlsl
#pragma surface surf Lambert noambient // 禁用环境光
五、纹理与渲染优化
1. 纹理采样优化
- 压缩格式:移动端使用 ETC2(Android)或 ASTC(iOS)压缩纹理,减少带宽消耗。
- Mipmap:启用纹理 Mipmap,利用
tex2Dlod
或自动 LOD 减少高频采样。hlsl
float4 color = tex2Dlod(_MainTex, float4(uv, 0, 0)); // 显式指定LOD
- 图集与数组:合并多张纹理为图集或纹理数组,减少
tex2D
调用次数。
2. 避免过度渲染
- Alpha 测试:
- 对 PowerVR GPU(iOS / 部分 Android),
clip()
开销较高,尽量用透明度混合(Blend)替代。 - 必须使用时,确保测试条件简单(如单通道阈值)。
- 对 PowerVR GPU(iOS / 部分 Android),
- ColorMask:非必要时避免屏蔽颜色通道(如
ColorMask RGB
),部分移动 GPU 对此优化不足。
六、平台特定优化策略
1. 移动端(iOS/Android)
- 精度优先:默认使用
half
和fixed
,仅在必要时提升为float
。 - 减少片元计算:将复杂光照逻辑移至顶点着色器或 CPU(如预计算光照贴图)。
- 避免特性滥用:禁用曲面细分、几何着色器等高端特性,使用
#pragma target 3.0
限制 Shader Model 版本。
2. PC / 主机
- 延迟渲染:多光源场景使用延迟渲染(Deferred Rendering),将光照计算移至 GBuffer 阶段。
- 计算着色器:利用
#pragma kernel
offload 并行计算(如粒子模拟、布料物理)。
七、性能分析与验证
- 工具链:
- Unity Profiler:定位 GPU 瓶颈(如
Gfx.WaitForPresent
表示渲染耗时)。 - RenderDoc/PIX:捕获着色器指令数、寄存器使用情况,识别低效代码。
- Unity Profiler:定位 GPU 瓶颈(如
- 测试流程:
- 在目标设备(尤其是低端机型)上实测,避免仅依赖开发机性能。
- 对比优化前后的 FPS 变化,确保优化未引入视觉失真(如精度损失导致的闪烁)。
总结:优化 Checklist
优化方向 | 具体措施 |
---|---|
计算逻辑 | 移除无效参数,顶点阶段预处理可插值数据。 |
数据精度 | 移动端用half /fixed ,PC 默认float ,避免过度精度。 |
数学运算 | 替代pow /sin 等函数,用内置函数和查找纹理。 |
Surface Shader | 使用approxview /noforwardadd 等指令,禁用冗余特性。 |
纹理与采样 | 使用压缩纹理、Mipmap、图集,减少采样次数。 |
平台适配 | 移动端规避 Alpha 测试和复杂分支,PC 利用延迟渲染和计算着色器。 |
通过系统性优化,可在保持视觉效果的前提下显著提升着色器性能,确保游戏在不同设备上的流畅运行。