Search Unity

Free Screen melt surface shader inside (issue solved):

Discussion in 'Shaders' started by esco1979, Aug 20, 2016.

  1. esco1979

    esco1979

    Joined:
    Mar 18, 2014
    Posts:
    138
    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: https://davidwalsh.name/canvas-effect).

    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.
    http://www.mediafire.com/download/tm7ig3m2d2tfey7/screenmelt.unitypackage


    Code (CSharp):
    1. Shader "SOTN Custom/ScreenMelt"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    7.         _MeltSpeed ("Melt Speed", range (0,0.2)) = 0            //used in helper C# script to set _Timer
    8.         [HideInInspector] _Timer ("Timer", float) = 0
    9.     }
    10.  
    11.     SubShader
    12.     {
    13.         Cull off
    14.         Zwrite off
    15.  
    16.         CGPROGRAM
    17.         #pragma surface surf Standard
    18.         #pragma target 3.0
    19.  
    20.         fixed4 _Color;
    21.         sampler2D _MainTex;
    22.         float _MeltSpeed;
    23.         float _Timer;          
    24.  
    25.         struct Input
    26.         {
    27.             float2 uv_MainTex;
    28.         };
    29.  
    30.         float2 _Offset[256];            //set by helper C# script; value is varied slightly so that each bar moves down a different amount
    31.  
    32.         void surf (Input IN, inout SurfaceOutputStandard o)
    33.         {
    34.             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
    35.                                                                                     //column (x value) and the amount of time in _timer that has gone by
    36.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    37.             o.Albedo = c.rgb;
    38.             o.Alpha = c.a;
    39.             if (o.Alpha < 0.01) discard;
    40.         }
    41.  
    42.         ENDCG
    43.     }
    44. }
     
  2. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    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.
     
    esco1979 likes this.
  3. esco1979

    esco1979

    Joined:
    Mar 18, 2014
    Posts:
    138
    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. :)


    Code (CSharp):
    1.  
    2. void surf (Input IN, inout SurfaceOutputStandard o)
    3. {
    4.     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
    5.                                                                              //column (x value) and the amount of time in _timer that has gone by
    6.     if (IN.uv_MainTex.y > 1) discard;                     //(ONLY NEW LINE) gets rid of bug where pixel at top of clamp texture stretches
    7.     fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    8.     o.Albedo = c.rgb;
    9.     o.Alpha = c.a;
    10.     if (o.Alpha < 0.01) discard;
    11. }
    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).

    Code (csharp):
    1.  
    2. //HELPER SCRIPT FOR SHADER: Controls shader for screen melting; starts effect by setting timer
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6. public class ScreenMelt : MonoBehaviour {
    7.  
    8.     public Material mat;
    9.     public bool effectOn = false;
    10.  
    11.  
    12.     //initializes array in shader to melt sprite
    13.    
    14. //initializes array in shader to melt sprite
    15.     void Start ()
    16.     {
    17.         Vector4[] vectorArray = new Vector4[257];
    18.         for (int count = 0; count <= 256; count ++)
    19.         {
    20.             vectorArray[count] = new Vector4 (Random.Range(1f, 1.25f), 0, 0, 0);
    21.         }
    22.  
    23.         mat.SetVectorArray("_Offset", vectorArray);
    24.     }
    25.  
    26.  
    27.     //resets timer on mat to 0
    28.     void OnApplicationQuit()
    29.     {
    30.         mat.SetFloat("_Timer", 0);
    31.     }
    32.  
    33.  
    34.     //starts the timer to the sprite starts melting
    35.     void FixedUpdate ()
    36.     {
    37.         if (effectOn)
    38.         {
    39.             mat.SetFloat("_Timer", mat.GetFloat("_Timer") + mat.GetFloat("_MeltSpeed"));
    40.         }
    41.     }
    42. }
     
    Last edited: Jan 22, 2018
  4. Chubzdoomer

    Chubzdoomer

    Joined:
    Sep 27, 2014
    Posts:
    131
    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.
     
  5. esco1979

    esco1979

    Joined:
    Mar 18, 2014
    Posts:
    138
    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:

    Code (CSharp):
    1. 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:

    Code (CSharp):
    1. //initializes array in shader to melt sprite
    2.     void Start ()
    3.     {
    4.         Vector4[] vectorArray = new Vector4[257];
    5.         for (int count = 0; count <= 256; count ++)
    6.         {
    7.             vectorArray[count] = new Vector4 (Random.Range(1f, 1.25f), 0, 0, 0);
    8.         }
    9.  
    10.         mat.SetVectorArray("_Offset", vectorArray);
    11.     }
    It works fine now.
     
    Last edited: Jan 22, 2018