# How to Use Multi-Stream Vertex Remapping with meshopt_generateVertexRemapMulti

> Learn multi-stream vertex remapping with meshopt_generateVertexRemapMulti. Optimize meshes with multiple attribute streams efficiently using this powerful function from meshoptimizer.

- Repository: [Arseny Kapoulkine/meshoptimizer](https://github.com/zeux/meshoptimizer)
- Tags: how-to-guide
- Published: 2026-07-11

---

**Use `meshopt_generateVertexRemapMulti` to generate a vertex remap table for meshes with multiple attribute streams by describing each channel as a `meshopt_Stream` structure and invoking the function with your vertex buffers and optional index data.**

The `meshopt_generateVertexRemapMulti` function in the [zeux/meshoptimizer](https://github.com/zeux/meshoptimizer) library enables **multi-stream vertex remapping** for complex mesh layouts where attributes like positions, normals, and UVs reside in separate buffers or interleaved memory. Unlike the single-stream `meshopt_generateVertexRemap`, this routine accepts an array of stream descriptors, treating vertices as duplicates only when every attribute stream matches byte-for-byte.

## Understanding the meshopt_Stream Structure

The foundation of multi-stream remapping is the `meshopt_Stream` structure defined in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) (lines 46‑51). This simple POD describes how to access individual attributes within your vertex data:

- `data` – Pointer to the raw buffer containing the attribute values.
- `size` – Size of each element in bytes (e.g., 12 for a 3-float vector).
- `stride` – Byte distance between consecutive elements in the buffer.

When passing multiple streams to `meshopt_generateVertexRemapMulti`, you provide an array of these descriptors—one per attribute channel. The function supports between **1 and 16 streams**, allowing you to handle everything from simple position-only meshes to complex layouts with colors, tangents, and bone weights.

## Generating the Remap Table

### Function Signature and Parameters

According to the implementation in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) (lines 403‑420), the function signature is:

```c
size_t meshopt_generateVertexRemapMulti(
    unsigned int* destination,
    const unsigned int* indices,
    size_t index_count,
    size_t vertex_count,
    const meshopt_Stream* streams,
    size_t stream_count
);

```

The parameters operate as follows:

- **destination** – Pre-allocated array of `unsigned int` with `vertex_count` entries; receives the old-to-new vertex mapping.
- **indices** – Optional index buffer; pass `NULL` for unindexed meshes.
- **index_count** / **vertex_count** – Count of indices and maximum vertices.
- **streams** – Pointer to an array of `meshopt_Stream` describing each attribute.
- **stream_count** – Number of streams (must be 1‑16).

The function returns the count of **unique** vertices after deduplication.

### Internal Hashing Logic

Behind the scenes, the function instantiates a `VertexStreamHasher` (defined in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp), lines 55‑87). This hasher iterates over every supplied stream, computing a hash from the `size` bytes of each element and comparing vertices element-wise across all streams. Two vertices are considered identical **only if all streams match exactly**, ensuring attribute fidelity during remapping.

## Practical Code Examples

### Separate Position and Normal Buffers

When attributes live in distinct arrays, define each stream independently:

```c
// Existing data: float positions[vertex_count*3], normals[vertex_count*3]
// Optional: unsigned int indices[index_count]

meshopt_Stream streams[2];
streams[0].data = positions;
streams[0].size = 12;      // 3 floats * 4 bytes
streams[0].stride = 12;    // Tightly packed

streams[1].data = normals;
streams[1].size = 12;
streams[1].stride = 12;

unsigned int* remap = (unsigned int*)malloc(vertex_count * sizeof(unsigned int));

size_t unique_vertex_count = meshopt_generateVertexRemapMulti(
    remap,
    indices,        // NULL if unindexed
    index_count,
    vertex_count,
    streams,
    2               // Two attribute streams
);

// Allocate new buffers with the reduced size
float* new_positions = (float*)malloc(unique_vertex_count * 3 * sizeof(float));
float* new_normals = (float*)malloc(unique_vertex_count * 3 * sizeof(float));

meshopt_remapVertexBuffer(new_positions, positions, vertex_count, sizeof(float) * 3, remap);
meshopt_remapVertexBuffer(new_normals, normals, vertex_count, sizeof(float) * 3, remap);

```

### Interleaved Vertex Layouts

For interleaved data, calculate offsets into the single buffer while maintaining the same stride for all streams:

```c
// Layout: [position][normal][uv] per vertex (3+3+2 floats = 8 floats total)
float* vertex_data = /* your buffer */;
size_t stride = (3 + 3 + 2) * sizeof(float); // 32 bytes

meshopt_Stream streams[3];
streams[0].data = vertex_data + 0;   // Position offset
streams[0].size = 12;                  // 3 floats
streams[0].stride = stride;

streams[1].data = vertex_data + 3;   // Normal offset (after 3 floats)
streams[1].size = 12;
streams[1].stride = stride;

streams[2].data = vertex_data + 6;   // UV offset (after 6 floats)
streams[2].size = 8;                   // 2 floats
streams[2].stride = stride;

unsigned int* remap = (unsigned int*)malloc(vertex_count * sizeof(unsigned int));

size_t unique = meshopt_generateVertexRemapMulti(
    remap,
    NULL,           // Unindexed mesh
    0,
    vertex_count,
    streams,
    3
);

// Remap the interleaved buffer
char* new_vertices = (char*)malloc(unique * stride);
meshopt_remapVertexBuffer(new_vertices, vertex_data, vertex_count, stride, remap);

```

## Applying the Remap to Index Buffers

After generating the remap table, update your index buffer to reference the new vertex positions:

```c
unsigned int* new_indices = (unsigned int*)malloc(index_count * sizeof(unsigned int));
meshopt_remapIndexBuffer(new_indices, indices, index_count, remap);

```

This step is required when working with indexed meshes to ensure triangles point to the deduplicated vertex array.

## Summary

- **`meshopt_generateVertexRemapMulti`** processes up to 16 attribute streams via `meshopt_Stream` descriptors defined in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h).
- The internal `VertexStreamHasher` (in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp)) treats vertices as duplicates only when **all streams match byte-for-byte** within their specified `size`.
- The function returns the unique vertex count and populates a remap array suitable for `meshopt_remapVertexBuffer` and `meshopt_remapIndexBuffer`.
- Works with both indexed meshes (supply an index buffer) and unindexed meshes (pass `NULL` for indices).
- Supports both separate attribute buffers and interleaved layouts by adjusting the `data` pointer and `stride` values in each stream descriptor.

## Frequently Asked Questions

### What is the maximum number of attribute streams supported?

The function accepts between **1 and 16 streams** via the `stream_count` parameter. Exceeding this range violates the API contract as implemented in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp).

### Can I use this function with unindexed mesh data?

Yes. Pass `NULL` for the `indices` parameter and `0` for `index_count`. The remapping logic operates purely on the vertex streams, generating a compact vertex buffer without requiring triangle indices.

### How does the library determine if two vertices are duplicates?

The `VertexStreamHasher` class performs **element-wise binary comparison** across all defined streams. For each vertex, it hashes `size` bytes from every stream; a collision occurs only if all bytes match exactly across every attribute channel.

### Is it necessary to remap the index buffer after vertex deduplication?

Yes. After calling `meshopt_generateVertexRemapMulti`, you must pass the resulting remap table to `meshopt_remapIndexBuffer` (if using indexed geometry) to update triangle indices to point to the new vertex locations. Failure to do so results in corrupted geometry referencing old vertex indices.