# How to Generate Shadow Index Buffers with meshopt_generateShadowIndexBuffer for Depth-Only Passes

> Generate shadow index buffers with meshopt_generateShadowIndexBuffer for depth-only passes. Reduce vertex fetch bandwidth by eliminating duplicate vertices for shadow mapping and Z-prepass rendering.

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

---

**Use `meshopt_generateShadowIndexBuffer` to create a position-only index buffer that eliminates duplicate vertices differing only in irrelevant attributes, reducing vertex fetch bandwidth during shadow map and Z-prepass rendering.**

The `meshoptimizer` library by zeux provides specialized tools for optimizing mesh data for GPU consumption. When rendering depth-only passes such as shadow maps or Z-prepasses, vertex attributes like normals, texture coordinates, and skinning data are unused, yet the GPU may still fetch these redundant values. Generating a **shadow index buffer** allows you to re-index geometry based solely on vertex positions, minimizing unnecessary memory bandwidth and improving cache locality.

## Understanding Shadow Index Buffers

A standard index buffer may reference multiple vertices that share identical positions but differ in other attributes. During depth-only rendering, the GPU wastes cycles fetching complete vertex structures when only the position matters. **Shadow index buffers** solve this by creating a remapped index list that references only unique position data, effectively deduplicating the vertex stream for position-only passes.

## Algorithm Implementation in meshoptimizer

The implementation in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) uses a hash-based approach to identify unique vertex positions efficiently.

### Vertex Hashing Strategy

The algorithm constructs a hash table keyed by the raw bytes of vertex data. In [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) lines 22-34, the `VertexHasher` struct processes `vertex_size` bytes at `vertex_stride` offsets using a Murmur-style hash routine (`hashUpdate4`). This stores the first occurrence of each distinct vertex position, ensuring O(1) lookup time for duplicate detection.

### Index Remapping Process

For every index in the original buffer, the function looks up the vertex in the hash table and writes the **canonical index** (the first occurrence of that position) into the destination buffer. The result is a new index buffer that references only the unique set of positions, leaving the original vertex buffer unmodified but producing a compact index list optimal for depth-only passes.

## Function Signatures and Constraints

The API declares two variants in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h):

- `meshopt_generateShadowIndexBuffer` – Single vertex stream version
- `meshopt_generateShadowIndexBufferMulti` – Multi-stream variant for complex vertex layouts

**Critical constraints** for the single-stream version:

| Parameter | Requirement |
|-----------|-------------|
| `index_count` | Must be a multiple of 3 (triangular mesh only) |
| `vertex_size` | 0 < size ≤ 256 and must be ≤ `vertex_stride` |
| `vertex_stride` | Byte distance between consecutive vertices in the buffer |
| `vertices` | Pointer to the start of the position attribute (usually the first field) |

The function performs **no vertex buffer modification**; it only generates a new index list compatible with the existing vertex data.

## Basic Usage Example

The following pattern from [`demo/main.cpp`](https://github.com/zeux/meshoptimizer/blob/main/demo/main.cpp) lines 909-915 demonstrates standard usage:

```cpp
// Assume mesh contains positions at offset 0
std::vector<unsigned int> shadowIndices(mesh.indices.size());

meshopt_generateShadowIndexBuffer(
    &shadowIndices[0],                 // destination buffer
    &mesh.indices[0],                   // original indices
    mesh.indices.size(),                // index count (multiple of 3)
    &mesh.vertices[0].px,               // pointer to position attribute
    mesh.vertices.size(),               // vertex count
    sizeof(float) * 3,                  // vertex_size (3 floats)
    sizeof(Vertex)                     // vertex_stride (full struct size)
);

// Optional: optimize vertex cache for the shadow pass
meshopt_optimizeVertexCache(
    &shadowIndices[0],
    &shadowIndices[0],
    shadowIndices.size(),
    mesh.vertices.size()
);

```

This example generates a shadow buffer from positions stored as the first field of a vertex structure, then applies vertex cache optimization to improve GPU locality during the depth pass.

## Multi-Stream Vertex Data

For engines using multi-stream vertex formats (position in buffer 0, other attributes elsewhere), use `meshopt_generateShadowIndexBufferMulti` as implemented in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) lines 36-48:

```cpp
meshopt_Stream streams[] = {
    { &positions[0], sizeof(float) * 3, sizeof(float) * 3 }, // positions only
    // Additional streams can be added for alpha-tested geometry requiring UVs
};

std::vector<unsigned int> shadowIndices(totalIndices);

meshopt_generateShadowIndexBufferMulti(
    &shadowIndices[0],
    &indices[0],
    totalIndices,
    totalVertices,
    streams,
    1 // number of streams to consider for hashing
);

```

This variant allows you to specify exactly which vertex streams contain relevant data for the depth pass, supporting complex asset pipelines where positions reside in separate buffers from vertex attributes.

## Post-Processing Recommendations

Because the shadow buffer preserves the original vertex order initially, always run **`meshopt_optimizeVertexCache`** on the generated indices before use. According to the library documentation in [`README.md`](https://github.com/zeux/meshoptimizer/blob/main/README.md) lines 165-176, this step maximizes vertex cache hit rates during depth-only rendering, compounding the bandwidth savings from the shadow buffer generation.

## Summary

- **Shadow index buffers** reduce vertex fetch bandwidth by deduplicating vertices based solely on position data.
- **Hash-based deduplication** in [`src/indexgenerator.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/indexgenerator.cpp) identifies unique positions using a Murmur-style hash over raw vertex bytes.
- **Function constraints** require triangular input (index count multiple of 3) and proper vertex size/stride parameters.
- **Post-process with `meshopt_optimizeVertexCache`** to ensure optimal GPU vertex cache utilization during depth passes.
- **Multi-stream support** via `meshopt_generateShadowIndexBufferMulti` handles split vertex attribute layouts common in modern engines.

## Frequently Asked Questions

### What is the difference between a regular index buffer and a shadow index buffer?

A regular index buffer references all vertex attributes, including normals and UVs. A shadow index buffer references only unique positions, allowing the GPU to skip fetching irrelevant attributes during depth-only passes. This reduces memory bandwidth when rendering shadow maps or performing Z-prepasses.

### When should I call meshopt_optimizeVertexCache on the shadow buffer?

Call `meshopt_optimizeVertexCache` immediately after generating the shadow buffer. Because `meshopt_generateShadowIndexBuffer` preserves the original vertex reference order, the resulting indices may have poor locality. The optimization pass reorders triangles to maximize vertex cache hits during the depth-only render pass.

### Can I use meshopt_generateShadowIndexBuffer with non-triangular meshes?

No. The function requires `index_count` to be a multiple of 3, as it is designed specifically for triangular meshes. Attempting to use line lists or point clouds will produce undefined behavior or incorrect results.

### Does the function modify the original vertex buffer?

No. Both `meshopt_generateShadowIndexBuffer` and `meshopt_generateShadowIndexBufferMulti` are read-only operations with respect to vertex data. They only write to the destination index buffer you provide, leaving the source vertices and original indices unchanged.