Packing and Unpacking Colors for Batched Per Sprite Effects

Hi,

So I have had the idea that I can write a custom shader for the built-in SpriteRenderer that lets me do per-sprite based effects without breaking batching because its per-vertex data that is being passed in.

What I would like to do is pack the sprite color into a single float then set that in a single channel of the color leaving me with the other 3 channels for whatever I want.

So this is what I have on the C# side:

[ExecuteInEditMode]
public class SpriteEffects : MonoBehaviour
{
    [Range(0, 2)]
    public float brightnessAmount = 1;
   
    [Range(0, 2)]
    public float greyscaleAmount = 1;
   
    public Color tint;
   
    private SpriteRenderer spriteRenderer;
   
    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    public void Update()
    {
        spriteRenderer.color = GetColor(tint, brightnessAmount, greyscaleAmount, 1f);
    }

    public Color GetColor(Color color, float brightness, float greyscale, float somethingelse)
    {
        // Pack the color into a single float
        var magic = new Vector4(1.0f, 1/255.0f, 1/65025.0f, 1/160581375.0f);
        var v4 = new Vector4(color.r, color.g, color.b, color.a);
        var packedColor = Vector4.Dot(v4, magic);

        return new Color(packedColor, brightness, greyscale, somethingelse);
    }
}

Then on the shader side:

Shader "Sprites/Little Lords"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    }
    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }
        Cull Off
        Lighting Off
        ZWrite Off
        Fog { Mode Off }
        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile DUMMY PIXELSNAP_ON
            #include "UnityCG.cginc"
          
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
            };
          
            fixed4 _Color;
            v2f vert(appdata_t IN)
            {
                v2f OUT;

                OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
                OUT.texcoord = IN.texcoord;   
               
                OUT.color = EncodeFloatRGBA(IN.color.r);

                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif
                return OUT;
            }
            sampler2D _MainTex;
            fixed4 frag(v2f IN) : COLOR
            {
                half4 texcol = tex2D (_MainTex, IN.texcoord);             
                texcol.rgb *= IN.color.rgb;
                return texcol;
            }           


        ENDCG
        }
    }
    Fallback "Sprites/Default"
}

For now im just using the .r component which should be the color.

But for some reason the result is just black and I cant figure out why:

1846430--118413--upload_2014-11-13_15-52-16.png

Does anyone have a clue where im going wrong?

I might be totally wrong but it could be that the SpriteRenderer mesh just doesn’t have any colour data in it’s mesh. Perhaps it’s colour was just controlled by a shader param like this:

Properties {
_Color (“Main Color”, Color) = (0.5,0.5,0.5,1)
_MainTex (“Base (RGB) Trans (A)”, 2D) = “white” {}
}

When you set spriterenderer.color it might just be setting the material colour.

You could add that to your shader and just use _Color instead of the vertex colour. Not sure if that would work though as I think you wanted to be able to have them in the vertex colour for a reason?

It is probably related to the decoding and encoding part. I would port EncodeFloatRGBA to C# and printf what its output is. It’s probably not even close to the input color. It’s the only thing that could go wrong in this shader :slight_smile:

BTW, EncodeFloatRGBA has issues with encoding a value of 1 (it’s a comment above the function at least).

Im pretty sure thats not the case as you can tint each sprite individually and they are still batched, the only way (as far as I am aware) that this is possible is via vertex data.

Good idea, ill give this a go and let you know!

Any success?
I have considered going down the same track for the “per button effects” we want to have in our next title. But if there’s no actual vertex color data sent to the UI buttons it won’t make much sense.

I haven’t gotten round to it yet unfortunately. For now im packing it in the alpha channel using a simple divide:

 public Color GetColor(Color color, float brightness, float greyscale, float somethingelse)
        {
            return new Color(color.r, color.g, color.b, brightness / 2f);
        }