Cluster-Relative Position Quantization in meshoptimizer: How to Use `meshopt_computePositionExponent`

Cluster-relative position quantization is a memory bandwidth optimization technique that encodes vertex positions as integer offsets from a cluster anchor using a shared exponent, ensuring shared vertices between clusters remain identical and can be reused without precision loss.

The meshoptimizer library provides utilities for compressing mesh data while maintaining geometric coherence across meshlets. This technique stores vertex positions as quantized integers relative to each cluster’s bounding box, significantly reducing memory footprint compared to full-precision floating-point storage. The function meshopt_computePositionExponent calculates the optimal scaling factor for this quantization based on each cluster’s axis-aligned bounding box (AABB).

What Is Cluster-Relative Position Quantization?

Cluster-relative position quantization reduces vertex position storage by expressing coordinates as integer offsets from a cluster anchor—typically the minimum corner of the cluster’s AABB. Instead of storing absolute float values, each vertex stores (vertex_position - cluster_min) * scale as an integer.

The key innovation is the use of a per-cluster exponent that determines the power-of-two scaling factor. All vertices within a cluster share this exponent, which guarantees that shared vertices across adjacent clusters quantize to the exact same integer values. This prevents cracks and allows vertex reuse without additional precision loss, unlike independent per-vertex quantization.

The meshopt_computePositionExponent API

Function Signature and Parameters

The library exposes the exponent calculation in src/meshoptimizer.h with the following signature:

int meshopt_computePositionExponent(const float* minv,
                                    const float* maxv,
                                    int min_exp,
                                    int max_bits);

The parameters control the quantization behavior:

  • minv / maxv – Pointers to float arrays representing the cluster’s AABB corners (minimum and maximum bounds).
  • min_exp – The lowest acceptable exponent (e.g., -10), acting as a floor for precision.
  • max_bits – The maximum bit width for quantized coordinates (commonly 16 for 16-bit integers).

The function returns the largest exponent in the range [min_exp, …] that fits the bounding box within max_bits bits. Internally, it computes the required scaling so that the range (maxv - minv) fits within the integer range [-2^(n-1), 2^(n-1)-1] where n = max_bits.

Implementation Details

The implementation resides in src/quantization.cpp. It calculates the ideal exponent by determining how many bits are necessary to represent the AABB dimensions and then selecting the coarsest scaling (largest exponent) that still satisfies the max_bits constraint while respecting min_exp.

Quantizing Vertex Positions

Once you have computed the exponent for a cluster, quantize each vertex position relative to the cluster anchor:

// Compute exponent for this cluster
float cluster_min[3] = { aabb.min[0], aabb.min[1], aabb.min[2] };
float cluster_max[3] = { aabb.max[0], aabb.max[1], aabb.max[2] };

int exponent = meshopt_computePositionExponent(cluster_min, cluster_max,
                                               /*min_exp=*/-10, 
                                               /*max_bits=*/16);

// Calculate scale factor: 2^exponent
float scale = (float)(1 << exponent);

// Quantize each vertex as 16-bit integer offset
for (size_t i = 0; i < vertex_count; ++i) {
    const float* p = &vertex_positions[i * 3];
    int16_t qx = (int16_t)roundf((p[0] - cluster_min[0]) * scale);
    int16_t qy = (int16_t)roundf((p[1] - cluster_min[1]) * scale);
    int16_t qz = (int16_t)roundf((p[2] - cluster_min[2]) * scale);
    // Store qx, qy, qz in your compressed vertex buffer
}

The quantized offsets occupy minimal storage (typically 16 bits per component) while preserving the relative precision needed for rendering.

Decoding and Reconstruction

At runtime, the GPU or CPU reconstructs the original position by reversing the quantization math. The cluster anchor and inverse scale must be available in the shader or reconstruction routine:

// Decode on CPU (useful for debugging or software rasterizers)
float invScale = 1.0f / scale;

for (size_t i = 0; i < vertex_count; ++i) {
    int16_t qx = /* read from compressed buffer */;
    int16_t qy = /* read from compressed buffer */;
    int16_t qz = /* read from compressed buffer */;
    
    float x = cluster_min[0] + qx * invScale;
    float y = cluster_min[1] + qy * invScale;
    float z = cluster_min[2] + qz * invScale;
    // (x, y, z) now contains the reconstructed position
}

Modern rendering pipelines often pass the quantized integers directly to the vertex shader, which performs the dequantization using GPU-friendly normalized integer formats or compute shaders.

Real-World Usage in the Demo

A concrete implementation appears in demo/main.cpp (approximately line 950). The demo calculates the exponent for each meshlet’s AABB, then calls encodeMeshletsDXR to apply cluster-relative quantization. This produces the DXR-compatible "Compressed1" position encoding used in the sample renderer.

This pattern follows the standard workflow:

  1. Compute cluster bounds (AABB)
  2. Call meshopt_computePositionExponent to determine optimal scaling
  3. Quantize positions relative to the cluster minimum
  4. Pack the integer offsets into the vertex buffer

Summary

  • Cluster-relative position quantization stores vertex positions as integer offsets from a cluster anchor, reducing memory bandwidth compared to 32-bit floats.
  • meshopt_computePositionExponent in src/quantization.cpp calculates the optimal power-of-two scaling factor that fits cluster bounds within a specified bit width (commonly 16 bits).
  • Shared vertices between clusters quantize to identical values when using the cluster-relative approach, preventing geometric cracks and enabling vertex reuse.
  • Implementation requires computing the exponent, applying the scale to generate integer offsets, and reconstructing positions at runtime using the inverse scale and cluster anchor.

Frequently Asked Questions

What is the difference between cluster-relative and per-vertex quantization?

Per-vertex quantization encodes each position independently using a global scale, which can cause shared vertices on cluster boundaries to quantize to different values, creating visible cracks. Cluster-relative quantization uses a per-cluster anchor and exponent, ensuring shared vertices match exactly because they use the same reference point and scaling factor.

Why does meshopt_computePositionExponent return an integer exponent rather than a float scale?

The function returns an exponent because cluster-relative quantization relies on power-of-two scaling (2^exp). This allows extremely efficient encoding and decoding using bit shifts on both CPU and GPU, and ensures that the quantization grid aligns perfectly across clusters that share the same exponent calculation.

How do I choose the min_exp and max_bits parameters?

Set max_bits based on your target storage format (16 for 16-bit integers, 10 or 11 for specific compressed formats). Set min_exp based on your minimum acceptable precision—typically -10 to -14, which corresponds to roughly 0.001 to 0.0001 unit precision. The function will automatically select the coarsest exponent (largest value) that fits your data within these constraints.

Where is the source code for the quantization implementation?

The implementation of meshopt_computePositionExponent resides in src/quantization.cpp, with the public API declaration in src/meshoptimizer.h. The demo showing integration with meshlets appears in demo/main.cpp, specifically in the section handling encodeMeshletsDXR and the "Compressed1" encoding path.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →