meshopt_encodeIndexBuffer Compression Ratio: Expected Results and Best Practices
meshopt_encodeIndexBuffer compresses triangle-list index data to approximately 1 byte per triangle under ideal conditions, yielding a 6× reduction compared to raw 16-bit indices, with real-world meshes typically achieving 1–1.2 bytes per triangle.
The meshopt_encodeIndexBuffer function in the zeux/meshoptimizer repository implements a specialized codec for geometry index buffers. According to the implementation in src/indexcodec.cpp, the algorithm achieves optimal compression only when the index buffer has been previously optimized for vertex cache and fetch locality. Without these optimizations, compression ratios can degrade to roughly 2 bytes per index.
Understanding the Expected Compression Ratio
The codec targets a specific compression benchmark that represents a significant savings over raw index data.
Ideal vs. Real-World Performance
In the ideal case, meshopt_encodeIndexBuffer reduces triangle-list index data to approximately 1 byte per triangle. This represents a 6× reduction when compared against raw 16-bit indices (which require 6 bytes per triangle).
Real-world meshes typically achieve 1–1.2 bytes per triangle. This slight increase over the ideal target accounts for geometric irregularities and cache-miss patterns that cannot be fully eliminated during preprocessing.
If the index buffer has not been optimized for locality, the compression ratio degrades significantly. Unoptimized sparse indices may require approximately 2 bytes per index, eliminating most of the compression benefits.
Prerequisites for Optimal Compression
To achieve the target 1 byte per triangle ratio, you must preprocess the geometry using meshoptimizer's optimization routines.
Vertex Cache Optimization
Always run meshopt_optimizeVertexCache before encoding. This function reorders triangles to maximize vertex cache hits, which creates the locality patterns the encoder exploits. The codec in src/indexcodec.cpp assumes that consecutive triangles share vertices, and without this optimization, the entropy coder cannot efficiently encode the index deltas.
Vertex Fetch Optimization
Follow cache optimization with meshopt_optimizeVertexFetch. This step reorders vertex data to match the index buffer order, further improving locality. According to the README documentation, the combination of these optimizations is essential for achieving the documented compression ratios.
Implementation Best Practices
Beyond the preprocessing requirements, several implementation details affect the final compression efficiency.
Triangle Order Preservation
The encoder may rotate individual triangles to improve compression, but it must not reorder triangles arbitrarily. If your application depends on specific triangle ordering (e.g., for alpha sorting or material grouping), ensure that the optimization steps preserve these constraints using the appropriate meshoptimizer flags.
Topology Constraints
The index buffer codec only supports triangle lists. If your data uses triangle strips or lines, use meshopt_encodeIndexSequence instead, which handles arbitrary index sequences without the triangle-specific optimizations.
Secondary Lossless Compression
After encoding, apply a general-purpose compressor such as LZ4 or ZSTD. Because meshopt_encodeIndexBuffer produces byte-aligned output with repetitive patterns, secondary compression can further shrink the data without affecting decode speed. This two-stage approach (specialized geometry codec followed by general-purpose compression) typically yields the smallest final payload.
Code Implementation
The following examples demonstrate the correct workflow: optimize first, then encode, and optionally apply secondary compression.
C++ Implementation
// 1. Optimize for vertex cache and fetch first
meshopt_optimizeVertexCache(indices, index_count, vertex_count);
meshopt_optimizeVertexFetch(indices, index_count, vertex_count);
// 2. Allocate worst-case buffer and encode
// Defined in src/meshoptimizer.h and implemented in src/indexcodec.cpp
std::vector<unsigned char> ibuf(
meshopt_encodeIndexBufferBound(index_count, vertex_count));
ibuf.resize(meshopt_encodeIndexBuffer(
&ibuf[0], ibuf.size(), indices, index_count));
// 3. (Optional) compress further with a lossless compressor, e.g. LZ4
// LZ4_compress_default(reinterpret_cast<const char*>(ibuf.data()), ... );
JavaScript (WebAssembly) Implementation
// Assuming `instance` is the compiled meshoptimizer.wasm module
const bound = instance.exports.meshopt_encodeIndexBufferBound(
indexCount, maxIndex + 1);
const encoded = new Uint8Array(bound);
const size = instance.exports.meshopt_encodeIndexBuffer(
encoded, bound, indexArray, indexCount);
const finalBuffer = encoded.subarray(0, size);
The meshopt_encodeIndexBufferBound function calculates the maximum possible size for the encoded buffer, ensuring you allocate sufficient memory before encoding.
Summary
- Target ratio:
meshopt_encodeIndexBufferachieves approximately 1 byte per triangle (6× reduction vs. raw 16-bit indices) when preprocessing guidelines are followed. - Real-world results: Most meshes compress to 1–1.2 bytes per triangle; unoptimized meshes may require 2 bytes per index.
- Required preprocessing: Run
meshopt_optimizeVertexCacheandmeshopt_optimizeVertexFetchbefore encoding, as implemented insrc/indexcodec.cpp. - Topology requirements: Use triangle lists only; other topologies require
meshopt_encodeIndexSequence. - Further optimization: Apply secondary compression (LZ4/ZSTD) to the encoded output for additional size reduction.
Frequently Asked Questions
What happens if I skip vertex cache optimization before encoding?
Without vertex cache optimization, the index buffer lacks the locality patterns the encoder expects. According to the source code in src/indexcodec.cpp, sparse or random index ordering forces the entropy coder to use larger bit codes, degrading compression to approximately 2 bytes per index and eliminating most of the 6× compression benefit.
Can I use meshopt_encodeIndexBuffer with triangle strips?
No. The function only supports triangle lists. For triangle strips or line lists, use meshopt_encodeIndexSequence, which handles arbitrary index sequences without assuming triangle topology. The specialized triangle-list codec achieves better compression ratios because it exploits vertex sharing patterns that are only present in optimized triangle lists.
How does secondary compression interact with the encoded data?
The output of meshopt_encodeIndexBuffer contains byte-aligned data with repetitive patterns that general-purpose compressors like LZ4 or ZSTD can further compress. This secondary pass does not affect decode speed because the geometry codec decompression happens first, and the secondary decompressor only processes the compacted bitstream. Real-world deployments typically see an additional 10–30% size reduction from this step.
Where is the compression ratio documented in the repository?
The expected compression ratios and optimization requirements are documented in the Index compression section of README.md in the zeux/meshoptimizer repository. The implementation details and API declarations are found in src/meshoptimizer.h and src/indexcodec.cpp, while practical usage examples appear in demo/main.cpp.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →