Skip to content

Commit b57556d

Browse files
committed
demo: Adjust simplifyClusters to use partitioning
Instead of generating large clusters, generate small clusters and partition them using meshopt_partitionClusters; the resulting groups are simplified as before, preserving the group boundary. This is closer to what hierarchical simplification workflows need and as such is a better, but still simple, example.
1 parent aeb470f commit b57556d

File tree

1 file changed

+70
-17
lines changed

1 file changed

+70
-17
lines changed

demo/main.cpp

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -657,12 +657,13 @@ void simplifyComplete(const Mesh& mesh)
657657

658658
void simplifyClusters(const Mesh& mesh, float threshold = 0.2f)
659659
{
660-
// note: we use clusters that are larger than normal to give simplifier room to work; in practice you'd use cluster groups merged from smaller clusters and build a cluster DAG
661-
const size_t max_vertices = 255;
662-
const size_t max_triangles = 512;
660+
const size_t max_vertices = 64;
661+
const size_t max_triangles = 64;
662+
const size_t target_group_size = 8;
663663

664664
double start = timestamp();
665665

666+
// build clusters (meshlets) out of the mesh
666667
size_t max_meshlets = meshopt_buildMeshletsBound(mesh.indices.size(), max_vertices, max_triangles);
667668
std::vector<meshopt_Meshlet> meshlets(max_meshlets);
668669
std::vector<unsigned int> meshlet_vertices(max_meshlets * max_vertices);
@@ -672,6 +673,44 @@ void simplifyClusters(const Mesh& mesh, float threshold = 0.2f)
672673

673674
double middle = timestamp();
674675

676+
// partition clusters in groups; each group will be simplified separately and the boundaries between groups will be preserved
677+
std::vector<unsigned int> cluster_indices;
678+
cluster_indices.reserve(mesh.indices.size()); // slight underestimate, vector should realloc once
679+
std::vector<unsigned int> cluster_sizes(meshlets.size());
680+
681+
for (size_t i = 0; i < meshlets.size(); ++i)
682+
{
683+
const meshopt_Meshlet& m = meshlets[i];
684+
685+
for (size_t j = 0; j < m.triangle_count * 3; ++j)
686+
cluster_indices.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]]);
687+
688+
cluster_sizes[i] = m.triangle_count * 3;
689+
}
690+
691+
// makes sure clusters are partitioned using position-only adjacency
692+
meshopt_generateShadowIndexBuffer(&cluster_indices[0], &cluster_indices[0], cluster_indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(float) * 3, sizeof(Vertex));
693+
694+
std::vector<unsigned int> partition(meshlets.size());
695+
size_t partition_count = meshopt_partitionClusters(&partition[0], &cluster_indices[0], cluster_indices.size(), &cluster_sizes[0], cluster_sizes.size(), mesh.vertices.size(), target_group_size);
696+
697+
// convert partitions to linked lists to make it easier to iterate over (vectors of vectors would work too)
698+
std::vector<int> partnext(meshlets.size(), -1);
699+
std::vector<int> partlast(partition_count, -1);
700+
701+
for (size_t i = 0; i < meshlets.size(); ++i)
702+
{
703+
unsigned int part = partition[i];
704+
705+
if (partlast[part] >= 0)
706+
partnext[partlast[part]] = int(i);
707+
708+
partlast[part] = int(i);
709+
partnext[i] = -1;
710+
}
711+
712+
double parttime = timestamp();
713+
675714
float scale = meshopt_simplifyScale(&mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex));
676715

677716
std::vector<unsigned int> lod;
@@ -681,33 +720,47 @@ void simplifyClusters(const Mesh& mesh, float threshold = 0.2f)
681720

682721
for (size_t i = 0; i < meshlets.size(); ++i)
683722
{
684-
const meshopt_Meshlet& m = meshlets[i];
723+
if (partlast[partition[i]] < 0)
724+
continue; // part of a group that was already processed
685725

686-
size_t cluster_offset = lod.size();
726+
// mark group as processed
727+
partlast[partition[i]] = -1;
687728

688-
for (size_t j = 0; j < m.triangle_count * 3; ++j)
689-
lod.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + j]]);
729+
size_t group_offset = lod.size();
730+
731+
for (int j = int(i); j >= 0; j = partnext[j])
732+
{
733+
const meshopt_Meshlet& m = meshlets[j];
734+
735+
for (size_t k = 0; k < m.triangle_count * 3; ++k)
736+
lod.push_back(meshlet_vertices[m.vertex_offset + meshlet_triangles[m.triangle_offset + k]]);
737+
}
738+
739+
size_t group_triangles = (lod.size() - group_offset) / 3;
690740

741+
// simplify the group, preserving the border vertices
742+
// note: this technically also locks the exterior border; a full mesh analysis (see nanite.cpp / lockBoundary) would work better for some meshes
691743
unsigned int options = meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute;
692744

693-
float cluster_target_error = 1e-2f * scale;
694-
size_t cluster_target = size_t(float(m.triangle_count) * threshold) * 3;
695-
float cluster_error = 0.f;
696-
size_t cluster_size = meshopt_simplify(&lod[cluster_offset], &lod[cluster_offset], m.triangle_count * 3, &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), cluster_target, cluster_target_error, options, &cluster_error);
745+
float group_target_error = 1e-2f * scale;
746+
size_t group_target = size_t(float(group_triangles) * threshold) * 3;
747+
float group_error = 0.f;
748+
size_t group_size = meshopt_simplify(&lod[group_offset], &lod[group_offset], group_triangles * 3, &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), group_target, group_target_error, options, &group_error);
697749

698-
error = cluster_error > error ? cluster_error : error;
750+
error = group_error > error ? group_error : error;
699751

700-
// simplified cluster is available in lod[cluster_offset..cluster_offset + cluster_size]
701-
lod.resize(cluster_offset + cluster_size);
752+
// simplified group is available in lod[group_offset..group_offset + group_size]
753+
lod.resize(group_offset + group_size);
702754
}
703755

704756
double end = timestamp();
705757

706-
printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec, clusterized in %.2f msec\n",
758+
printf("%-9s: %d triangles => %d triangles (%.2f%% deviation) in %.2f msec, clusterized in %.2f msec, partitioned in %.2f msec (%d clusters in %d groups)\n",
707759
"SimplifyN", // N for Nanite
708760
int(mesh.indices.size() / 3), int(lod.size() / 3),
709761
error / scale * 100,
710-
(end - middle) * 1000, (middle - start) * 1000);
762+
(end - parttime) * 1000, (middle - start) * 1000, (parttime - middle) * 1000,
763+
int(meshlets.size()), int(partition_count));
711764
}
712765

713766
void optimize(const Mesh& mesh, const char* name, void (*optf)(Mesh& mesh))
@@ -1402,7 +1455,7 @@ void processDev(const char* path)
14021455
if (!loadMesh(mesh, path))
14031456
return;
14041457

1405-
meshlets(mesh);
1458+
simplifyClusters(mesh, 0.2f);
14061459
}
14071460

14081461
void processNanite(const char* path)

0 commit comments

Comments
 (0)