文档

Unity Engine


User Manual

Script Reference

Unity Engine


Custom vertex data

阅读时间5 分钟最后更新于 1 个月前

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 shaders 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 mesh 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.

Enable channels on a panel

The opt-in lives on
PanelSettings.extraVertexChannels
for runtime panels, and on
IPanel.extraVertexChannels
for Editor panels.
In the Panel Settings Inspector, 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 tree 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.

Provide channel data per draw

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
:

Allocate from the temp allocator

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.

Bring your own arrays

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.

Validation rules

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.

Read the channels in a Shader Graph

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 shader. 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.
重要
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.

Additional resources