Skip to content

Commit

Permalink
Merge pull request #773 from zeux/simp-prune
Browse files Browse the repository at this point in the history
simplify: Implement experimental support for component pruning
  • Loading branch information
zeux authored Sep 26, 2024
2 parents 8078703 + 2bb3b15 commit 58a0290
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 41 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ For basic customization, a number of options can be passed via `options` bitmask
- `meshopt_SimplifyLockBorder` restricts the simplifier from collapsing edges that are on the border of the mesh. This can be useful for simplifying mesh subsets independently, so that the LODs can be combined without introducing cracks.
- `meshopt_SimplifyErrorAbsolute` changes the error metric from relative to absolute both for the input error limit as well as for the resulting error. This can be used instead of `meshopt_simplifyScale`.
- `meshopt_SimplifySparse` improves simplification performance assuming input indices are a sparse subset of the mesh. This can be useful when simplifying small mesh subsets independently, and is intended to be used for meshlet simplification. For consistency, it is recommended to use absolute errors when sparse simplification is desired, as this flag changes the meaning of the relative errors.
- `meshopt_SimplifyPrune` allows the simplifier to remove isolated components regardless of the topological restrictions inside the component. This is generally recommended for full-mesh simplification as it can improve quality and reduce triangle count; note that with this option, triangles connected to locked vertices may be removed as part of their component.

While `meshopt_simplify` is aware of attribute discontinuities by default (and infers them through the supplied index buffer) and tries to preserve them, it can be useful to provide information about attribute values. This allows the simplifier to take attribute error into account which can improve shading (by using vertex normals), texture deformation (by using texture coordinates), and may be necessary to preserve vertex colors when textures are not used in the first place. This can be done by using a variant of the simplification function that takes attribute values and weight factors, `meshopt_simplifyWithAttributes`:

Expand Down
11 changes: 6 additions & 5 deletions demo/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ void packMesh(std::vector<PackedVertexOct>& pv, const std::vector<Vertex>& verti
}
}

void simplify(const Mesh& mesh, float threshold = 0.2f)
void simplify(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)
{
Mesh lod;

Expand All @@ -462,7 +462,7 @@ void simplify(const Mesh& mesh, float threshold = 0.2f)
float result_error = 0;

lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count
lod.indices.resize(meshopt_simplify(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, 0, &result_error));
lod.indices.resize(meshopt_simplify(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error, options, &result_error));

lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()
lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));
Expand All @@ -476,7 +476,7 @@ void simplify(const Mesh& mesh, float threshold = 0.2f)
(end - start) * 1000);
}

void simplifyAttr(const Mesh& mesh, float threshold = 0.2f)
void simplifyAttr(const Mesh& mesh, float threshold = 0.2f, unsigned int options = 0)
{
Mesh lod;

Expand All @@ -490,7 +490,7 @@ void simplifyAttr(const Mesh& mesh, float threshold = 0.2f)
const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};

lod.indices.resize(mesh.indices.size()); // note: simplify needs space for index_count elements in the destination array, not target_index_count
lod.indices.resize(meshopt_simplifyWithAttributes(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), &mesh.vertices[0].nx, sizeof(Vertex), attr_weights, 3, NULL, target_index_count, target_error, 0, &result_error));
lod.indices.resize(meshopt_simplifyWithAttributes(&lod.indices[0], &mesh.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), &mesh.vertices[0].nx, sizeof(Vertex), attr_weights, 3, NULL, target_index_count, target_error, options, &result_error));

lod.vertices.resize(lod.indices.size() < mesh.vertices.size() ? lod.indices.size() : mesh.vertices.size()); // note: this is just to reduce the cost of resize()
lod.vertices.resize(meshopt_optimizeVertexFetch(&lod.vertices[0], &lod.indices[0], lod.indices.size(), &mesh.vertices[0], mesh.vertices.size(), sizeof(Vertex)));
Expand Down Expand Up @@ -1372,6 +1372,7 @@ void process(const char* path)
encodeVertex<PackedVertexOct>(copy, "O");

simplify(mesh);
simplify(mesh, 0.1f, meshopt_SimplifyPrune);
simplifyAttr(mesh);
simplifySloppy(mesh);
simplifyComplete(mesh);
Expand All @@ -1391,7 +1392,7 @@ void processDev(const char* path)
if (!loadMesh(mesh, path))
return;

simplifyAttr(mesh);
simplifyAttr(mesh, 0.1f, meshopt_SimplifyPrune);
}

void processNanite(const char* path)
Expand Down
5 changes: 5 additions & 0 deletions demo/simplify.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
ratio: 1.0,
debugOverlay: false,
lockBorder: false,
prune: false,
weldVertices: false,
useAttributes: false,
errorThresholdLog10: 1,
Expand Down Expand Up @@ -113,6 +114,7 @@
var guiSimplify = gui.addFolder('Simplify');
guiSimplify.add(settings, 'ratio', 0, 1, 0.01).onChange(simplify);
guiSimplify.add(settings, 'lockBorder').onChange(simplify);
guiSimplify.add(settings, 'prune').onChange(simplify);
guiSimplify.add(settings, 'weldVertices').onChange(simplify);
guiSimplify.add(settings, 'errorThresholdLog10', 0, 3, 0.1).onChange(simplify);
guiSimplify.add(settings, 'useAttributes').onChange(simplify).onFinishChange(updateSettings);
Expand Down Expand Up @@ -199,6 +201,9 @@
if (settings.lockBorder) {
flags.push('LockBorder');
}
if (settings.prune) {
flags.push('Prune');
}

var stride = geo.attributes.position instanceof THREE.InterleavedBufferAttribute ? geo.attributes.position.data.stride : 3;

Expand Down
154 changes: 124 additions & 30 deletions demo/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -909,39 +909,19 @@ static void simplify()
// 1 2
// 3 4 5
unsigned int ib[] = {
0,
2,
1,
1,
2,
3,
3,
2,
4,
2,
5,
4,
0, 2, 1,
1, 2, 3,
3, 2, 4,
2, 5, 4, // clang-format :-/
};

float vb[] = {
0,
4,
0,
0,
1,
0,
2,
2,
0,
0,
0,
0,
1,
0,
0,
4,
0,
0,
0, 4, 0,
0, 1, 0,
2, 2, 0,
0, 0, 0,
1, 0, 0,
4, 0, 0, // clang-format :-/
};

unsigned int expected[] = {
Expand Down Expand Up @@ -1431,6 +1411,117 @@ static void simplifySeamFake()
assert(meshopt_simplify(ib, ib, 6, vb, 4, 16, 0, 1.f, 0, NULL) == 6);
}

static void simplifyDebug()
{
// 0
// 1 2
// 3 4 5
unsigned int ib[] = {
0, 2, 1,
1, 2, 3,
3, 2, 4,
2, 5, 4, // clang-format :-/
};

float vb[] = {
0, 4, 0,
0, 1, 0,
2, 2, 0,
0, 0, 0,
1, 0, 0,
4, 0, 0, // clang-format :-/
};

unsigned int expected[] = {
0 | (9u << 28),
5 | (9u << 28),
3 | (9u << 28),
};

const unsigned int meshopt_SimplifyInternalDebug = 1 << 30;

float error;
assert(meshopt_simplify(ib, ib, 12, vb, 6, 12, 3, 1e-2f, meshopt_SimplifyInternalDebug, &error) == 3);
assert(error == 0.f);
assert(memcmp(ib, expected, sizeof(expected)) == 0);
}

static void simplifyPrune()
{
// 0
// 1 2
// 3 4 5
// +
// 6 7 8 (same position)
unsigned int ib[] = {
0, 2, 1,
1, 2, 3,
3, 2, 4,
2, 5, 4,
6, 7, 8, // clang-format :-/
};

float vb[] = {
0, 4, 0,
0, 1, 0,
2, 2, 0,
0, 0, 0,
1, 0, 0,
4, 0, 0,
1, 1, 1,
1, 1, 1,
1, 1, 1, // clang-format :-/
};

unsigned int expected[] = {
0,
5,
3,
};

float error;
assert(meshopt_simplify(ib, ib, 15, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3);
assert(error == 0.f);
assert(memcmp(ib, expected, sizeof(expected)) == 0);

// re-run prune with and without sparsity on a small subset to make sure the component code correctly handles sparse subsets
assert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune, &error) == 3);
assert(meshopt_simplify(ib, ib, 3, vb, 9, 12, 3, 1e-2f, meshopt_SimplifyPrune | meshopt_SimplifySparse, &error) == 3);
assert(memcmp(ib, expected, sizeof(expected)) == 0);
}

static void simplifyPruneCleanup()
{
unsigned int ib[] = {
0, 1, 2,
3, 4, 5,
6, 7, 8, // clang-format :-/
};

float vb[] = {
0, 0, 0,
0, 1, 0,
1, 0, 0,
0, 0, 1,
0, 2, 1,
2, 0, 1,
0, 0, 2,
0, 4, 2,
4, 0, 2, // clang-format :-/
};

unsigned int expected[] = {
6,
7,
8,
};

float error;
assert(meshopt_simplify(ib, ib, 9, vb, 9, 12, 3, 1.f, meshopt_SimplifyLockBorder | meshopt_SimplifyPrune, &error) == 3);
assert(fabsf(error - 0.37f) < 0.01f);
assert(memcmp(ib, expected, sizeof(expected)) == 0);
}

static void adjacency()
{
// 0 1/4
Expand Down Expand Up @@ -1687,6 +1778,9 @@ void runTests()
simplifyErrorAbsolute();
simplifySeam();
simplifySeamFake();
simplifyDebug();
simplifyPrune();
simplifyPruneCleanup();

adjacency();
tessellation();
Expand Down
4 changes: 4 additions & 0 deletions gltf/mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,9 +625,13 @@ static void simplifyMesh(Mesh& mesh, float threshold, float error, bool attribut
size_t target_index_count = size_t(double(mesh.indices.size() / 3) * threshold) * 3;
float target_error = error;
float target_error_aggressive = 1e-1f;

unsigned int options = 0;
if (lock_borders)
options |= meshopt_SimplifyLockBorder;
else
options |= meshopt_SimplifyPrune;

if (debug)
options |= meshopt_SimplifyInternalDebug;

Expand Down
6 changes: 4 additions & 2 deletions js/meshopt_simplifier.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/meshopt_simplifier.module.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is part of meshoptimizer library and is distributed under the terms of MIT License.
// Copyright (C) 2016-2024, by Arseny Kapoulkine ([email protected])
export type Flags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute';
export type Flags = 'LockBorder' | 'Sparse' | 'ErrorAbsolute' | 'Prune';

export const MeshoptSimplifier: {
supported: boolean;
Expand Down
6 changes: 4 additions & 2 deletions js/meshopt_simplifier.module.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/meshoptimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ enum
meshopt_SimplifySparse = 1 << 1,
/* Treat error limit and resulting error as absolute instead of relative to mesh extents. */
meshopt_SimplifyErrorAbsolute = 1 << 2,
/* Experimental: remove disconnected parts of the mesh during simplification incrementally, regardless of the topological restrictions inside components. */
meshopt_SimplifyPrune = 1 << 3,
};

/**
Expand Down
Loading

0 comments on commit 58a0290

Please sign in to comment.