# Correct Order for the Mesh Optimization Pipeline in zeux/meshoptimizer

> Discover the correct mesh optimization pipeline order in zeux/meshoptimizer. Follow this critical seven-step sequence for optimal results and efficient processing.

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

---

**The canonical mesh optimization pipeline in `zeux/meshoptimizer` follows a strict seven-step sequence: indexing, vertex-cache optimization, optional overdraw optimization, vertex-fetch optimization, vertex quantization, index filtering, and optional shadow indexing.** This order is critical because each stage depends on the optimized layout produced by the previous step.

The `zeux/meshoptimizer` library provides algorithms for preparing geometry for modern GPU rendering pipelines. Applying these operations in the correct sequence—exactly as documented in the repository’s README under *Core pipeline* (lines 38‑47)—ensures maximum vertex cache utilization, minimal overdraw, and optimal memory bandwidth usage.

## Why Pipeline Order Matters

Each optimization step in `meshoptimizer` assumes the previous stage has produced an optimal index or vertex layout. **Later stages depend on earlier stages**; for example, vertex-fetch optimization expects a fully indexed mesh, and quantization should occur after all reordering so attribute data is as compact as possible. Reordering steps or skipping prerequisites yields suboptimal performance and can invalidate previous optimizations.

## The Seven-Step Mesh Optimization Pipeline

### 1. Indexing

The first step transforms unindexed triangle soup into an indexed mesh. **Create a compact index buffer and eliminate duplicate vertices** using `meshopt_generateVertexRemap` to generate a remap table, then apply it with `meshopt_remapIndexBuffer` and `meshopt_remapVertexBuffer`.

This operation is defined in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) and establishes the foundation for all subsequent cache and fetch optimizations.

### 2. Vertex-Cache Optimization

After indexing, reorder indices to improve post-transform cache hits. The function `meshopt_optimizeVertexCache` (implemented in [`src/vcacheoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vcacheoptimizer.cpp)) rearranges the index buffer to maximize GPU vertex reuse, reducing the number of vertices processed by the GPU.

### 3. Overdraw Optimization (Optional)

This optional step rearranges triangles to reduce pixel overdraw. Using `meshopt_optimizeOverdraw` from [`src/overdrawoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/overdrawoptimizer.cpp), the algorithm reorders indices based on vertex positions to minimize the number of pixels shaded multiple times during forward rendering.

### 4. Vertex-Fetch Optimization

Once the index order is fixed, reorder the vertex buffer to improve memory bandwidth usage. The function `meshopt_optimizeVertexFetchRemap` generates an optimal remap, which is then applied via `meshopt_remapVertexBuffer` and `meshopt_remapIndexBuffer`. This stage is implemented in [`src/vfetchoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vfetchoptimizer.cpp).

### 5. Vertex Quantization

Compress vertex attributes to smaller formats (e.g., `half`, `snorm`) using `meshopt_encodeVertexBuffer`. Quantization must occur after all reordering to ensure the attribute data is in its final compact form. This functionality leverages utilities in [`src/meshletutils.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/meshletutils.cpp) for advanced encoding.

### 6. Index Filtering

Remove degenerate or unused indices that may have been introduced during previous steps. The function `meshopt_optimizeVertexFetch` performs final cleanup on the index buffer, ensuring only valid triangles remain for rendering.

### 7. Shadow Indexing (Optional)

The final optional step generates a separate index buffer optimized for depth-only passes. `meshopt_generateShadowIndices` creates a version of the index buffer that is optimal for shadow map rendering, where vertex positions are the only attributes accessed.

## Complete Implementation Example

The following C++ implementation demonstrates the exact sequence using the public API from [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h):

```c++
#include "meshoptimizer.h"
#include <vector>

// Step 1: Indexing – generate remap table and create indexed buffers
std::vector<unsigned int> remap(unindexedVertexCount);
size_t vertexCount = meshopt_generateVertexRemap(
    remap.data(), nullptr, indexCount,
    unindexedVertices, unindexedVertexCount, sizeof(Vertex));

std::vector<unsigned int> indices(indexCount);
meshopt_remapIndexBuffer(indices.data(), nullptr, indexCount, remap.data());

std::vector<Vertex> vertices(vertexCount);
meshopt_remapVertexBuffer(vertices.data(),
    unindexedVertices, unindexedVertexCount, sizeof(Vertex), remap.data());

// Step 2: Vertex-cache optimization
meshopt_optimizeVertexCache(indices.data(), indices.data(),
    indexCount, vertexCount);

// Step 3: Overdraw optimization (optional)
meshopt_optimizeOverdraw(indices.data(), indices.data(),
    indexCount, &vertices[0].position[0], vertexCount, sizeof(Vertex), 1.05f);

// Step 4: Vertex-fetch optimization
meshopt_optimizeVertexFetchRemap(remap.data(), indices.data(),
    indexCount, vertexCount);
meshopt_remapVertexBuffer(vertices.data(), vertices.data(),
    vertexCount, sizeof(Vertex), remap.data());
meshopt_remapIndexBuffer(indices.data(), indices.data(),
    indexCount, remap.data());

// Step 5: Vertex quantization (example: positions to half-float)
meshopt_encodeVertexBuffer(&vertices[0], vertices.size(),
    sizeof(Vertex), meshopt_EncodeVertexBuffer_Float16);

// Step 6: Index filtering (remove degenerate triangles)
meshopt_optimizeVertexFetch(indices.data(), indices.data(),
    indexCount, vertices.data(), vertexCount, sizeof(Vertex));

// Step 7: Shadow indexing (optional)
meshopt_generateShadowIndices(indices.data(), indices.data(),
    indexCount, vertexCount);

```

The equivalent JavaScript implementation using the Node.js bindings follows the same logical order:

```javascript
import {
    generateVertexRemap,
    remapIndexBuffer,
    remapVertexBuffer,
    optimizeVertexCache,
    optimizeOverdraw,
    optimizeVertexFetchRemap,
    encodeVertexBuffer,
    optimizeVertexFetch,
    generateShadowIndices
} from "meshoptimizer";

// Step 1: Indexing
const remap = generateVertexRemap(null, vertexCount);
remapIndexBuffer(indices, null, indexCount, remap);
remapVertexBuffer(vertices, vertexCount, vertexSize, remap);

// Step 2: Vertex-cache optimization
optimizeVertexCache(indices, indexCount, vertexCount);

// Step 3: Overdraw (optional)
optimizeOverdraw(indices, indexCount, vertices, vertexCount, vertexSize, 1.05);

// Step 4: Vertex-fetch optimization
const fetchRemap = optimizeVertexFetchRemap(indices, indexCount, vertexCount);
remapVertexBuffer(vertices, vertexCount, vertexSize, fetchRemap);
remapIndexBuffer(indices, indexCount, fetchRemap);

// Step 5: Quantization (example: positions to half-float)
encodeVertexBuffer(vertices, vertexCount, vertexSize, meshopt.EncodeVertexBuffer_Float16);

// Step 6: Index filtering (removes degenerate tris)
optimizeVertexFetch(indices, indexCount, vertices, vertexCount, vertexSize);

// Step 7: Shadow indexing (optional)
generateShadowIndices(indices, indexCount, vertexCount);

```

## Source Code Architecture

The pipeline implementation is distributed across specific source files in the repository:

- **[`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h)** – Public API header exposing all pipeline functions including `meshopt_optimizeVertexCache` and `meshopt_generateVertexRemap`.
- **[`src/vcacheoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vcacheoptimizer.cpp)** – Implements vertex-cache optimizations (`meshopt_optimizeVertexCache*`).
- **[`src/overdrawoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/overdrawoptimizer.cpp)** – Implements overdraw reduction (`meshopt_optimizeOverdraw`).
- **[`src/vfetchoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/vfetchoptimizer.cpp)** – Implements vertex-fetch reordering and remapping (`meshopt_optimizeVertexFetch*`).
- **[`src/meshletutils.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/meshletutils.cpp)** – Provides meshlet-level utilities used for advanced quantization and clustering.
- **[`README.md`](https://github.com/zeux/meshoptimizer/blob/main/README.md)** – Documents the core pipeline order and usage guidelines under the *Core pipeline* section.

## Summary

- **Indexing** must always occur first to establish the vertex/remap structure required by subsequent steps.
- **Vertex-cache optimization** (step 2) and **overdraw optimization** (step 3) modify the index buffer order and must precede vertex buffer layout changes.
- **Vertex-fetch optimization** (step 4) should occur after all index reordering is complete.
- **Quantization** (step 5) compresses data only after the final vertex order is established.
- **Index filtering** (step 6) cleans up degenerate geometry introduced by previous transformations.
- **Shadow indexing** (step 7) is optional and creates specialized buffers for depth-only passes.

## Frequently Asked Questions

### Can I skip steps in the mesh optimizer pipeline?

You can skip optional steps like overdraw optimization and shadow indexing, but you should not skip mandatory stages like indexing or reorder core steps. **Vertex-cache optimization must precede vertex-fetch optimization**, and quantization must follow all reordering. Skipping indexing will cause subsequent functions to fail or produce incorrect results because they expect compact, unique vertex buffers.

### Why must vertex quantization happen after vertex-fetch optimization?

Quantization compresses vertex attributes into smaller formats (such as half-precision floats). If you quantize before reordering vertices for fetch efficiency, you waste computational effort compressing data that will be rearranged anyway. Additionally, **vertex-fetch optimization determines the final memory layout**, so quantization should only be applied to the final vertex order to achieve maximum cache locality for the compressed data.

### What happens if I run overdraw optimization before vertex-cache optimization?

The overdraw optimizer in [`src/overdrawoptimizer.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/overdrawoptimizer.cpp) rearranges triangles to minimize pixel shader work, but this reordering typically destroys the vertex-cache friendly ordering established by `meshopt_optimizeVertexCache`. **The pipeline places overdraw optimization after cache optimization** because it is less sensitive to small changes in vertex reuse, whereas the cache optimizer creates a specific access pattern that overdraw optimization respects when given the `threshold` parameter (typically 1.05).

### Is shadow indexing always necessary?

**No, shadow indexing is optional** and only required when you render shadow maps or depth-only passes. The standard pipeline produces an index buffer optimized for full forward rendering. If your application uses the same mesh for both color and depth passes, generating a separate shadow index buffer with `meshopt_generateShadowIndices` can improve depth-pass performance by optimizing for position-only vertex fetches, but it consumes additional memory for the extra index buffer.