在Unity的UI开发中,遮罩(Mask)是个高频使用的功能。简单来说,遮罩就像给内容加了个"相框"——只有框内的部分可见,框外的都被隐藏。这种效果在制作滚动列表、圆形头像、特殊形状UI时特别有用。
我最近负责一个需要适配手机、Pico一体机和华为Glass眼镜的项目,深刻体会到不同平台对遮罩支持的差异。Unity自带的Mask组件在PC上表现完美,但到了移动端就可能出现各种"掉链子"的情况。比如在华为手机上,明明设置了Mask,内容却像脱缰的野马一样不受控制地全部显示出来。
这种情况在跨平台开发中很常见。不同设备的GPU架构、渲染管线、Shader支持都存在差异。比如某些低端手机可能不支持模板缓冲(Stencil Buffer),而Pico这样的VR设备对渲染性能有更严格的要求。理解这些底层差异,才能选对合适的遮罩方案。
Unity的标准Mask组件使用起来非常简单:
csharp复制// 示例:通过代码动态添加Mask
GameObject maskObj = new GameObject("UI Mask");
maskObj.AddComponent<Image>();
maskObj.AddComponent<Mask>();
关键点在于遮罩图片不能完全透明。我在项目中用过一张中间透明、边缘半透明的圆形图片,在PC和部分安卓设备上效果不错。但正如我同事遇到的,同样的设置在华为某些机型上就失效了。
经过反复测试,发现Mask组件的这些问题:
根本原因在于Mask依赖Unity的模板测试(Stencil Test)实现。当设备GPU不支持或限制了模板缓冲时,遮罩就会失效。这种情况在WebGL平台也时有发生。
RectMask2D是Unity后来引入的矩形专用遮罩方案。与Mask不同,它不依赖模板测试,而是通过计算像素位置来实现裁剪。使用方法:
csharp复制// RectMask2D代码示例
RectMask2D rectMask = gameObject.AddComponent<RectMask2D>();
rectMask.padding = new Vector4(10, 10, 10, 10); // 左,上,右,下
实测发现RectMask2D在以下平台表现稳定:
但在Pico一体机上发现个有趣的现象:当配合ScrollRect使用时,快速滑动会导致被遮罩的内容突然消失。经过分析,这是因为VR设备的高刷新率(72/90Hz)与UI重绘节奏不同步导致的。
解决方案是调整Canvas的渲染模式:
SoftMask是Asset Store上的第三方插件,支持更灵活的遮罩效果。安装后需要:
csharp复制// SoftMask初始化代码
SoftMask softMask = gameObject.AddComponent<SoftMask>();
softMask.channel = MaskChannel.Red; // 使用红色通道作为遮罩
项目中用TextMeshPro时遇到了shader不兼容的问题。错误提示明确指出需要自定义shader支持。解决方法:
shader复制// 关键Shader代码片段
Stencil {
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
在华为Glass眼镜上测试三种方案:
虽然SoftMask功能最强,但在性能敏感场景,RectMask2D仍是更好的选择。
| 特性 | Mask | RectMask2D | SoftMask |
|---|---|---|---|
| 形状支持 | 任意 | 仅矩形 | 任意 |
| 边缘柔化 | 不支持 | 不支持 | 支持 |
| TMP支持 | 部分 | 完全 | 需配置 |
| 性能开销 | 中 | 低 | 中高 |
| 跨平台稳定性 | 一般 | 优秀 | 良好 |
根据项目经验,我总结这些选择原则:
对于我的多平台项目,最终方案是:
当遮罩不工作时,可以这样排查:
Pico设备上特有的两个问题及解法:
发现同时使用多个SoftMask会打断UI合批。优化方法是:
csharp复制void Update() {
// 每3帧更新一次遮罩
if(Time.frameCount % 3 == 0) {
softMask.UpdateMask();
}
}
针对移动端的内存优化:
csharp复制// 对象池示例
public class MaskPool : MonoBehaviour {
Queue<SoftMask> pool = new Queue<SoftMask>();
public SoftMask GetMask() {
return pool.Count > 0 ? pool.Dequeue() : Instantiate(maskPrefab);
}
public void ReturnMask(SoftMask mask) {
mask.Reset();
pool.Enqueue(mask);
}
}
当需要支持特殊效果时,可以修改SoftMask的shader:
shader复制fixed4 frag(v2f i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv);
float mask = tex2D(_SoftMask, i.uv).r;
col.a *= smoothstep(_Softness, 1.0, mask);
return col;
}
对于需要多层遮罩的情况,可以使用RGB通道分别控制:
shader复制float maskR = tex2D(_SoftMask, i.uv).r;
float maskG = tex2D(_SoftMask, i.uv).g;
float maskB = tex2D(_SoftMask, i.uv).b;
float finalMask = max(maskR, max(maskG, maskB));
这套方案在制作复杂UI转场时特别有用,我在华为Glass项目中使用它实现了视差滚动效果。