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

> Learn cluster-relative position quantization with meshoptimizer. Discover how to use meshopt_computePositionExponent to optimize vertex positions and reduce memory bandwidth for better performance.

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

---

**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`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) with the following signature:

```c
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`](https://github.com/zeux/meshoptimizer/blob/main/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:

```c
// 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:

```c
// 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`](https://github.com/zeux/meshoptimizer/blob/main/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`](https://github.com/zeux/meshoptimizer/blob/main/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`](https://github.com/zeux/meshoptimizer/blob/main/src/quantization.cpp)**, with the public API declaration in **[`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h)**. The demo showing integration with meshlets appears in **[`demo/main.cpp`](https://github.com/zeux/meshoptimizer/blob/main/demo/main.cpp)**, specifically in the section handling `encodeMeshletsDXR` and the "Compressed1" encoding path.