UI Toolkit panels ship a fixed GPU vertex layout: position, color, the primary UV channel, and a handful of internal channels used for clipping, transforms, and other renderer state. Custom shadersA program that runs on the GPU. More info
See in Glossary authored in UI Shader Graph can’t read additional inputs unless the panel allocates the storage for them on the GPU.
The ExtraVertexChannels opt-in lets a panel append any combination of TexCoord1, TexCoord2, TexCoord3, Normal, and Tangent to each vertex. Custom meshThe main graphics primitive of Unity. Meshes make up a large part of your 3D worlds. Unity supports triangulated or Quadrangulated polygon meshes. Nurbs, Nurms, Subdiv surfaces must be converted to polygons. More info
See in Glossary generation code can then push values into those channels, and a custom Shader Graph can read them at the same semantics (TEXCOORD1-TEXCOORD3, NORMAL, TANGENT).
Use this when you need to feed data into a shader that the built-in vertex layout doesn’t carry — for example, fake-lighting normals, tangent-space deformations, or extra per-vertex parameters for procedural effects.
The opt-in lives on PanelSettings.extraVertexChannels for runtime panels, and on IPanel.extraVertexChannels for Editor panels.
In the Panel Settings InspectorA Unity window that displays information about the currently selected GameObject, asset or project settings, allowing you to inspect and edit the values. More info
See in Glossary, find the Extra Vertex Channels dropdown under Buffer Management and enable the flags you need:
// Runtime panel — at edit time on the PanelSettings asset, or at runtime in code.
panelSettings.extraVertexChannels = ExtraVertexChannels.Normal | ExtraVertexChannels.TexCoord1;
// Editor panel — set the flag on the IPanel directly.
panel.extraVertexChannels = ExtraVertexChannels.Normal;
Changing the channel set after the panel has started rendering rebuilds the panel’s render chain. The runtime panel and its visual treeAn object graph, made of lightweight nodes, that holds all the elements in a window or panel. It defines every UI you build with the UI Toolkit.
See in Glossary are preserved.
Each enabled channel adds 16 bytes per vertex on the GPU. Leave the property at None unless a shader on the panel actually needs the data.
Custom mesh generation runs inside a VisualElement.generateVisualContent callback. From there, allocate a UIMesh and submit it through MeshGenerationContext.DrawMesh(ref UIMesh, Texture).
There are two ways to populate a UIMesh:
The simplest path is to ask MeshGenerationContext to allocate the slices for you. The allocator returns a UIMesh whose mandatory vertices / indices slices are sized as requested, plus one slice per channel in the mask you pass in. Channels you didn’t request come back as empty default slices.
void OnGenerateContent(MeshGenerationContext mgc)
{
const int vertexCount = 4;
const int indexCount = 6;
mgc.AllocateTempMesh(
ExtraVertexChannels.Normal | ExtraVertexChannels.TexCoord1,
vertexCount, indexCount,
out UIMesh mesh);
for (int i = 0; i < vertexCount; ++i)
{
mesh.vertices[i] = new Vertex { position = ..., tint = Color.white };
mesh.normal[i] = new Vector3(0, 0, 1);
mesh.uv1[i] = new Vector4(i, 0, 0, 0);
}
mesh.indices[0] = 0; mesh.indices[1] = 1; mesh.indices[2] = 2;
mesh.indices[3] = 0; mesh.indices[4] = 2; mesh.indices[5] = 3;
mgc.DrawMesh(ref mesh);
}
This path is recommended for most cases. The slices are valid until the renderer flushes the current repaint pass, which is always after generateVisualContent returns.
If you already have channel data in your own NativeArray<T> (for example, produced by an upstream Burst job), assign each into the matching UIMesh field and submit:
void OnGenerateContent(MeshGenerationContext mgc)
{
// Slices owned by the caller. They must remain valid until the next
// MeshGenerationContext flush (the end of the current repaint pass).
NativeSlice<Vertex> vertices = m_VertexBuffer.AsSlice();
NativeSlice<ushort> indices = m_IndexBuffer.AsSlice();
NativeSlice<Vector3> normals = m_NormalBuffer.AsSlice();
var mesh = new UIMesh
{
vertices = vertices,
indices = indices,
normal = normals,
};
mgc.DrawMesh(ref mesh);
}
The slices inside UIMesh are read at flush time, not when DrawMesh returns. All non-empty slices in the UIMesh must remain valid until the next MeshGenerationContext flush (the end of the current repaint pass). Slices returned by AllocateTempMesh are guaranteed valid by the temp allocator’s lifecycle.
DrawMesh(ref UIMesh, …) enforces two rules at submit time. Violations are logged via Debug.LogError and the offending data is dropped — the renderer never throws from per-frame mesh-gen code.
Length consistency. Every non-empty extras slice must have the same length as vertices. If any disagree, the entire draw is dropped — indices may reference vertex slots that don’t exist in the shorter slice.
Channel consistency. Providing a slice for a channel the panel did not enable via extraVertexChannels is an error. The offending slice is cleared from the draw and the rest of the draw proceeds. The panel has no GPU slot for that channel — a shader written against it would read undefined data anyway.
It’s fine to draw without providing any extras on an extras-enabled panel, and it’s fine to provide only a subset of the enabled channels. Within a draw that does provide extras, channels the panel enabled but the caller left empty are zero-filled in that draw’s stream–1 vertex slots. Draws that provide no extras at all leave the stream–1 slots untouched — only meaningful if the shader bound for those vertices does not read stream 1.
In a UI Shader Graph, add a UV node (channel UV1, UV2, or UV3), Normal Vector node, Tangent Vector node, or Bitangent Vector node. They wire automatically to the matching channels in VertexDescriptionInputs and the matching semantics in the generated vertex shaderA program that runs on each vertex of a 3D model when the model is being rendered. More info
See in Glossary. The Shader Graph editor accepts UV0-UV3 on UI materials; UV4 and UV5 are reserved for internal renderer use. The Bitangent Vector node derives from the normal and tangent, so the panel must enable both the Normal and Tangent channels for it to produce meaningful values.
Set the Space property on Normal Vector, Tangent Vector, and Bitangent Vector nodes to Object. UI Toolkit doesn’t apply a per-element world transform during rendering, so the other space options would multiply the value by an unrelated matrix and produce incorrect data. Object returns the value exactly as it was packed into the vertex stream.
Important: Normal Vector, Tangent Vector, and Bitangent Vector nodes can only be used in the vertex stage of a UI Shader Graph. To read these values in the fragment stage, connect the node to a Custom Interpolator block in the vertex context, then read the matching Custom Interpolator node in the fragment context.
The Shader Graph asset doesn’t know which panel will use it. It’s your responsibility to set extraVertexChannels on every panel that hosts a VisualElement whose mesh data feeds into your shader.