Multi-angle sprites mechanics

So here’s the basic premise of what I’m trying to do. I’m in the middle of making a Racing game, where 2D characters move around a 3D environment.

The player object is set up as a parent which consists of target object, the camera, and graphics component. The Parent object itself is assigned with a box collider, and rigidbody. and a simple controller script for the moment being. The graphics object is a basic quad object set up with a simple billboard script, which makes the object face towards the assigned camera relative to said cameras forward position.

public Camera m_Camera;
   
    void Update()
    {
        transform.LookAt(transform.position + m_Camera.transform.rotation * Vector3.forward,
            m_Camera.transform.rotation * Vector3.up);
    }

What I want, is to create a player object that shows different sprites states depending on the angle the camera views it from. Sort of like what was done with Doom like the first video shown,

or in this case, Mario Kart 64.

Now I have a sample script, which I believe was used for enemy sprites, but is probably goes by the same principle.

int GetAngleIndex()
     {
         var dir = GetComponent<Camera>().transform.position - transform.parent.forward;
         var enemyAngle = Mathf.Atan2(dir.z, dir.x) * Mathf.Rad2Deg;
         if (enemyAngle < 0.0f)
             enemyAngle += 360;
         Debug.Log("Angle from the player is: " + enemyAngle);
         if (enemyAngle >= 292.5f && enemyAngle < 337.5f)
             return 8;
         else if (enemyAngle >= 22.5f && enemyAngle < 67.5f)
             return 2;
         else if (enemyAngle >= 67.5f && enemyAngle < 112.5f)
             return 3;
         else if (enemyAngle >= 112.5f && enemyAngle < 157.5f)
             return 4;
         else if (enemyAngle >= 157.5f && enemyAngle < 202.5f)
             return 5;
         else if (enemyAngle >= 202.5f && enemyAngle < 247.5f)
             return 6;
         else if (enemyAngle >= 247.5f && enemyAngle < 292.5f)
             return 7;
         else if (enemyAngle >= 337.5f || enemyAngle < 22.5f)
             return 1;
         else return 0;
     }

Still, If anyone has any questions to ask, advice to give out, or any critiques to give, I probably would appreciate the attention. There’s a lot I got to figure out.

I used to do this by having a model that contained all view directions. (A bit like the classic + shaped tree models.) The shader would then reject the views that were not needed. Indeed based on the angle between the forward direction of the camera and the normal of the view. (Using the view direction leads to some undesired results.)

The idea behind this is that it is more batch friendly and the GPU is fast enough to skip some extra pixels anyway. This might apply less directly to mobile hardware.

do you mean like the ones used in The Legend of Zelda: Ocarina of Time? Just how might something like that be constructed?

You can do the rejection using the cameras forward dir in the vertex shader by moving the vertex behind the camera. Way cheaper than pixel rejection, mobile friendly.

could it work for a simple quad object as well? Also, how does rejection work with vertex shaders?

You’re rejecting each vertex individually, so you have to be sure the test you use to reject is the same for all vertices of a polygon, but by placing the polygon behind the camera it will get automatically rejected by the GPU during rasterization.

ohhh, okay starting to get it now. thanks for the explanation.

alright, so I’ve created a simple vertex shader to start things off. I just need to figure out where I edit it so that able to move the vertex behind my camera.

Shader "Unlit/sprite shader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
       
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
       
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
       
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);           
                return col;
            }
            ENDCG
        }
    }
}

Now do I adjust the vertex for postition here

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

of perhaps here?

struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            }

Anyone familiar with vertex shaders?

Read through all 5 parts:

Alright, so I’ve read through every part of shaders introduction, and now I think a have a good idea on what I should do. It might not be exactly though, so feel free to correct me on thing mentioned.

So for my shader, it has to work with a sprite sheet, specifically it various units. For my character, there are twelve sprites being used for it, ten of which will be flip horizontally. In total that makes 22 images used for a three hundred sixty degree rotation around a player, and as “bgolus” said in the comments,

Using a vertex shader, If I program the units of my sprite sheets with

&

in way that similar to an animation transition, then maybe it might give the illusion of a 2d character with a 3d rotation. Basically, depending on the angle the graphics face the assigned camera from, it will show a different view of character.

POSITION in the appdata struct is just a vertex position from the mesh, and TEXCOORD0 is just the first UV map for that vertex.

The vertex shader takes that data and transforms the vertex positions into the screen space position (called clip space or projection space), and passing the texture coordinates on to the fragment shader (optionally transforming those too, in Unity’s case often using the scale and offset values you set on the material).

To move the position behind the camera after:
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
you can set:
o.vertex.z = -1;

Except that won’t necessarily work properly in Unity 5.5 on Windows, or in OpenGL. You’ll actually need to do:
#ifdef UNITY_REVERSED_Z
o.vertex.z = 1; // note, that’s not a negative
#else
o.vertex.z = -o.vertex.w * 2.0;
#endif

For the rest of it, like determining if it should be shown or not, you’ll need to have some decent understanding of vector math and dot products. I suggest reading some tutorials on cat like coding.

You’ll need to compare the camera’s forward vector to the surface normal and, depending on the angle hide or show it.

There’s also the task of creating the original mesh with the multiple planes and UVs setup for each of the rotations. And then you’ll have to deal with animation somehow.

edit: Or you can try this one which may or may not use the same technique.
http://1darray.com/blog/2015/04/06/doom-style-billboard-shader/