资讯动态

Unity Shader消融效果实战:从噪声采样到边缘发光的完整实现

发布时间:2026/10/14 6:38:44 来源:尧图企业网站定制
1. 消融效果到底在做什么从视觉需求倒推技术方案第一次接触消融效果是在做一个角色被击败后的退场动画。美术给的需求很直接角色不要直接消失也不要简单的淡出而是从脚底开始像被火烧过一样边缘带着发光的焦痕一点点往上“化掉”最后只剩灰烬飘散。当时我脑子里第一反应是用粒子系统硬堆但试了两天发现粒子很难贴合角色复杂的网格表面尤其是角色有披风、有飘带的时候粒子根本跟不上形变。后来换成在Shader里做顶点偏移加透明度裁剪才真正把效果做稳。消融效果英文一般叫Dissolve Effect核心逻辑其实一句话就能说清用一张噪声图去控制每个像素的“消失阈值”低于阈值的像素被裁掉高于阈值的保留同时在阈值边界附近叠加一层发光或变色模拟燃烧或腐蚀的边缘。听起来简单但真正落地到项目里涉及的问题远比想象中多——噪声图怎么选、阈值怎么动、边缘光怎么加、阴影怎么处理、和现有材质怎么兼容每一个环节都有坑。这篇文章适合谁看如果你已经写过基础的顶点着色器和片元着色器知道UV、采样、插值这些概念但还没做过消融效果那这篇可以直接抄作业。如果你做过淡入淡出但觉得效果太平、没有“质感”那这篇里的边缘光处理和噪声选型部分应该能给你一些新思路。如果你是完全零基础建议先把Shader的基本渲染流程过一遍至少要知道顶点着色器负责什么、片元着色器负责什么不然直接看代码会有点吃力。我下面会从整体设计思路开始拆然后逐层深入到噪声采样、阈值控制、边缘发光、阴影适配这些细节最后给一份可以直接跑的完整Shader代码以及我在实际项目里踩过的坑和排查方法。整个方案基于Unity的ShaderLab和HLSL但核心原理在任何渲染管线里都通用换成其他引擎只是语法差异。2. 整体设计思路与方案选型为什么不用粒子为什么不用简单透明2.1 粒子方案 vs Shader方案的真实取舍很多人第一反应是用粒子系统做消融觉得粒子灵活、好调。我一开始也是这么想的但实际做下来发现几个致命问题。第一粒子是独立于网格的它不知道角色表面的精确形状角色一动粒子就和模型穿帮。第二粒子很难做到“从下往上均匀推进”的效果因为粒子的发射器是空间分布的不是沿模型表面分布的。第三性能开销大一个角色几百个粒子十个角色就是几千个移动端直接崩。Shader方案的优势在于它直接作用在模型的每个像素上天然贴合表面。你不需要关心模型长什么样噪声图一贴阈值一控效果自然就出来了。而且性能开销极低本质上只是在片元着色器里多了一次纹理采样和一次比较运算对GPU的压力几乎可以忽略。缺点也有就是灵活性不如粒子比如你想让消融的边缘飘出火星还是得配合粒子来做但主体消融逻辑放在Shader里粒子只做点缀这样既稳又省。2.2 噪声图的选择Perlin、Simplex还是白噪声消融效果的核心是噪声图。噪声图的品质直接决定了消融边缘的形状。我试过三种白噪声、Perlin噪声、Simplex噪声。白噪声就是完全随机的像素值用上去之后消融边缘非常碎像被虫子啃过一样颗粒感太重不适合做燃烧或腐蚀效果。Perlin噪声比较平滑有连续的过渡消融边缘会呈现比较自然的块状适合做“融化”或“腐蚀”的感觉。Simplex噪声是Perlin的改进版计算量更小而且在高维表现更好但在2D情况下和Perlin差别不大。我最终选的是Perlin噪声因为它的平滑度刚好边缘不会太碎也不会太糊。如果你想要更“火焰”一点的感觉可以把Perlin噪声和一层高频白噪声叠加让边缘既有大块的推进感又有细碎的燃烧细节。噪声图的平铺方式也很关键一般用UV直接采样就行但如果模型UV有接缝消融边缘会在接缝处断裂这时候需要用世界坐标或对象空间坐标来采样噪声保证连续性。2.3 阈值控制从全局参数到顶点颜色最简单的做法是给一个全局的_ClipThreshold从0到1变化低于这个值的像素裁掉。但这样有个问题所有像素同时开始消融没有先后顺序。我想要的是从脚底开始逐渐往上。这时候就需要一个“高度权重”。我试过三种方案。第一种是用模型空间的Y坐标归一化之后作为权重Y越小越先消融。这个方案简单但只适合直立角色如果角色躺下或者倒挂消融方向就乱了。第二种是用顶点颜色美术在建模的时候把消融顺序画在顶点色里这样最灵活但增加美术工作量。第三种是用一张渐变图用UV的V方向采样控制消融从下往上。我最终选了第一种和第三种结合默认用模型空间Y坐标同时开放一张渐变图接口美术需要精细控制的时候可以切换。阈值的计算公式大概是这样的最终裁剪值 _ClipThreshold 高度权重 * _HeightInfluence。_ClipThreshold从0到1变化_HeightInfluence控制高度影响的强度。当_ClipThreshold为0时所有像素的裁剪值都大于0全部保留当_ClipThreshold为1时所有像素的裁剪值都小于1全部裁掉。中间过程就是逐渐消融。2.4 边缘发光的实现思路消融边缘如果只是硬切看起来会很假像被剪刀剪掉一样。真实的燃烧或腐蚀边缘会有一圈发光的焦痕。实现思路是在片元着色器里计算当前像素的裁剪值和阈值的差值如果差值在一个很小的范围内就认为这个像素处于边缘给它叠加一个发光颜色。具体来说假设裁剪值是clipValue阈值是threshold那么边缘强度edge 1 - saturate((clipValue - threshold) / _EdgeWidth)。_EdgeWidth控制边缘的宽度越小边缘越细。然后把这个edge值乘以边缘颜色_EdgeColor叠加到最终输出上。如果想要更丰富的效果还可以让边缘颜色随时间变化或者叠加一张噪声图让边缘有火焰的跳动感。这里有个细节边缘发光是在裁剪之前计算的因为裁剪之后像素就没了没法再算边缘。所以顺序是先采样噪声计算裁剪值计算边缘强度计算最终颜色最后才做clip或discard操作。3. 核心细节解析与实操要点噪声采样、阈值计算与边缘处理3.1 噪声采样的坐标空间选择噪声采样用哪个坐标空间直接决定了消融效果在不同模型上的表现。我试过三种UV空间、对象空间、世界空间。UV空间最简单直接用模型的UV采样噪声图。优点是噪声图跟着模型走模型移动旋转不影响消融图案。缺点是如果模型UV有接缝消融边缘会在接缝处断裂而且UV的密度不均匀有些地方拉伸严重噪声会被拉长。对象空间是用模型空间的顶点坐标作为UV采样噪声。优点是噪声在模型表面分布均匀不受UV影响。缺点是如果模型缩放噪声也会跟着缩放需要额外处理。而且对象空间坐标范围可能很大需要先归一化到0-1之间。世界空间是用世界坐标采样噪声。优点是噪声在世界空间固定模型移动的时候消融图案会“滑过”模型表面有一种动态感。缺点是模型移动时消融图案会变化不适合需要稳定图案的场景。我最终选的是对象空间因为它在均匀性和稳定性之间取得了最好的平衡。具体做法是把对象空间的顶点坐标乘以一个缩放系数_NoiseScale然后取xy或xz作为UV。缩放系数很关键太小噪声太大块太大噪声太碎。一般根据模型尺寸来调角色的话0.5到2之间比较合适。3.2 阈值计算的数学细节阈值的计算看起来简单但里面有几个容易忽略的细节。首先是高度权重的归一化。如果用模型空间Y坐标需要先减去模型的最低点再除以模型的高度得到0到1之间的值。这个最低点和高度可以通过脚本传入也可以在Shader里用顶点坐标的范围来估算。其次是阈值的插值方式。我一开始用的是线性插值但发现消融的起始和结束阶段变化太慢中间阶段变化太快。后来改成用smoothstep让阈值在0和1附近变化更平缓中间更陡峭这样消融的节奏感更好。还有一个细节是阈值的随机偏移。如果所有像素的阈值都一样消融边缘会是一条直线看起来很假。我加了一个基于噪声的随机偏移让每个像素的阈值有微小的差异这样消融边缘就会呈现自然的锯齿状。偏移量不能太大否则会破坏整体的推进方向一般控制在0.05到0.1之间。3.3 边缘发光的颜色与强度控制边缘发光的颜色选择很讲究。如果是燃烧效果边缘一般是橙红色到黄色如果是腐蚀效果边缘可能是绿色或紫色如果是魔法消融边缘可能是蓝色或白色。我一般会开放两个颜色参数_EdgeColorInner和_EdgeColorOuter分别控制边缘内侧和外侧的颜色然后用边缘强度做插值这样边缘会有渐变感。边缘强度除了用差值计算还可以叠加一层噪声让边缘有跳动感。具体做法是采样一张高频噪声图用时间做偏移然后和边缘强度相乘。这样边缘就会像火焰一样闪烁。注意噪声的强度不能太大否则边缘会断断续续看起来像bug。还有一个容易忽略的点是边缘发光和模型原有颜色的混合方式。我试过直接相加、lerp、以及用边缘强度做alpha混合。直接相加会让边缘过曝lerp会让边缘颜色被模型颜色污染最终我选的是用边缘强度做alpha混合再叠加一个加法的高光这样边缘既有颜色又有亮度。3.4 阴影和深度写入的处理消融效果如果只处理颜色不处理阴影会出现一个很尴尬的情况模型已经消融了一部分但阴影还是完整的。这是因为阴影贴图的渲染和主渲染是分开的主渲染里裁掉的像素阴影贴图里还在。解决办法是在阴影渲染的Pass里也做同样的裁剪。Unity的ShaderLab里需要在ShadowCaster Pass里也加上噪声采样和clip操作。这样阴影就会和主模型同步消融。注意阴影Pass里不需要边缘发光只需要裁剪逻辑。深度写入也是类似的问题。如果模型消融了但深度还写着后面的物体会被错误遮挡。解决办法是在片元着色器里做clip操作clip会自动丢弃像素的深度写入。但要注意clip的性能开销移动端上尽量用discard代替clip或者用alpha test。4. 完整实操流程从零写一个可用的消融Shader4.1 准备工作噪声图生成与导入设置首先需要一张Perlin噪声图。可以用Photoshop的云彩滤镜生成也可以用代码生成。我一般用代码生成因为可以精确控制噪声的频率和对比度。生成之后导入Unity有几个设置很关键。第一Wrap Mode要设为Repeat这样噪声可以平铺。第二Filter Mode设为Bilinear让噪声有平滑过渡。第三关闭sRGB因为噪声是数据图不是颜色图开启sRGB会导致采样值偏移。第四如果是移动端把压缩格式设为ASTC或ETC2不要用RGBA32太占内存。噪声图的分辨率不用太高512x512就够了消融效果对噪声的细节要求不高。如果要做高频的火焰跳动可以再生成一张256x256的高频噪声单独采样。4.2 Shader骨架搭建属性块与SubShader结构先搭一个标准的Unlit Shader骨架或者基于Surface Shader改。我一般用Unlit因为消融效果通常不需要复杂的光照而且Unlit性能更好。属性块里需要这些参数Properties { _MainTex (Main Texture, 2D) white {} _NoiseTex (Noise Texture, 2D) white {} _ClipThreshold (Clip Threshold, Range(0,1)) 0 _EdgeWidth (Edge Width, Range(0,0.5)) 0.1 _EdgeColorInner (Edge Color Inner, Color) (1,0.5,0,1) _EdgeColorOuter (Edge Color Outer, Color) (1,1,0,1) _NoiseScale (Noise Scale, Float) 1 _HeightInfluence (Height Influence, Range(0,1)) 1 }SubShader里需要两个Pass一个ForwardBase用于主渲染一个ShadowCaster用于阴影。如果项目用的是URP或HDRP结构会不一样但核心逻辑相同。4.3 顶点着色器坐标变换与高度权重计算顶点着色器里主要做三件事坐标变换、UV传递、高度权重计算。v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); // 对象空间坐标用于噪声采样 o.objPos v.vertex.xyz; // 高度权重模型空间Y坐标归一化 float height v.vertex.y; o.heightWeight saturate((height - _MinY) / (_MaxY - _MinY)); return o; }_MinY和_MaxY可以通过脚本传入也可以在Shader里用顶点坐标的范围估算。如果不想用脚本可以在Shader里用UNITY_COMPUTE...之类的宏但比较麻烦。我一般是在材质面板上手动调或者写个小脚本自动计算。4.4 片元着色器噪声采样、阈值比较与边缘计算片元着色器是核心顺序很重要。fixed4 frag (v2f i) : SV_Target { // 1. 采样主纹理 fixed4 mainColor tex2D(_MainTex, i.uv); // 2. 采样噪声 float2 noiseUV i.objPos.xy * _NoiseScale; float noise tex2D(_NoiseTex, noiseUV).r; // 3. 计算裁剪值 float clipValue noise i.heightWeight * _HeightInfluence; // 4. 计算边缘强度 float edge 1 - saturate((clipValue - _ClipThreshold) / _EdgeWidth); // 5. 计算边缘颜色 fixed3 edgeColor lerp(_EdgeColorInner.rgb, _EdgeColorOuter.rgb, edge); // 6. 混合最终颜色 fixed3 finalColor lerp(mainColor.rgb, edgeColor, edge); // 7. 裁剪 clip(clipValue - _ClipThreshold); return fixed4(finalColor, mainColor.a); }这里有几个细节。第一噪声采样用的是对象空间的xy但如果模型是竖着的xy可能不是最佳选择有时候用xz更好。第二clipValue的计算里noise和heightWeight的权重需要调_HeightInfluence越大高度的影响越明显。第三edge的计算里_EdgeWidth不能为0否则会除零。4.5 阴影Pass的同步处理阴影Pass里只需要裁剪逻辑不需要边缘发光。float4 fragShadow (v2f i) : SV_Target { float2 noiseUV i.objPos.xy * _NoiseScale; float noise tex2D(_NoiseTex, noiseUV).r; float clipValue noise i.heightWeight * _HeightInfluence; clip(clipValue - _ClipThreshold); return 0; }注意阴影Pass里的顶点着色器需要传递objPos和heightWeight所以v2f结构体要包含这些字段。如果用的是Unity的Surface Shader可以用addshadow参数自动生成阴影Pass但自定义的裁剪逻辑需要手动加。4.6 参数调优从“能看”到“好看”的关键调整Shader写完之后参数调优才是真正花时间的地方。我一般按这个顺序调先调_NoiseScale让噪声的颗粒大小合适。太大像马赛克太小像噪点。然后调_HeightInfluence控制消融的方向性。设为0就是全局同时消融设为1就是严格从下往上。接着调_EdgeWidth控制边缘的宽度。太窄边缘不明显太宽边缘会糊成一片。最后调_EdgeColorInner和_EdgeColorOuter让边缘颜色和模型整体色调协调。还有一个隐藏参数是噪声图的对比度。如果噪声图对比度太低消融边缘会很模糊对比度太高边缘会很硬。我一般会在Photoshop里把噪声的色阶调一下让中间调更丰富。5. 常见问题与排查技巧实录5.1 消融边缘出现接缝或断裂这是最常见的问题原因通常是UV接缝或噪声平铺设置不对。如果用的是UV采样噪声模型UV有接缝的地方噪声值会突变导致消融边缘断裂。解决办法是改用对象空间或世界空间采样噪声保证空间连续性。如果必须用UV可以在接缝处做平滑处理但比较麻烦。另一个原因是噪声图的Wrap Mode设成了Clamp导致平铺时边缘不连续。改成Repeat就好了。5.2 消融后阴影还在前面提过阴影Pass没有同步裁剪。解决办法是在ShadowCaster Pass里加上同样的clip逻辑。如果用的是Surface Shader确保addshadow参数加上并且自定义的裁剪代码放在shadow pass能执行的位置。还有一个可能是阴影的投射方式设成了“仅阴影”这种模式下模型不渲染但阴影还在。改成“投射阴影”就好了。5.3 边缘发光过曝或颜色不对边缘发光过曝通常是因为颜色值超过了1或者叠加方式用了加法。解决办法是把边缘颜色限制在0-1之间或者用lerp代替加法。颜色不对可能是sRGB设置问题检查噪声图和主纹理的sRGB设置是否一致。还有一个可能是边缘强度的计算方式不对。如果_EdgeWidth太大边缘会覆盖整个模型导致整体过曝。把_EdgeWidth调小就好了。5.4 移动端性能问题消融Shader在移动端的主要开销是纹理采样和clip操作。如果项目里大量使用消融效果可以考虑几个优化第一把噪声图的分辨率降到256x256第二用discard代替clip第三如果不需要边缘发光可以关掉相关计算第四用Shader LOD控制低端机上简化计算。还有一个容易忽略的点是overdraw。消融效果通常伴随着透明混合如果多个消融模型重叠overdraw会很严重。解决办法是尽量用alpha test代替alpha blend或者控制消融模型的数量。5.5 消融过程中模型穿插或闪烁这个问题通常是因为深度写入和裁剪不同步。如果模型消融了但深度还写着后面的像素会被错误遮挡导致闪烁。解决办法是确保clip操作在深度写入之前执行或者用alpha test模式。另一个可能是多个Pass之间的裁剪阈值不一致。比如主渲染Pass和阴影Pass用了不同的_ClipThreshold导致阴影和模型不同步。检查所有Pass是否用了相同的参数。5.6 常见问题速查表问题现象可能原因排查方法解决方案边缘断裂UV接缝或噪声平铺不对检查噪声Wrap Mode和采样坐标改用对象空间采样Wrap设为Repeat阴影残留阴影Pass未同步裁剪检查ShadowCaster Pass在阴影Pass加同样的clip逻辑边缘过曝颜色值超1或叠加方式错误检查边缘颜色和混合方式限制颜色值改用lerp移动端卡顿纹理采样过多或overdraw用Profiler查看GPU开销降分辨率用discard控制数量模型闪烁深度写入与裁剪不同步检查clip和深度写入顺序确保clip在深度写入前消融方向乱高度权重计算错误检查模型空间Y坐标范围手动传入_MinY和_MaxY6. 进阶扩展让消融效果更有质感的几个技巧6.1 叠加火焰粒子做点缀Shader负责主体消融粒子负责边缘的火星和烟雾。具体做法是在消融边缘的位置发射粒子粒子的发射率由边缘强度控制。这样既有Shader的稳定性又有粒子的灵动感。注意粒子的渲染顺序要在模型之后否则会被模型遮挡。6.2 用顶点偏移做“膨胀”效果在消融的同时让模型沿着法线方向微微膨胀模拟被烧焦后体积膨胀的感觉。具体做法是在顶点着色器里根据裁剪值计算一个偏移量裁剪值越接近阈值偏移越大。这样消融边缘会有一种“鼓起来”的质感。6.3 多噪声叠加做复杂图案单张噪声图的图案比较单一可以叠加两张不同频率的噪声一张控制大块的消融区域一张控制细碎的边缘。具体做法是采样两张噪声图用不同的缩放系数然后相乘或相加。这样消融图案会更丰富适合做魔法消融或腐蚀效果。6.4 用时间控制消融节奏消融的节奏感很重要。如果_ClipThreshold线性变化消融速度是均匀的看起来比较机械。可以用动画曲线控制_ClipThreshold的变化让消融在开始和结束阶段慢一点中间快一点。或者在Shader里用sin函数做周期性变化做呼吸式的消融效果。6.5 适配URP和HDRP的注意事项如果项目用的是URP或HDRPShader的写法会不一样。URP里需要用HLSLPROGRAM代替CGPROGRAM用Core.hlsl里的函数代替UnityCG.cginc。HDRP更复杂需要遵循HDRP的材质系统。但核心的噪声采样、阈值计算、边缘发光逻辑是一样的只是语法和结构需要调整。我在URP里做消融的时候最大的坑是阴影Pass的写法。URP的阴影Pass需要用ShadowCasterPass.hlsl里的函数不能直接照搬Built-in的写法。建议先找一个URP的Unlit Shader模板在它的基础上改这样不容易出错。7. 我个人在实际操作中的体会做了这么多消融效果我最大的体会是Shader本身不难难的是和美术、策划的沟通。美术想要的效果往往是“感觉”比如“要像被火烧过一样”但Shader参数是精确的数值。这时候需要把“感觉”翻译成参数比如“火烧”对应橙红色的边缘、较宽的边缘宽度、从下往上的方向性。我一般会先做一个参数可调的版本让美术自己拖拖到满意为止然后再根据最终参数优化性能。另一个体会是消融效果的性能开销比想象中低但overdraw比想象中高。尤其是多个消融模型重叠的时候GPU的填充率会成为瓶颈。所以我在项目里会限制同时消融的模型数量或者用LOD控制远处模型的消融精度。最后分享一个小技巧如果消融效果在编辑器里看起来很好但打包到真机上就出问题大概率是噪声图的压缩格式不对。移动端上ASTC和ETC2对噪声图的压缩会导致采样值偏移消融边缘会变得很奇怪。解决办法是把噪声图的压缩关掉或者用高质量压缩。这个坑我踩过两次每次都是打包之后才发现排查起来很费时间。

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

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

免费获取报价 →
↑