资讯动态

Unity SpriteMask详解:从模板缓冲原理到遮罩不生效的排查思路

发布时间:2026/9/12 18:15:39 来源:尧图企业网站定制
做2D游戏时遇到一个需求角色立绘要显示在一个圆形相框里相框外的部分全部裁掉。我第一反应就是用Unity自带的功能做精灵遮罩于是顺手就给相框物体挂上了SpriteMask组件。结果挂上去之后画面一点反应都没有角色该露还是露。当时还以为SpriteMask是坏的后来把项目翻了个底朝天才发现这组件的使用条件远不止挂一个组件那么简单。这篇文章就把我在实战里踩过的坑全部拆开讲什么情况下SpriteMask会正常工作、哪些条件缺一个就完全失效、层级和渲染顺序到底怎么影响裁剪、URP里为什么突然不显示以及几个高频问题的完整排查思路。给第一次用SpriteMask的新手一条能直接照着走的路也给用了一段时间但还是偶尔被它坑的朋友一些可以参考的排错经验。1. 先把SpriteMask的渲染逻辑搞明白它为什么会挑对象1.1 SpriteMask的本质一次模板写入和一次模板读取要理解SpriteMask为什么有这么多使用条件得先知道它底层干了什么。SpriteMask本质上不是裁剪贴图而是利用了图形渲染里的模板缓冲Stencil Buffer机制。整个过程可以拆成两步第一步SpriteMask把自己指定的那张Sprite渲染到一个特殊的缓冲里也就是模板缓冲区。渲染时只考察这张图的透明度透明度达标的地方标记为遮罩区域不达标的地方标记为非遮罩区域。第二步场景里所有SpriteRenderer在真正画颜色的时候都会去查这个模板缓冲区。查到自己是在遮罩区域内并且被允许显示就正常画出颜色查到自己在遮罩区域外且被设置为只显示遮罩内那就直接丢弃像素不画。用喷漆来类比最好理解你先拿一块镂空钢板盖在墙面上然后再喷漆。钢板是SpriteMask漆是SpriteRenderer。先放钢板还是先喷漆顺序反了镂空效果就出不来。这里有一个很容易被误解的地方SpriteMask并不是对某个具体的GameObject做绑定式裁剪它改变的是渲染流程里的一道关卡。只要你把某个SpriteRenderer标记为参与遮罩测试那么场景里所有带SpriteMask的组件理论上都可能管到你。至于到底谁管谁就涉及后面要说的层级、顺序和影响范围了。1.2 它能做什么、不能做什么适用场景边界搞明白原理之后SpriteMask能做什么、不能做什么就会清晰很多。它擅长的场景包括2D世界空间里的不规则形状裁剪比如角色站在屋檐下被屋顶遮住半个身体探索类型的战争迷雾用遮罩把地图的可见区域切成一个圆形或扇形2D灯光照射范围配合遮罩做出只照亮某一块区域的感觉角色头像框、立绘框这类世界空间里的异形显示区域。它不擅长的场景也很明确UGUI界面元素无法用它裁剪。Image、RawImage、Text挂在Canvas下面走的完全是一套独立的UI渲染流程不参与SpriteRenderer的模板测试。很多新手把SpriteMask挂在UI物体上期待它裁掉Image结果必然是无效的。3D Mesh不能被它裁剪。MeshRenderer没有Mask Interaction这个属性它只存在于SpriteRenderer和部分2D相关的Renderer上。它不会挖洞改纹理。很多人想做刮刮乐以为用SpriteMask把刮过的地方裁掉就行但SpriteMask只改变显示不修改纹理本身。要实现刮刮乐需要另外操作Texture2D或者RenderTexture。把这些边界搞清楚可以省掉后面一大半排错时间。我自己早期就是不清楚边界在UI上白白折腾了一个下午。2. 缺一个就失效的硬性条件包体、Alpha通道、Mask Interaction2.1 包体检查为什么我的项目里找不到SpriteMask先说一个可能让某些人卡住的问题打开Unity点了Add Component搜索SpriteMask结果什么都搜不到。这种情况通常不是Unity坏了而是工程里没有2D Sprite相关的包。SpriteMask在较新版本的Unity里放在com.unity.2d.sprite这个包下面。如果你当初创建工程时选的是3D模板、Universal 3D模板这种不包含2D Sprite的模板那么包管理器里可能没有自动装上这个模块。解决办法是打开Window Package Manager在搜索框里输入2D Sprite找到之后点击Install。安装完SpriteMask组件就能正常搜到了。另外要特别注意一个情况如果你用的是URP渲染管线光有2D Sprite包还不够。URP下面分2D Renderer和3D Renderer两种SpriteMask只能在2D Renderer下正常工作。如果你的工程是从3D模板改过来的当前渲染管线用的还是UniversalRenderer而不是2D Renderer Data那SpriteMask挂上去之后Game视图里经常就是什么都不显示。关于URP的完整排查放到后面专门讲这里先提醒一句。2.2 遮罩贴图的Alpha通道与导入设置第二个硬性条件是SpriteMask组件上指定的Sprite必须带Alpha通道。注意这里说的带Alpha不是说你随便在PhotoShop里保存了一张PNG就一定有能用的Alpha。Unity导入Sprite时有一个选项叫Alpha Is Transparency如果这张图被识别成不带透明通道的图那么整个精灵的任何区域都会被当成完全不透明来处理。一个非常典型的翻车现场美术给了一张圆形的光晕图中间是白色、四周是渐变的暖黄色边缘想让它透明。图本身在PhotoShop里看着没问题但导入Unity之后因为导入设置里没勾Alpha Is Transparency整张图被处理成了一张不透明的方块图。拿它做SpriteMask的遮罩图遮罩出来的形状就不是圆形而是一个正方形边缘还带着一圈难看的黑边。所以拿到遮罩用图之后第一件事就是检查纹理导入设置Texture Type选择Sprite(2D and UI)Alpha Is Transparency一定要勾选如果有黑边检查一下Sprite本身的边缘像素是否带半透明黑边必要时在PS里清理干净。还有一点容易被忽略遮罩贴图的透明区域最好是硬过渡还是软过渡取决于你想要的效果。SpriteMask本质上是用一个阈值来判断像素属不属于遮罩区域具体看Alpha Cutoff怎么设下一节会说。2.3 Mask Interaction不主动声明遮罩就不会管你这是整个SpriteMask使用条件里最容易踩、也是文档翻阅半天才意识到的一个坑。哪怕你SpriteMask挂好了、Sprite也指定了、Alpha通道也正确只要被遮罩的SpriteRenderer上那个Mask Interaction属性还是默认的None遮罩就不会对这个物体起任何作用。Mask Interaction在SpriteRenderer组件的最下面有三个选项None不参与任何遮罩测试SpriteMask对你无效Visible Inside Mask只显示在遮罩区域内部区域外的像素全部丢弃Visible Outside Mask只显示在遮罩区域外部区域内的像素全部丢弃。这个设计初看觉得多余但实际上非常合理。它给了开发者一个选择权一个场景里挂了十个SpriteMask但并不是所有SpriteRenderer都需要被它们裁剪。每个精灵可以自己决定我要不要被遮罩管。如果你希望一个精灵在遮罩范围内才显示就得手动把它改成Visible Inside Mask。如果你想做反向裁剪比如只要不在相框内就显示那就改成Visible Outside Mask。批量修改也有技巧。选中多个SpriteRenderer时Inspector里可以直接统一修改Mask Interaction。如果是通过脚本控制可以这么写SpriteRenderer sr GetComponentSpriteRenderer(); sr.maskInteraction SpriteMaskInteraction.VisibleInsideMask;2.4 Alpha Cutoff边缘软硬的旋钮SpriteMask组件上有一个Alpha Cutoff参数默认值是0.1。它的含义是遮罩贴图里像素Alpha值大于等于这个阈值的才算有效遮罩区域低于这个阈值的视为完全透明区域。这个参数直接影响最终裁剪边缘的软硬程度。如果把Alpha Cutoff设成0.5那么只有半透明值在0.5以上的像素才能显示遮罩区域会整体缩小边缘会变得非常硬甚至出现虫蛀一样的小缺口。如果设成0.1更多低透明度像素会被算进遮罩区域边缘相对柔和一些。需要注意Alpha Cutoff只是遮罩自身的显示阈值它不会让被遮罩的精灵变半透明。你可以把遮罩理解成一个开关只判断像素在不在区域内不做透明度混合。所以千万别指望靠Alpha Cutoff实现遮罩内半透明遮罩外全透明这种效果那不是这个参数能干的活。实操建议遮罩贴图边缘如果带柔和渐变可以把Alpha Cutoff保持在默认0.1左右裁剪边缘会比较自然。如果遮罩贴在移动设备上出现边缘破碎可以适当把这个值调到0.2到0.3过滤掉压缩格式带来的Alpha噪声。3. 层级关系才是隐形规则谁先渲染、谁被裁剪3.1 渲染顺序决定一切Order in Layer的实战取值很多人在SpriteMask不生效时检查了包体、检查了Alpha、检查了Mask Interaction全部正常但依然裁剪不出来。这时候十有八九是渲染顺序的问题。前面说了SpriteMask的工作原理是先写模板缓冲、后读模板缓冲。读的那一方必须排在被读缓冲已经画好之后。在Unity的2D渲染体系里所有SpriteRenderer包括SpriteMask组件自身都按照Sorting Layer和Order in Layer从小到大排列数值越小的越先渲染。也就是说想让一个SpriteRenderer被裁掉它的渲染次序必须排在SpriteMask之后。简单讲被遮罩物体的Order in Layer要比遮罩物体的大。我自己的习惯配置是遮罩物体放在一个单独的Sorting Layer或者把Order in Layer调到比被遮罩物体小1到2的数值。例如遮罩物体的Order是0被遮罩物体的Order是10效果就很稳定。如果你把遮罩的Order设成100被遮罩物体设成0被遮罩物体会先渲染遮罩后写入模板这时候模板测试读不到已经写好的数据裁剪就会失效。这里还需要注意一个细节SpriteMask自己也占用一个渲染槽位。也就是说哪怕它只是个功能组件你照样可以在Inspector上看到Sorting Layer和Order in Layer。这地方千万别留默认值就不管了尤其是在多个SpriteMask叠加的场景里每一个Mask的渲染顺序都要想清楚。3.2 Custom Range把遮罩的影响范围圈起来SpriteMask的Inspector上有一个Custom Range选项默认是关闭的。一旦勾选之后下面会出现两组下拉框分别叫Start Sorting Layer和End Sorting Layer用来指定这个遮罩能影响的排序范围。这个功能在你只有一两个遮罩的时候好像没什么用但实际项目里场景元素一多层级动不动五六个Sorting Layer问题就来了。如果Custom Range是关闭的理论上SpriteMask影响的是所有启用了Mask Interaction的SpriteRenderer范围非常宽很难控制某个遮罩只管这一层。开启Custom Range之后规则就变成只有排序层和Order在这个区间内的SpriteRenderer才会被该遮罩影响。范围之外的物体即使你把Mask Interaction设成了Visible Inside Mask也不会被裁到。一个典型例子角色站在房子后面需要被屋顶遮住但前背景的树不应该受到屋顶遮罩的影响。如果没有Custom Range树也会参与屋顶的模板测试结果就是树也被屋顶裁掉一块画面直接穿帮。这时候把屋顶遮罩的Start和End Sorting Layer都限定在建筑层到角色层之间树所在的背景层就不会被波及。设置Custom Range时区间最好稍微留一点余量不要掐得太死。因为如果被遮罩物体正好落在边界附近不同的Unity小版本对开区间和闭区间的判定会有一点差异实际测试时容易出现有的机器裁得到、有的机器裁不到的诡异情况。3.3 多个遮罩叠加和嵌套遮罩的注意点项目里的大场景很少只用一个遮罩。比如战争迷雾可能要同时放好几块不同形状的遮罩拼接起来或者一个角色被门框裁、又被前景草裁。这种情况下多个SpriteMask之间怎么协作就成了关键。我踩过的坑是两个SpriteMask都想对同一个SpriteRenderer生效一个设成Visible Inside Mask另一个设成Visible Outside Mask。结果画面表现非常混乱一会儿整个Sprite突然消失一会儿又完整显示。后来查阅了官方文档和实际测试才明白多个SpriteMask嵌套使用时尽量把所有相关对象的Mask Interaction设成一致。也就是说要么全是Visible Inside Mask要么全是Visible Outside Mask混搭很容易出现不可预期的结果。如果想实现必须同时满足两个遮罩区域重叠部分才显示这种交集运算原生SpriteMask并不容易做好。更省心的做法是在PhotoShop里先把两张遮罩图合成一张再放到SpriteMask里用。比如门框和草叶的遮挡区域美术直接把最终可见区域画成一张图运行时就完全不用处理多个遮罩的叠加逻辑性能和稳定性都好很多。4. 从能用到好看边缘锯齿、旋转缩放和包围盒4.1 边缘锯齿的三种处理方式SpriteMask的裁剪边缘是否平滑这个问题在屏幕上非常显眼。尤其是从远处走过来的角色被遮罩挡住一半时如果是锯齿边缘观感会大打折扣。要理解锯齿为什么会出现得回到模板缓冲的工作方式。模板测试是按像素逐个判断的每个像素要么在区域内、要么在区域外没有半个像素的概念。所以即使你在项目里开了MSAA抗锯齿模板缓冲本身并不会因此变得平滑。这也是为什么很多人开了抗锯齿SpriteMask边缘还是锯齿密布。我实践下来比较有效的处理办法有三种第一种在遮罩贴图边缘留出1到2像素的Alpha渐变过渡同时把Alpha Cutoff保持在一个较低值。边缘从无到有渐变的像素越多模板测试判定时有些像素在、有些像素不在的割裂感就越弱视觉上看起来会柔和不少。第二种把遮罩贴图的分辨率提高一些。比如原来是256x256可以换成512甚至1024。遮罩图分辨率上去了同样一段弧线被更多像素覆盖锯齿的感觉自然减轻。代价是显存占用多一点但这个开销一般可以接受。第三种给SpriteMask写自定义Shader在Shader里做边缘模糊或者SDF平滑。这个方案效果最好但实现成本也最高需要对渲染管线有足够理解。如果不是特别追求极致画质我不建议新手第一版就上Shader方案先把美术贴图的边缘处理好效果就能达到八十分。另外要注意被遮罩物体本身的贴图如果带硬边描边即使遮罩边缘很柔裁剪完后边缘依然是硬的。所以做角色裁剪时难看的往往不是遮罩而是角色贴图自带的那圈轮廓。这种情况需要美术配合在角色贴图上预留出边缘过渡区域。4.2 旋转和非均匀缩放的实际限制SpriteMask作为一个普通组件挂在物体上当然可以旋转和缩放。从原理上Unity会先把遮罩精灵的网格变换到世界空间再写入模板缓冲所以旋转和缩放都会反映到最终的遮罩形状上。但实际使用中有一个限制值得注意非均匀缩放。也就是X轴的Scale和Y轴的Scale不一样的时候遮罩贴图的形状会被拉伸。如果美术给的遮罩图是圆形你在场景里把Scale设成(2, 1, 1)那它裁出来的形状就是椭圆这通常是符合预期的。但如果遮罩图本身是复杂的不规则形状非均匀缩放后边缘的走样会更明显因为图片被拉伸后同样的像素差在屏幕上跨度变大锯齿感自然加剧。另一个容易踩的坑是负缩放。比如把Scale设置成(-1, 1, 1)物体在视觉上水平翻转了SpriteMask的遮罩形状在部分Unity版本里会跟着翻转但在另一些版本里模板写入的方向会异常导致裁剪区域出现镜像错乱。如果确实需要翻转遮罩我建议在贴图导入时通过Sprite Editor的翻转来处理不要依赖运行时的负Scale避免平台差异。还有一点是关于缩放大小的把握。遮罩贴图是固定的分辨率如果你把SpriteMask的Scale放大到原来的5倍同一张图覆盖的屏幕区域变大像素密度下降边缘锯齿会更明显。所以不要试图用一张很小的遮罩图去覆盖超大区域正确的做法是准备一张足够大的遮罩图或者把多张小遮罩拼成一个大遮罩区域。4.3 包围盒判定带来的意外裁剪SpriteMask在判断遮挡时虽然最终是按像素测试但在测试之前还有一道粗略判断遮罩和被遮罩物体的包围盒如果不相交引擎可能直接跳过裁剪流程。这个优化逻辑本身是合理的但它会带来一些意外现象。最常见的现象是被遮罩的物体很大遮罩很小物体的中心点离遮罩很有一段距离只有边缘和遮罩相交。这种情况下如果引擎的裁剪判定是以整个包围盒为准那么物体的包围盒遮罩是相交的会被纳入模板测试。如果你发现某个物体明明位置在遮罩外面却不该被裁剪的结果被裁剪了很大概率就是包围盒判断把它划拉进来了。反过来也有一种情况遮罩非常小物体很大物体轮廓虽然在遮罩区域内有一小块重叠但因为物体的包围盒中心点离遮罩太远被视锥体裁剪提前剔除了。结果该显示的部分没显示出来像凭空消失了一块。所以实战中尽量让遮罩和被遮罩物体的位置保持合理的近距离。不要设计遮罩在屏幕左边被遮罩物体中心在屏幕右边二者靠边缘擦碰这种布局。这种布局不仅容易出现包围盒相关的诡异现象还会在渲染时产生多余的像素计算浪费性能。要么把遮罩区域做得足够覆盖实际需要的范围要么把被遮罩物体拆小让包围盒更贴近实际形状。5. 高频问题全链路排查完全不生效、整屏被裁、URP不显示5.1 排查链路一遮罩完全没反应这是最常见的问题症状是SpriteMask挂了、Sprite也指定了但被遮罩物体一点没有变化。遇到这种情况我建议按下面的顺序一项一项检查而不是随机试。第一步先看被遮罩物体上的Mask Interaction是不是None。这一步能解决掉50%以上的没反应问题。记住SpriteMask不会主动对任何SpriteRenderer生效必须把SpriteRenderer的Mask Interaction改成Visible Inside Mask或者Visible Outside Mask。第二步看SpriteMask上有没有指定Sprite。如果Sprite是空的遮罩宽度为零等于没有遮罩。第三步检查渲染顺序。遮罩物体的Sorting Layer和Order in Layer必须小于被遮罩物体也就是先渲染。如果发现遮罩的Order反而更大把它调小再试一次。第四步检查Custom Range。如果之前勾选了Custom Range确认被遮罩物体确实落在Start到End的范围内。常常有这种情况层级调了半天结果是被遮罩物体压根不在遮罩影响范围内。第五步如果以上都正确考虑是不是渲染管线的锅。URP下如果用的不是2D RendererSpriteMask就是摆设后面专门讲。第六步检查场景里有没有后处理或者自定义的全屏Shader比如描边、阴影、模糊之类。某些后处理效果会操作模板缓冲把SpriteMask写入的值清掉或者覆盖了导致SpriteMask失效。如果你用了比较高阶的后处理栈可以试着暂时禁掉后处理再看遮罩是否恢复。5.2 排查链路二边缘又黑又粗遮罩有效果了但边缘出现一圈黑边或者非常粗糙这个问题多半出在贴图导入和Alpha Cutoff配合上。黑边最直接的原因是遮罩贴图边缘的Alpha值不是从1平滑降为0而是从1直接掉到0。比如贴图边缘像素的颜色是深色的同时透明度还是半透明状态这个半透明深色在模板测试里如果没被完全裁掉就会留下一圈黑边。解决办法在PhotoShop里把遮罩贴图边缘做羽化让过渡更柔和在Unity纹理导入设置里勾选Alpha Is Transparency把Alpha Cutoff适当调高过滤掉那些又黑又半透明的边缘像素如果贴图边缘确实有无法处理的脏边直接在图片编辑工具里把最外圈的像素Alpha清零。另外被遮罩物体贴图本身如果带描边描边越宽裁剪后的视觉黑边越明显。这个只能在角色或者物体的美术设计层面来解决比如把描边做成只出现在物体外圈而不在裁剪边缘处出现。如果遮罩边缘出现的是破碎感或者颗粒感而不是完整的黑边那通常是压缩格式的问题。Android平台下ETC、ASTC压缩会对Alpha通道做有损处理低质量的压缩档会让Alpha值产生明显偏移。这时把纹理压缩质量调高或者把Alpha Cutoff调高一点过滤掉偏移导致的杂点。5.3 排查链路三URP管线下面突然不工作了这是URP项目里最高频的一个问题。症状是同样的场景在Built-in管线下SpriteMask工作得好好的一换到URP就完全没效果。原因我在前面提到过URP下SpriteMask依赖的是2D Renderer。URP支持多种Renderer类型2D Renderer是专门为2D精灵、SpriteMask、2D光照准备的。如果你的URP Asset配置里用的是UniversalRenderer也就是默认的3D Renderer那SpriteMask就不会被渲染。解决方案分几步第一步在Project窗口里右键Create Rendering URP 2D Renderer创建一个2D Renderer Data资源。第二步打开你的URP管线资源URP Asset在General下的Renderer List里把刚刚创建的2D Renderer Data加进去。第三步如果工程里多个URP Asset或者有Quality设置要确认当前运行的平台实际使用的是哪一个URP Asset别改了半天发现换的是另一个品质级别下面的资源。改完之后2D场景里的SpriteMask应该能正常工作了。但要注意2D Renderer和3D Renderer的渲染特性不一样如果你是一个2D/3D混合项目切到2D Renderer可能会影响3D物体的光照和渲染效果。这种情况下要么把3D部分单独用不同的相机和管线处理要么放弃SpriteMask改用Shader方案来实现类似裁剪效果。没有万能的解决方案全看项目需求。6. 边界场景与替代方案UI遮罩、粒子、动态挖洞6.1 UGUI想用SpriteMask怎么办先说结论SpriteMask不能用在UGUI上。如果你打开一个Canvas把SpriteMask挂在Canvas下的Image物体上它不会有任何效果。这是因为UGUI有自己独立的渲染队列不走SpriteRenderer的模板测试通道。如果想在UI里做异形裁剪常见的替代方案有三个第一个RectMask2D。这是UGUI里最简单的矩形裁剪组件适合头像框、滚动列表这类矩形区域裁剪。它只支持矩形不支持异形。第二个Mask组件。它需要搭配一张带Alpha的Image作为遮罩图。把Mask组件挂在父物体上子物体就会被父物体的Alpha区域裁剪。圆形头像框、五角星按钮这类异形UI裁剪用Mask组件都能做。缺点是Mask会产生额外的Draw Call如果一个界面里有很多Mask批处理会被打散性能要留意。第三个自定义Shader裁剪。这是UI异形裁剪里最灵活但成本也最高的方案。你可以在UI的Shader里加入裁剪区域的判断甚至用参数控制裁剪位置和形状。适合对性能要求高、Mask数量多的项目。缺点是Shader一旦写在材质里后续调整就不是美术在Inspector里拖拖弄弄就能完成的了需要程序介入。如果你确实需要世界空间里的UI被SpriteMask裁剪比如一个飘在空中的立绘框答案依然是不行。World Space Canvas里的元素同样不受SpriteMask影响。这种情况我会建议把你的UI元素改成SpriteRenderer来实现用精灵图片代替Image这样就能回到SpriteMask的适用范围里。代价是UI碰撞、事件处理都要自己做但对于世界空间里被遮挡的标签、立绘这类需求这条路反而是最稳的。6.2 粒子系统的遮罩控制ParticleSystem虽然是独立的粒子系统但它的Renderer模块里也有Mask Interaction选项。也就是说粒子可以被SpriteMask裁剪。具体操作是在粒子系统的Renderer模块里找到Mask Interaction属性改成Visible Inside Mask或者Visible Outside Mask。这里有几个坑第一粒子的Material必须支持模板测试。默认的Particle/Standard Unlit和URP下的Particle/Unlit是支持的但如果你使用了自己写的自定义Shader这个Shader又没有做模板相关的处理那Mask Interaction设置了也没用。第二粒子系统渲染模式需要是Billboard或者Sprite如果是Mesh模式行为会不一样有的版本甚至完全不参与模板测试。第三粒子系统本身不能作为SpriteMask的遮罩来源。意思是你没有办法让一团粒子形成的形状充当遮罩去裁剪其他SpriteRenderer。如果你需要粒子形状的遮罩思路要反过来让粒子被一个SpriteMask裁剪到只剩下你想要的形状或者用一个相机把粒子渲染到RenderTexture再把这个RenderTexture当作SpriteMask的Sprite来源。第二种方式灵活很多但实现复杂度也高。6.3 刮刮乐和战争迷雾动态修改遮罩的正确姿势很多游戏想用SpriteMask做刮刮乐比如玩家用手指刮开涂层看到下面的奖励。直观的思路是把遮罩贴图上刮过的区域变透明这样该区域就从遮罩区域里消失涂层下面的东西露出来。这个思路本身没错但实现方式要注意。如果直接用Texture2D的SetPixel然后Apply在小尺寸贴图上勉强能用但一旦遮罩贴图是512x512甚至更高性能会非常差因为每帧都要把整张贴图重新上传到GPU还可能出现画面明显的卡顿。更实际的做法是使用RenderTexture准备一个RenderTexture作为遮罩纹理用一个专门的Brush相机或者Graphics.DrawTexture之类的接口把玩家的笔触画到这张RenderTexture上每次手指移动时往RenderTexture上画擦除用的笔触再把这张RenderTexture动态创建成一个Sprite赋值给SpriteMask的sprite字段。这里要特别注意SpriteMask使用的Sprite可以由Sprite.Create从RenderTexture实时创建但要注意释放旧Sprite避免每帧都new一个Sprite造成内存泄漏。另外RenderTexture在部分移动端上的格式兼容也要提前测试。战争迷雾比刮刮乐更复杂一点因为通常不是擦除而是要实时更新可见范围。更稳妥的方案不是单个SpriteMask而是用多张遮罩图拼合或者直接改用Shader做半径剔除、用2D光照配合法线遮挡。我的建议是如果只是短期表现、范围可控可以用SpriteMask加动态修改贴图如果是全地图大范围的探索迷雾还是老老实实走RenderTexture或者Shader路线别在SpriteMask一棵树上吊死。6.4 性能账单一个SpriteMask的隐藏开销SpriteMask用起来方便但性能上并不是完全免费的。它的开销来自几个方面第一遮罩本身要额外渲染一次这是一个多余的Pass。哪怕遮罩图只是一个简单圆形GPU也要遍历一遍这张贴图的所有像素去写模板缓冲。第二被遮罩的SpriteRenderer在渲染时多增加了一次模板读取和判断虽然这个开销不算大但如果场景里有大量SpriteRenderer都被设成了Visible Inside MaskDraw Call和像素处理量会明显上升。第三SpriteMask会打乱合批。正常情况下同一排序层里相同材质的Sprite可以被Unity合批。挂了SpriteMask之后参与模板测试的物体和没参与的物体可能被拆成不同的批次Draw Call数量增加。第四如果你开了Custom Range或者同时存在多个不同模板状态的SpriteMask有些GPU需要切换模板状态进一步影响性能。基于这些我个人对性能的取舍经验是场景中同时可见的SpriteMask数量尽量控制在10个以内移动端建议更少能用一张大遮罩解决的就不要拆成十张小遮罩遮罩贴图尺寸不要无脑上4K够用就好毕竟它不参与最终颜色显示只需要精确表达区域边界用Custom Range把影响范围收紧避免远处那些根本不需要被裁的物体都挂上Mask Interaction静态场景里的固定遮挡区域最好让美术直接把画面裁好或者烘焙成遮挡贴图不要在运行时再交给SpriteMask。从一个性能敏感的项目角度看SpriteMask更适合做动态的、数量有限的、和玩法交互强相关的裁剪。静态的、大批量的遮挡用美术图预处理永远是最划算的。我自己现在的处理习惯是每次接到带裁剪需求的任务先问自己三个问题能被美术直接裁掉吗能被Shader更轻量地实现吗一定要运行时动态裁剪吗如果前两个答案是能我就不会用SpriteMask。只有动态、不规则、跟玩法实时交互的裁剪需求才是SpriteMask真正不可替代的舞台。把它的使用条件摸透了它就是一个非常好用的2D裁剪利器摸不透就是项目里最让人抓狂的幽灵组件。希望这篇文章能让你少走一些我走过的弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价