Render Two Sprites in One Shader

Most of the character faces in Academia are generated in a procedural way. Each character has its own combination of face and head. Heads can either be hair or construction hat if they are workers. Both faces and heads are contained in a single texture so Unity may apply batching.

FaceHair
Part of the faces and heads texture

To generate a character, you simply render a face and render its head on top of it.

Characters

This can be done with two sprites. However, there are disadvantages. First, more draw calls. Second, it looks weird when the characters are overlapping because of z fighting. I needed a way to render both the face and head in only one sprite. This reduces draw calls and prevents weird z fighting. Fortunately, this is easy enough to do using a custom shader.

While working on our custom quad mesh where the characters would be displayed on, I realized that vertices can have more than one UV. This means that I can use the first UV for the face and use a second UV to draw the head. These two renders can be done within a single shader.

Shader "Common/TwoUvsLayeredTexture"
{
	Properties
	{
		_Texture("Main Texture", 2D) = "white" {}
	}

	SubShader
	{
		Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
		ZWrite Off Lighting Off Cull Off Fog{ Mode Off } Blend SrcAlpha OneMinusSrcAlpha
		LOD 110

		Pass
		{
			CGPROGRAM
			#pragma vertex vert_vct
			#pragma fragment frag_mult 
			#pragma fragmentoption ARB_precision_hint_fastest
			#include "UnityCG.cginc"

			sampler2D _Texture;

			struct vin_vct
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 texcoord0 : TEXCOORD0;
				float2 texcoord1 : TEXCOORD1;
			};

			struct v2f_vct
			{
				float4 vertex : SV_POSITION;
				fixed4 color : COLOR;
				float2 texcoord0 : TEXCOORD0;
				float2 texcoord1 : TEXCOORD1;
			};

			v2f_vct vert_vct(vin_vct v)
			{
				v2f_vct o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.color = v.color;
				o.texcoord0 = v.texcoord0;
				o.texcoord1 = v.texcoord1;
				return o;
			}

			fixed4 frag_mult(v2f_vct i) : SV_Target
			{
				fixed4 col1 = tex2D(_Texture, i.texcoord0) * i.color;
				fixed4 col2 = tex2D(_Texture, i.texcoord1) * i.color;

				fixed4 output;
				output.rgb = (col1.rgb * (1.0f - col2.a)) + (col2.rgb * col2.a);
				output.a = min(col1.a + col2.a, 1.0f);
				return output;
			}

			ENDCG
		}
	}
}

The magic is in frag_mult(v2f_vct i). Actually, I’m not too sure if that’s the proper way to blend two images. I’m not very good with shaders. I arrived with that using trial and error. Let me know if there’s a better way.

[Edit] Some people pointed out that it’s generally not ok to have conditionals in shader code. Thus, I’ve updated the code with a better one. This is why I blog.