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 |
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 |
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
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)
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
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.
In our example, the inspector looks like this and each texture can be assigned like any other texture field.
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