# How to Use meshopt_simplifyWithAttributes for Mesh Simplification with Attribute Preservation

> Learn to use meshopt_simplifyWithAttributes to reduce mesh triangle count while preserving vertex attributes like normals UVs and colors using the meshoptimizer library.

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

---

**`meshopt_simplifyWithAttributes` is the attribute-aware variant of the meshoptimizer simplifier that reduces triangle count while preserving per-vertex attributes like normals, UVs, and colors by incorporating them into the quadric error metric.**

When simplifying meshes for real-time rendering, preserving vertex attributes is often as critical as maintaining geometric fidelity. The `meshopt_simplifyWithAttributes` function in the [zeux/meshoptimizer](https://github.com/zeux/meshoptimizer) library extends the standard simplification algorithm to account for per-vertex data, ensuring that normals remain smooth and UV seams stay intact during decimation.

## Understanding the Function Signature

The declaration in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) (lines 535-537) defines the complete interface:

```cpp
size_t meshopt_simplifyWithAttributes(
    unsigned int* destination,
    const unsigned int* indices,
    size_t index_count,
    const float* vertex_positions,
    size_t vertex_count,
    size_t vertex_positions_stride,
    const float* vertex_attributes,
    size_t vertex_attributes_stride,
    const float* attribute_weights,
    size_t attribute_count,
    const unsigned char* vertex_lock,
    size_t target_index_count,
    float target_error,
    unsigned int options,
    float* result_error
);

```

**Key parameters:**
- **destination** – Buffer receiving the simplified index list; must be large enough for the original index count.
- **vertex_positions** – Float3 positions (first 12 bytes of each vertex).
- **vertex_attributes** – Packed attribute data with one float per attribute component per vertex.
- **attribute_weights** – Per-component importance values; higher weights prioritize preservation.
- **vertex_lock** – Optional per-vertex flags to prevent modification.
- **target_error** – Allowed geometric/attribute error (relative to mesh extents unless `meshopt_SimplifyErrorAbsolute` is set).

## Preparing Attribute Data and Strides

Unlike the basic simplifier, `meshopt_simplifyWithAttributes` requires explicit separation between positions and attributes. The **vertex_positions_stride** and **vertex_attributes_stride** parameters specify the byte stride between consecutive vertices in each array.

For example, if your attributes consist of normals (float3) and UVs (float2), you would pack them as 5 floats per vertex with a stride of `20` bytes. The function assumes **one float per attribute component**, so a float3 normal occupies three consecutive entries in the weights array.

## Configuring Attribute Weights

The **attribute_weights** array controls how aggressively the simplifier preserves each component:

- **1.0f** – Treat the attribute with equal importance to geometric position.
- **0.5f** – Allow twice as much error in this attribute relative to position.
- **0.0f** – Ignore this attribute entirely during simplification.

This weighting system prevents the artifacts that occur when collapsing edges based solely on position, which can distort UV islands or flip normals.

## Simplification Options and Vertex Locks

As documented in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) (lines 668-682), several bitmask options control the algorithm:

- **meshopt_SimplifyLockBorder** – Preserves vertices on open borders.
- **meshopt_SimplifySparse** – Optimizes for sparse index subsets, significantly improving performance for LOD generation.
- **meshopt_SimplifyErrorAbsolute** – Interprets `target_error` as absolute world units rather than relative to mesh extents.
- **meshopt_SimplifyPermissive** – Allows collapsing across attribute discontinuities when combined with protection flags.

For fine-grained control, the **vertex_lock** buffer accepts flags defined in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) (lines 889-894):
- **meshopt_SimplifyVertex_Lock** – Prevents the vertex from moving or being removed.
- **meshopt_SimplifyVertex_Protect** – Guards attribute discontinuities when using `meshopt_SimplifyPermissive`.
- **meshopt_SimplifyVertex_Priority** – Increases preservation priority for critical feature points.

## Implementation Details

According to the source in [`src/simplifier.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/simplifier.cpp) (lines 2635-2640), `meshopt_simplifyWithAttributes` forwards to `meshopt_simplifyEdge`, which builds a **grid-based spatial hierarchy** and computes **quadrics per cell**. The algorithm selects edge collapses that minimize combined geometric-plus-attribute error, then filters the result to eliminate duplicate triangles. This approach preserves both silhouette integrity and per-vertex data fidelity.

## Complete Usage Example

The following pattern demonstrates practical integration:

```cpp
#include "meshoptimizer.h"
#include <vector>
#include <cfloat>

// Input data
std::vector<float> positions;        // float3 per vertex
std::vector<float> attributes;       // normals (float3) + UVs (float2) = 5 floats
std::vector<unsigned int> indices;   // Original triangle list

size_t vertex_count = positions.size() / 3;
size_t pos_stride = sizeof(float) * 3;
size_t attr_stride = sizeof(float) * 5;

// Weight normals heavily, UVs moderately
float attr_weights[5] = { 1.0f, 1.0f, 1.0f, 0.5f, 0.5f };

// Optional: lock boundary vertices
std::vector<unsigned char> lock(vertex_count, 0);
lock[0] = meshopt_SimplifyVertex_Lock;

// Target 50% of original triangles
size_t target_index_count = indices.size() / 2;
float target_error = 0.01f;
unsigned int options = meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute;

std::vector<unsigned int> out_indices(indices.size());
float final_error = 0.0f;

size_t out_count = meshopt_simplifyWithAttributes(
    out_indices.data(),
    indices.data(),
    indices.size(),
    positions.data(),
    vertex_count,
    pos_stride,
    attributes.data(),
    attr_stride,
    attr_weights,
    5,                              // attribute_count
    lock.data(),                    // Can be nullptr
    target_index_count,
    target_error,
    options,
    &final_error);

out_indices.resize(out_count);

```

## Integration in Real-World Tools

The `meshopt_simplifyWithAttributes` function powers production pipelines:

- **[`demo/clusterlod.h`](https://github.com/zeux/meshoptimizer/blob/main/demo/clusterlod.h)** (lines 68-74) demonstrates usage within cluster-based LOD generation.
- **[`gltf/gltfpack.h`](https://github.com/zeux/meshoptimizer/blob/main/gltf/gltfpack.h)** implements attribute-aware simplification via flags like `simplify_attributes` and `simplify_lock_borders`, showing how the API integrates into asset processing workflows.

## Summary

- **meshopt_simplifyWithAttributes** extends the standard meshoptimizer simplifier to preserve per-vertex attributes by incorporating them into the error metric alongside geometric positions.
- The function requires separate buffers for positions and attributes, with explicit stride values and per-component weights that control preservation priority.
- According to the implementation in [`src/simplifier.cpp`](https://github.com/zeux/meshoptimizer/blob/main/src/simplifier.cpp), the algorithm forwards to `meshopt_simplifyEdge` which builds a grid-based spatial hierarchy and computes quadrics per cell.
- Use **meshopt_SimplifySparse** for faster processing on indexed subsets and **meshopt_SimplifyErrorAbsolute** when specifying error in absolute units rather than relative to mesh extents.
- Vertex locking via **meshopt_SimplifyVertex_Lock** and protection flags allow fine-grained control over specific vertices during simplification.

## Frequently Asked Questions

### What is the difference between meshopt_simplify and meshopt_simplifyWithAttributes?

`meshopt_simplify` optimizes purely for geometric fidelity, minimizing position error during edge collapses. `meshopt_simplifyWithAttributes` extends this by accepting additional attribute buffers and weight vectors, allowing the algorithm to minimize combined geometric and attribute error. This prevents UV stretching and normal distortion that occur when simplifying purely based on position.

### How do I choose attribute weights for meshopt_simplifyWithAttributes?

Attribute weights scale the contribution of each component to the total error metric. A weight of `1.0f` treats an attribute component with equal importance to geometric position, while lower values like `0.5f` allow more distortion in that specific attribute. For critical attributes like normals that affect lighting, use higher weights (1.0f); for less critical data like secondary UV channels, use lower values.

### Can I lock specific vertices while using meshopt_simplifyWithAttributes?

Yes, by providing a non-null `vertex_lock` buffer populated with flags such as `meshopt_SimplifyVertex_Lock`, you can prevent specific vertices from being moved or removed during simplification. This is essential for preserving boundary edges or critical feature points. The flags are defined in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) lines 889-894.

### When should I use the meshopt_SimplifySparse option?

Enable `meshopt_SimplifySparse` when your input indices represent a sparse subset of the vertex buffer rather than a dense mesh. This option skips dense mesh optimization passes, significantly improving performance for LOD chain generation and partial mesh updates. According to the source in [`src/meshoptimizer.h`](https://github.com/zeux/meshoptimizer/blob/main/src/meshoptimizer.h) lines 668-682, this flag works alongside `meshopt_SimplifyErrorAbsolute` for precise error control.