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", "import": "./base_sourceengine.json",
"assets": [ "assets": [
// { "filename": "./models/animal_crossing.glb" }, { "filename": "./models/animal_crossing.glb" },
// { "filename": "./models/animal_crossing/graph.json" }, // { "filename": "./models/animal_crossing/graph.json" },
// { "filename": "./models/animal_crossing_small.glb" }, // { "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 } { "filename": "/craeture.json", "delay": 2.0 }
], ],

View File

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

View File

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

View File

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

View File

@ -2,10 +2,56 @@
#include <uf/config.h> #include <uf/config.h>
#include <uf/utils/mesh/mesh.h> #include <uf/utils/mesh/mesh.h>
#include <uf/ext/meshopt/meshopt.h>
#if UF_USE_MESHOPT
#include <meshoptimizer.h>
#endif
namespace ext { namespace ext {
namespace meshopt { 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 ); uf::meshgrid::calculate( grid, eps );
// it's better to naively clip the mesh multiple times rather than calculate the triangles needed to clip // it's better to naively clip the mesh multiple times rather than calculate the triangles needed to clip
/*
if ( clip ) { if ( clip ) {
for ( auto& pair : grid.nodes ) { for ( auto& pair : grid.nodes ) {
++atlasID; ++atlasID;
@ -129,6 +130,7 @@ namespace uf {
return partitioned; return partitioned;
} }
*/
for ( auto& meshlet : meshlets ) uf::meshgrid::partition<T,U>( grid, meshlet.vertices, meshlet.indices, meshlet.primitive ); for ( auto& meshlet : meshlets ) uf::meshgrid::partition<T,U>( grid, meshlet.vertices, meshlet.indices, meshlet.primitive );
if ( cleanup ) uf::meshgrid::cleanup( grid ); 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; bool cleanup = true;
} meshgrid; } 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 ) { 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 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"]; 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 ( ext::json::isObject( meshgrid.metadata ) ) {
if ( meshgrid.metadata["size"].is<size_t>() ) { if ( meshgrid.metadata["size"].is<size_t>() ) {
size_t d = meshgrid.metadata["size"].as<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 ) { for ( auto& keyName : graph.meshes ) {
size_t level = SIZE_MAX; size_t level = SIZE_MAX;
float simplify = 1.0f; float simplify = 1.0f;
bool print = false;
if ( graph.metadata["exporter"]["optimize"].as<uf::stl::string>("") == "tagged" ) { if ( graph.metadata["exporter"]["optimize"].as<uf::stl::string>("") == "tagged" ) {
bool should = false; 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"] ) ) { if ( ext::json::isObject( value["optimize mesh"] ) ) {
level = value["optimize mesh"]["level"].as(level); level = value["optimize mesh"]["level"].as(level);
simplify = value["optimize mesh"]["simplify"].as(simplify); 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]; auto& mesh = /*graph.storage*/storage.meshes[keyName];
UF_MSG_DEBUG("Optimizing mesh at level {}: {}", level, 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 ); 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 ); 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 indexID = 0;
size_t vertexID = 0; size_t vertexID = 0;

View File

@ -2,9 +2,9 @@
#if UF_USE_MESHOPT #if UF_USE_MESHOPT
#include <meshoptimizer.h> #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() ) { 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; return false;
} }
mesh.updateDescriptor(); mesh.updateDescriptor();
@ -26,14 +26,28 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o ) {
stream.stride = p.attribute.stride; 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); 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 // 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); 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 ) { 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 targetError = 1e-2f / simplify;
float realError = 0.0f; 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_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_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; indicesCount = realIndices;
indices.swap( indicesSimplified ); 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 ) { } else if ( lastID + 1 == id.x ) {
lastID = id.x; lastID = id.x;
} else { } 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; discontinuityDetected = true;
lastID = id.x; lastID = id.x;
} }