Shader Information API #
Since Avatar Optimizer v1.8.0, Avatar Optimizer provides the Shader Information API to help optimize materials that use your custom shaders. By registering shader information, you can enable Avatar Optimizer to perform advanced optimizations like texture atlasing and UV packing.
What is Shader Information? #
Shader Information is a way to tell Avatar Optimizer how your shader uses textures, UV channels, and other material properties.
Current Avatar Optimizer optimizes avatars with this information in the following way, but more optimizations might be added later.1 Please note that not all optimizations are performed automatically with Trace and Optimize.
- Pack multiple textures into texture atlases (with components like
AAO Merge Material) - Remove textures used by shader features but disabled by material settings
Without Shader Information, Avatar Optimizer treats your shader conservatively and cannot perform some of these optimizations.
Core Concepts #
Main Classes #
The Shader Information API consists of three main classes:
ShaderInformation: Base class you extend to provide information about your shader. OverrideGetMaterialInformationto register texture and UV usage for materials using your shader.ShaderInformationRegistry: Static class used to register yourShaderInformationimplementation with Avatar Optimizer during editor initialization.MaterialInformationCallback: Passed toGetMaterialInformation, provides methods to read material properties and register texture/UV usage information.
Null Values #
Throughout the Shader Information API, null values have a consistent meaning: they represent either unknown values or animated (statically undecidable) values. When a material property might be animated or its value cannot be determined at build time, the API returns null to indicate uncertainty. You should pass null for parameters when their values cannot be determined statically.
Getting Started #
To provide Shader Information for your shader, follow these steps:
1. Create an Assembly Definition #
If your shader package doesn’t have an Editor assembly definition, create one. The assembly should be Editor-only since Shader Information is only used at build time and Shader Information API is only available for Editor build.
2. Add Assembly Reference #
Add com.anatawa12.avatar-optimizer.api.editor to your assembly definition’s references.
If you don’t want to require Avatar Optimizer as the dependency, use Version Defines with symbols like AVATAR_OPTIMIZER, to detect if Avatar Optimizer is installed and if AAO version is newer than specified version.

We recommended version range like [1.8,2.0) (supports v1.8.0 and later but will require updates for v2.0.0). Note that some APIs may have been added in later versions, so you may need to adjust the version range based on which APIs you use.
3. Create Shader Information Class #
Create a class that extends ShaderInformation and register it with ShaderInformationRegistry.
Registration must be done in InitializeOnLoad to ensure it’s registered before Avatar Optimizer processes materials:
#if AVATAR_OPTIMIZER && UNITY_EDITOR
using Anatawa12.AvatarOptimizer.API;
using UnityEditor;
using UnityEngine;
namespace YourNamespace
{
[InitializeOnLoad]
internal class YourShaderInformation : ShaderInformation
{
static YourShaderInformation()
{
// Register with shader GUID (recommended for shader assets)
string shaderGuid = "your-shader-guid-here";
ShaderInformationRegistry.RegisterShaderInformationWithGUID(
shaderGuid,
new YourShaderInformation()
);
}
public override ShaderInformationKind SupportedInformationKind =>
ShaderInformationKind.TextureAndUVUsage;
public override void GetMaterialInformation(MaterialInformationCallback matInfo)
{
// Register texture and UV usage here (see examples below)
}
}
}
#endif
ShaderInformationKind Flags #
The SupportedInformationKind property tells Avatar Optimizer what information you’re providing.
Currently, the following kinds are available for registration:
TextureAndUVUsage: Indicates you provide information about which textures the shader uses, which UV channels each texture samples from, UV transform matrices, and sampler states. See Registering Texture Usage.VertexIndexUsage: Indicates your shader uses vertex indices. If you don’t provide this flag, Avatar Optimizer assumes vertex indices are not used and may shuffle vertices during optimization. See Registering Vertex Index Usage.
This is a flags enum, so you can combine multiple values with the | operator.
public override ShaderInformationKind SupportedInformationKind =>
ShaderInformationKind.TextureAndUVUsage | ShaderInformationKind.VertexIndexUsage;
Registering Material Information #
The GetMaterialInformation method is called for each material using your shader.
Use the MaterialInformationCallback to register texture and UV usage.
See the API documentation comments for more details on each method.
Reading Material Properties #
The callback provides methods to read material properties on the shader:
// Read float properties
float? value = matInfo.GetFloat("_PropertyName");
// Read int properties
int? value = matInfo.GetInt("_PropertyName");
// Read Vector4 properties (like _MainTex_ST)
Vector4? value = matInfo.GetVector("_MainTex_ST");
// Check if shader keyword is enabled
bool? enabled = matInfo.IsShaderKeywordEnabled("KEYWORD_NAME");
These methods return null if the property doesn’t exist or the value is unknown.
Registering Texture Usage #
Use RegisterTextureUVUsage to tell Avatar Optimizer about each 2D texture. See the API documentation comments for details on the parameters.
public override void GetMaterialInformation(MaterialInformationCallback matInfo)
{
// Get the UV transform (scale/offset)
var mainTexST = matInfo.GetVector("_MainTex_ST");
Matrix2x3? uvMatrix = mainTexST is { } st
? Matrix2x3.NewScaleOffset(st)
: null;
// Register the texture
matInfo.RegisterTextureUVUsage(
textureMaterialPropertyName: "_MainTex",
samplerState: "_MainTex", // Uses sampler from _MainTex property
uvChannels: UsingUVChannels.UV0,
uvMatrix: uvMatrix
);
}
Sampler States #
Sampler states define texture wrapping and filtering. Most shaders use a sampler from a material property - use the property name (string implicitly converts to SamplerStateInformation):
matInfo.RegisterTextureUVUsage(
"_MainTex",
samplerState: "_MainTex", // String implicitly converts
UsingUVChannels.UV0,
uvMatrix
);
If your shader uses inline samplers (e.g., SamplerState linearClampSampler), use predefined constants like SamplerStateInformation.LinearRepeatSampler.
If the sampler cannot be determined, use SamplerStateInformation.Unknown.
UV Channels #
Specify which UV channel(s) the texture samples from using UsingUVChannels. For textures that don’t use mesh UVs (screen space, MatCap, view-direction based, etc.), use UsingUVChannels.NonMesh:
matInfo.RegisterTextureUVUsage(
"_MatCapTexture",
"_MatCapTexture",
UsingUVChannels.NonMesh, // Not from mesh UVs
null // No UV transform
);
If the UV channel depends on a material property,
var uvChannel = matInfo.GetFloat("_UVChannel") switch
{
0 => UsingUVChannels.UV0,
1 => UsingUVChannels.UV1,
_ => UsingUVChannels.UV0 | UsingUVChannels.UV1 // Unknown, could be either
};
matInfo.RegisterTextureUVUsage("_DetailTex", "_DetailTex", uvChannel, uvMatrix);
UV Transform Matrices #
UV transform matrices describe how UVs are transformed before sampling the texture.
Tipical Unity shaders use Scale Offset value from material properties (like _MainTex_ST).
You can convert Scale Offset to a Matrix2x3 using Matrix2x3.NewScaleOffset.
var texST = matInfo.GetVector("_MainTex_ST");
Matrix2x3? uvMatrix = texST is { } st
? Matrix2x3.NewScaleOffset(st)
: null;
You can also build matrices manually if needed. If the UV transform is animated or calculated dynamically, use null.
Registering Vertex Index Usage #
When your shader registers vertex index usage, Avatar Optimizer will try to preserve vertex indices from the original mesh. This currently disables automatic Merge Skinned Mesh feature in Trace and Optimize, but more features may be affected later.
Since this method is to preserve vertex indices, when your shader uses vertex indices just for generating random sequences, it’s not a good idea to register RegisterVertexIndexUsage.
public override void GetMaterialInformation(MaterialInformationCallback matInfo)
{
// ... register textures ...
// Check if the feature using vertex indices is enabled
if (matInfo.GetFloat("_UseVertexIdEffect") != 0)
{
// Tell this shader wants Avatar Optimizer to preserve vertex indices
matInfo.RegisterVertexIndexUsage();
}
}
Registering Shader Information #
You have to register your ShaderInformation implementation to link information to your shader. There are two ways to register Shader Information:
Register by GUID (Recommended) #
For shader assets, use the shader’s GUID. This method is recommended because GUIDs don’t change, won’t duplicate, and don’t require accessing AssetDatabase (accessing AssetDatabase in InitializeOnLoad methods is not valid, so registering by shader instance can become invalid).
ShaderInformationRegistry.RegisterShaderInformationWithGUID(
"your-shader-asset-guid",
new YourShaderInformation()
);
Register by Shader Instance #
For shaders dynamically created on build or when you have the shader instance, you can register with shader instance.
Shader shader = Shader.Find("Your/Shader/Name");
ShaderInformationRegistry.RegisterShaderInformation(
shader,
new YourShaderInformation()
);
Best Practices #
Use InitializeOnLoad #
Register your Shader Information in a static constructor with [InitializeOnLoad] to register before ‘apply on play’ builds.
[InitializeOnLoad]
internal class YourShaderInformation : ShaderInformation
{
static YourShaderInformation()
{
// Registration happens automatically when Unity loads
Register();
}
private static void Register()
{
ShaderInformationRegistry.RegisterShaderInformationWithGUID(
"guid", new YourShaderInformation()
);
}
}
Handle Unknown Values #
Material properties might be animated or unknown. Handle null values. Even when your shader does not support animating a property, Avatar Optimizer may pass it as null since Avatar Optimizer may process multiple materials at once.
// Use pattern matching
var st = matInfo.GetVector("_MainTex_ST");
Matrix2x3? uvMatrix = st is { } st2 ? Matrix2x3.NewScaleOffset(st2) : null;
// Use union value for null cases
var uvChannel = matInfo.GetFloat("_UVChannel") switch
{
0 => UsingUVChannels.UV0,
1 => UsingUVChannels.UV1,
null => UsingUVChannels.UV0 | UsingUVChannels.UV1, // Unknown
_ => UsingUVChannels.UV0 | UsingUVChannels.UV1
};
Check Keywords and Properties #
Only register textures that are actually used:
if (matInfo.IsShaderKeywordEnabled("_NORMALMAP") != false)
{
// Keyword might be enabled, register normal map
}
if (matInfo.GetFloat("_UseEmission") != 0)
{
// Emission is enabled, register emission map
}
Note: != false checks if the value is true or null (unknown).
This conservative approach assumes features are enabled if unknown.
Provide Accurate Information #
- Only set
VertexIndexUsageif vertex indices truly matter - Use correct sampler states (affects texture filtering during atlasing)
- Set UV matrices to
nullif they’re dynamic or animated - Use
UsingUVChannels.NonMeshfor screen-space UVs
Use internal class for Shader Information Classes
#
To avoid exposing your Shader Information classes in your assembly’s public API, we recommend declaring them as internal class.
This helps keep your codebase clean and prevents accidental misuse of internal details.
If your editor doesn’t have public API, you may set your assembly definition Auto Reference to false to avoid exposing classes to Assembly-CSharp.
Complete Examples #
Here’s some simple ShaderInformation examples for simple shaders.
For more complex examples, see Avatar Optimizer’s built-in shader information implementations on GitHub.
Simple Shader with Main Texture #
[InitializeOnLoad]
internal class SimpleShaderInformation : ShaderInformation
{
static SimpleShaderInformation()
{
ShaderInformationRegistry.RegisterShaderInformationWithGUID(
"your-shader-guid",
new SimpleShaderInformation()
);
}
public override ShaderInformationKind SupportedInformationKind =>
ShaderInformationKind.TextureAndUVUsage;
public override void GetMaterialInformation(MaterialInformationCallback matInfo)
{
var mainTexST = matInfo.GetVector("_MainTex_ST");
Matrix2x3? uvMatrix = mainTexST is { } st
? Matrix2x3.NewScaleOffset(st)
: null;
matInfo.RegisterTextureUVUsage(
"_MainTex",
"_MainTex",
UsingUVChannels.UV0,
uvMatrix
);
}
}
Shader with Conditional Features #
[InitializeOnLoad]
internal class FeatureShaderInformation : ShaderInformation
{
static FeatureShaderInformation()
{
ShaderInformationRegistry.RegisterShaderInformationWithGUID(
"your-shader-guid",
new FeatureShaderInformation()
);
}
public override ShaderInformationKind SupportedInformationKind =>
ShaderInformationKind.TextureAndUVUsage;
public override void GetMaterialInformation(MaterialInformationCallback matInfo)
{
// Main texture (always present)
var mainTexST = matInfo.GetVector("_MainTex_ST");
Matrix2x3? mainUVMatrix = mainTexST is { } st
? Matrix2x3.NewScaleOffset(st)
: null;
matInfo.RegisterTextureUVUsage(
"_MainTex", "_MainTex", UsingUVChannels.UV0, mainUVMatrix
);
// Normal map (conditional on keyword)
if (matInfo.IsShaderKeywordEnabled("_NORMALMAP") != false)
{
matInfo.RegisterTextureUVUsage(
"_BumpMap", "_BumpMap", UsingUVChannels.UV0, mainUVMatrix
);
}
// Detail texture (conditional on property)
if (matInfo.GetFloat("_UseDetail") != 0)
{
var detailST = matInfo.GetVector("_DetailTex_ST");
Matrix2x3? detailUVMatrix = detailST is { } st2
? Matrix2x3.NewScaleOffset(st2)
: null;
var detailUV = matInfo.GetFloat("_DetailUV") switch
{
0 => UsingUVChannels.UV0,
1 => UsingUVChannels.UV1,
_ => UsingUVChannels.UV0 | UsingUVChannels.UV1
};
matInfo.RegisterTextureUVUsage(
"_DetailTex", "_DetailTex", detailUV, detailUVMatrix
);
}
// MatCap (screen-space, no UV transform)
if (matInfo.IsShaderKeywordEnabled("_MATCAP") != false)
{
matInfo.RegisterTextureUVUsage(
"_MatCap",
SamplerStateInformation.LinearClampSampler,
UsingUVChannels.NonMesh,
null
);
}
}
}
Shader Using Vertex Indices #
[InitializeOnLoad]
internal class VertexShaderInformation : ShaderInformation
{
static VertexShaderInformation()
{
ShaderInformationRegistry.RegisterShaderInformationWithGUID(
"your-shader-guid",
new VertexShaderInformation()
);
}
public override ShaderInformationKind SupportedInformationKind =>
ShaderInformationKind.TextureAndUVUsage | ShaderInformationKind.VertexIndexUsage;
public override void GetMaterialInformation(MaterialInformationCallback matInfo)
{
var mainTexST = matInfo.GetVector("_MainTex_ST");
Matrix2x3? uvMatrix = mainTexST is { } st
? Matrix2x3.NewScaleOffset(st)
: null;
matInfo.RegisterTextureUVUsage(
"_MainTex", "_MainTex", UsingUVChannels.UV0, uvMatrix
);
// Shader uses SV_VertexID for effects
matInfo.RegisterVertexIndexUsage();
}
}
Support #
If you have questions or need help, ask me on
- Discord: NDMF Discord mention
@anatawa12 - Fediverse: @[email protected]
- GitHub Issues: AvatarOptimizer Issues
For example, UV channel optimization is not currently implemented but may be added in future versions. ↩︎