[Solved] Curved horizon with sprites

Hey guys!

I am trying to modify Unitys default sprite shader to achieve a curved effect. Basically, I want to decrease the Y-position of vertices by the square distance from the camera along the X-axis.

I found this curved world shader that helped me get started. Here is the main part of the functionality in that shader, slightly modified so that it curves based on the X-axis rather than Z:

// This is where the curvature is applied
void vert( inout appdata_full v)
{
    // Transform the vertex coordinates from model space into world space
    float4 vv = mul( _Object2World, v.vertex );
    // Now adjust the coordinates to be relative to the camera position
    vv.xyz -= _WorldSpaceCameraPos.xyz;
    // Reduce the y coordinate (i.e. lower the "height") of each vertex based
    // on the square of the distance from the camera in the z axis, multiplied
    // by the chosen curvature factor
    vv = float4( 0.0f, (vv.x * vv.x) * - _Curvature, 0.0f, 0.0f );
    // Now apply the offset back to the vertices in model space
    v.vertex += mul(_World2Object, vv);
}

So in the sprite shader I a have tried this:

v2f vert(appdata_full IN)
    {
    v2f OUT;
    float4 vv = mul(UNITY_MATRIX_MVP, IN.vertex);
    vv.xyz -= _WorldSpaceCameraPos.xyz;
    vv = float4( 0.0f, (vv.x * vv.x) * - _Curvature, 0.0f, 0.0f );
    vv += mul(_World2Object, vv);
    OUT.vertex = vv;
    OUT.texcoord = IN.texcoord;
    OUT.color = IN.color * _Color;
    #ifdef PIXELSNAP_ON
    OUT.vertex = UnityPixelSnap (OUT.vertex);
    #endif

    return OUT;
}

I am very new to this, and I don’t really understand what UNITY_MATRIX_MVP is. I have tried to replace it with _Object2World but that didn’t help. Obviously I am doing something very wrong, because the sprite disappears.

If someone could point me in the right direction I would really appreciate it! I guess I need a better understanding of the math involved here.

Full modified sprite shader below.

Shader "Sprites/Curved"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        _Curvature ("Curvature", Float) = 0.001
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ PIXELSNAP_ON
            #include "UnityCG.cginc"
          
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
            };
          
            fixed4 _Color;
            uniform float _Curvature;

            v2f vert(appdata_full IN)
            {
                v2f OUT;
                float4 vv = mul(UNITY_MATRIX_MVP, IN.vertex);
                vv.xyz -= _WorldSpaceCameraPos.xyz;
                vv = float4( 0.0f, (vv.x * vv.x) * - _Curvature, 0.0f, 0.0f );
                vv += mul(_World2Object, vv);
                OUT.vertex = vv;
                OUT.texcoord = IN.texcoord;
                OUT.color = IN.color * _Color;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif

                return OUT;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f IN) : SV_Target
            {
                fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
                c.rgb *= c.a;
                return c;
            }
        ENDCG
        }
    }
}

Yay, I managed to fix it myself!

It’s not perfect. Some sprites behave a little strange close to the edge of the screen. It’s like the new vertex positions disappear sometimes. But still, I’m very happy to have made it this far!

So if anyone else would like to use or improve on it, feel free. And please share the results!

Shader "Sprites/Curved"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        _Curvature ("Curvature", Float) = 0.001
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ PIXELSNAP_ON
            #include "UnityCG.cginc"
           
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
            };
           
            fixed4 _Color;
            uniform float _Curvature;

            v2f vert(appdata_t IN)
            {
                v2f OUT;
                float4 vv = mul( _Object2World, IN.vertex );
                vv.xyz -= _WorldSpaceCameraPos.xyz;
               
                //Curvature and taper effects are calculated here
                vv = float4( vv.x * (vv.y * _Curvature), (vv.x * vv.x) * - _Curvature, 0.0f, 0.0f );
               
                //Use this instead if you don't want the taper effect
                //vv = float4( 0.0f, (vv.x * vv.x) * - _Curvature, 0.0f, 0.0f );
               
                OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex) + mul(_World2Object, vv);
                OUT.texcoord = IN.texcoord;
                OUT.color = IN.color * _Color;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif

                return OUT;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f IN) : SV_Target
            {
                fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
                c.rgb *= c.a;
                return c;
            }
        ENDCG
        }
    }
}