一、效果总览溶解效果Dissolve Effect是游戏开发中极为常见的视觉技巧—— 角色死亡时化为碎片消散、传送门边缘的灼烧侵蚀、魔法书扉页的火焰翻涌—— 它的核心原理只有三行字对噪声纹理采样、与阈值比较、clip 掉不该显示的像素。本文从原理到实现完整拆解 Unity URPUniversal Render Pipeline下的溶解 Shader 涵盖HLSL 手写代码与Shader Graph 节点两种路线 并给出边缘发光Emissive Border的扩展写法。二、核心原理2.1 噪声纹理的作用溶解效果的本质是非均匀透明度遮罩。 如果直接对 alpha 做线性插值物体会均匀淡出毫无质感。 噪声纹理Noise Texture为每个像素提供了一个[0, 1]范围内的随机灰度值 使哪些像素先消失具有不规则的自然形态。2.2 clip / discard 的工作机制HLSL 的clip(x)函数在x 0时丢弃当前片元等价于discard// 当参数 0 时当前像素被完全丢弃不写入深度/颜色缓冲 void clip(float x); // 溶解核心语句noiseValue - threshold 0 → 被溶解掉 clip(noiseValue - _Threshold);当_Threshold从0向1增大时 越来越多的像素满足noiseValue - _Threshold 0被丢弃物体溶解消失 反向减小则物体凝聚出现。渐隐 vs 渐显渐隐时_Threshold从 0 → 1 渐显时从 1 → 0。两者复用同一 Shader只需在 C# 中控制方向即可。三、URP HLSL Shader 完整实现3.1 项目准备Unity 6 / 2022 LTS URP 14准备一张灰度噪声纹理推荐 Perlin / Simplex分辨率 256×256 即可新建Dissolve.shader置于Assets/Shaders/3.2 完整 Shader 代码Shader Custom/URP/Dissolve { Properties { _MainTex (Albedo, 2D) white {} _NoiseTex (Noise Texture, 2D) white {} _Threshold (Dissolve Threshold, Range(0,1)) 0 _EdgeWidth (Edge Width, Range(0,0.2)) 0.05 _EdgeColor (Edge Color, Color) (1,0.48,0.1,1) _EdgeIntensity(Edge Intensity,Float) 3.0 } SubShader { Tags { RenderTypeTransparentCutout RenderPipelineUniversalPipeline QueueAlphaTest } LOD 300 Pass { Name ForwardLit Tags { LightMode UniversalForward } // 双面渲染避免溶解边缘穿帮 Cull Off HLSLPROGRAM #pragma vertex DissolveVert #pragma fragment DissolveFrag #pragma multi_compile_fog #pragma multi_compile_instancing #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl // ── 属性声明 ──────────────────────────── TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); TEXTURE2D(_NoiseTex); SAMPLER(sampler_NoiseTex); CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; float4 _NoiseTex_ST; float _Threshold; float _EdgeWidth; float4 _EdgeColor; float _EdgeIntensity; CBUFFER_END // ── 顶点输入 / 输出 ───────────────────── struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionHCS : SV_POSITION; float2 uvMain : TEXCOORD0; float2 uvNoise : TEXCOORD1; float3 normalWS : TEXCOORD2; UNITY_VERTEX_OUTPUT_STEREO }; // ── 顶点着色器 ────────────────────────── Varyings DissolveVert(Attributes input) { Varyings output; UNITY_SETUP_INSTANCE_ID(input); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); output.positionHCS TransformObjectToHClip(input.positionOS.xyz); output.normalWS TransformObjectToWorldNormal(input.normalOS); output.uvMain TRANSFORM_TEX(input.uv, _MainTex); output.uvNoise TRANSFORM_TEX(input.uv, _NoiseTex); return output; } // ── 片元着色器 ────────────────────────── half4 DissolveFrag(Varyings input) : SV_Target { // 1. 采样噪声纹理只取 R 通道即可 half noiseVal SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, input.uvNoise).r; // 2. 核心溶解 clip clip(noiseVal - _Threshold); // 3. 采样主贴图 half4 albedo SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uvMain); // 4. 计算边缘发光 // noiseVal 处于 [_Threshold, _Threshold _EdgeWidth] 视为边缘 half edge saturate(1.0 - (noiseVal - _Threshold) / _EdgeWidth); half3 emissive _EdgeColor.rgb * edge * _EdgeIntensity; // 5. 简易 Lambert 光照可替换为 URP PBR Light mainLight GetMainLight(); half NdotL saturate(dot(normalize(input.normalWS), mainLight.direction)); half3 lighting albedo.rgb * mainLight.color * (NdotL * 0.8 0.2); return half4(lighting emissive, 1.0); } ENDHLSL } } }关键行逐行解析第 83 行SAMPLE_TEXTURE2D采样噪声纹理的 R 通道得到[0,1]的标量。第 86 行clip(noiseVal - _Threshold)是溶解效果的全部核心一行代码。第 93 行将[_Threshold, _Threshold_EdgeWidth]这段区间映射到[1,0]计算边缘强度。第 94 行边缘强度乘以_EdgeIntensity1产生 HDR 溢出感配合 Bloom 后处理效果更佳。四、边缘发光原理图五、C# 控制脚本在 Inspector 拖动或通过脚本动态修改_Threshold即可驱动溶解动画using UnityEngine; using System.Collections; public class DissolveController : MonoBehaviour { [SerializeField] private float _duration 2f; // 溶解总时长秒 [SerializeField] private bool _autoPlay false; private Material _mat; private static readonly int ThresholdID Shader.PropertyToID(_Threshold); void Awake() { // 获取材质副本避免污染共享材质 _mat GetComponentRenderer().material; } void Start() { if (_autoPlay) StartCoroutine(DissolveOut()); } /// summary渐隐threshold 从 0 → 1/summary public IEnumerator DissolveOut() { yield return AnimateThreshold(0f, 1f); gameObject.SetActive(false); // 完全溶解后隐藏 } /// summary渐显threshold 从 1 → 0/summary public IEnumerator DissolveIn() { gameObject.SetActive(true); yield return AnimateThreshold(1f, 0f); } private IEnumerator AnimateThreshold(float from, float to) { float t 0f; while (t _duration) { t Time.deltaTime; // smoothstep 缓出视觉上更自然 float progress Mathf.SmoothStep(0f, 1f, t / _duration); _mat.SetFloat(ThresholdID, Mathf.Lerp(from, to, progress)); yield return null; } _mat.SetFloat(ThresholdID, to); } }⚠️注意务必使用renderer.material而非sharedMaterial 获取独立副本否则修改会影响场景中所有使用该材质的物体。 若需批量溶解同材质物体应改用MaterialPropertyBlock。六、Shader Graph 节点等效实现如果你偏好可视化编辑下图展示了与上述 HLSL 等价的 Shader Graph 节点连接方式 Shader Graph 中没有内置Clip节点需使用Alpha Clip Threshold在 Fragment 节点 Alpha 接口配合 Alpha Clip 开关 或手动连接StepMultiply节点模拟丢弃效果。 手写 HLSL 更直接灵活推荐在复杂场景下使用。七、材质参数速查参数类型范围说明_MainTexTexture2D—物体主贴图Albedo_NoiseTexTexture2D—灰度噪声纹理驱动溶解形状_ThresholdFloat[0, 1]溶解阈值0 完整1 完全溶解_EdgeWidthFloat[0, 0.2]边缘发光宽度噪声空间非世界空间_EdgeColorColor (HDR)—边缘发光颜色建议 HDR 开启以配合 Bloom_EdgeIntensityFloat[1, 10]边缘强度倍数1 产生 HDR 溢出触发 Bloom八、进阶拓展思路8.1 方向性溶解将溶解从随机改为有方向用顶点的世界空间 Y 坐标与噪声混合 可以实现从脚底向上燃烧的溶解效果。// 将世界空间高度归一化后混合进噪声值 float heightFactor (_WorldSpaceCameraPos.y - input.positionWS.y) * _HeightScale; half dissolveVal noiseVal saturate(heightFactor); clip(dissolveVal - _Threshold);8.2 世界空间噪声消除 UV 拉伸对于没有 UV 展开或 UV 拉伸严重的模型用世界空间 XZ 坐标作为噪声采样 UV 溶解形状在模型表面分布更均匀。// 顶点着色器中传出世界坐标 output.positionWS TransformObjectToWorld(input.positionOS.xyz); // 片元着色器用世界 XZ 作为噪声 UV float2 worldUV input.positionWS.xz * _NoiseScale; half noiseVal SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, worldUV).r;8.3 配合 MaterialPropertyBlock 批量控制场景中同一材质的多个物体需要不同溶解进度如死亡的每个敌人各自独立消散时 使用MaterialPropertyBlock避免实例化多份材质private MaterialPropertyBlock _mpb; private Renderer _renderer; void Awake() { _renderer GetComponentRenderer(); _mpb new MaterialPropertyBlock(); } void SetThreshold(float t) { _renderer.GetPropertyBlock(_mpb); _mpb.SetFloat(ThresholdID, t); _renderer.SetPropertyBlock(_mpb); // 不产生新的 draw call 合批 }九、性能注意事项clip / discard 不等于免费被 clip 的像素不写入颜色缓冲但在 Tile-Based GPU移动端上仍会影响 Early-Z 优化大量使用时需测量 overdraw。关闭阴影接收的 clip 同步ShadowCaster Pass 中也需要同样的 clip 逻辑否则溶解物体仍然投射完整阴影。记得为ShadowCasterPass 添加相同的噪声采样和clip。噪声纹理压缩使用 R8 或 BC4 压缩格式节省显存带宽。灰度噪声不需要 RGB 通道。动态批处理使用MaterialPropertyBlock可保持动态批处理使用.material实例化则会打破批次。十、小结溶解效果的实现思路极为简洁但细节决定品质通过噪声纹理提供天然的不规则性、clip函数实现无中间灰度的硬截断、边缘宽度构造发光带、C# 协程驱动_Threshold随时间变化—— 四个模块各司其职任何一个都可以单独替换和扩展 比如把噪声换成 SDF 字体就能得到文字侵蚀效果 把方向性溶解与粒子系统结合就是经典的死亡灰烬特效。掌握这一套思路你会发现 URP Shader 里的诸多炫技效果 本质上都是噪声 × 阈值 × 片元丢弃的变体。