How to Use meshopt_simplifyWithAttributes for Mesh Simplification with Attribute Preservation
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 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 (lines 535-537) defines the complete interface:
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_SimplifyErrorAbsoluteis 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 (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_erroras 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 (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 (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:
#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(lines 68-74) demonstrates usage within cluster-based LOD generation.gltf/gltfpack.himplements attribute-aware simplification via flags likesimplify_attributesandsimplify_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, the algorithm forwards tomeshopt_simplifyEdgewhich 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 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 lines 668-682, this flag works alongside meshopt_SimplifyErrorAbsolute for precise error control.
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 →