How can I improve this shader?

I have a shader, which I use to set which sprite is showing depending on the angle of the camera (Basically, a doom style 4 sided sprite).
It’s a very simple shader, and i would like to know how i could implement Animation/SpriteSheet Animation, Interaction with lights (receive or cast shadows) and normal maps.

//https://github.com/unitycoder/DoomStyleBillboardTest

Shader "UnityCoder/DoomSprite3" 
{

	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Frames ("Frames", Float) = 8
	}

	SubShader 
	{
    	Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
    	
    	ZWrite Off
    	Blend SrcAlpha OneMinusSrcAlpha		
	    
		Pass 
		{
	        CGPROGRAM

		    #pragma vertex vert
		    #pragma fragment frag
			#define PI 3.1415926535897932384626433832795
			#define RAD2DEG 57.2957795131
		    #define SINGLEFRAMEANGLE (360/_Frames)
		    #define UVOFFSETX (1/_Frames)
		    #include "UnityCG.cginc"

			uniform sampler2D _MainTex;
			uniform float4 _MainTex_ST;

            struct appdata {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

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

            float4x4 _CameraToWorld;
            float _Frames;

            float atan2Approximation(float y, float x) // http://http.developer.nvidia.com/Cg/atan2.html
			{
				float t0, t1, t2, t3, t4;
				t3 = abs(x);
				t1 = abs(y);
				t0 = max(t3, t1);
				t1 = min(t3, t1);
				t3 = float(1) / t0;
				t3 = t1 * t3;
				t4 = t3 * t3;
				t0 =         - 0.013480470;
				t0 = t0 * t4 + 0.057477314;
				t0 = t0 * t4 - 0.121239071;
				t0 = t0 * t4 + 0.195635925;
				t0 = t0 * t4 - 0.332994597;
				t0 = t0 * t4 + 0.999995630;
				t3 = t0 * t3;
				t3 = (abs(y) > abs(x)) ? 1.570796327 - t3 : t3;
				t3 = (x < 0) ?  PI - t3 : t3;
				t3 = (y < 0) ? -t3 : t3;
				return t3;
			}

            v2f vert (appdata v) 
            {
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

                // object world position
   				float3 objWorldPos=float3(_Object2World._m03,_Object2World._m13,_Object2World._m23);

				// get angle between object and camera
				float3 fromCameraToObject = normalize(objWorldPos - _WorldSpaceCameraPos.xyz);
				float angle = atan2Approximation(fromCameraToObject.z, fromCameraToObject.x)*RAD2DEG+180;

				// get current tilesheet frame and feed it to UV
				int index = angle/SINGLEFRAMEANGLE;
                o.uv = float2(v.texcoord.x*UVOFFSETX+UVOFFSETX*index,v.texcoord.y);
                          
               // billboard mesh towards camera
  				float3 vpos=mul((float3x3)_Object2World, v.vertex.xyz);
 				float4 worldCoord=float4(_Object2World._m03,_Object2World._m13,_Object2World._m23,1);
				float4 viewPos=mul(UNITY_MATRIX_V,worldCoord)+float4(vpos,0);
				float4 outPos=mul(UNITY_MATRIX_P,viewPos);
				
				o.pos = UnityPixelSnap(outPos); // uses pixelsnap
				
                return o;
            }

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

in my own project i do all my animations in c#. (i also have doom style but 8-sided.) the way i do animations is by passing a material property block with the offset and dimensions of the sprite, and resize/move the quad in the shader to fit the specific sprite in the spritesheet. there are specific functions in the enemy script that decide when to play specific animations. i store the offsets in an array in a separate script so it can be called agnostically from the enemy, allowing all enemies to use the same animation structure and script.

you could also use the legacy animation component, and play specific animations by index. it would be pretty cheap as the animation component is simple by nature. you can also take advantage of animation events etc to change material property blocks if you want to use them.

how you want do deal with lights is a separate matter, if you want just light direction and color, you can go with vertex lighting (normal maps are compatible if you combine all lights and pass the dir/color to the fragment). if you need per pixel lighting or shadows, you will have to write multiple passes for your shader that will be much more complex. a trick i use to help sell simple lighting is casting fake planar shadows by having a second shader pass and flattening the sprite towards the direction of the light.

i don’t really know anything about implementing normal maps, so i can’t help you there.

a great video to help you understand how things worked back in the day:
How Doom’s Enemy AI Works

That is unlit transparent shader, so would have to modify that part too (to get lights etc)…

if you don’t have many objects, definitely easier to handle with script.

or look for better alternatives,

there used to be some paid ones too.
(and probably can find more in github)

Unity even has mysterious billboard asset, but not sure if its usable