top of page
Writer's picturesantosh nalla

Burning Paper Effect using Unity Shaders

Final result:


In the above effect, we punch holes randomly in our model. To do this, we use a random black and white noise texture(where r = g = b).


Since we are punching holes in our model, by zeroing the alpha, we need to make a transparent shader.

        Tags { "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha   

We need a slider on the inspector, to test our effect. As you see in the above gif, the burning effect is gradually consuming the paper as I increase the slider value.

        _DissolveFactor("Dissolve Factor", Range(0,1)) = 0

If you closely observe the effect, we have 2 colors in our effect - Ember(red/orange) and Char(black) colors along the border of alpha holes.


Lets get our Ember color.

    _BurnEmberColor("Buring Ember Color", Color) = (1,1,1,1)
    _EmberColorFactor("Ember Color Factor", float) = 1

Firstly we need to get our noise texture into our shader as secondary texture(primary texture being the actual paper(model) texture). Drag and drop our noise texture on to this secondary texture slot.

    Properties
    {
        //Textures
        _MainTex ("Texture", 2D) = "white" {}
        _SecondaryTex ("Noise Texture", 2D) = "white" {}
        .
        .
        .
        .

Shader "SanShaders/BurningEffect"
{
    Properties
    {
        //Textures
        _MainTex ("Texture", 2D) = "white" {}
        _SecondaryTex ("Noise Texture", 2D) = "white" {}

        _DissolveFactor("Dissolve Factor", Range(0,1)) = 0

        _BurnEmberColor("Buring Ember Color", Color) = (1,1,1,1)
        _EmberColorFactor("Ember Color Factor", float) = 1
    }

    SubShader
    {
        Tags { "RenderType"="Transparent" }

        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct vertexData
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct fragmentData
            {
                float2 uv1 : TEXCOORD0;
                float2 uv2 : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            //Textures
            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _SecondaryTex;
            float4 _SecondaryTex_ST;

  fragmentData vert (vertexData v)
            {
                fragmentData o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                o.uv1 = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv2 = TRANSFORM_TEX(v.uv, _SecondaryTex);

                return o;
            }

            fixed4 frag (fragmentData i) : SV_Target
            {
                // sample the texture
                float4 mainTex = tex2D(_MainTex, i.uv1);
                float4 secondaryTex = tex2D(_SecondaryTex, i.uv2);
                .
                .
                .
                .
                .
                .
                .
                .

Sample both textures in fragment shader as shown above.

Now the actual logic goes to our fragment shader.


//Giving a burn Ember Color!!
float edgeValue = step(secondaryTex.r - _BurnEdgeLength, _DissolveFactor);
float3 emberColor = lerp(mainTex.rgb, _BurnEmberColor * _EmberColorFactor, edgeValue);
                
return fixed4(emberColor, mainTex.a);

_BurnEdgeLength is the edge length of our ember/char color coat.


So as you drag your _DissolveFactor slider, when _DissolveFactor value is greater than (secondaryTex.r - _BurnEdgeLength) of a pixel, the edgeValue becomes 1 else it gets 0.

When edgeValue is 1, emberColor will completely lerp to _BurnEmberColor for a pixel, else when edgeValue is 0, the pixel color remains the same(_mainTex color).


So you can observe pixels gradually turning to _EmberColor.


Now we need to punch holes by zeroing the alpha.

 //Dissolve Effect
finalAlpha = saturate(mainTex.a - step(secondaryTex.r + _BurnEdgeLength, _DissolveFactor));

return fixed4(finalColor, finalAlpha * mainTex.a);

saturate makes sure the value is always positive.

This punches holes in our paper leaving the burn edge of thickness _BurnEdgeLength red.



Finally, to make this effect look more realistic, we can add a char color.

//Giving a burn char Color on the edge!!
float charValue = smoothstep(secondaryTex.r - _BurnEdgeLength, secondaryTex.r + _BurnEdgeLength, _DissolveFactor);

finalColor = lerp(emberColor, _BurnCharColor * _CharColorFactor, charValue);

 //Dissolve Effect
finalAlpha = saturate(mainTex.a - step(secondaryTex.r + _BurnEdgeLength,_DissolveFactor));

return fixed4(finalColor, finalAlpha * mainTex.a);

Full Code:

Shader "SanShaders/BurningEffect"
{
    Properties
    {
        //Textures
        _MainTex ("Texture", 2D) = "white" {}
        _SecondaryTex ("Noise Texture", 2D) = "white" {}

        //Dissolve Factor. Animate this value in your code if required
        [Header(DISSOLVE FACTOR AND EDGE LENGTH)]
        [Space(10)]
        _DissolveFactor("Dissolve Factor", Range(0,1)) = 0
        _BurnEdgeLength("Buring Color Edge length", Range(0,1)) = 0

        [Space(10)]
        //Burn Char and Ember colors!!
        [Header(BURNING EMBER AND CHAR COLORS)]
        [Space(10)]
        _BurnEmberColor("Buring Ember Color", Color) = (1,1,1,1)
        _EmberColorFactor("Ember Color Factor", float) = 1

        _BurnCharColor("Buring Char Color", Color) = (1,1,1,1)
        _CharColorFactor("Char Color Factor", float) = 1
    }

    SubShader
    {
        Tags { "RenderType"="Transparent" }

        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct vertexData
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct fragmentData
            {
                float2 uv1 : TEXCOORD0;
                float2 uv2 : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };

            //Textures
            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _SecondaryTex;
            float4 _SecondaryTex_ST;

            //Effect Parameters
            float _DissolveFactor;
            float4 _BurnEmberColor;
            float _BurnEdgeLength;
            float4 _BurnCharColor;
            float _EmberColorFactor;
            float _CharColorFactor;

            fragmentData vert (vertexData v)
            {
                fragmentData o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                o.uv1 = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv2 = TRANSFORM_TEX(v.uv, _SecondaryTex);

                return o;
            }

            fixed4 frag (fragmentData i) : SV_Target
            {
                // sample the texture
                float4 mainTex = tex2D(_MainTex, i.uv1);
                float4 secondaryTex = tex2D(_SecondaryTex, i.uv2);

                float3 finalColor;
                float finalAlpha;

                //Giving a burn Ember Color!!
                float edgeValue = step(secondaryTex.r - _BurnEdgeLength, _DissolveFactor);
                float3 emberColor = lerp(mainTex.rgb, _BurnEmberColor * _EmberColorFactor, edgeValue);

                //Giving a burn char Color on the edge!!
                float charValue = smoothstep(secondaryTex.r - _BurnEdgeLength, secondaryTex.r + _BurnEdgeLength, _DissolveFactor);
                finalColor = lerp(emberColor, _BurnCharColor * _CharColorFactor, charValue);

                //Dissolve Effect
                finalAlpha = saturate(mainTex.a - step(secondaryTex.r + _BurnEdgeLength, _DissolveFactor));

                return fixed4(finalColor, finalAlpha * mainTex.a);
            }

            ENDCG
        }
    }
}


57 views0 comments

Recent Posts

See All

Comments


bottom of page