Repaired meshopt integration, it's preferable to instead optimize on meshlets instead of the raw mesh

This commit is contained in:
mrq 2024-12-03 15:46:41 -06:00 committed by ecker
parent 5c8edbbcdc
commit a521f045ba
10 changed files with 124 additions and 23 deletions

View File

@ -1,10 +1,10 @@
{
"import": "./base_sourceengine.json",
"assets": [
// { "filename": "./models/animal_crossing.glb" },
{ "filename": "./models/animal_crossing.glb" },
// { "filename": "./models/animal_crossing/graph.json" },
// { "filename": "./models/animal_crossing_small.glb" },
{ "filename": "./models/animal_crossing_small/graph.json" },
// { "filename": "./models/animal_crossing_small/graph.json" },
{ "filename": "/craeture.json", "delay": 2.0 }
],

View File

@ -2,7 +2,7 @@
"import": "/model.json",
"metadata": {
"graph": {
// "renderer": { "separate": true },
"renderer": { "separate": false },
"exporter": {
"optimize": "tagged"
},
@ -11,13 +11,13 @@
// exact matches
"worldspawn": {
"physics": { "type": "mesh", "static": true },
"grid": { "size": [3,1,3], "epsilon": 0.001, "cleanup": true, "print": true },
// "optimize mesh": { "simplify": 0 },
"grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true },
"optimize meshlets": { "simplify": 0.125, "print": false },
"unwrap mesh": true
},
"worldspawn_skybox": {
"grid": { "size": [3,1,3], "epsilon": 0.001, "cleanup": true, "print": true },
// "optimize mesh": { "simplify": 0 },
"grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true },
"optimize meshlets": { "simplify": 0.125, "print": false },
"unwrap mesh": true
},
"info_player_spawn": { "action": "attach", "filename": "./player.json", "transform": { "orientation": [ 0, 1, 0, 0 ] } },

View File

@ -2,7 +2,7 @@
// "import": "./rp_downtown_v2.json"
// "import": "./ss2_medsci1.json"
// "import": "./sh2_mcdonalds.json"
// "import": "./animal_crossing.json"
"import": "./mds_mcdonalds.json"
"import": "./animal_crossing.json"
// "import": "./mds_mcdonalds.json"
// "import": "./gm_construct.json"
}

View File

@ -1,8 +1,8 @@
{
"import": "./base_sourceengine.json",
"assets": [
// { "filename": "./models/ss2_medsci1.glb" }
{ "filename": "./models/ss2_medsci1/graph.json" }
{ "filename": "./models/ss2_medsci1_small.glb" }
// { "filename": "./models/ss2_medsci1_small/graph.json" }
],
"metadata": {
"graph": {

View File

@ -1,5 +1,7 @@
# Engine Docs
## Features
To be filled.
## Notices and Citations

View File

@ -2,10 +2,56 @@
#include <uf/config.h>
#include <uf/utils/mesh/mesh.h>
#include <uf/ext/meshopt/meshopt.h>
#if UF_USE_MESHOPT
#include <meshoptimizer.h>
#endif
namespace ext {
namespace meshopt {
bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX );
bool UF_API optimize( uf::Mesh&, float simplify = 1.0f, size_t = SIZE_MAX, bool verbose = false );
template<typename T, typename U = uint32_t>
bool optimize( uf::Meshlet_T<T,U>& meshlet, float simplify = 1.0f, size_t o = SIZE_MAX, bool verbose = false ) {
#if UF_USE_MESHOPT
size_t indicesCount = meshlet.indices.size();
uf::stl::vector<U> remap(indicesCount);
size_t verticesCount = meshopt_generateVertexRemap(&remap[0], &meshlet.indices[0], indicesCount, &meshlet.vertices[0], meshlet.vertices.size(), sizeof(T));
uf::stl::vector<T> vertices(verticesCount);
uf::stl::vector<U> indices(indicesCount);
meshopt_remapIndexBuffer(&indices[0], &meshlet.indices[0], indicesCount, &remap[0]);
meshopt_remapVertexBuffer(&vertices[0], &meshlet.vertices[0], meshlet.vertices.size(), sizeof(T), &remap[0]);
//
meshopt_optimizeVertexCache(&indices[0], &indices[0], indicesCount, verticesCount);
//
meshopt_optimizeOverdraw(&indices[0], &indices[0], indicesCount, &vertices[0].position.x, verticesCount, sizeof(T), 1.05f);
//
meshopt_optimizeVertexFetch(&vertices[0], &indices[0], indicesCount, &vertices[0], verticesCount, sizeof(T));
// almost always causes ID discontinuities
if ( 0.0f < simplify && simplify < 1.0f ) {
uf::stl::vector<U> indicesSimplified(indicesCount);
size_t targetIndices = indicesCount * simplify;
float targetError = 1e-2f / simplify;
float realError = 0.0f;
size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetError, realError);
// size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetIndices);
if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError);
indicesCount = realIndices;
indices.swap( indicesSimplified );
}
meshlet.primitive.drawCommand.indices = indicesCount;
meshlet.primitive.drawCommand.vertices = verticesCount;
#endif
return true;
}
}
}

View File

@ -84,6 +84,7 @@ namespace uf {
uf::meshgrid::calculate( grid, eps );
// it's better to naively clip the mesh multiple times rather than calculate the triangles needed to clip
/*
if ( clip ) {
for ( auto& pair : grid.nodes ) {
++atlasID;
@ -129,6 +130,7 @@ namespace uf {
return partitioned;
}
*/
for ( auto& meshlet : meshlets ) uf::meshgrid::partition<T,U>( grid, meshlet.vertices, meshlet.indices, meshlet.primitive );
if ( cleanup ) uf::meshgrid::cleanup( grid );

View File

@ -281,6 +281,12 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize
bool cleanup = true;
} meshgrid;
struct {
bool should = false;
bool print = false;
size_t level = SIZE_MAX;
float simplify = 1.0f;
} meshopt;
ext::json::forEach( graph.metadata["tags"], [&]( const uf::stl::string& key, ext::json::Value& value ) {
if ( !ext::json::isObject( value["grid"] ) ) return; // no tag["grid"] defined
@ -291,6 +297,26 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize
meshgrid.metadata = value["grid"];
});
#if UF_USE_MESHOPT
// cleanup if blender's exporter is poopy
if ( graph.metadata["exporter"]["optimize"].as<bool>(false) || graph.metadata["exporter"]["optimize"].as<uf::stl::string>("") == "tagged" ) {
if ( graph.metadata["exporter"]["optimize"].as<uf::stl::string>("") == "tagged" ) {
ext::json::forEach( graph.metadata["tags"], [&]( const uf::stl::string& key, ext::json::Value& value ) {
if ( ext::json::isNull( value["optimize meshlets"] ) ) return;
if ( uf::string::isRegex( key ) ) {
if ( !uf::string::matched( keyName, key ) ) return;
} else if ( keyName != key ) return;
meshopt.should = true;
if ( ext::json::isObject( value["optimize meshlets"] ) ) {
meshopt.level = value["optimize meshlets"]["level"].as(meshopt.level);
meshopt.simplify = value["optimize meshlets"]["simplify"].as(meshopt.simplify);
meshopt.print = value["optimize meshlets"]["print"].as(meshopt.print);
}
});
}
}
#endif
if ( ext::json::isObject( meshgrid.metadata ) ) {
if ( meshgrid.metadata["size"].is<size_t>() ) {
size_t d = meshgrid.metadata["size"].as<size_t>();
@ -497,6 +523,7 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize
for ( auto& keyName : graph.meshes ) {
size_t level = SIZE_MAX;
float simplify = 1.0f;
bool print = false;
if ( graph.metadata["exporter"]["optimize"].as<uf::stl::string>("") == "tagged" ) {
bool should = false;
@ -510,6 +537,7 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize
if ( ext::json::isObject( value["optimize mesh"] ) ) {
level = value["optimize mesh"]["level"].as(level);
simplify = value["optimize mesh"]["simplify"].as(simplify);
print = value["optimize mesh"]["print"].as(print);
}
});
@ -518,7 +546,7 @@ pod::Graph ext::gltf::load( const uf::stl::string& filename, const uf::Serialize
auto& mesh = /*graph.storage*/storage.meshes[keyName];
UF_MSG_DEBUG("Optimizing mesh at level {}: {}", level, keyName);
if ( !ext::meshopt::optimize( mesh, simplify, level ) ) {
if ( !ext::meshopt::optimize( mesh, simplify, level, print ) ) {
UF_MSG_ERROR("Mesh optimization failed: {}", keyName );
}
}

View File

@ -360,6 +360,15 @@ if ( meshgrid.grid.divisions.x > 1 || meshgrid.grid.divisions.y > 1 || meshgrid.
meshlets = std::move( partitioned );
}
// optimize each meshlet if requested
if ( meshopt.should ) {
for ( auto& meshlet : meshlets ) {
if ( !ext::meshopt::optimize( meshlet, meshopt.simplify, meshopt.level, meshopt.print ) ) {
UF_MSG_ERROR("Mesh optimization failed: {}", keyName );
}
}
}
{
size_t indexID = 0;
size_t vertexID = 0;

View File

@ -2,9 +2,9 @@
#if UF_USE_MESHOPT
#include <meshoptimizer.h>
bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) {
bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verbose ) {
if ( mesh.isInterleaved() ) {
UF_MSG_ERROR("optimization of interleaved meshes is currently not supported");
UF_MSG_ERROR("optimization of interleaved meshes is currently not supported. Consider optimizing on meshlets.");
return false;
}
mesh.updateDescriptor();
@ -26,14 +26,28 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) {
stream.stride = p.attribute.stride;
}
size_t indicesCount = mesh.vertex.count;
bool hasIndices = mesh.index.count > 0;
size_t indicesCount = hasIndices ? mesh.index.count : mesh.vertex.count;
uf::stl::vector<uint32_t> remap(indicesCount);
size_t verticesCount = meshopt_generateVertexRemapMulti( &remap[0], NULL, indicesCount, indicesCount, &streams[0], streams.size() );
uint32_t* sourceIndicesPointer = NULL;
uf::stl::vector<uint32_t> sourceIndices(indicesCount);
if ( hasIndices ) {
uint8_t* pointer = (uint8_t*) mesh.getBuffer(mesh.index).data();
for ( auto index = 0; index < indicesCount; ++index ) {
switch ( mesh.index.size ) {
case 1: sourceIndices[index] = (( uint8_t*) pointer)[index]; break;
case 2: sourceIndices[index] = ((uint16_t*) pointer)[index]; break;
case 4: sourceIndices[index] = ((uint32_t*) pointer)[index]; break;
}
}
}
size_t verticesCount = meshopt_generateVertexRemapMulti( &remap[0], sourceIndicesPointer, indicesCount, indicesCount, &streams[0], streams.size() );
// generate new indices, as they're going to be specific to a region of vertices due to drawcommand shittery
uf::stl::vector<uint32_t> indices(indicesCount);
meshopt_remapIndexBuffer(&indices[0], NULL, indicesCount, &remap[0]);
meshopt_remapIndexBuffer(&indices[0], sourceIndicesPointer, indicesCount, &remap[0]);
//
for ( auto& p : attributes ) {
@ -64,10 +78,10 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) {
float targetError = 1e-2f / simplify;
float realError = 0.0f;
// size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetError, realError);
size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetIndices);
size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetError, realError);
// size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, (float*) positionAttribute.pointer, verticesCount, positionAttribute.stride, targetIndices);
UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError);
if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError);
indicesCount = realIndices;
indices.swap( indicesSimplified );
@ -106,7 +120,7 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) {
} else if ( lastID + 1 == id.x ) {
lastID = id.x;
} else {
UF_MSG_DEBUG("Discontinuity detected: {} | {} | {} | {}", index, vertex, lastID, id.x);
if ( verbose ) UF_MSG_DEBUG("Discontinuity detected: {} | {} | {} | {}", index, vertex, lastID, id.x);
discontinuityDetected = true;
lastID = id.x;
}