ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MeshRenderer渲染排序:SortingLayer与Order in Layer设置与排查

MeshRenderer渲染排序:SortingLayer与Order in Layer设置与排查 做Unity的都知道渲染排序是个看着不起眼、踩坑能踩到怀疑人生的东西。尤其是项目里2D和3D混着来的时候一个MeshRenderer突然飘到UI上面或者被不该挡的东西挡住了那种感觉就像你明明排好了队结果有人直接插队到你前面还一脸无辜。这篇就专门聊聊MeshRenderer的SortingLayer和Order in Layer怎么设置、为什么设置了没反应、以及实际项目中怎么排查这类排序问题。不管你是刚开始接触Unity的新手还是被排序逼疯过的老手这篇应该都能给你一点参考。1. 渲染排序的核心机制与SortingLayer解析1.1 渲染管线是怎么决定“谁先画”的要搞懂排序问题得先明白一个基本事实Unity最终提交给GPU绘制的顺序是由CPU端计算出来的一个“绘制顺序”决定的。GPU本身不关心谁是角色、谁是背景、谁先谁后它只知道按顺序执行DrawCall。也就是说你看到的遮挡关系、谁压谁、谁透明谁不透明本质上都是CPU端把这个顺序排好之后的结果。Unity内部有好几套排序规则在同时起作用它们之间不是“取一个最终值”那么简单而是有严格优先级。按优先级从高到低大概是这样渲染队列RenderQueueShader里Queue标签定义的值这个优先级最高不透明物体默认在透明物体之前绘制。由摄像机depth深度参数决定的相机渲染顺序多个相机叠加时后渲染的相机会盖住先渲染的。SortingLayer和Order in Layer同属于同一渲染队列且由同一相机渲染时这两个参数决定谁在前面。当以上全部相同时才轮到Transform的Z轴位置、材质ID等更深层的排序规则。很多人在2D项目里习惯用SortingLayer和Order in Layer去控制一切但一旦某个物体用了不透明Shader另一个用了透明Shader规则就变了不透明物体永远先画哪怕它的Order是-1000透明物体后画哪怕它的Order是1000。这是渲染队列本身的限制不是Bug。那这个时候该怎么办后面第三章会讲到怎么通过自定义Shader的Queue值来突破这个限制这里先记住结论Shder的Queue标签是总闸门SortingLayer和Order in Layer只是同一闸门下的细排。1.2 SortingLayer与Order in Layer到底改了渲染管线里的什么在Unity编辑器里选中一个挂载了MeshRenderer的物体Inspector面板最底部通常能找到一个叫“Sorting Layer”的下拉框以及旁边一个“Order in Layer”的数值输入框。很多从3D项目转过来的朋友会一脸懵我这MeshRenderer上找不到这个选项啊。先说这个选项在哪。默认情况下MeshRenderer的Inspector上是不会直接显示SortingLayer的——只有当你给这个物体用的Shader是“透明系”的比如Sprites/Default、Legacy Shaders/Transparent/Diffuse、或者是自定义的Transparent队列ShaderInspector最底下才会冒出来这两个选项。如果是标准不透明Shader比如Standard、Universal Render Pipeline/Lit默认是不会显示的。这不是因为你的Unity坏了而是因为不透明物体压根不需要排序——它靠深度缓冲来决定谁挡谁。所以这里就出现了一个很典型的误区网上很多教程说“给MeshRenderer设置SortingLayer就可以让它显示在UI前面”结果照着做发现找不到设置项。大概率就是材质Shader用的不透明队列。你需要先确认你的ShaderQueue是不是Transparent系如果不是就得先把Shader换成透明系或者在代码里动态设置material.renderQueueSortingLayer和Order才会真正生效。当你成功设置了SortingLayerUnity其实是在内部维护一张排序链表。每个相机渲染前会把所有要绘制的物体按“队列 - SortingLayer ID - Order in Layer - 相机距离”这个顺序塞进一个有序列表里然后依次发DrawCall。SortingLayer的下拉顺序就是这张链表的优先级顺序列表排在下面的Layer会优先绘制渲染时自然就被后面绘制的物体遮盖。记住一个关键点SortingLayer的排序优先级和那些Layer在列表里显示的上下位置有关最底部的Layer最先绘制最顶部的最后绘制。所以如果你有背景BackgroundLayer和前景ForegroundLayer背景应该在列表靠下前景靠上。1.3 透明还是不透明结果完全不同我用一个很直白的例子来说明透明和不透明在排序上的巨大差异。假设场景里有两个MeshRenderer物体一个挡在另一个前面。如果它们都用的是不透明Shader那么GPU会先画后面的物体再把前面物体的像素写到颜色缓冲里同时更新深度缓冲后面物体被挡住的部分直接在深度测试阶段被剔除掉了最终结果完全正确。这个过程压根不依赖SortingLayer或者说SortingLayer在这里的作用微乎其微。但如果这两个物体都用的是透明Shader情况就变了。透明物体在Unity里默认关闭了深度写入ZWrite Off绘制顺序就显得至关重要必须先画远处的透明物体再画近处的透明物体否则远处的透明物体会被近处的透明物体挡住或者出现异常的混合效果。这就是为什么透明物体必须依赖SortingLayer和Order in Layer来做人为排序以及为什么同一排序层级下Unity还会按照“从后往前”相对于相机距离的顺序去画。但如果你用的是透视相机两个透明物体距离相机都差不多或者网格模型本身比较复杂排序还是容易出错。所以我的建议是能用不透明Shader就尽量不要用透明Shader去做层级叠加。特别是带AlphaTest的树叶、栅栏这类东西能拆成不透明块就拆开。透明排序永远是Unreal和Unity都绕不开的坑它跟SortingLayer本身没什么关系而是透明混合模式的物理局限性。2. 排序优先级全景图UI、粒子、Sprite、Mesh怎么互相排队2.1 渲染队列Render Queue优先于一切我把渲染队列单独拎出来讲是因为真正常见的“排序失效”问题根源往往在这里。Unity的Shader里有一个Queue标签常用的值包括Background1000、Geometry2000、AlphaTest2450、Transparent3000和Overlay4000。数字越小绘制越早。Background是天空盒和背景专用的Geometry是所有不透明物体的默认值AlphaTest是带透明度测试的比如树叶、草Transparent是标准透明物体Overlay则是UI、镜头光晕这类最顶层的东西。当你在MeshRenderer上设置了SortingLayer和Order in Layer时它们只对处于同一个渲染队列里的物体有效。举个例子一个Geometry队列的MeshRenderer哪怕Order in Layer设成9999也不可能画在一个Transparent队列的物体前面因为前者早就画完了后者是最后画的。这就像你先让一班同学进教室坐好不透明再让二班同学进教室站在他们旁边透明一班的同学永远不可能因为“坐的位置靠前”就跑到二班同学前面。所以排查排序问题时第一步永远是先看材质当前的Queue值。你可以用以下代码在运行时打印出来看看using UnityEngine; public class CheckRenderQueue : MonoBehaviour { void Start() { Renderer renderer GetComponentRenderer(); if (renderer ! null) { foreach (Material mat in renderer.materials) { Debug.Log(${gameObject.name} 的材质 {mat.name} 的Queue是: {mat.renderQueue}); } } } }正常情况下不透明物体打印出来是1000、2000、2450这类小于3000的值透明物体是3000UI常用的是3000或4000。如果你发现自己设置的Order没生效先跑这段代码看看Queue到底是多少。2.2 UI层和世界空间Mesh谁上谁下这是Unity开发中日常出现频率最高的排序冲突之一3D物体飘到了UI上面。例如角色头上有血条血条是UI Canvas里的Image而角色是场景里的MeshRenderer两者明明不在同一个坐标系里却经常出现互相穿插、遮盖不对的情况。要理清这个问题首先得知道Unity UI的渲染机制Canvas默认使用Screen Space - Overlay模式时UI元素会被单独渲染到一个图层上这个图层的渲染队列是Overlay4000。而场景中的MeshRenderer默认是Geometry2000或Transparent3000。所以理论上UI永远会盖在场景物体上面因为Overlay队列最后画。但这里有一个隐性前提相机。如果Canvas用的是Screen Space - Camera模式也就是UI被指定到一个特定的相机比如UICamera上去渲染那么UI的绘制就发生在该相机渲染的那一步。如果UICamera的Depth比主相机大UI依然会盖在上面但如果两个相机的Depth设置反了或者混合使用了一些后期处理特效UI就可能被场景物体遮挡。至于MeshRenderer本身想盖住UI我个人可以很明确地回复你在默认的Screen Space - Overlay模式下普通MeshRenderer没有SortingLayer和Order in Layer能压过Overlay队列的UI除非把渲染队列改成Overlay4000以上。但这样做会带来其他问题——你的MeshRenderer会跟UI抢绘制顺序UI的Canvas自身内部也有管理排序的机制结果很容易把整个场景搞乱。真要做3D物件浮在UI上方的效果更稳妥的方案是用World Space Canvas或在Shader里用单独的Overlay Pass去处理。2.3 粒子系统和MeshRenderer如何互相排队另一个高频冲突是粒子系统和MeshRenderer之间的排序。粒子的渲染也是走Renderer管的ParticleSystemRenderer同样有SortingLayer和Order in Layer用法和MeshRenderer完全一致。但粒子材质的Shader很多是Transparent队列例如Particles/Standard Unlit这意味着粒子天然属于“后画”的那一批。如果你想让一个不透明MeshRenderer显示在粒子前面设置SortingLayer是没用的因为二者不在同一Queue——除非你给MeshRenderer的Shader也改成透明系。而两个物体都是Transparent队列时SortingLayer和Order in Layer就可以决定优先级了。这也是为什么很多2D游戏里关战斗卡牌特效、飘字、光环都会把SortingLayer设为“Foreground”、“FX”这类靠上的层级并把Order设成很大比如999确保它们能盖住角色和地面。我在一个项目中就遇到过角色的刀光特效粒子系统在特定角度被地面上的召唤法阵MeshRenderer盖住。排查了半天最后发现法阵的Shader是Geometry队列但开了AlphaClip粒子是Transparent队列死活排不上去。最终给法阵的ShaderQueue从Geometry改成AlphaTest再到Transparent配合SortingLayer才解决。如果你的粒子特效本身也是Mesh比如使用了Mesh Particle那就更要注意了ParticleSystemRenderer的排序规则和MeshRenderer是两套独立配置即使同一个物体上又挂MeshRenderer又挂ParticleSystemRenderer它们各自都有独立的SortingLayer和Order in Layer需要分别设置。3. 实操过程MeshRenderer排序的完整配置方案3.1 在编辑器中给MeshRenderer开启SortingLayer选项这节直接上操作。第一步新建一个Mesh物体比如Cube或Quad挂上MeshRenderer。确保它的材质Shader是透明队列的。如果不会改Shader最简单的方式是降级到内置管线用Sprites/Default这个Shader如果你的项目是URP用Universal Render Pipeline/Unlit并勾选Surface Type为Transparent。第二步选中MeshRenderer在Inspector最底部找到Sorting Layer下拉框。刚创建的项目默认只有Default一层这时候需要点“Sorting Layers...”进入Tag Manager或者直接在Project Settings - Tags and Layers里操作把图层列表按你的需求从上往下排好。举一个常规2DRPG项目的SortingLayer排序示例从底部到顶部Background最底层背景Ground地面Character角色Effect特效Foreground前景遮挡物记住我刚才说的列表靠下的画得越晚显示越靠前。所以Priority最高的层放在最下面。这个顺序很多人第一次搞反结果角色死活被地板挡住。第三步把MeshRenderer的Sorting Layer选为CharacterOrder in Layer设成具体数值。Order in Layer的数值只在同一个SortingLayer内部起到比较作用数值大的画在数值小的前面。不同SortingLayer之间则只看层本身的上下顺序跟Order无关。这里有一个容易产生误区的地方你设了Order 100不一定能盖过另一个Order 0但SortingLayer更靠下的物体。因为层优先级高直接碾压Order值。3.2 代码动态排序运行时改SortingLayer和Order编辑器里手动设置是一种方式但项目开发中更常见的是运行时动态修改。代码访问接口特别简单using UnityEngine; public class MeshSortingSetter : MonoBehaviour { [SerializeField] private string sortingLayerName Character; [SerializeField] private int orderInLayer 100; private void Start() { MeshRenderer meshRenderer GetComponentMeshRenderer(); if (meshRenderer null) return; // 修改SortingLayer有两种方式一种是直接传字符串名字 meshRenderer.sortingLayerName sortingLayerName; // 一种是通过LayerID推荐用这个避免字符串拼错 int layerID SortingLayer.NameToID(sortingLayerName); meshRenderer.sortingLayerID layerID; meshRenderer.sortingOrder orderInLayer; } }这段代码同样适用于SpriteRenderer、ParticleSystemRenderer、LineRenderer、TrailRenderer它们都继承自Renderer都有sortingLayerName、sortingLayerID和sortingOrder这三个属性。另外如果是Shader里已经写死了Queue标签和渲染队列改Renderer的SortingLayer不会改变Queue需要单独改材质参数// 把材质的渲染队列设为透明 GetComponentRenderer().material.renderQueue (int)UnityEngine.Rendering.RenderQueue.Transparent;改renderQueue是一把双刃剑。好处是你可以把一个本来不透明的物体硬塞到透明队列来启用SortingLayer坏处是你可能因此破坏了Unity原本针对不透明物体的深度写入优化可能出现透明穿插、深度测试混乱的奇怪效果。所以我个人的态度是能不改Queue就不改Queue尽量通过Shader里的Queue标签来决定而不是运行时硬改材质。3.3 和Shader、Camera配合的关键细节到了这一层就涉及真正的工程经验了。先说Shader侧。一个标准透明Shader的Queue标签大概长这样SubShader { Tags { QueueTransparent RenderTypeTransparent IgnoreProjectorTrue } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off ... }大概长这样。你如果综合使用d fro 上述的Tags里“Queue”设为TransparentMeshRenderer的SortingLayer和Order才会被Unity识别并纳入排序。但这里有一个容易被忽略的问题URP环境下URP的Lit/Unlit Shader里的Surface Type如果设成TransparentUnity会自动把Queue改成Transparent这个没毛病。但如果你创建了自己的ShaderGraph很多人只在Surface选项里选了Transparent却没有设置Alpha最后看到物体还是以不透明方式渲染排序不生效排查半天一无所获——这往往是因为URP的ShaderGraph里“Surface”和“Alpha Clip”是分开的两个选项你只改了Surface却没处理Alpha输出导致的。再说Camera侧。如果你有多个相机每个相机执行Culling和排序是独立的。MeshRenderer的SortingLayer和Order只对该相机最终的输出图像有效不同相机叠加时最终图像取决于相机的Depth值和ClearFlags。你可以用Camera.depth来控制后渲染的相机图像盖住先渲染的。这种情况下让后渲染相机把ClearFlags设为Dont Clear再渲染一次场景排序通常是就相机顺序来的。有一个非常好用的技巧在URP里如果你想实现类似“3D物体显示在UI后面但依然在世界空间”的效果可以让主相机只渲染场景物体Culling Mask去掉UI然后用另一个专门渲染UI的相机Culling Mask只选UI并且把这个UI相机的Depth设得比主相机大。这样UI必然盖住场景。具体能不能被MeshRenderer反过来盖住则看你给UI相机设置的ClearFlags和渲染顺序和SortingLayer反而关系不大了。3.4 一个典型实例地图角色脚下光环的排序修复这里分享一个我实际遇到的案例。项目里有这么个需求角色脚下要有一个圆形的法阵特效MeshRenderer透明材质角色本身是SpriteRenderer脚下还要有一块地形地形是白模MeshRenderer不透明材质。问题出现了法阵特效在Z轴上明明比角色低更远离相机却依然挡住了角色导致角色站在法阵里显得像被踩在特效上面视觉极其违和。排查过程第一步确认法阵Shader的Queue。打印出来是3000Transparent角色的Sprite默认Queue也是3000。好两者同队列SortingLayer可以起作用。第二步看SortingLayer。法阵被分在“Ground”层角色在“Character”层。根据排序优先级Character层在列表更靠下晚绘制所以角色应该盖住法阵但实际却是法阵盖住角色——这说明问题不在SortingLayer而在同一个SortingLayer内的一切可能。第三步检查Transform的Z轴。Unity透明队列在相同SortingLayer内会按距离相机的远近来决定谁先画。在2D正交相机下Z越小的物体离相机越近应该后绘制。但我发现法阵的Transform Z被设得比角色更小也就是离相机更近这才导致法阵后绘制盖在角色上了。法阵明明在地面上这是美术摆模型时手滑把Z轴拖过头了。把法阵Z轴调整到角色Z轴之后更大的Z值排序立刻恢复正常。这个案例很有代表性很多时候排序Bug不是SortingLayer配错了而是同一个Layer里不同物体的Z坐标互相“打架”。尤其是透视相机下Unity会根据相机到物体包围盒的距离计算绘制顺序模型中心点位置稍有偏移排序顺序就会乱。所以在使用透明队列时建议把物体的Pivot统一放在底部或中心对齐避免因为模型原点不同导致距离测算异常。4. 常见排序问题与排查技巧实录4.1 问题速查表我整理了实际项目中排序问题的排查顺序和信息把它们整理成一个速查表现象优先检查项排查方向设置了Order但完全不生效材质Shader的Queue标签把材质renderQueue打印出来确认是不是1000/2000这类不透明队列透明物体互相遮挡错乱两个物体的SortingLayer是否一致、Z轴远近统一SortingLayer调整Z轴或Order让近处的物体后画MeshRenderer挡住UICanvas渲染模式、相机DepthScreen Space - Overlay下Mesh几乎不可能合法盖过UI考虑改World Space Canvas粒子被地面盖住粒子材质是否为透明队列、粒子的SortingLayer把粒子SortingLayer放靠上层或把地面Queue改成Transparent同SortingLayer同Order仍错乱Transform Z轴、模型包围盒中心在透视相机下包围盒中心越近的越后画调整Pivot位置物体在部分手机上排序不同是否开启动态合批合批会改变绘制顺序临时关闭Dynamic Batching试一下这张表不是标准答案但基本覆盖了日常80%的排序问题。你先对着现象找到对应行往往能节省大量排查时间。4.2 为什么Order in Layer一样还是会乱序这是新手问得最多的问题。Order in Layer相同但透明物体之间依然会出现“你挡我我挡你”的错乱。原因在于透明队列本身有一个隐藏的次级排序规则距离相机远近。透视相机下Unity按物体包围盒中心到相机的距离进行排序距离近的反而后画以保证近处透明物体能正确地混合在远处物体前面。这里就有一个经典陷阱你的模型原点在中心地面模型又大又长相机看过去的时候地面模型的包围盒中心可能离相机非常近于是地面被判定为“近处物体”后画了把角色身上一层本该在后面的半透明披风给遮挡住了。你无论怎么调Order in Layer都没有用因为Order相同Unity在内部比较的是距离而距离计算依据的是整个模型包围盒中心不是单个顶点或贴图的可见范围。针对这个问题我的习惯做法是透明物体尽量缩小模型体积拆成小块网格。在代码里给Renderer设置一个自定义的排序偏移如果你用Built-in Render Pipeline可以尝试修改Renderer的bounds中心点。比如// 偏移包围盒中心让Unity认为这个物体的中心更远或者更近 Renderer r GetComponentRenderer(); Bounds bounds r.bounds; bounds.center new Vector3(0, 100, 0); // 这里的修改可能不会直接改变渲染顺序严谨地说Renderer.bounds是只读的不能直接这样改。要让Unity对距离排序产生偏移更靠谱的办法是把模型的Pivot改到合适位置或者在透明Shader里根据视角方向做深度偏移URP里可以加一个Depth Offset节点。复杂归复杂但这才是最根本的玩法。4.3 同屏2D和3D混合排序的终极方案如果你的项目像我的项目一样同屏既有Tilemap2D、又有角色Sprite、还有少量3D装饰MeshRenderer再加一堆特效ParticleSystem我最推崇的排序方案是所有相关渲染器共用一个排序根SortingLayer并且通过Order in Layer做细分。例如创建一个名为“World”的SortingLayer然后统一分配区间0-99地面、地板100-199站立角色人物脚下200-299特效刀光、脚印300-399角色身体主体400-499弹道、飞行的箭矢500-599前景遮挡物树叶、栏杆同一个2D平面上的物体就用Order in Layer区分而不再用多个SortingLayer。这样可以最大化利用Unity的批量渲染机会同时减少层之间的切换开销。多数情况下SortingLayer的数量一般控制在5个以内就够了。我见过一些项目把SortingLayer建了几十层最后美术自己都分不清哪个层在哪个层前面一旦做批量改层级就是灾难。这个方法还有个别的好处代码里做临时排序非常方便需要让某个物体显示在别的物体上时直接改 meshRenderer.sortingOrder 200; 然后改回来不需要去动SortingLayer的全局配置。但要注意SortingLayer和Order only对透明队列有效。如果你的3D装饰物是标准不透明材质它们依然会被按照深度缓冲去排序和Order in Layer没关系。要强制让不透明物体也遵循这套2D排序规则要么给它们改用透明Shader要么在Shader的Queue标签上做文章。很多打通2D和3D的团队会写一个允许开关“RenderQueue Transparent”的标准透明色Shader以实现一个材质同时兼容深度和2D排序。4.4 性能与合批排序修改会不会影响性能这个问题得老实交代频繁修改SortingLayer和Order in Layer通常不会带来很大的性能压力因为它们只改CPU端的一个整型字段不涉及网格、材质或Shader重新提交。真正可能影响性能的是你因为修改了Order导致Unity无法合批。合批的一个前置条件之一是参与合批的渲染器需要有相同的SortingLayer和Order in Layer。如果你把一个角色身上的MeshRenderer从Order 100改成101即使它旁边另一个MeshRenderer还是100两者依然可以各合各的。但如果你把十几个原本Order相同的物件改得各不相同就会破坏GPU Instancing / SRP Batcher可能产生的合批机会DrawCall数量就会随着上浮。所以我的建议是静态场景中固定不变的排序关系尽量在编辑器里配置好不要写脚本每帧去改。需要动态改变排序的对象限制在少数几个比如角色脚下光环、当前选中的高亮效果避免大范围动态修改。使用SRP BatcherURP默认开启的项目排序变化对合批的影响通常小于使用传统内置渲染管线的项目因为SRP Batcher对材质属性变化的处理更高效但SortingOrder变化依然会导致渲染顺序重排确实会造成一定的CPU开销只是没那么明显。我遇到过一帧里大量物体排序动态变化的Demo帧数从120掉到40就是因为每个物体都在Update里改sortingOrder导致Unity重建整个渲染队列。后来把排序改成只有状态变化时才更新物体进入某个区域时才触发修改帧数立刻就恢复回来了。5. 几个容易忽略的排序细节与经验补充5.1 SortingLayer在URP和内置管线里的差异如果你从内置管线升级到URP很可能会踩一个隐形的坑URP环境下某些Shader的Queue处理方式会和内置管线不一样。比如内置管线的Standard Shader你在Surface Options里可以选择TransparentUnity会把它切到Transparent队列排序正常。但URP的Lit Shader里如果你选择了Transparent模式但没设置Alpha输出渲染效果往往是半透明排序逻辑根本没启用物体照样按不透明去画天上飘着一个看起来半透明却完全不参与透明排序的Mesh。这种情况下如果你发现MeshRenderer找不到SortingLayer请打开URP的材质面板确认Surface Type是Transparent且Alpha值被连接了。如果是自定义ShaderGraph还要额外勾选Depth Write避免透明物体自身遮挡自己。5.2 SpriteRenderer和MeshRenderer谁先谁后很多教程会说SpriteRenderer的SortingLayer和Order in Layer用法和MeshRenderer一样。从API层面来说确实如此它们继承同一个Renderer基类。但在实际项目中两者混合使用时有一个额外注意事项SpriteRenderer的默认渲染队列是由Sprite材质Shader是Sprites/Default决定的这个Shader的Queue是Transparent3000所以Sprite天然属于透明队列而MeshRenderer如果用的是标准不透明Shader它属于Geometry队列2000。当Sprite想“显示在”一个不透明Mesh的上面时不需要设置任何东西因为Mesh早就画完了Sprite后画自然在前面。但如果想让不透明Mesh“显示在”Sprite前面让Mesh盖住Sprite就麻烦一点——你需要给Mesh的Shader改成透明队列否则它的排序只在Geometry队列内部有效压根不会和Sprite比谁前谁后。这个概念很多人第一次接触时会觉得很奇怪我们平时总觉得“3D物体在2D角色后面是天经地义”。在Unity里并不是天经地义——它是靠渲染队列差序强行实现的。一旦你不小心把角色Sprite的材质从Sprites/Default换成一个不透明的3D专用Shader角色立刻就会被所有3D物体盖住什么SortingLayer都救不回来。5.3 阴影和接收阴影对排序的影响还有一个经常被忽视的维度阴影。Unity里阴影的绘制是独立于普通物体渲染的。一个物体即使被其他物体遮挡只要它的Cast Shadows开启它仍会向阴影贴图里写入。你可能会看到一种诡异的现象一个3D物体被2D地面覆盖之后地面是半透明的但半透明地面上依然出现了3D物体的阴影轮廓。这不是排序问题而是阴影贴图阶段不受透明排序影响的结果。如果你想让半透明地面也接收或遮蔽阴影通常需要在材质上处理ShadowCaster Pass或者在URP的Decal、Depth Priming等功能里单独配置。遇到这种场景我的建议是不要试图通过调整Order去影响阴影而是直接控制阴影选项把不需要阴影的透明物体Cast Shadows设为Off把地面Receive Shadows设为On。这一块设计到项目里的表现效果具体怎么配看项目需求但我看到过不少项目为了省事统一打开阴影导致透明排序的视觉问题雪上加霜。所以排查也提供了一个新维度如果MeshRenderer排序看着没问题但阴影不对先从Cast Shadows和Receive Shadows下手而不是动Shader队列。5.4 运行时修改材质与排序的坑很多时候你的MeshRenderer材质是在代码里实例化出来的比如为了给不同角色赋予不同颜色。这时如果对材质调用了material.renderQueue很可能会破坏SRP Batcher的合批条件导致DrawCall飙升而且还会在修改材质的瞬间出现一闪而过的透明排序错误。我推荐的做法是所有需要针对排序做控制的材质在导入时就通过材质资产的“Render Queue”属性在Inspector面板的Advanced Options里直接设定好。代码里尽量只修改sortingLayerName和sortingOrder不要频繁改renderQueue。确有必要在运行时动态切换透明和非透明考虑使用两个材质球做切换而不是在同一个材质上反复改Queue。原因在于Unity对材质和渲染队列的缓存机制比较微妙频繁修改会导致RenderThread阻塞甚至引起某些移动端设备的驱动异常。6. 我个人的一些实战习惯这些习惯不一定适合所有项目但如果你恰好也被乱七八糟的排序问题整得头疼可以拿走直接用。第一项目初期就建SortingLayer的规范文档。哪怕团队只有你一个人也要把每层的用途和预留区间写清楚。等美术把几十个Prefab做出来后再回头补这些配置成本至少翻三倍。第二遇到排序问题先开Frame Debugger。Unity的Window Analysis Frame Debugger能看到每一个DrawCall的排序依据包括Renderer的Queue、SortingLayer、Order。这比对着代码猜快太多了。我经常看到有人在一个排序Bug上卡一下午打开Frame Debugger五分钟定位问题。第三透明物体的SortingLayer和Order配置尽量在Prefab级别做而不是场景实例级别做。这样你调整了一次可以同步到所有引用避免在不同场景里漏改。第四不要迷信“把Order设成9999就能永远在最上面”这种粗暴方案。它短期内有效但一旦项目里长出四五个把Order设成9999的物体排序规则就变成了谁后写的谁赢最终结果完全不可预测。我见过一个项目里所有特效Order都是9999结果新加一个全屏闪光特效时它出现在所有UI的最顶层但又得让它显示在部分UI下面代码里强制改半天最后还是把所有特效的Order规划成阶梯区间才彻底解决。排序这个东西本质上和项目里的代码架构一样你前期不规划后期债就找上门。你在一个Prefab上的随手一拖可能就是别人排查三个小时的痛苦根源。所以哪怕多花十分钟整理一下规则后面省下的时间绝对不止十分钟。
返回列表