After much trial and error, I was able to achieve what I wanted by modifing Unity's default "GUI/Text Shader" shader, which I downloaded from here under "Downloads (Win)" > "Built in shaders".
I have very little idea how shaders work, but here's my crude explanation of what I think is happening in the shader code below:
- It stores the original pixel art in a variable named
fixed4 sprite_texture - It stores a solid colored copy of the pixel art, painted with the SpriteRenderer's current color, in a variable named
fixed4 solid_color - It merges the two variables together using
lerp, with the SpriteRenderer's current color's alpha value being used to determine how much the original pixel art should be colorized.
Once that shader has been applied to a GameObject's SpriteRenderer's material, I can easily change the GameObject's color through its MonoBehavior code.
Usage example (C#):
"0% red":
this.sprite_renderer.color = new Color(1f, 0f, 0f, 0f);
"50% red":
this.sprite_renderer.color = new Color(1f, 0f, 0f, .5f);
"100% red":
this.sprite_renderer.color = new Color(1f, 0f, 0f, 1f);
I'm not sure if there any drawbacks to this approach, but from what I can tell, it's easy to use, supports all colors (including pure white and black), and supports pixel art with a transparent background, which is exactly what I wanted.
Usage example (GUI):

Full shader code:
Shader "DynamicColorShader" { Properties { _MainTex ("Font Texture", 2D) = "white" {} } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" } Lighting Off Cull Off ZTest Always ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ UNITY_SINGLE_PASS_STEREO STEREO_INSTANCING_ON STEREO_MULTIVIEW_ON #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; uniform float4 _MainTex_ST; uniform fixed4 _Color; v2f vert (appdata_t v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.vertex = UnityObjectToClipPos(v.vertex); o.color = v.color; o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex); return o; } fixed4 frag (v2f IN) : SV_Target { fixed4 sprite_texture = tex2D (_MainTex, IN.texcoord); fixed4 solid_color = IN.color; solid_color.a *= tex2D(_MainTex, IN.texcoord).a; fixed4 final = lerp(sprite_texture, solid_color, solid_color.a); return final; } ENDCG } } }