4
\$\begingroup\$

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):

enter image description here

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

\$\endgroup\$
5
  • 1
    \$\begingroup\$ I can think of two possibilities: 1) a geometry shader that creates/modifies the mesh to cause a physical distortion. 2) a refraction shader that warps whatever was rendered behind it--this would require a separate object that lays on top. Both options have their limitations, however. The first requires enough geometry that it can be altered smoothly. The second would be unable to handle the transparency of the mesh seen here (the downward dip would disappear over the "horizon"). \$\endgroup\$ Dec 12, 2017 at 4:16
  • \$\begingroup\$ The example image was more for just clarity - I wanted to convey what I want. All I'm really after is something that can warp the image behind so that it seems like it's being "pulled" into the well. Replace the grid in the sample pic with a generic space background and you'll get what I'm after. \$\endgroup\$
    – BotskoNet
    Dec 12, 2017 at 5:08
  • 1
    \$\begingroup\$ (It was better you used 2D photos in your question, because the question is about two-dimensional.) for pulling and pushing it into a small hole you can use vertex shader.i think this tutorial is useful for you. \$\endgroup\$ Dec 12, 2017 at 12:15
  • \$\begingroup\$ Ya, sorry, I tried to find a 2D example but couldn't find something that worked. Everything was 3D/angled because they were focusing on the curvature. \$\endgroup\$
    – BotskoNet
    Dec 12, 2017 at 15:39
  • \$\begingroup\$ Oh, two dimensional? Yeah, a refraction shader would be perfect. \$\endgroup\$ Dec 12, 2017 at 16:17

1 Answer 1

1
\$\begingroup\$

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"));
    }
}
\$\endgroup\$
5
  • \$\begingroup\$ That example shot from within the game is really hard to understand unless you look really close enough. How about replacing the ground with a checkerboard texture and give the shots a simple shape such as a circle with an outline? Just if it isn't too much to ask for. \$\endgroup\$
    – Mario
    Dec 12, 2017 at 18:48
  • \$\begingroup\$ @Mario It's a little hidden, but I did include this image (as a link): i.stack.imgur.com/NBUzm.png \$\endgroup\$ Dec 12, 2017 at 19:11
  • \$\begingroup\$ Ah! That's a lot better. It's consider embedding both directly. \$\endgroup\$
    – Mario
    Dec 13, 2017 at 13:55
  • \$\begingroup\$ Thanks! Looking at the grid image it seems like this is exactly what I'm after. What would need to change for this to be usable as a post-processing effect? My game is actually 3D but I want this effect applied to a camera as a post-processing effect so that whatever is behind it gets warped. In that context the shader itself isn't put onto the camera but rather a simple script that uses the material and Graphics.Blit. \$\endgroup\$
    – BotskoNet
    Dec 13, 2017 at 15:12
  • \$\begingroup\$ @BotskoNet I have no idea. I poked at trying to do a post processing shader a few weeks ago and couldn't get it to work. \$\endgroup\$ Dec 13, 2017 at 15:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .