Send custom vertex data to a UI shader
Configure a panel, write per-vertex values during mesh generation, and read them in a custom shader graph.
Read time 6 minutesLast updated 21 days ago
UI Toolkit panels use a fixed GPU vertex layout that contains position, color, the primary UV channel, and internal channels for clipping, transforms, and other renderer state. Custom shaders authored in UI Shader Graph can only read additional inputs if the panel allocates GPU storage for them.
Use to append any combination of , , , , and to each vertex. Custom mesh generation code writes values into those channels, and a custom shader graph reads them at the matching semantics: to , , and .
ExtraVertexChannelsTexCoord1TexCoord2TexCoord3NormalTangentTEXCOORD1TEXCOORD3NORMALTANGENTEnable extra channels when a shader needs per-vertex data that the built-in layout doesn't carry, such as normals for simulated lighting, tangent-space deformations, or per-vertex parameters for procedural effects.
Enable channels on a panel
To enable channels in code, set for runtime panels, or for Editor panels.
PanelSettings.extraVertexChannelsIPanel.extraVertexChannelsIn the window of the Panel Settings asset, under Buffer Management, set Extra Vertex Channels to the channels you need. For more information, refer to Panel Settings properties reference.
Inspector
?
The following example shows how to set the property in code:
// Enables extra channels on a runtime panel through its PanelSettings asset. // Note: Changing extraVertexChannels at runtime is expensive because it regenerates all vertex // buffers. Assign it statically in the PanelSettings asset in the Inspector instead. public static void EnableChannelsOnRuntimePanel(PanelSettings panelSettings) { panelSettings.extraVertexChannels = ExtraVertexChannels.Normal | ExtraVertexChannels.TexCoord1; } // Enables extra channels on an Editor panel through the IPanel interface. public static void EnableChannelsOnEditorPanel(IPanel panel) { panel.extraVertexChannels = ExtraVertexChannels.Normal; }
If you change the channel set after the panel starts rendering, Unity rebuilds the panel's render chain and preserves the runtime panel and its visual tree.
Each enabled channel adds 16 bytes per vertex on the GPU. Leave the property at unless a shader on the panel needs the data.
NoneProvide channel data per draw
Custom mesh generation runs inside a callback. From there, allocate a and submit it with .
VisualElement.generateVisualContentUIMeshMeshGenerationContext.DrawMeshYou can populate a with the temporary allocator, or with your own arrays.
UIMeshAllocate a mesh with the temporary allocator
Use to allocate the slices. It returns a whose mandatory and slices are sized as requested, plus one slice per channel in the mask you provide. Channels you don't request are empty default slices.
MeshGenerationContext.AllocateTempMeshUIMeshverticesindicesThe following example shows how to allocate a temporary mesh with and channels, fill the slices, and submit the mesh:
TexCoord1Normal// Allocates a quad and its extra channels with the temporary allocator, then submits it. private static void DrawWithTempMesh(MeshGenerationContext mgc) { const int vertexCount = 4; const int indexCount = 6; mgc.AllocateTempMesh( ExtraVertexChannels.Normal | ExtraVertexChannels.TexCoord1, vertexCount, indexCount, out UIMesh mesh); NativeSlice<Vertex> vertices = mesh.vertices; NativeSlice<ushort> indices = mesh.indices; NativeSlice<Vector3> normals = mesh.normal; NativeSlice<Vector4> uv1 = mesh.uv1; // Corners of the element's content rectangle, in clockwise winding order. Rect rect = mgc.visualElement.contentRect; var corners = new Vector2[] { new Vector2(rect.xMin, rect.yMin), new Vector2(rect.xMax, rect.yMin), new Vector2(rect.xMax, rect.yMax), new Vector2(rect.xMin, rect.yMax) }; for (int i = 0; i < vertexCount; ++i) { vertices[i] = new Vertex { position = new Vector3(corners[i].x, corners[i].y, Vertex.nearZ), tint = Color.white }; normals[i] = new Vector3(0, 0, 1); uv1[i] = new Vector4(i, 0, 0, 0); } indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 0; indices[4] = 2; indices[5] = 3; mgc.DrawMesh(ref mesh); }
Use this method in most cases. The slices stay valid until the renderer flushes the current repaint pass, which is always after returns.
generateVisualContentProvide your own arrays
If you already have channel data in your own , for example from an upstream Burst job, call on each array, assign the result to the matching field, and submit the mesh.
NativeArray<T>SliceUIMeshThe following example shows how to provide your own arrays for , , and the channel:
verticesindicesNormal// Submits a mesh whose channel data comes from buffers that this component owns. private void DrawWithOwnArrays(MeshGenerationContext mgc) { // This component owns these slices and keeps them valid until the next // MeshGenerationContext flush, at the end of the current repaint pass. var mesh = new UIMesh { vertices = m_VertexBuffer.Slice(), indices = m_IndexBuffer.Slice(), normal = m_NormalBuffer.Slice(), }; mgc.DrawMesh(ref mesh); }
The slices inside are read at flush time, not when returns.
UIMeshDrawMeshValidate channel data
DrawMeshDebug.LogErrorKeep slice lengths consistent
Every non-empty extra channel slice must have the same length as . If any lengths disagree, Unity drops the entire draw, because indices might reference vertex slots that don't exist in the shorter slice.
verticesProvide only enabled channels
If you provide a slice for a channel that the panel didn't enable through , Unity clears that slice from the draw and processes the rest of the draw. The panel has no GPU slot for that channel, so a shader written against it reads undefined data.
extraVertexChannelsYou can draw without any extra channels on a panel that has them enabled, and you can provide a subset of the enabled channels.
If a draw provides at least one extra channel, Unity zero-fills the enabled channels that you leave empty. If a draw provides no extra channels, Unity leaves those vertex slots untouched, so use this only when the shader bound to those vertices doesn't read them.
Read the channels in a shader graph
In a UI Shader Graph, add one of the following nodes to read an extra channel:
- UV node, set to channel UV1, UV2, or UV3.
- Normal Vector node.
- Tangent Vector node.
- Bitangent Vector node.
These nodes connect automatically to the matching channels in and the matching semantics in the generated vertex shader. The Shader Graph editor accepts UV0 to UV3 on UI materials. UV4 and UV5 are reserved for internal renderer use.
VertexDescriptionInputsThe Bitangent Vector node derives its value from the normal and the tangent, so enable both the Normal and Tangent channels on the panel for it to produce meaningful values.
Set the Space property on the 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 multiply the value by an unrelated matrix and produce incorrect data. Object returns the value exactly as the mesh generation code packed it into the vertex stream.
A shader graph asset doesn't specify which panel uses it. Set on every panel that hosts a whose mesh data feeds into your shader.
extraVertexChannelsVisualElement