When working with shader, there is a limit on how many texture samplers a shader can use. Every sampler is also quite expensive so there is a value in using a few as possible.


Example of a bad shader

Below is a simple Shader example that will use three different grey scale maps to create a (rather ugly) shield effect.


This is the ShaderLab code making the effect.

Shader "MageQuest/MagicForwardShield" {
   Properties {
      _PatternTex ("Pattern", 2D) = "white" {}
      _NoiseTex("Noise", 2D) = "white" {}
      _AlphaGuideTex("Alpha", 2D) = "white" {}
      _Scroll("Scroll", Range(0, 10)) = 1
      _BaseColor("Color (Base)", Color) = (1, 1, 1, 1)
      _PatternColor("Color (Pattern)", Color) = (1, 1, 1, 1)
   }

   SubShader{
      Tags {
         "Queue" = "Transparent"
         "IgnoreProjector" = "True"
         "RenderType" = "Transparent"
      }

      Cull Back

      Blend SrcAlpha OneMinusSrcAlpha

      Pass{
      CGPROGRAM
           #pragma target 3.0
           #pragma vertex vert
           #pragma fragment frag
           #pragma exclude_renderers nomrt

           #include "UnityCG.cginc"

           struct appdata
           {
               float4 vertex : POSITION;
               float2 uv : TEXCOORD0;
           };

           struct v2f
           {
               float2 uv : TEXCOORD0;
               float4 vertex : SV_POSITION;
           };

           sampler2D _PatternTex;
           sampler2D _NoiseTex;
           sampler2D _AlphaGuideTex;
           float4 _PatternTex_ST;
           float4 _NoiseTex_ST;
           float4 _AlphaGuideTex_ST;

           fixed _Scroll;
           fixed4 _BaseColor;
           fixed4 _PatternColor;

           v2f vert (appdata v) {
               v2f o;
               o.vertex = UnityObjectToClipPos(v.vertex);
               o.uv = TRANSFORM_TEX(v.uv, _PatternTex);
               return o;
           }

           fixed4 frag (v2f i) : SV_Target {
               fixed4 alpha = tex2D(_AlphaGuideTex, i.uv);
               fixed4 noise = tex2D(_NoiseTex, i.uv + fixed2(0, _Time.y * _Scroll));

               fixed4 pattern01 = tex2D(_PatternTex, i.uv + fixed2(_Time.y * _Scroll, _Time.y * _Scroll));
               fixed4 pattern02 = tex2D(_PatternTex, i.uv + fixed2(- _Time.y * _Scroll, _Time.y * _Scroll));

               fixed4 patternColor = pattern01 + pattern02;
               fixed4 col = _BaseColor + (patternColor * _PatternColor);
               col.a = alpha * max(noise, patternColor);

               return col;
           }
           ENDCG
       }
   }
}

What is of important is the shader using three separate samplers (

sampler2D
) and three separate textures.



Each one of these texture are in grey scale and could fit into a single 8-bit color channel, but right now they are all 32-bit rgba png files. That is a lot of wasted data.


Packing the textures using GIMP

Now we've come to the heart of this article: how we can use GIMP take these textures and create a single texture without losing any data in the process.


First download GIMP. Its free and open source. Create a new file with the dimensions of 512x512 pixels. Go into Image -> Mode and select Grey scale.


Now drag in the three textures into GIMP as new layers. Sometimes GIMP gives a warning that they will be converted to grey scale. Just accept it.


Once all three images are imported, go to Color -> Components -> Combine.


It will allow you to pick what layer will go into which channel. Make the output color model is RGB. Press OK and you will have a new combined texture where all texture data is still present.


The texture might look a bit spaced out but remember it's not supposed to be used as-is.


Adapting the shader

Head back to the shader and remove the first three 2D texture properties and replace them with a new one.

_MainTex ("Texture", 2D) = "white" {}

Also remove the three sampler2D variables and the float4 *_ST fields and replace them with these two new variables.

sampler2D _MainTex;
float4 _MainTex_ST;


In the vertex shader replace the old sampler with the new one

o.uv = TRANSFORM_TEX(v.uv, _MainTex);


In the fragment shader, the different textures used to be sample the texture many time to account for individual scrolling UV's. If we want them to sample at different uv-cordinates we still need to sample the texture four times. We can however store the result of in a

fixed
rather than a
fixed4
variable.


We will replace the old sample lines, like this one

fixed4 alpha = tex2D(_AlphaGuideTex, i.uv);

with this (I stored the alpha guide in the green channel, hence the "g" at the end)

fixed alpha = tex2D(_MainTex, i.uv).g;


This is the final fragment shader, now only using a single texture to sample all its values.

fixed4 frag (v2f i) : SV_Target
{
   fixed alpha = tex2D(_MainTex, i.uv).g;
   fixed noise = tex2D(_MainTex, i.uv + fixed2(0, _Time.y * _Scroll)).b;

   fixed pattern01 = tex2D(_MainTex, i.uv + fixed2(_Time.y * _Scroll, _Time.y * _Scroll)).r;
   fixed pattern02 = tex2D(_MainTex, i.uv + fixed2(- _Time.y * _Scroll, _Time.y * _Scroll)).r;

   fixed patternColor = pattern01 + pattern02;

   fixed4 col = _BaseColor + (patternColor * _PatternColor);

   col.a = alpha * max(noise, patternColor);

   return col;
}

If all went well, the output of the shader will look identical to before, but perform a lot faster. That's how you pack three separate grey scale into a single texture using GIMP in about 5 minutes. I can be worth mentioning that this can be quite a tedious task and is best suited once the effects are finished.