Horizontal wave distortion

does anyone know of a shader that can be put on a quad and make this happen to things behind it / seen through it:

All the better if it could be animated and use layers too. Simple horizontal pixel shifting.

(this is a duplicate thread because I can’t edit my old one, can a mod merge these?)

This effect is most often referred to as distortion. google for something like unity distortion shader, it should yield useful results.

TBH the nearest I could find was the screen wipe code http://wiki.unity3d.com/index.php/ScreenWipes which isn’t a shader
Looks like this hasn’t been done much

I put an example together for you (see below for the download link).

Here is how it looks:

(sorry for the bad hiccups in the video, my recorder obviously sucks)

The idea is to have a texture that describes how strong the distortion is at a certain pixel (black=full distortion to the left, white=full distortion to the right. In the following example, the Red channel represents horizontal and Green channel the vertical distortion. The Blue channel represents a mask (black=no distortion, white=full distortion). The distortion texture is scrolled using the built-in _Time variable, so you get motion into it.

Old Unity 5 Shader Example, see shader files in the package below for the latest version

[code=CSharp]//
// Created with Unity 5.1
// Example code to help answer the forum post at:
// http://forum.unity3d.com/threads/horizontal-wave-distortion.295769/
//
// This source code is not a full fledged distortion solution.
// It's just an example and hopefully proves useful for someone.
//
Shader "Custom/GrabPass Disortion" {
Properties {
        _MainTex ("Texture (R,G=X,Y Distortion; B=Mask; A=Unused)", 2D) = "white" {}
        _IntensityAndScrolling ("Intensity (XY); Scrolling (ZW)", Vector) = (0.1,0.1,1,1)
        [Toggle(MASK)] _MASK ("Texture Blue channel is Mask", Float) = 0
        [Toggle(DEBUGUV)] _DEBUGUV ("Debug Texture Coordinates", Float) = 0
}

SubShader {
        Tags {"Queue" = "Transparent" "IgnoreProjector" = "True"}
        Lighting Off
        Fog { Mode Off }
        ZWrite Off
        LOD 200
   
        // See http://docs.unity3d.com/Manual/SL-GrabPass.html
        GrabPass { "_GrabTexture" }
 
    Pass {
        CGPROGRAM
            //#pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature MASK
            #pragma shader_feature DEBUGUV
            #include "UnityCG.cginc"

            // _MainTex is our distortion texture and
            // should use "Bypass sRGB Sampling" import setting.
            sampler2D _MainTex;
            float4 _MainTex_ST; // texture tiling and offset
       
            // _GrabTexture contains the contents of the screen
            // where the object is about to be drawn.
            sampler2D _GrabTexture;
       
            // x=horizontal intensity, y=vertical intensity
            // z=horizontal scrolling speed, w=vertical scrolling speed
            float4 _IntensityAndScrolling;
               
            struct appdata_t {
                float4 vertex  : POSITION;
                half2 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 vertex  : SV_POSITION;
                half2 texcoord : TEXCOORD0;
                half2 screenuv : TEXCOORD1;
            #if MASK
                half2 maskuv   : TEXCOORD2;
            #endif
            };
       
            v2f vert (appdata_t v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
           
                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); // Apply texture tiling and offset.
                o.texcoord += _Time.gg * _IntensityAndScrolling.zw; // Apply texture scrolling.

            #if MASK
                // We don't scroll this texture lookup, because we want the mask to be static.
                o.maskuv = v.texcoord;
            #endif
                           
                half4 screenpos = ComputeGrabScreenPos(o.vertex);
                o.screenuv = screenpos.xy / screenpos.w;
                return o;
            }
               
            fixed4 frag (v2f i) : COLOR
            {       
                half2 distort = tex2D(_MainTex, i.texcoord).xy;
           
                // distort*2-1 transforms range from 0..1 to -1..1.
                // negative values move to the left, positive to the right.
                half2 offset = (distort.xy * 2 - 1) * _IntensityAndScrolling.xy;
                       
            #if MASK
                // _MainTex stores in the blue channel the mask.
                // The mask intensity represents how strong the distortion should be.
                // black=no distortion, white=full distortion
                half  mask = tex2D(_MainTex, i.maskuv).b;           
                offset *= mask;
            #endif                       
                                                                           
                // get screen space position of current pixel
                half2 uv = i.screenuv + offset;
                half4 color = tex2D(_GrabTexture, uv);
                UNITY_OPAQUE_ALPHA(color.a);
           
           
            #if DEBUGUV
                color.rg = uv;
                color.b = 0;
            #endif
           

                return color;
            }
        ENDCG
    }
}
}

Old Unity 4 Shader Example

// This source code is NOT a full fledged distortion solution.
// It's just an example and hopefully proves useful for someone.
//
// I'm not sure if Unity Pro is required due to the usage of "GrabPass".
Shader "Custom/DistortionExample" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _IntensityAndScrolling ("Intensity (XY), Scrolling (ZW)", Vector) = (0.1,0.1,1,1)
    }
    SubShader {
        Tags {"Queue" = "Transparent" "IgnoreProjector" = "True"}
        Lighting Off
        LOD 200

        // See http://docs.unity3d.com/Manual/SL-GrabPass.html
        GrabPass { "_GrabTexture" }

        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;
        sampler2D _GrabTexture;

        // x=horizontal intensity, y=vertical intensity
        // z=horizontal scrolling speed, w=vertical scrolling speed
        float4 _IntensityAndScrolling;

        struct Input
        {
            float2 uv_MainTex;
            float4 screenPos;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            // get screen space position of current pixel
            half2 screenUV = IN.screenPos.xy / IN.screenPos.w;
        #if UNITY_UV_STARTS_AT_TOP
            screenUV.y = 1-screenUV.y;
        #endif
  
            // _MainTex should use "Bypass sRGB Sampling" import setting.
            // d.x = horizontal offset 0..1
            // d.y = vertical offset 0..1
            float4 d = tex2D (_MainTex, IN.uv_MainTex+_Time.gg*_IntensityAndScrolling.zw);
  
            // d.x*2-1 transforms range from 0..1 to -1..1.
            // negative values move to the left, positive to the right.
            screenUV.x += (d.x*2-1) * _IntensityAndScrolling.x;
            screenUV.y += (d.y*2-1) * _IntensityAndScrolling.y;
  
            half4 c = tex2D (_GrabTexture, screenUV);
            o.Emission = c.rgb;
            o.Alpha = 1;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

You can download the Unity project of this example at:
http://www.console-dev.de/bin/UnityGrabPassDistortion.zip

EDIT, April 15th 2017: Package updated to allow to tint distortion areas. Project uses Unity 5.4.

EDIT, September 18th 2015: Package updated with a couple of more features, such as distance fade, culling mode and a particle shader. The package also contains examples that show what causes artifacts/problems with the shaders or perhaps distortion effects in general.

1 Like

Thanks Peter, you’re a legend :smile:

Hi Peter,

Thanks for the shader :slight_smile:

Since Unity Grab Pass would be not so cheap about performance, you know any trick without grabpass?

Thanks,

I believe it depends on what kind of effect you try to achieve, I don’t think there is a generic recipe.

If you try to get things fast, you often end up faking in very creative ways that will just work in specific places. Thinking about the water refraction in Super Mario Galaxy:

See http://www.codersnotes.com/graphics/mario-refraction for the entire text.

I found problem in our project under unity3d 5.2. the DistortionPlane_Masking move with the screen size. it is very werid. when i import that to one new probject, still under unity3d 5.2, it is okay. you can see my screenshots.



I tried to reproduce the problem, but it does not occur over here. I uploaded a new version (couple of new features and more examples) a minute ago (see post above for the link), you could give it a try if it changes anything for you (I don’t think it will though).

Thank you for your kindness, i know how to reproduce that. the problem just occurs after i switch to android platform. 100% occurs. i don’t know if it is related to unity3d version or some api. hope you can help me about that.

I was able to reproduce it now as well. It’s a new problem they introduced with Unity 5.2, it works well in Unity 5.1 though.

I submitted a bug-report to Unity:
(Case 728833) Regression: GrabPass offsets when resizing the screen

I’ll leave a note here when I get a response from them.

Thank you. :slight_smile:

Unity just replied to the bug-report with:

The next patch release would be Unity 5.2.0p2. If they stick to their patch release plan, it could be available around September 23rd.

Hi Peter and thanks for the shader. I’m however having some problems with the mask not working properly. Even with the mask on, it still looks like the first example where the edges are visible instead of it being a smooth transition. It works fine when I open your project as is, but when I import your shaders and use it in my existing project, the mask doesn’t seem to work properly. It use to work fine in 5.1, but I started having this problem when I upgraded to 5.2. Is there something special I have to set in my project settings?

EDIT: Installed 5.2.1 which came out today and it seems to have fixed the issue.

Original reply really good solution for waves. Eventually I found an example of what I was after here: http://haxeflixel.com/demos/FlxWaveSprite/

(a really simple horizontal wave)

Any clues on how to do this? Maybe it just applies this effect to anything behind it, or even a culling selection based on layer? (needs to work with sprites and meshes)

Hi Peter, I hate to ask more help from you, your shader was awesome, until I updated to unity 5.3. Now it is not working anymore. I tried to look into it by its waaay over my head. Maybe is a quick fix but I don’t know where to begin. If you can take a look I would be really grateful, but I would understand if you can get to it.

thanks!

Edit: The shader looks fine in the editor window, but not in the game (there seems to be some sort of offset and the mask is not working, I believe)

I tested a windows standalone build but it works just fine here, proper offset and mask is working too. If you can throw together an example, where it’s not working and send me the project, I can take a look at it.

Hi Peter, I did a few test and the problem was in my camera. I switch off hdr and everything is working now…
Thanks again!

Hello Peter !!! :wink:

Is it posible to extract this shader from unity5 and use it in a “direct x 9” game?
I wanna use this effect in my engine exhaust.to make heat distortion.

If yes, which files do i need to extract … and what needs to be done then, to combine them?

If you downloaded the provided zip archive already, you can find the relevant or interesting part in “Assets/GrabPassDistortion/GrabPassDistortion.cginc”.