Drawing an animated arc on the screen

Hi! I need to draw an animated arc on the screen. I already created a 3D solution that I hold at 90 degrees to the camera:
7727292--970281--arcSS.png
It works by extending the slices of a mesh. However:

  • The mesh goes through perspective transformation. I need constant view size.
  • The arc will interact with the mouse/tap drag. The calculations would be much more straightforward.

I couldn’t find the proper way to use UnityObjectToViewPos. The mesh disappears when I use this:
float4(UnityObjectToViewPos(v.vertex),1)

Any recommendations/alternatives are welcome. Thanks for reading!

So you want these to be a constant size on screen regardless of resolution, distance, fov, etc., correct? The easiest solution is … do everything in screen space!

//vertex shader
// get clip space position of the mesh's pivot
float4 clipPivot = UnityObjectToClipPos(float3(0,0,0));

// get the normalized device coordinate space position of that pivot
float3 ndcPivot = clipPivot.xyz / clipPivot.w;

// do stuff you're already doing to get the object space vertex position
float3 vertexPos = // stuff;

// the screen's aspect ratio
float aspect = _ScreenParams.x / _ScreenParams.y;

// scale to the size you want it to be on screen
vertexPos.xy *= _ArcScreenSize * 2.0;

// correct for the aspect ratio
vertexPos.x *= aspect;

// flip upside down, for reasons
vertexPos.y *= -1;

// add ndc pivot and vertex position together
float3 outPos = vertexPos  + ndcPivot;

// SV_Position output
o.pos = float4(outPos, 1.0);

Homogeneous Clip Space is a 4 component projective coordinate system that can be thought of as “screen space”, but where the x and y values are in a -w to +w range for what will appear on screen, where w is literally the w value of that coordinate. It exists in part as a byproduct of linear projection matrices (how real time rendering calculates results of the camera’s FOV), and has the advantageous property of being a value that can be interpolated linearly in screen space while remaining perspective correct. Basically it helps prevent the weird PS1 style texture warping.

The Normalized Device Coordinates are the clip space after the “perspective divide”, which a fancy term for dividing the clip space by the w component. The x and y are now a value that is a -1 to +1 range for what appears on screen.

Assuming your arc mesh has a width of 1 unit on the x and y, you’ll want to scale it up or down to the size you want it to be on screen. Because we’re working in NDC space which has a -1 to +1 range, I multiply that by 2.0. Otherwise you’re setting the half size, which you can do too. The x axis is divided by the aspect ratio.

Shader "Screen Space Mesh"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Size ("Screen Space Size", Range(0,1)) = 0.25
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "DisableBatching"="True" }
        LOD 100

        Pass
        {
            Cull Off
            ZTest Always
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _Size;

            v2f vert (appdata v)
            {
                v2f o;
                // get clip space position of the mesh's pivot
                float4 clipPivot = UnityObjectToClipPos(float3(0,0,0));

                // get the normalized device coordinate space position of that pivot
                float3 ndcPivot = clipPivot.xyz / clipPivot.w;

                // do stuff you're already doing to get the object space vertex position
                float3 vertexPos = v.vertex.xyz;

                // just something to make it rock back and forth
                float angle = sin(_Time.y);
                float c = cos(angle);
                float s = sin(angle);
                vertexPos.xy = mul(float2x2(c,-s,s,c), vertexPos.xy);

                // scale
                vertexPos.xy *= _Size * 2.0;

                // correct for aspect ratio and screen space y flip
                vertexPos.x /= _ScreenParams.x / _ScreenParams.y;
                vertexPos.y *= -1;

                // add ndc pivot and vertex position together and output as SV_Position
                o.pos = float4(vertexPos + ndcPivot, 1.0);

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}
2 Likes

Maybe render arc as post processing effect ? Assign script to camera:

using UnityEngine;
using System.Collections;

public class Arc : MonoBehaviour
{
    public Shader ArcShader;
    private Material _Material;

    void Start()
    {
        _Material = new Material(ArcShader);
    }

    void OnRenderImage (RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit (source, destination, _Material);
    }
}
Shader "Arc"
{
    Properties
    {
        [HideInInspector] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain

            uniform sampler2D _MainTex;

            // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
            float DrawArc (in float2 _p, in float2 _sca, in float2 _scb, in float _ra, in float _rb)
            {
                _p = mul(_p, transpose(float2x2(_sca.x, _sca.y, (-_sca.y), _sca.x)));
                _p.x = abs(_p.x);
                float k = ((_scb.y * _p.x) > (_scb.x * _p.y)) ? dot(_p, _scb) : length(_p);
                return (1.0 - step((sqrt(max(0.0, ((dot(_p, _p) + (_ra * _ra)) - ((2.0 * _ra) * k)))) - _rb), 0.0));
            }

            float4 VSMain (float4 vertex : POSITION, inout float2 uv : TEXCOORD0) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            float4 PSMain (float4 vertex : SV_POSITION, float2 uv : TEXCOORD0) : SV_TARGET
            {
                float time = fmod(_Time.g, 3.14159);
                float2 texcoord = float2(2.0 * uv - 1.0);
                float aspect = _ScreenParams.x / _ScreenParams.y;
                texcoord.x = texcoord.x * aspect;
                float d = DrawArc(texcoord, float2(sin(time),cos(time)), float2(sin(time),cos(time)), 0.4, 0.08);
                return (d > 0.0) ? tex2D(_MainTex, uv) : float4(d.xxx, 1.0);
            }
            ENDCG
        }
    }
}

7735173--971862--arc.gif

Thank you both! It may take some time but I’ll make the most of these awesome replies!

I ended up building the arc in object space and converting it to view space.
There’s an image explaining my mesh at the bottom.

Shader "Unlit/Arc"
{
    Properties
    {
        _totalArcAngle("Total arc angle", Range(-7,7)) = 0.03
        _viewPosARRad("View position(XY), aspect ratio(Z), arc radius", Vector) = (0,0,1.7777777,0.17)
    }
    SubShader
    {
        Tags { "DisableBatching" = "True" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
          
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
            };
          
            float _totalArcAngle;//Updated each frame
            float4 _viewPosARRad;//Set only on instantiation
          
            v2f vert (appdata_full v)
            {
                v2f o;
                UNITY_INITIALIZE_OUTPUT(v2f,o);
              
                //Keep position 2D until final
                float2 vPos = float2(v.vertex.x, v.vertex.z);
              
                /*Place verts onto an arc in object space
                Mesh U is circular tip extension(0 to mesh dot's radius).
                Mesh V increases with each mid-section slice(0 to 1)*/
                float vAng = v.texcoord.x/_viewPosARRad.w +v.texcoord.y*_totalArcAngle;//Vert's angle on the arc
                vPos += (vPos.y -_viewPosARRad.w)*float2(sin(vAng), cos(vAng)-1);//Vert placed on the arc(radius can go into mesh)
              
                //Object->Viewport transformation
                vPos.x /= _viewPosARRad.z;//Fit to aspect ratio
                vPos.x += _viewPosARRad.x;
                vPos.y += _viewPosARRad.y;
              
                o.pos = float4(vPos.x, -vPos.y, 0.007, 1);//Flip Y!
              
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(0.66,0.66,0.66,1);
            }
            ENDCG
        }
    }
}

7749969--974994--arcExplain.png