ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

UE4半透明渲染顺序:Instanced Mesh排序问题与解决方案

UE4半透明渲染顺序:Instanced Mesh排序问题与解决方案 1. 先搞清楚半透明渲染顺序到底是谁在排序半透明物体渲染顺序不对这几乎是所有做过材质、特效或场景整合的UE4开发者都绕不过去的坑。尤其是当你把一堆带透明度材质的东西拖进Instanced Mesh或者HISM/ISM里统一绘制之后画面上的穿帮一下子就冒出来了树叶明明应该被前面的树枝挡住结果透明部分却把树干整个透视过来或者两个半透明面片互相穿插视觉上乱七八糟。要解决这个问题第一步不是急着改材质、调节点而是得先明白UE4到底是怎么决定半透明物体绘制顺序的。搞懂这一层后面对症下药才不容易踩一踩一坑又掉进另一个坑。1.1 半透明材质为什么必须排序先聊一个最基本的概念不透明物体不需要排序。因为不透明物体在渲染时会写深度缓冲GPU能通过深度测试自动判断哪个像素在前面、哪个在后面后画的物体如果离摄像机更远它的片元深度比缓冲里已存的深度更大就直接被丢弃了。这个过程是逐像素级别的画错顺序也无所谓反正深度测试兜底。但半透明物体不是这样。它需要混合也就是把当前片元的颜色和帧缓冲里已有的颜色做混合运算这就意味着“先画谁、后画谁”会直接影响最终结果。比如一个绿色半透明面片和一个红色半透明面片叠加先画绿再画红和先画红再画绿出来的颜色完全不一样。所以引擎必须把半透明物体按“从远到近”的顺序一个一个画出来这种策略你不需要去查论文它就是典型的画家算法。问题就出在这个排序上。UE4里对半透明物体做排序的依据主要是两个一个是Sort Priority排序优先级另一个是物体到摄像机的距离。引擎会先把所有Primitive按照关键字排序生成一个绘制顺序列表然后按这个顺序逐个执行Draw Call。一旦排序的键值出了问题或者有一个特殊对象没参与排序那画面就穿帮了。注意这个排序不只是UE4的问题Unity、CryEngine都有类似机制。但UE4里Instanced Mesh的情况特别容易让人困惑因为它表面上是一个Actor、一个材质实际内部却被当成一个整体来做排序。1.2 Instanced Mesh为什么容易变成“排序钉子户”Instanced Static MeshISM和Hierarchical Instanced Static MeshHISM作为UE4里做大规模场景、植被、散布物件的利器最大的优势就是把大量相似的网格合并成一个“大实例缓冲区”用一次或少数几次Draw Call画完几万个物件。但便利是要付出代价的。对引擎而言一个ISM组件就是一个PrimitiveComponent它内部包含了几百个甚至几万个Instance每个Instance有自己的Transform位置、旋转、缩放可是从渲染管线的角度它们是一个统一的整体。于是问题来了半透明排序是按Primitive粒度进行的。引擎排序的时候只知道这个ISM组件离摄像机有多远它不可能去细分到每一个Instance再排序——至少在UE4的默认设置里它不会这么做。结果就是如果一个ISM组件里一半Instance在摄像机前面、一半在后面引擎只会按这个组件整体的中心点或者边界盒来判断它应该插在排序列表的哪一位。于是远处本该被遮挡的透明面片可能先画近处的透明面片后画混合结果自然是错的。HISM更狠一点。它带层级结构会做集群裁剪但这也改变不了排序粒度的问题。HISM内部把实例分组到Cluster然后按Cluster做视锥剔除但半透明绘制排序依然是整体优先最多是依赖Instance的Bounds来做更细的排序可是在实际项目里这个排序依然和逐Actor、逐物体的排序结果没法比。1.3 判断你的项目是不是踩中了排序问题在动手改之前先确认一下你遇到的现象到底是不是排序问题而不是别的毛病。因为有的情况看着像排序错误实际却是混合模式不对、深度写入没开、或者Cull Distance设置太激进。我在项目中遇到过最典型的情况是用ISM种了一片带半透明羽化贴花的草摄像机一拉远草叶之间出现严重的“互透”现象看起来像玻璃碎片叠在屏幕上。还有一个情况是半透明树叶在远近转角处闪烁画面像老式电视雪花。这些基本都是排序问题的典型表现。建议你先用下面几步做验证查看材质的Blend Mode是不是Translucent如果是Masked或者Opaque那大概率不是排序问题而是Alpha测试或者Cull Mode设置的事。确认Translucency Pass是否被关闭或者渲染优先级设成了很极端的值。用RenderDoc或者直接在编辑器里把半透明物体数量减少到两个看看两层透明片是否还在互相穿插。如果两个物体就能复现那排序问题没跑。排查清楚了再往下看解决方案。2. 解决问题之前先选一条最省事的路烂项目里半透明Instanced Mesh的排序问题几乎没有单一标准答案因为不同项目对性能、效果、美术资产形态的要求完全不一样。所以接下来这套方案我按“改动成本从低到高”来排你可以结合自己项目的实际情况选。另外提醒一句这套思路不只适用于UE4UE5里的Nanite和Lumen普及之后很多半透明排序的老问题换了身马甲继续存在核心逻辑还是相通的。2.1 不是所有透明都非要“半透明”改用Masked从根上绕开排序这是我在很多项目里用的第一招也是成本最低的一招把材质从Translucent改成Masked。为什么要这么做因为Masked根本不参与半透明排序它走的是和不透明物体一样的深度测试与深度写入流程。你只要用一张Alpha贴图或者噪点图去生成一个遮罩让该出现的像素出现、不该出现的像素直接clip掉那排序问题就不存在了。它的缺点是会让边缘变得很硬树叶、头发这类材质如果用Masked边缘会出现明显的锯齿或“纸片感”。后来我学到的解决方法是Masked材质里把Opacity Mask的对比度拉高然后结合Dithered Opacity抖动透明度利用时间或屏幕空间噪点让边缘看起来像是有半透明过渡。注意Dithered Opacity在距离近的时候噪点颗粒感明显拉远后视觉上会跟半透明很像。只要摄像机不会怼到物体表面贴脸看效果完全够用。如果场景里是树叶、草丛这类细小又密集的东西我强烈建议你做两套材质近距离用Translucent远距离用Masked再用Material Quality Switch或者Distance Fade做切换。这样既保证近处的柔和过渡又避免远处几百个透明面片同时参与排序造成的视觉灾难。需要注意材质Quality Switch在默认的材质编辑器里不是直接暴露的需要建个Material Function或者用Quality Level去区分。如果是UE5还可以直接考虑用Pixel Depth Offset配合Virtual Texture去做更精细的过渡效果但那就有点超纲了先记住思路就好。2.2 把Instance拆成独立Actor排序粒度变大视觉正确如果说Masked是用“不做半透明”来绕开问题那拆分Instance就是用“放弃实例化”来换取排序的准确性。具体做法有两种一种是把一个ISM组件换成多个普通的Static Mesh Actor每个Actor都挂单独的StaticMeshComponent另一种是仍然用ISM但控制Instance数量让每个ISM只管理一小块区域比如一棵树一个ISM或者一面墙一个ISM让ISM中心点和视觉中心点重合。为什么要这么干因为引擎在半透明排序时会把每个Primitive当成独立单元。当一棵树整体是一个ISM时它内部的树叶、树枝都会被当做一个整体参与排序大概率不会出问题但如果你把整片森林的树塞进同一个ISM里那排序就只能按照森林的整体范围来算了摄像机走到树丛中间的时候前后的树梢、树干必然乱套。我在一个开放世界项目里就是这么处理的把植被从“全地图一个HISM”改成“按地块拆分每块地一个HISM”每个HISM的实例数量控制在几百以内。这样排序只需要在不同地块之间进行单块内部的排序风险大大降低性能上损失也不大还换来了视觉稳定。唯一的代价是Draw Call会变多。但好在有HISM在几百个实例用一个组件依然划算不会像退回StaticMesh逐Actor摆放那么夸张。2.3 设置Sort Priority小参数也有大作用如果拆不了Instance、又舍不得换Masked那可以考虑在材质或Actor层面调整排序优先级。在PrimitiveComponent上有一个Translucency Sort Priority属性默认值是0。这个值越大物体越靠后绘制。通过调整这个参数能让一部分透明物体强制优先画、另一部分强制延后画从而在某种程度上模拟正确的远近距离关系。但这里有个大坑对于ISM/HISM组件Translucency Sort Priority只作用于整个组件不是每个Instance各自设置的。如果组件里所有实例的优先级都一样你调整来调整去它还是整体参与排序。所以这个方法只能解决“ISM和其他非ISM透明物体谁先谁后”的问题解决不了ISM内部各Instance之间的排序。即使如此这个参数仍然值得设置。举个例子你的场景里有半透明武器特效和烟雾粒子特效一定是优先级的那个不能让它跟远处的半透明墙体混在一起。把特效组件的Sort Priority调到10墙体调到-10可以避免很多看起来非常诡异的遮挡闪烁。2.4 关闭部分实例的深度测试脏但有效还有一种我见过但不太推荐的做法把半透明材质的Depth Test关掉。这样物体不会和背景半透明物体做深度比较排序问题看起来似乎消失了但要付出的代价是一个透明物体可能被错误地画在另一个透明物体前面而且这个错误是隐性的画面看着还可以实际上完全不符合物理遮挡逻辑。这个方案只适合处理在特定视角下、特定小范围区域内的表现平时不建议作为工程化方案。排错的时候可以拿来临时验证但正式项目里我基本不用。3. 实操过程一个Instanced Mesh透明穿插问题的完整修复记录下面我把一个实际项目的修复过程完整梳理一遍。当时场景里需要摆几百个半透明的小树丛我直接把所有树丛塞进了一个HISM材质用的是Translucent结果进入场景后树丛之间的那种透明穿插关系彻底乱了远处树丛的叶子时不时穿到近处树丛前面。排查和修复的过程大概花了大半个工作日我按顺序整理如下每一步都是当时真实做的事情方便你对号入座。3.1 复现问题并定位根因我当时先在编辑器里把HISM的Instance数量减到3个放在一条直线上然后环绕视角观察。果然当摄像机在三个物体之间移动时排序立即开始出问题中间物体的被遮挡部分会突然透视出后面的物体简单的三个物体也复现了问题。接着我打开材质确认Blend Mode确实是Translucent并且材质里没有开Depth Write。然后我意识到问题可能出在实例排序粒度上跟材质没关系。这一步看起来简单但非常重要。很多人在这一步就急急忙忙改材质结果越改越乱。先确认根因只有一个排序粒度不对。3.2 尝试在材质上做文章走不通我先试了给材质加Translucency Pass和自定义深度都没有效果。因为问题不在材质管线的Pass设置而在于ISM组件没有把每个Instance的排序信息传给渲染器。随后我又尝试在材质里用Pixel Depth Offset调整深度发现没用。Pixel Depth Offset是给深度偏移用的但半透明本身不写深度排序不会参考这个偏移量除非你开了Separate Translucency或者Depth in Translucency而这些在本场景里没有意义。这个尝试虽然失败但帮我确认了一件事不要和引擎的排序机制硬刚要顺着它的逻辑去调整。3.3 用HISM拆分替代整体实例这是我最终采用的方案。我把原来一个包含500个实例的HISM拆成了10个HISM每个HISM负责50棵左右的树丛按照地形区块划分。这样引擎在排序时会把10个HISM组件当成10个Primitive远近距离关系明显更精确了。拆完之后再次观察虽然单块HISM内部的50个Instance在某些极端角度下仍然会有一点透视穿插但视觉上已经远不如之前那么明显。再配合树丛本身较高的叶密度基本看不出来。这里有一个关键细节HISM的Cluster设置会影响排序细分。在HISM的属性面板里你可以调整Cluster Tree的构建参数让每个Cluster覆盖的区域更小。默认情况下Cluster按物体数量自动生成但如果你的实例密度特别高可以手动调小Cluster的区域范围这样引擎在做半透明排序时的粒度会更细。Cluster改小会增加可视性计算的性能开销因为引擎要对更多的Cluster做视锥剔除测试。在HISM实例数量不太大的情况下比如少于2000这个开销完全可接受。3.4 调整材质让视觉更稳顺便隐藏残余问题拆分HISM后残余的小幅度透明度穿帮我通过给材质做了Distance Fade基于距离的渐隐来处理。具体就是在材质里用Global Position WS的Distance搭配FadeOpacity让树丛在远距离时透明度整体降低中近距离保持正常。为什么要这么做因为距离越远人眼对排序错误的敏感度越低反而对视觉“柔和度”更敏感。远处的透明物体就算有轻微排序错误只要透明度降一点就非常难被察觉。这是利用人的视觉特性去掩盖技术缺陷在很多项目里都比硬修渲染管线更实用。材质里需要连的节点基本是GlobalPositionWS - 减去CameraPositionWS - 取长度得到距离再用一维Curve或RemapValueRange输出透明度系数接到Opacity节点上。如果你在材质节点大全里翻过Distance相关的节点应该已经知道这个套路了它本质是做距离淡出但我在这里是拿它做“距离淡透明”是同一个思路的不同用法。3.5 最终参数表与验证结果我在项目里最终形成的HISM配置如下参数项设置值说明实例数量每块HISM 30~60避免单块太大导致排序粒度太粗地块拆分按地形1km×1km一块配合World Composition划分材质Blend ModeTranslucent保留柔和过渡效果Distance Fade3m开始渐隐10m完全透明距离远后降低透明度Cluster区域默认偏细保证视图剔除粒度合适Translucency Sort Priority0默认不额外干预整体排序最终验证结果把摄像机从低空视角绕场一周树丛之间没有再出现大面积的互透现象只有个别正对视角的极端角度会有轻微穿插但基本不影响观感性能上Draw Call从原来的1次变成了10次完全可接受。4. 常见问题速查与经验总结下面这些是近几年我在不同项目里反复遇到的半透明Instanced Mesh“经典问题”和对应的处理经验整理成速查表遇到问题时可以先对号入座。现象可能原因处理办法叶子之间的互透严重HISM实例跨度过大排序粒度太粗拆分HISM缩小Cluster范围半透明贴花闪烁多个透明Plane重叠排序优先级相同调整Translucency Sort Priority透明物体被不透明物体透穿半透明材质未正确写深度开Separate Translucency或改用Masked远处透明大量闪烁粒子或ISM数量过多排序压力大用Dithered Opacity或Distance FadeISM内部个别实例排序异常没啥好办法实例排序粒度就是组件级尽量让实例聚类缩小包围盒透明材质套了Mesh Switch后卡顿透明物体排序开销大改用Masked或减少半透明物体数量镜头近距离看树叶出现“纸片感”Masked边缘硬增加噪点纹理做伪透明度如果你在用HISM做物理相关的东西比如碰撞检测、物理模拟记得HISM的物理引擎默认是把实例烘焙成静态碰撞体它和渲染排序没有直接关系但如果你用了复杂的物理模拟可能会间接影响视图剔除和Instance数的分布尤其是PhysicsState更新之后渲染数据也可能被重新组织。这时候如果出现排序问题建议先刷新HISM实例再检查别被物理数据干扰了排查节奏。关于材质节点这块半透明排序问题和材质节点本身关系不大但你一旦要改Blend Mode或者做Distance Fade就绕不开材质编辑器推荐花点时间把Opacity、Opacity Mask、Pixel Depth Offset、GlobalPositionWS这几个核心节点的逻辑搞清楚很多渲染问题归根到底就是在这些基础节点组合上出的岔子。在实际项目里我见过不少人纠结“为什么我把Sort Priority调成很大的值还是没用”原因我也讲了Sort Priority只在Primitive之间生效ISM内部不管用。所以如果项目里大量使用HISM把注意力放在拆分、材质和混合模式上比调参数有用得多。最后分享一个个人习惯只要美术允许我会尽量把半透明材质改成Masked Dithered Opacity这是性能收益最大、视觉风险最低的方案。不仅是Instanced Mesh普通StaticMesh和SkeletalMesh遇到类似排序问题也可以先试这一招再考虑其他更复杂的渲染管线改动。
返回列表