Gravity well shader for Unity2D

Gravity well shader for Unity2D

I'm trying to find or create a gravity well distortion shader for Unity. I've been looking around and all I can find are "lenses" which bend space around a black hole.
I'd rather have a distortion that "pulls" or "bends" the image behind into a small hole. Essentially, what this would look like if viewed from above (ignoring the sphere):

I can't seem to find any examples or a common terminology for it. 

Solutions/Answers:

Answer 1:

This should, at least, give you a starting point, if not be exactly what you want:

I believe I modified the original shader I’d found to arrive at this, which appears to be from this tutorial.

Shader "Dvornik/Distort" {
    Properties{
        _Refraction("Refraction", Range(-10.00, 10.0)) = 0
        [HideInInspector][PerRendererData]_MainTex("Sprite Texture", 2D) = "white" {}
        _DistortTex("Distort (RG)",2D) = "gray" {}
    }

    SubShader{

        Tags{ "RenderType" = "Transparent" "Queue" = "Overlay" }
        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha

        GrabPass{

        }

        CGPROGRAM
            #pragma surface surf NoLighting alpha
            #pragma vertex vert
            #pragma multi_compile ___ DISTORTION_OFF

            fixed4 LightingNoLighting(SurfaceOutput s, fixed3 lightDir, fixed atten) {
                fixed4 c;
                c.rgb = s.Albedo;
                c.a = s.Alpha;
                return c;
            }

            sampler2D _GrabTexture : register(s0);
            sampler2D _MainTex : register(s2);
            sampler2D _DistortTex : register(s3);
            float _Refraction;

            float4 _DistortTex_TexelSize;

            struct Input {
                float2 uv_MainTex;
                #if !DISTORTION_OFF
                float2 uv_DistortTex;
                float4 screenPos;
                #endif
                float3 color;
                INTERNAL_DATA
            };

            void vert(inout appdata_full v, out Input o) {
                UNITY_INITIALIZE_OUTPUT(Input,o);
                o.color = v.color;
            }

            void surf(Input IN, inout SurfaceOutput o) {
                float4 main = tex2D(_MainTex, IN.uv_MainTex);
                #if !DISTORTION_OFF
                    float4 dist = tex2D(_DistortTex, IN.uv_DistortTex);
                    float2 distort = float2(dist.r - 0.5, dist.g - 0.5);
                    float2 offset = distort * _Refraction * _DistortTex_TexelSize.xy;
                    //float4 bgColor = tex2Dproj(_GrabTexture, IN.screenPos);
                    #if UNITY_UV_STARTS_AT_TOP
                    IN.screenPos.y = 1 - IN.screenPos.y;
                    #endif
                    IN.screenPos.xy = offset + IN.screenPos.xy;
                    float4 refrColor = tex2Dproj(_GrabTexture, IN.screenPos);
                    o.Emission = main.rgb * main.a * main.a + (1 - main.a) * refrColor.rgb;
                #endif


                #if DISTORTION_OFF
                    o.Emission = main.rgb;
                    o.Alpha = main.a;
                #else
                    o.Alpha = 1;
                #endif
            }
        ENDCG
    }
    CustomEditor "DistortToggleInspector"
}

This, along with the right distortion texture (included below) results in this effect:

Distortion

At you can see, the neighboring pixels from other shots and the background have been pulled in towards the center (but leaves the white arc shaped bullet alone). The white arc is the sprite this shader is attached to (no distortion comparison and effect on grid). It also looks a lot better in motion, the blurriness isn’t really noticed as it blends in with everything moving around. I tuned the settings to be noticeable at the speeds I was working with, without being too overwhelming.

Settings:

Sprite settings

Distortion map:

Distortion map

From what I recall, the way this shader works is that the screen is grabbed and passed into the shader, which determines how to drag the pixels around based on the distortion map (which I created for this specific effect, trying to create a “round” sort of bubble, although it ends up being more conical) and the _Refraction amount (higher values, strong effect, negative values pull inward instead of pushing outward–at least on this distortion map).

Note that the effect won’t work properly in the scene view. This is due to scene view and game view having different UV coordinates and it didn’t bother me enough to write compiler directives to account for it (but I did learn about the multi_compile pragma!).

The effect can also be disabled at runtime (in case you want to disable flashy effects for people with weaker computers):

Material mat = Resources.Load<Material>("Distortion");
if(all_effects) {
    mat.DisableKeyword("DISTORTION_OFF");
}
else {
    mat.EnableKeyword("DISTORTION_OFF");
}

And via custom inspector script:

public class DistortToggleInspector : CustomMaterialEditor {
    protected override void CreateToggleList() {
        Toggles.Add(new FeatureToggle("Distortion Enabled", "Distort", "___", "DISTORTION_OFF"));
    }
}

References