You can use SetVertexBufferData to set vertex data directly, without using format conversions for each vertex attribute. Elements are copied from the source container to the destination range within the mesh's vertex buffer.
This method does not update vertex attributes or vertex layout of the Mesh. You must configure the vertex buffer structure via SetVertexBufferParams before calling this method, defining the vertex count, layout, and which vertex streams to use.
This is a high-performance, low-level method for populating mesh vertex data. It provides direct access to the underlying vertex buffer with minimal validation or data conversion overhead, making it suitable for performance-critical scenarios where you need maximum control over mesh data.
For simpler ways to set vertex data that handle vertex attribute configuration automatically, use SetVertices, SetNormals, SetUVs, or other attribute-specific methods instead.
// Generates a procedural triangle mesh with custom vertex format.
// Attach this script to a GameObject and enter Play mode.
// Tip: Use the Rendering Debugger window to inspect vertex attributes in the Scene view.
using System.Runtime.InteropServices;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
// A 2-component vector storing 32-bit floats as 16-bit floats.
public readonly struct Float16x2
{
readonly ushort m_X, m_Y;
public Float16x2(float x, float y)
{
m_X = Mathf.FloatToHalf(x);
m_Y = Mathf.FloatToHalf(y);
}
}
// A 4-component vector storing normalized floats [-1, 1] as 16-bit signed normalized values (SNorm16).
public readonly struct SNorm16x4
{
readonly ushort m_X, m_Y, m_Z, m_W;
public SNorm16x4(float x, float y, float z, float w)
{
m_X = FloatToSNorm16(x);
m_Y = FloatToSNorm16(y);
m_Z = FloatToSNorm16(z);
m_W = FloatToSNorm16(w);
}
// Converts a float in range [-1, 1] to a 16-bit signed normalized representation by clamping and scaling by 32767.
static ushort FloatToSNorm16(float v) => (ushort)(32767f * Mathf.Clamp(v, -1f, 1f));
}
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
class SetVertexBufferDataExample : MonoBehaviour
{
// Vertex structure with sequential memory layout containing position, compressed 16-bit normal, 32-bit color, and half-precision texture coordinates
[StructLayout(LayoutKind.Sequential)]
struct Vertex
{
public Vector3 position;
public SNorm16x4 normal;
public Color32 color;
public Float16x2 texCoord;
}
// Vertex layout descriptor array defining the format and component count for each vertex attribute, matching the Vertex struct
static readonly VertexAttributeDescriptor[] k_VertexDescriptors = new VertexAttributeDescriptor[]
{
new(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new(VertexAttribute.Normal, VertexAttributeFormat.SNorm16, 4),
new(VertexAttribute.Color, VertexAttributeFormat.UNorm8, 4),
new(VertexAttribute.TexCoord0, VertexAttributeFormat.Float16, 2),
};
Mesh m_Mesh;
void Start()
{
// Create mesh with predefined bounds to avoid recalculation
m_Mesh = new Mesh
{
name = "SampleMesh",
bounds = new Bounds(Vector3.zero, Vector3.one * 2f),
};
// Create and assign triangle vertices
using (var vertices = GenerateVertices())
{
// Configure vertex buffer layout and allocate space
m_Mesh.SetVertexBufferParams(vertices.Length, k_VertexDescriptors);
// Assigns the generated vertices to the mesh
m_Mesh.SetVertexBufferData(vertices, 0, 0, vertices.Length);
}
// Create and assign triangle indices
using (var indices = new NativeArray<uint>(new uint[] { 0, 1, 2 }, Allocator.Temp))
{
// Configure index buffer format and allocate space
m_Mesh.SetIndexBufferParams(indices.Length, IndexFormat.UInt32);
// Assigns the generated indices to the mesh
m_Mesh.SetIndexBufferData(indices, 0, 0, indices.Length);
// Define single sub-mesh spanning all indices
m_Mesh.SetSubMesh(0, new SubMeshDescriptor(0, indices.Length));
}
// Assign mesh to MeshFilter & MeshRenderer
GetComponent<MeshFilter>().sharedMesh = m_Mesh;
GetComponent<MeshRenderer>().material = GraphicsSettings.currentRenderPipeline != null ? GraphicsSettings.currentRenderPipeline.defaultMaterial : new Material(Shader.Find("Standard"));
}
void OnDestroy() => Destroy(m_Mesh);
// Creates an array with the triangle vertices
NativeArray<Vertex> GenerateVertices()
{
var vertices = new NativeArray<Vertex>(3, Allocator.Temp);
for (int i = 0; i < 3; i++)
{
// Create and assign the vertex with the computed position, matching normal, UVs, and position-derived color
vertices[i] = new Vertex
{
position = new Vector3(i - 1f, i % 2 * Mathf.Sqrt(2f), 0f),
normal = new SNorm16x4(0f, 0f, -1f, 0f),
texCoord = new Float16x2(i, i % 2 * Mathf.Sqrt(2f)),
color = new Color32(i == 0 ? byte.MaxValue : byte.MinValue, i == 1 ? byte.MaxValue : byte.MinValue, i == 2 ? byte.MaxValue : byte.MinValue, byte.MaxValue),
};
}
return vertices;
}
}