资讯动态

Unity Shader消融效果实战:从clip裁剪到边缘发光与性能优化

发布时间:2026/10/13 13:16:01 来源:尧图企业网站定制
1. 从物体凭空消失说起消融效果到底在解决什么问题如果你做过游戏里的死亡表现、传送特效、场景切换或者给某个道具做被腐蚀被吞噬的视觉反馈那你大概率绕不开一个经典需求让一个完整的模型不是啪地一下消失而是从某个方向开始一点点被啃掉边缘还带着一圈发光的焦痕。这就是消融效果Dissolve Effect。我第一次接触这个概念是在做一个角色受击反馈的时候。当时美术给的需求很朴素角色被打死的时候别直接隐藏要像被烧掉一样慢慢没掉。我一开始想的是用透明度渐变把材质的Alpha从1慢慢降到0。结果做出来效果非常塑料——整个模型均匀地变淡像幽灵一样完全没有被侵蚀的质感。后来才明白透明度渐变和消融是两回事前者是整体均匀地变透明后者是沿着某个空间方向用一张噪声图作为进度尺逐像素地裁剪掉片元。消融效果的核心价值在于它把消失这个动作变成了一个有方向、有纹理、有边缘反馈的过程。它解决的不只是怎么让物体不见而是怎么让消失这件事本身具有表现力。在动作游戏里它是死亡和复活的视觉语言在解谜游戏里它是机关触发、空间传送的提示在恐怖游戏里它是怪物被腐蚀、被吞噬的氛围营造。甚至在一些非游戏场景比如数据可视化的粒子消散、UI元素的退场动画消融的思路同样适用。这篇文章我会围绕Unity Shader中消融效果的完整实现来讲从最基础的裁剪原理到噪声图的选择再到边缘发光、方向控制、性能优化这些实战细节。适合已经写过简单Shader、想深入理解片元裁剪和噪声应用的读者。如果你连Shader的基本结构都还没写过建议先补一下顶点/片元着色器的基本流程不然中间一些概念会有点吃力。但我会尽量把每个为什么这么做讲清楚让你不只是抄代码而是真正理解每一步的意图。2. 消融的底层逻辑clip函数与噪声图的配合2.1 为什么是裁剪而不是透明要理解消融先要理解它和透明效果的本质区别。透明效果是通过混合Blending实现的片元照常输出颜色只是把Alpha值调低让后面的颜色透过来。这意味着透明物体仍然参与深度测试、仍然会被渲染只是最终颜色被混合了。而消融用的是裁剪Clipping也就是clip()函数它直接在片元着色器里丢弃掉某些片元被丢弃的片元根本不会写入颜色缓冲和深度缓冲。这个区别非常关键。用透明做消失你会遇到排序问题多个半透明物体叠加时谁先谁后、深度怎么写都是麻烦事。而裁剪是硬的被裁掉的片元就像不存在一样不参与任何后续计算也不会有排序困扰。所以消融天然适合那些消失后就不该再被看到的场景。clip(x)的行为很简单如果传入的参数小于0这个片元就被丢弃否则保留。所以消融的核心思路就是构造一个值让它在该消失的区域小于0在该保留的区域大于0然后用一个外部参数控制这个分界线移动。2.2 噪声图让消融边界不规则如果只用一张纯色或者线性渐变来控制裁剪消融的边界会是一条笔直的线看起来非常机械。真实的腐蚀、燃烧、溶解边界都是参差不齐的。所以我们需要一张噪声图Noise Texture来打乱这个边界。噪声图的选择有讲究。最常用的是Perlin噪声或者Simplex噪声生成的灰度图它的特点是连续、平滑、没有明显的重复感。也有人用Voronoi噪声出来的效果更像细胞状的碎裂。还有一种做法是用一张普通的云朵贴图或者火焰贴图只要它的灰度分布足够随机就行。这里有个经验噪声图的对比度会直接影响消融边缘的锐利程度。如果噪声图整体偏灰、对比度低那么消融边界会比较宽、比较柔和如果噪声图黑白分明、对比度高边界就会很锐利。我一般会准备两三张不同对比度的噪声图根据美术需求切换。2.3 核心公式clip(noise - threshold)把噪声和阈值结合起来就是消融最核心的一行代码float noiseValue tex2D(_NoiseTex, i.uv).r; clip(noiseValue - _DissolveAmount);_DissolveAmount是一个从0到1的外部参数。当它为0时noiseValue - 0对于所有噪声值大于0的像素都大于0所以全部保留当它逐渐增大噪声值小于_DissolveAmount的像素就会被裁掉。因为噪声值是随机分布的所以被裁掉的区域也是不规则的看起来就像被啃掉一样。这里有个细节噪声图的采样UV。如果你直接用模型本身的UV那么消融的纹理走向会跟着模型的UV走不同模型之间的消融效果可能不一致。如果你想让消融沿着世界空间或者某个固定方向进行就需要用世界坐标或者对象空间坐标来采样噪声。这两种做法各有用途后面会详细讲。注意clip()函数在某些移动端GPU上性能开销比桌面端大因为它会打断早期的深度测试。如果目标平台是低端移动设备消融效果要谨慎使用或者考虑用Alpha Test配合更简单的噪声。3. 从零搭建一个可用的消融Shader3.1 属性面板的设计哪些参数必须暴露给美术写Shader不只是写代码还要考虑美术怎么用。一个设计良好的属性面板能让美术自己调出想要的效果而不是每次都来找你改代码。消融Shader至少需要暴露这几个属性主纹理_MainTex模型本身的颜色贴图。噪声纹理_NoiseTex控制消融边界的形状。消融进度_DissolveAmount0到1的滑动条控制消融进行到哪一步。边缘颜色_EdgeColor消融边缘那圈发光的颜色。边缘宽度_EdgeWidth控制边缘发光带的宽窄。边缘强度_EdgeIntensity控制边缘发光的亮度。这几个参数里_DissolveAmount是动画驱动的核心其他几个是美术调效果的。我见过有人把噪声图的平铺次数Tiling也暴露出来方便美术调整噪声的密度这个看具体需求。3.2 顶点着色器的准备工作顶点着色器本身不需要做太多事情主要是把UV和顶点位置传递到片元着色器。但如果你想让消融沿着世界空间方向进行就需要在这里把世界坐标算出来v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; return o; }worldPos在片元着色器里可以用来采样噪声这样消融就会沿着世界空间的方向推进而不是跟着模型UV走。这个细节在做从下往上烧或者从某个点向外扩散的效果时特别有用。3.3 片元着色器裁剪、边缘计算、颜色输出片元着色器是消融效果的主战场。完整的逻辑分三步先采样噪声并裁剪再计算边缘发光最后输出颜色。fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); float noise tex2D(_NoiseTex, i.uv).r; // 裁剪 clip(noise - _DissolveAmount); // 边缘计算 float edge 1.0 - smoothstep(0.0, _EdgeWidth, noise - _DissolveAmount); col.rgb _EdgeColor.rgb * edge * _EdgeIntensity; return col; }这段代码里smoothstep的作用是让边缘有一个平滑的过渡而不是硬邦邦的一条线。noise - _DissolveAmount表示当前像素距离被裁掉还有多远这个值越小说明越接近边界。1.0 - smoothstep(...)把它反转过来让边界处的值接近1远离边界的地方接近0这样边缘发光就只出现在消融的边界上。_EdgeWidth控制的是发光的宽度。值越大发光带越宽值越小发光越集中在边界线上。我一般会把它设在0.05到0.2之间具体看模型的大小和噪声图的频率。3.4 完整代码与逐行注释把上面的片段拼起来就是一个完整的消融ShaderShader Custom/DissolveEffect { Properties { _MainTex (Main Texture, 2D) white {} _NoiseTex (Noise Texture, 2D) white {} _DissolveAmount (Dissolve Amount, Range(0, 1)) 0 _EdgeColor (Edge Color, Color) (1, 0.5, 0, 1) _EdgeWidth (Edge Width, Range(0, 0.5)) 0.1 _EdgeIntensity (Edge Intensity, Range(0, 5)) 2 } SubShader { Tags { RenderTypeOpaque QueueGeometry } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _NoiseTex; float _DissolveAmount; fixed4 _EdgeColor; float _EdgeWidth; float _EdgeIntensity; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); float noise tex2D(_NoiseTex, i.uv).r; clip(noise - _DissolveAmount); float edge 1.0 - smoothstep(0.0, _EdgeWidth, noise - _DissolveAmount); col.rgb _EdgeColor.rgb * edge * _EdgeIntensity; return col; } ENDCG } } }这个版本是最基础的没有处理光照、没有阴影、没有法线。如果你需要消融效果和场景光照配合就需要把它改成Surface Shader或者手动加上光照计算。但作为入门和理解原理这个版本已经足够。4. 让消融有方向世界空间采样与方向控制4.1 为什么默认的UV采样不够用用模型UV采样噪声消融的纹理是贴在模型表面的。这意味着无论模型怎么旋转、移动消融的图案都跟着模型走。对于某些场景这是对的比如你想让消融图案和模型的纹理对齐。但更多时候美术想要的是从下往上烧或者从某个方向扫过来的效果这时候UV采样就不行了。举个例子一个角色站在地上你要做从脚底开始往上烧的效果。如果用UV采样噪声图在模型上的分布是固定的你没法控制消融从哪个方向开始。但如果你用世界坐标的Y轴来采样噪声那么消融就会自然地沿着世界空间的Y方向推进脚底先消失头顶最后消失。4.2 用世界坐标Y轴做从下往上消融实现方式很简单把噪声采样从UV改成世界坐标的某个分量float noise tex2D(_NoiseTex, i.worldPos.yz * _NoiseScale).r;这里用worldPos.yz作为UV意味着噪声在YZ平面上展开消融会沿着X轴方向推进。如果你想从下往上就用worldPos.xz让噪声在XZ平面上展开消融沿着Y轴推进。_NoiseScale控制噪声的密度值越大噪声越密消融的碎片越小。但这里有个问题直接用世界坐标采样噪声图会在世界空间里平铺如果模型很大噪声可能会重复如果模型很小噪声可能只采样到一小块。所以_NoiseScale需要根据模型的实际尺寸来调。我一般的做法是先用一个默认值然后在场景里看着调直到消融的碎片大小合适。4.3 用顶点颜色或自定义方向控制消融起点除了世界坐标还有一种更灵活的方式用顶点颜色Vertex Color的某个通道来控制消融的优先级。比如你可以在建模软件里给模型刷上顶点色让脚底的顶点色偏暗、头顶偏亮然后在Shader里用顶点色来偏移噪声值float noise tex2D(_NoiseTex, i.uv).r i.color.r * _GradientStrength;这样顶点色暗的地方噪声值小会先被裁掉顶点色亮的地方噪声值大会后被裁掉。通过刷顶点色你可以精确控制消融的顺序和方向比世界坐标更灵活。缺点是需要在建模软件里额外处理增加了美术的工作量。还有一种做法是用一个空物体比如一个空GameObject的位置作为消融的起点在Shader里计算每个像素到起点的距离用距离来偏移噪声。这种方式适合做从某个点向外扩散的消融比如爆炸后的消散效果。5. 边缘发光的进阶处理颜色渐变与HDR5.1 单色边缘的局限基础的边缘发光就是给边界加一圈固定颜色的光。但看多了会发现单色边缘有点单调尤其是当消融面积很大的时候整圈边缘都是同一个颜色缺乏层次感。真实的燃烧边缘往往是中心温度高、颜色偏白或偏黄外围温度低、颜色偏红或偏暗。要做出这种渐变可以用edge值来插值两种颜色fixed3 edgeColor lerp(_EdgeColorInner.rgb, _EdgeColorOuter.rgb, edge); col.rgb edgeColor * edge * _EdgeIntensity;edge值在边界处接近1在远离边界处接近0。所以lerp的结果是越靠近边界颜色越接近_EdgeColorInner越远离边界颜色越接近_EdgeColorOuter。这样边缘就有了从内到外的颜色过渡。5.2 用HDR颜色让边缘发光如果你想让边缘有真正的发光感而不仅仅是亮一点的颜色就需要用HDR颜色。在Unity里把颜色属性的Color改成HDR就可以设置超过1的亮度值[HDR] _EdgeColor (Edge Color, Color) (1, 0.5, 0, 1)然后在片元着色器里边缘颜色会以更高的亮度输出。配合相机的Bloom后处理边缘就会产生光晕看起来就像真的在发光。这个技巧在做魔法、能量、火焰类的消融时特别有用。注意HDR颜色的亮度值不要设得太高否则Bloom会过曝整个边缘变成一团白。我一般把亮度控制在2到5之间具体看场景的曝光设置。5.3 边缘噪声扰动让发光边界也不规则基础的边缘发光是沿着噪声边界均匀分布的但如果你仔细看会发现发光的宽度在整个边界上是一致的有点规整。真实的燃烧边缘发光的宽度也是参差不齐的。要做出这种效果可以用第二张噪声图来扰动边缘宽度float edgeNoise tex2D(_EdgeNoiseTex, i.uv * _EdgeNoiseScale).r; float edgeWidth _EdgeWidth * (0.5 edgeNoise); float edge 1.0 - smoothstep(0.0, edgeWidth, noise - _DissolveAmount);这样边缘宽度会随着第二张噪声图变化有的地方宽、有的地方窄看起来更自然。这个技巧在追求高质量视觉效果的项目里很常用代价是多一次纹理采样。6. 性能与兼容性消融效果在移动端的取舍6.1 clip函数的性能代价前面提过clip()在移动端GPU上开销较大。原因是它打断了GPU的早期深度测试Early-Z。正常情况下GPU会先做深度测试把被遮挡的像素提前丢弃避免不必要的片元着色器计算。但clip()是在片元着色器里执行的GPU没法提前知道哪些像素会被裁掉所以只能先跑完片元着色器再做深度测试。这就意味着被遮挡的像素也白白跑了一遍着色器。对于消融效果来说这个问题尤其明显消融过程中模型的一部分已经被裁掉了但GPU仍然会为这些已经不存在的像素跑片元着色器。如果模型面数很高、屏幕占比很大这个开销就很可观。6.2 移动端的替代方案如果目标平台是低端移动设备有几个替代方案用Alpha Test代替clipAlpha Test在固定管线时代就有现代GPU对它的优化比clip好一些。但本质上它也是片元着色器里的操作性能提升有限。用透明度渐变代替消融如果只是想让物体消失不需要被啃掉的质感直接用透明度渐变是最省性能的。降低噪声图分辨率噪声图不需要很高的分辨率256x256甚至128x128就够用了。低分辨率纹理的采样开销更小缓存命中率更高。限制消融的屏幕占比不要让消融效果在全屏范围内大面积出现尽量控制在小物件或者局部特效上。6.3 阴影和光照的处理基础的消融Shader没有处理阴影。如果模型需要投射阴影你需要在Shader里加上阴影投射的Pass并且在阴影Pass里也做同样的裁剪。否则会出现模型已经消融了但影子还在的穿帮。处理光照也是类似如果你用的是Surface ShaderUnity会自动帮你处理光照和阴影但你需要把裁剪逻辑写在surf函数里。如果你用的是手写的顶点/片元Shader就需要自己加上光照计算或者用#pragma multi_compile_fwdbase来支持前向渲染的光照。我个人的建议是如果项目用的是标准光照流程优先用Surface Shader写消融省去很多手动处理光照和阴影的麻烦。只有在需要高度自定义渲染效果时才用手写的顶点/片元Shader。7. 实战中容易踩的几个坑7.1 噪声图的Wrap Mode设置错误噪声图的Wrap Mode默认是Repeat这本身没问题。但如果你用世界坐标采样噪声而世界坐标的范围很大噪声图就会在场景里重复很多次导致消融图案出现明显的重复感。解决办法是把噪声图的Wrap Mode设成Clamp或者用一张更大、更随机的噪声图。另一个办法是在Shader里对世界坐标做一定的缩放和偏移让采样范围落在噪声图的有效区域内。7.2 消融进度从0到1时边缘突然消失这个问题通常是因为_DissolveAmount到达1时所有像素的噪声值都小于1全部被裁掉边缘发光也就没有了。如果你希望消融到最后边缘还有一个收尾的发光可以把_DissolveAmount的范围设成0到1.1让它在1.0之后还有一点余量。或者单独用一个参数控制边缘发光的淡出让边缘在消融结束前先消失。7.3 多个消融物体之间的排序问题虽然裁剪本身不涉及排序但如果你的消融Shader同时用了透明混合比如边缘发光用了Additive混合就会引入排序问题。多个半透明的消融物体叠加时可能会出现边缘发光互相遮挡、颜色不对的情况。解决办法是尽量让边缘发光用Opaque或者AlphaTest的渲染队列避免和透明物体混在一起。7.4 消融和GPU Instancing的冲突如果你用了GPU Instancing来批量渲染相同的模型消融参数_DissolveAmount需要作为Instanced Property才能每个实例独立控制。否则所有实例会共享同一个消融进度看起来就像所有物体同时消失。在Shader里需要用UNITY_INSTANCED_PROP来声明这个属性并且在C#端用MaterialPropertyBlock来设置每个实例的值。8. 消融效果的扩展玩法8.1 配合粒子系统做消散成灰消融本身只是让模型消失但消失后的灰烬可以用粒子系统来补。做法是在消融的边界位置发射粒子粒子的颜色和边缘发光颜色一致运动方向沿着消融推进的方向。这样看起来就像模型被烧成了灰灰烬飘散在空中。这个效果在动作游戏里很常见实现难度也不高主要是粒子系统的调参。8.2 用消融做生长效果消融的反向就是生长。把clip(noise - _DissolveAmount)改成clip(_DissolveAmount - noise)消融就变成了从无到有的生长。这个效果可以用来做建筑建造、植物生长、魔法阵展开等。边缘发光的逻辑是一样的只是方向反过来了。8.3 消融UV动画做流动腐蚀如果让噪声图的UV随时间缓慢移动消融的边界就会有一种流动的感觉像腐蚀在蔓延。做法是在采样噪声时给UV加上时间偏移float2 noiseUV i.uv _Time.y * _FlowSpeed; float noise tex2D(_NoiseTex, noiseUV).r;_FlowSpeed控制流动的速度和方向。这个技巧在做毒液、腐蚀、能量侵蚀类的效果时很好用。8.4 消融和顶点动画结合如果让顶点在消融过程中也发生位移比如被裁掉的顶点向外炸开就能做出更夸张的消散效果。这需要在顶点着色器里根据噪声值计算顶点的偏移量让接近边界的顶点先动起来。这个做法比单纯的片元裁剪复杂但视觉效果也更强烈适合做Boss死亡、大招释放这类高光时刻。9. 我个人在消融效果上的一些经验做了这么多年的Shader消融是我用得最多的效果之一因为它足够简单、足够通用又足够有表现力。有几个心得是文档里不会写的分享给你。第一噪声图的选择比Shader代码更重要。我见过很多人花大量时间调Shader参数但效果始终不对问题其实出在噪声图上。一张好的噪声图灰度分布均匀、没有明显的重复图案、对比度适中能让消融效果直接提升一个档次。我建议你多收集几张不同风格的噪声图Perlin、Simplex、Voronoi各备一张用的时候根据需求切换。第二边缘发光的颜色不要用纯色。纯红、纯黄、纯白看起来都很假。真实的燃烧边缘颜色是有层次的最里面偏白或偏黄中间偏橙外面偏红或偏暗。用lerp做两层甚至三层颜色过渡效果会真实很多。如果项目支持HDR和Bloom一定要用上发光感会强很多。第三消融的进度曲线要调。默认的线性进度消融是匀速进行的看起来有点机械。你可以用AnimationCurve或者代码里的缓动函数让消融先慢后快、或者先快后慢配合角色的动作节奏效果会更有张力。比如角色死亡时消融可以先慢后快最后唰地一下全部消失配合音效冲击力很强。第四别忘了测试不同模型。消融效果在球体上看起来很好在复杂的角色模型上可能就出问题因为UV的分布不均匀噪声的密度在不同部位可能差异很大。我一般会在一个简单的球体上先调好参数然后放到实际模型上再微调确保效果一致。第五移动端要克制。消融效果在PC上随便用但在移动端尤其是低端机上大面积使用会明显掉帧。如果非要用尽量控制在小物件上或者用更简单的噪声和更少的纹理采样。性能永远是移动端的第一优先级。消融这个效果入门很容易写好很难。它涉及片元裁剪、噪声应用、边缘计算、光照阴影、性能优化等多个方面是一个很好的Shader练习项目。如果你能把消融的每一个细节都吃透那你对Unity Shader的理解就已经上了一个台阶。

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

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

免费获取报价 →
↑