Repaired meshopt integration, it's preferable to instead optimize on meshlets instead of the raw mesh
This commit is contained in:
parent
5c8edbbcdc
commit
a521f045ba
@ -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 }
|
||||
],
|
||||
|
@ -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 ] } },
|
||||
|
@ -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"
|
||||
}
|
@ -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": {
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Engine Docs
|
||||
|
||||
## Features
|
||||
|
||||
To be filled.
|
||||
|
||||
## Notices and Citations
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user