Skip to content

Creating a Material Using a Texture Array

The index referring to the inspector position of a texture is named the constant index in the sub-graphs and this page for less confusion

Creating a material with a texture array requires a material, a shader, and a custom GUI.

Accessing the Texture Array requires a bit more work rather than regular textures as a saved array will only have as many textures as assigned in the inspector

Variables Example

For example, I have 5 texture fields in the inspector but only assign 4, the array will only have 4 textures.

Texture Assigned Array Index
1 true 0
2 true 1
3 true 2
4 false -
5 true 3

In this case if we try to get the 4th texture which we would assume is at index 3, we actually get the 5th texture due to how it is saved. That is why the shader requires an extra variable for each Texture array so it can tell which texture is assigned to what index.

The custom shader functions provide an easy way to get around this so there is no extra work required on your end

Creating The Shader

I will be using the shader graph for this example example but using a written shader shader should work the same, the sub-graphs used in the shader graph are just functions in the HLSL files you can find at "TextureArrayEssentials/Shaders/HLSL"

Per every texture array in the shader, you need to have two variables:

Type Description
Texture2DArray The Texture Array
int Tells the shader and GUI the location of each texture in the array

image

For using the variables there are various sub-graphs to access the array and tell which texture is at what index. Using the texture array with this extra variable also allows you to tell if a texture is assigned in the inspector inside the shader which can be helpful at times.

Function Returns Description
Array Texture Exists bool Checks if a texture is assigned at the index
Same as Get Compressed Bool
Get Index In Array int Takes a constant index (inspector index) and gets its index in the Texture Array
Unless you know the texture is assigned, it is a good idea to check Array Texture Exists before using this as it may not return what you expect
Sample Array At Constant Index float4 Samples the array at a constant index (inspector index)
Add Compressed Bool int Inserts a bool into a compressed set of bools at a given index
This compression is used by the AssignedTextures
Get Compressed Bool bool Gets a bool value from a set of compressed bools
This compression is used by the AssignedTextures

image

In this example we will be setting up a material with 4 assignable textures, using noise to separate them all.

To get each texture you can use the Sample Array At Constant Index node and input the index for each texture, here being from 0-3

image

The texture at this point can be used for anything you like with the output of these functions

For this example though, we are using noise to separate the textures so here is the shader, sampling three simple noise functions and lerping the 4 textures with the noise. (It is a bit messy but more to be used as an example to show how it can be used)

image

Creating a Custom Inspector

For creating a GUI with the shader graph, we will create a class inherited by ShaderGUI

Drawing fields with the Texture2DArray requires a TextureArrayGUIDrawer that must be initialized on Enable of the inspector. This is not a function in the ShaderGUI so you should create one, or at least check for the first call of OnGUI to create the object.

Still following the previous example, we are creating an inspector with 4 assignable textures that will update the Texture Array. Here they are just being drawn as regular textures but there are other functions to draw it along side other fields like a slider or colour field.

For more information on the TextureArrayGUIDrawer, you can find the documentation here

If you want any extra GUI functions, GUIUtilities has more functions and helpers for drawing various elements without the need for MaterialProperties. You can find the documentation here

#if UNITY_EDITOR
using UnityEditor;
using TextureArrayEssentials.GUIUtilities;

public class CustomEditor : ShaderGUI
{
    TextureArrayGUIDrawer _arrayDrawer;

    // ShaderGUI doesnt have an OnEnable function, using this instead
    private bool _firstSetup = true;

    private void OnEnable(MaterialProperty[] properties)
    {
        // Get material properties
        MaterialProperty textureArrayProperty = FindProperty("_TextureArray", properties);
        MaterialProperty assignedTexturesProperty = FindProperty("_AssignedTextures", properties);

        // Create the array drawer
        // In this example there are 4 textures that can be assigned
        _arrayDrawer = new TextureArrayGUIDrawer(textureArrayProperty, assignedTexturesProperty, 4);
    }

    public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
    {
        // OnEnable if first call
        if (_firstSetup) {
            OnEnable(properties);
            _firstSetup = false;
        }

        // Draw textures
        _arrayDrawer.DrawTextures();

        // Can be drawn seperately using:
        //_arrayDrawer.DrawTexture(0);
        //_arrayDrawer.DrawTexture(1);
        //_arrayDrawer.DrawTexture(2);
        //_arrayDrawer.DrawTexture(3);
    }
}

#endif

Assigning the Custom Inspector

You can assign the custom inspector to a Shader Graph through the GraphSettings in the Custom Editor GUI Field

This has the naming convention of "namespace.classname" and in this case since there is no namespace, we just put CustomEditor, the name of the class

image

Using the Material

Now the material is done! You can use it like any other material and the Texture array will handle itself.

Since the Texture2DArray is its own asset, the material has its own folder that holds each texture array along side it. The folder is only used on creation of a new array and can be moved to any location as the GUI gets the path from the assigned array.

image image

In our example, the inspector looks like this and each texture can be assigned like any other texture field.

image

You can see in the scene view that an unassigned texture will default to the colour white. This can be changed in the shader graph with the UnassignedColor value for each Sample Array At Constant Index node

image