Dissolve Shader for Unity URP

A URP dissolve shader for Unity 6 that burns a mesh away along procedural noise with a glowing HDR edge. No noise texture needed, and its shadow dissolves with it.

Also called: burn shader, disintegrate effect, death effect shader, teleport effect.

Effects URP, Unity 6000.0+ beginner MIT licensed

Use it for: Enemy deaths, teleports, spawning objects in or out, and burning paper or cloth.

Inspector Try the properties
Shader Scripts For Unity/Dissolve
None (Texture)
HDR
Dissolve Drag to rotate

The preview is a WebGL copy of the shader's maths with one fixed light, so it shows no shadows, Bloom or textures.

What it does

  • Procedural 3D noise in object space, so it needs no noise texture and does not stretch across UV seams
  • One Dissolve slider from 0 (whole) to 1 (gone), easy to animate from code or the Timeline
  • HDR edge colour that glows with Bloom
  • Shadows and depth dissolve with the mesh
  • Back faces drawn by default so the inside shows through the holes

How it works

Each pixel samples four octaves of value noise at its object-space position. Pixels whose noise is below Dissolve are discarded with clip(), and pixels just above it are tinted with the edge colour, which forms the burning band.

Because the noise is computed from the object's own position, the pattern stays fixed to the mesh as it moves, and the same function runs in the shadow and depth passes so their holes match.

Setup

  1. Save the file as Dissolve.shader under Assets and create a material that uses Scripts For Unity > Dissolve.
  2. Turn on Bloom in your Volume if you want the edge to glow.
  3. Animate the Dissolve property: renderer.material.SetFloat("_Dissolve", amount); from 0 to 1 over the effect's length.
  4. Raise Noise Scale for smaller, busier holes; lower it for large patches.

Common mistakes

  • Every object dissolves together: setting the property on sharedMaterial changes the material asset for every renderer that uses it. Use renderer.material, or a MaterialPropertyBlock, for one object.
  • The edge does not glow: the edge colour needs Bloom in a post-processing Volume, and HDR enabled on the URP asset, to bloom.

Questions

How do I animate the dissolve from a script?
Set the _Dissolve property from 0 to 1 over time, for example in a coroutine: renderer.material.SetFloat("_Dissolve", t);. Using renderer.material gives this object its own copy, so other objects with the same material are not affected.
Does it need a noise texture?
No. The noise is calculated in the shader from the object's own position, so it never stretches across UV seams and stays fixed to the mesh as it moves.
Why does the edge not glow?
The edge colour is HDR. It only blooms with a Bloom override in a post-processing Volume and HDR enabled on the URP asset.

Source

Dissolve.shader · 305 lines · ShaderLab and HLSL for the Universal Render Pipeline 17 (Unity 6)

Dissolve.shader
cpp
// Dissolve: burns the mesh away along procedural noise, with a glowing
// edge. Shadows dissolve with it. URP 17 (Unity 6).
// Scripts For Unity, MIT licensed.
Shader "Scripts For Unity/Dissolve"
{
    Properties
    {
        [MainTexture] _BaseMap ("Base Map", 2D) = "white" {}
        [MainColor] _BaseColor ("Base Color", Color) = (0.55, 0.62, 0.7, 1)
        _Dissolve ("Dissolve", Range(0, 1)) = 0.45
        _NoiseScale ("Noise Scale", Range(0.5, 20)) = 4
        _EdgeWidth ("Edge Width", Range(0.001, 0.2)) = 0.06
        [HDR] _EdgeColor ("Edge Color", Color) = (4, 1.4, 0.3, 1)
        [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "TransparentCutout"
            "Queue" = "AlphaTest"
            "RenderPipeline" = "UniversalPipeline"
        }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

        CBUFFER_START(UnityPerMaterial)
            float4 _BaseMap_ST;
            half4 _BaseColor;
            float _Dissolve;
            float _NoiseScale;
            float _EdgeWidth;
            half4 _EdgeColor;
            float _Cull;
        CBUFFER_END

        TEXTURE2D(_BaseMap);
        SAMPLER(sampler_BaseMap);

        float SfuHash13(float3 p)
        {
            p = frac(p * 0.1031);
            p += dot(p, p.zyx + 31.32);
            return frac((p.x + p.y) * p.z);
        }

        float SfuValueNoise(float3 p)
        {
            float3 i = floor(p);
            float3 f = frac(p);
            float3 u = f * f * (3.0 - 2.0 * f);
            float n000 = SfuHash13(i);
            float n100 = SfuHash13(i + float3(1, 0, 0));
            float n010 = SfuHash13(i + float3(0, 1, 0));
            float n110 = SfuHash13(i + float3(1, 1, 0));
            float n001 = SfuHash13(i + float3(0, 0, 1));
            float n101 = SfuHash13(i + float3(1, 0, 1));
            float n011 = SfuHash13(i + float3(0, 1, 1));
            float n111 = SfuHash13(i + float3(1, 1, 1));
            return lerp(lerp(lerp(n000, n100, u.x), lerp(n010, n110, u.x), u.y),
                        lerp(lerp(n001, n101, u.x), lerp(n011, n111, u.x), u.y), u.z);
        }

        // Four octaves, normalised back to 0..1
        float SfuFractalNoise(float3 p)
        {
            float n = SfuValueNoise(p) * 0.5;
            n += SfuValueNoise(p * 2.03) * 0.25;
            n += SfuValueNoise(p * 4.01) * 0.125;
            n += SfuValueNoise(p * 8.05) * 0.0625;
            return n / 0.9375;
        }

        // How far a point is above the dissolve threshold; negative means burned away
        float SfuDissolveAmount(float3 positionOS)
        {
            return SfuFractalNoise(positionOS * _NoiseScale) - _Dissolve * 1.01;
        }
        ENDHLSL

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode" = "UniversalForward" }
            Cull [_Cull]

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
            #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH
            #pragma multi_compile_fog
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normalWS : TEXCOORD1;
                float3 positionWS : TEXCOORD2;
                float3 positionOS : TEXCOORD3;
                float fogFactor : TEXCOORD4;
            };

            float4 SfuShadowCoord(float3 positionWS)
            {
            #if defined(_MAIN_LIGHT_SHADOWS_SCREEN)
                return ComputeScreenPos(TransformWorldToHClip(positionWS));
            #else
                return TransformWorldToShadowCoord(positionWS);
            #endif
            }

            Varyings vert(Attributes input)
            {
                Varyings output;
                VertexPositionInputs positions = GetVertexPositionInputs(input.positionOS.xyz);
                output.positionCS = positions.positionCS;
                output.positionWS = positions.positionWS;
                output.positionOS = input.positionOS.xyz;
                output.normalWS = TransformObjectToWorldNormal(input.normalOS);
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                output.fogFactor = ComputeFogFactor(positions.positionCS.z);
                return output;
            }

            half4 frag(Varyings input, FRONT_FACE_TYPE face : FRONT_FACE_SEMANTIC) : SV_Target
            {
                float amount = SfuDissolveAmount(input.positionOS);
                clip(amount);

                half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
                // Back faces show through the holes; light them from their own side
                float3 normalWS = normalize(input.normalWS) * IS_FRONT_VFACE(face, 1.0, -1.0);

                Light mainLight = GetMainLight(SfuShadowCoord(input.positionWS));
                float diffuse = saturate(dot(normalWS, mainLight.direction)) * mainLight.shadowAttenuation;
                float3 color = albedo.rgb * (mainLight.color * diffuse + SampleSH(normalWS));

                // Glowing band just above the threshold
                float edge = (1.0 - smoothstep(0.0, _EdgeWidth, amount)) * step(0.0001, _Dissolve);
                color = lerp(color, _EdgeColor.rgb, edge);

                color = MixFog(color, input.fogFactor);
                return half4(color, 1.0);
            }
            ENDHLSL
        }

        // Shadows dissolve with the mesh
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }
            ZWrite On
            ZTest LEqual
            ColorMask 0
            Cull [_Cull]

            HLSLPROGRAM
            #pragma vertex ShadowVert
            #pragma fragment ShadowFrag
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"

            float3 _LightDirection;
            float3 _LightPosition;

            struct ShadowAttributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct ShadowVaryings
            {
                float4 positionCS : SV_POSITION;
                float3 positionOS : TEXCOORD0;
            };

            ShadowVaryings ShadowVert(ShadowAttributes input)
            {
                ShadowVaryings output;
                float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
                float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
            #if _CASTING_PUNCTUAL_LIGHT_SHADOW
                float3 lightDirectionWS = normalize(_LightPosition - positionWS);
            #else
                float3 lightDirectionWS = _LightDirection;
            #endif
                float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirectionWS));
            #if UNITY_REVERSED_Z
                positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE);
            #else
                positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE);
            #endif
                output.positionCS = positionCS;
                output.positionOS = input.positionOS.xyz;
                return output;
            }

            half4 ShadowFrag(ShadowVaryings input) : SV_Target
            {
                clip(SfuDissolveAmount(input.positionOS));
                return 0;
            }
            ENDHLSL
        }

        // Depth with the same holes, for the depth texture and depth priming
        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode" = "DepthOnly" }
            ZWrite On
            ColorMask R
            Cull [_Cull]

            HLSLPROGRAM
            #pragma vertex DepthVert
            #pragma fragment DepthFrag

            struct DepthAttributes
            {
                float4 positionOS : POSITION;
            };

            struct DepthVaryings
            {
                float4 positionCS : SV_POSITION;
                float3 positionOS : TEXCOORD0;
            };

            DepthVaryings DepthVert(DepthAttributes input)
            {
                DepthVaryings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.positionOS = input.positionOS.xyz;
                return output;
            }

            half DepthFrag(DepthVaryings input) : SV_Target
            {
                clip(SfuDissolveAmount(input.positionOS));
                return input.positionCS.z;
            }
            ENDHLSL
        }

        // Normals with the same holes, for SSAO
        Pass
        {
            Name "DepthNormals"
            Tags { "LightMode" = "DepthNormals" }
            ZWrite On
            Cull [_Cull]

            HLSLPROGRAM
            #pragma vertex NormalsVert
            #pragma fragment NormalsFrag

            struct NormalsAttributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
            };

            struct NormalsVaryings
            {
                float4 positionCS : SV_POSITION;
                float3 normalWS : TEXCOORD0;
                float3 positionOS : TEXCOORD1;
            };

            NormalsVaryings NormalsVert(NormalsAttributes input)
            {
                NormalsVaryings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.normalWS = TransformObjectToWorldNormal(input.normalOS);
                output.positionOS = input.positionOS.xyz;
                return output;
            }

            half4 NormalsFrag(NormalsVaryings input, FRONT_FACE_TYPE face : FRONT_FACE_SEMANTIC) : SV_Target
            {
                clip(SfuDissolveAmount(input.positionOS));
                float3 normalWS = normalize(input.normalWS) * IS_FRONT_VFACE(face, 1.0, -1.0);
                return half4(NormalizeNormalPerPixel(normalWS), 0.0);
            }
            ENDHLSL
        }
    }

    FallBack "Hidden/Universal Render Pipeline/FallbackError"
}

More shaders