Using sprites for button states

Changing the state of a button in Unity (ON, OFF, FAULT, etc.) can be done in a few different ways. The easiest is to make a few different materials and change the material at runtime. However, this is not good for performance.

There is a better way. Use a texture atlas (sprite sheet) and shift the UV’s. This way the operation runs entirely on the GPU which is many times faster. It is not as easy to set up though, so here is a detailed description how to do this..

First you need to render separate emissive textures for each button state. In most cases there are 4 states: no lights, ON light, FAULT light, ON and FAULT light. This is great because each texture can be stored in a corner of the main texture making it both efficient and easy to set up.

The textures can be rendered in Substance Painter by creating a layer for each button state, each with different emissive materials placed using the ID map. Then disable all emissive layers except one and export the textures. Rename the emissive texture and export again with another button state enabled. Do this for each button state until you have 4 separate textures. Click on the thumbnail for a better view. I wrote a plugin for Substance Painter which makes exporting the textures more easy. You can find it here:

Once the plugin is installed, just press on the “Export Emissive” button and it will automatically save the emissive channel and rename the texture as necessary.

Below you can see the 4 exported emissive channel textures. Note that the orientation is on its side. This is due to the automatic UV unwrapping from Unwrella. It might not look nice but it is completely irrelevant in our workflow.


The next step is to place each of the 4 textures in the corner of a new, bigger texture. This can be done in Gimp using a plugin called “fuse layers”. You can find the plugin here:

Place the plugin in the following directory:
C:\Program Files\GIMP 2\share\gimp\2.0\scripts\

Once the plugin is installed, fuse the 4 textures into a single one.
File->new-> set the same resolution of the input image. The resolution of the final image will be automatically increased accordingly.
Set Image->mode to RGB.
File->Open as layers-> select all 4 images.
Delete the background layer.
Filters->Combine-Fuse layers. Set x = 2.
To save, use file->export.

Now we have a single texture containing a button state in each corner:


This texture can’t be used as-is because the UV mapping needs to be changed in code. This is what happens when you apply the texture in Unity without any UV modifications:


To fix this, the Unity standard shader needs to be modified. Here is how to do that:

-Download build in shaders.

-Copy “Standard.shader”, rename to “StandardShift.shader”, and put into project in the same folder called Shaders.

-Copy the following files and put into project in a folder called Shaders. Note that only the file “UnityStandardInput.cginc” from this list will be modified, but all other files are needed, otherwise it won’t work.


-Open StandardShift.shader.
-Modify the line —–Shader “Standard”—– at the beginning of the shader to:

Shader "StandardShift"

Place this code below the line —–_DetailNormalMap(“Normal Map”,—–

_EmissionTileOffset("EmissionTileOffset", Vector) = (1,1,0,0)

Note: because the programmers at WordPress think it is a good idea to change the quote format (“), you might not be able to find a line of code using copy-paste-search.  Just search for a single word instead.

-Open UnityStandardInput.cginc.
-Place this code just below the line —–sampler2D  _EmissionMap;—–

half4   _EmissionTileOffset;

-Search for this function:
—–half3 Emission(float2 uv)—–
-Place this code just above the line —–return tex2D(…—–

uv.x *= _EmissionTileOffset.x;
uv.y *= _EmissionTileOffset.y;
uv.x += _EmissionTileOffset.z;
uv.y += _EmissionTileOffset.w;

-Create a material and set the shader to StandardShift.
-Add the material to an object.
-Place the texture with the 4 button state in the emissive slot.
-Create a script and add some code to change the button state using SetVector(). Here is an example:

//The vector format is:Tile X, Tile Y, Offset X, Offset Y
Renderer rend = GetComponent();
//Bottom left.
rend.material.SetVector("_EmissionTileOffset", new Vector4(0.5f, 0.5f, 0f, 0f));
//Bottom right.
rend.material.SetVector("_EmissionTileOffset", new Vector4(0.5f, 0.5f, 0.5f, 0f));
//Top left.
rend.material.SetVector("_EmissionTileOffset", new Vector4(0.5f, 0.5f, 0f, 0.5f));
//Top right.
rend.material.SetVector("_EmissionTileOffset", new Vector4(0.5f, 0.5f, 0.5f, 0.5f));

Now we can cycle through the different button states using a script in Unity. Here is the result:

If it doesn’t work, make sure all required files are copied to the Shader folder. Then go to Unity->Assets->Reimport All. After that, select the StandardShift shader -> Inspector -> compile and show code.

Added a fix so it now works in Unity 5.5+ and is tested using Unity 2017.3


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s