Free Screen melt surface shader inside (issue solved):

Hey, everyone. Although I am very new to shaders, I managed to figure out how to do a screen melt effect on the game screen to reproduce the game over screen in Castlevania: SOTN. It basically takes a picture of the screen and applies the melting effect to it that then reveals the game over screen underneath. This effect is just like the screen melt effect in doom:

For my purposes, the best way to do this seems to be to draw the screen to a render texture, put the render texture on a quad then apply the screen melt to it. All the effect basically does is take each column of pixels (indicated by the x coordinate of them) and move them down by a different amount each (more details can be found here for those who want them: Replicating the DOOM Screen Melt with JavaScript and Canvas).

I managed to reproduce this effect in a simple surface shader that works great for quads and sprites except for one small bug: for some reason if the sprite/texture touches the top of the texture, I get this bleeding effect (circled here):

The same happens with any pixels that touch the bottom of the texture if I reverse the screen melt to go upwards.

Why is this? I’ve attached a package with a scene setup with the shader too for anyone who wants to test it.

Shader "SOTN Custom/ScreenMelt"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _MeltSpeed ("Melt Speed", range (0,0.2)) = 0            //used in helper C# script to set _Timer
        [HideInInspector] _Timer ("Timer", float) = 0
    }

    SubShader
    {
        Cull off
        Zwrite off

        CGPROGRAM
        #pragma surface surf Standard
        #pragma target 3.0

        fixed4 _Color;
        sampler2D _MainTex;
        float _MeltSpeed;
        float _Timer;           

        struct Input
        {
            float2 uv_MainTex;
        };

        float2 _Offset[256];            //set by helper C# script; value is varied slightly so that each bar moves down a different amount

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            IN.uv_MainTex.y += _Timer *_Offset[round(IN.uv_MainTex.x*256.0f)].x;    //moves pixel down by an amount setup in the _Offset array based on current
                                                                                    //column (x value) and the amount of time in _timer that has gone by
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
            if (o.Alpha < 0.01) discard;
        }

        ENDCG
    }
}

The stretching problem is not with your shader, your texture is set to “clamp”, so if you if you try to access texture coordinates less than 0 or greater than 1, they will be clamped inside that range.

Though if you set your texture to repeat instead of clamp, instead of stretching the side of the image you’re melting from, it will repeat the other side of the texture.

It looks like what you want to do is “discard” pixels who’s uv’s fall outside the 0 to 1 range.

1 Like

Yup! That did it. Thanks so much Gambit.

Everyone: here is the corrected Surf portion of the script. Feel free to take and use this shader wherever and whenever. No credit is needed. Enjoy. :slight_smile:

void surf (Input IN, inout SurfaceOutputStandard o)
{
    IN.uv_MainTex.y += _Timer *_Offset[round(IN.uv_MainTex.x*256.0f)].x;    //moves pixel down by an amount setup in the _Offset array based on current
                                                                             //column (x value) and the amount of time in _timer that has gone by
    if (IN.uv_MainTex.y > 1) discard;                     //(ONLY NEW LINE) gets rid of bug where pixel at top of clamp texture stretches
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    o.Alpha = c.a;
    if (o.Alpha < 0.01) discard;
}

And here is the helper script I use in the object as well to control when it melts (Melt speed on the material controls how fast it melts).

//HELPER SCRIPT FOR SHADER: Controls shader for screen melting; starts effect by setting timer
using UnityEngine;
using System.Collections;

public class ScreenMelt : MonoBehaviour {

    public Material mat;
    public bool effectOn = false;


    //initializes array in shader to melt sprite
   
//initializes array in shader to melt sprite
    void Start () 
    {
        Vector4[] vectorArray = new Vector4[257];
        for (int count = 0; count <= 256; count ++)
        {
            vectorArray[count] = new Vector4 (Random.Range(1f, 1.25f), 0, 0, 0);
        }

        mat.SetVectorArray("_Offset", vectorArray);
    }


    //resets timer on mat to 0
    void OnApplicationQuit()
    {
        mat.SetFloat("_Timer", 0);
    }


    //starts the timer to the sprite starts melting
    void FixedUpdate ()
    {
        if (effectOn)
        {
            mat.SetFloat("_Timer", mat.GetFloat("_Timer") + mat.GetFloat("_MeltSpeed"));
        }
    }
}

This shader no longer seems to be working properly. Even after downloading the package in the first post and testing its included scene, nothing happens. I also copied over the corrected “surf” function in the post above, to no avail. Again, nothing happens what-so-ever. The included sprites/quad remain perfectly still when the scene is played.

Does anyone know why this is? I’m completely new to shaders, so perhaps I could learn a thing or two from this.

I’m having the same issue. It works in an older version of unity, but not the newest one. It has something to do with how I assign the array in the helper script above. This line seems to be the culprit:

mat.SetVector("_Offset"  + count.ToString(), new Vector2 (Random.Range(1f, 1.25f), 0));

That is what assigns slightly randomized amounts to the float2 variable _Offset in the shader. I’m messing with it now and it is just a matter of figuring out the syntax but I seem to be stuck on it. And I’m sure it is something really dumb I am doing wrong. I just need to assign an array of vector2 randomized values to the float2 in the shader.

EDIT: got the little bitch. I replaced the start method in the C# script above with this:

//initializes array in shader to melt sprite
    void Start ()
    {
        Vector4[] vectorArray = new Vector4[257];
        for (int count = 0; count <= 256; count ++)
        {
            vectorArray[count] = new Vector4 (Random.Range(1f, 1.25f), 0, 0, 0);
        }

        mat.SetVectorArray("_Offset", vectorArray);
    }

It works fine now.