Skip to content

Commit

Permalink
Updated meshoptimizer
Browse files Browse the repository at this point in the history
  • Loading branch information
fo76utils committed Aug 18, 2024
1 parent 7d99d95 commit f9ff6fd
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 50 deletions.
54 changes: 36 additions & 18 deletions lib/meshoptimizer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

When a GPU renders triangle meshes, various stages of the GPU pipeline have to process vertex and index data. The efficiency of these stages depends on the data you feed to them; this library provides algorithms to help optimize meshes for these stages, as well as algorithms to reduce the mesh complexity and storage overhead.

The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt).
The library provides a C and C++ interface for all algorithms; you can use it from C/C++ or from other languages via FFI (such as P/Invoke). If you want to use this library from Rust, you should use [meshopt crate](https://crates.io/crates/meshopt). JavaScript interface for some algorithms is available through [meshoptimizer.js](https://www.npmjs.com/package/meshoptimizer).

[gltfpack](gltf), which is a tool that can automatically optimize glTF files, is developed and distributed alongside the library.
[gltfpack](./gltf/README.md), which is a tool that can automatically optimize glTF files, is developed and distributed alongside the library.

## Installing

Expand All @@ -18,15 +18,9 @@ git clone -b v0.21 https://github.com/zeux/meshoptimizer.git

Alternatively you can [download the .zip archive from GitHub](https://github.com/zeux/meshoptimizer/archive/v0.21.zip).

The library is also available as a package ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer), [Vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer)).
The library is also available as a Linux package in several distributions ([ArchLinux](https://aur.archlinux.org/packages/meshoptimizer/), [Debian](https://packages.debian.org/libmeshoptimizer), [FreeBSD](https://www.freshports.org/misc/meshoptimizer/), [Nix](https://mynixos.com/nixpkgs/package/meshoptimizer), [Ubuntu](https://packages.ubuntu.com/libmeshoptimizer)), as well as a [Vcpkg port](https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer) (see [installation instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started)) and a [Conan package](https://conan.io/center/recipes/meshoptimizer?version=0.21).

### Installing gltfpack

`gltfpack` is a CLI tool for optimizing meshes using meshoptimizer.

You can download a pre-built binary for gltfpack on [Releases page](https://github.com/zeux/meshoptimizer/releases), or install [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended over npm since they can work with larger files, run faster, and support texture compression.

[Learn more about gltfpack](./gltf/README.md)
[gltfpack](./gltf/README.md) is available as a pre-built binary on [Releases page](https://github.com/zeux/meshoptimizer/releases) or via [npm package](https://www.npmjs.com/package/gltfpack). Native binaries are recommended since they are more efficient and support texture compression.

## Building

Expand All @@ -35,11 +29,7 @@ meshoptimizer is distributed as a set of C++ source files. To include it into yo
* Use CMake to build the library (either as a standalone project or as part of your project)
* Add source files to your project's build system

The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the files for the algorithms you use.

## Installing from vcpkg

The meshoptimizer port in [vcpkg](https://github.com/Microsoft/vcpkg) is kept up to date by Microsoft team members and community contributors. You can download and install meshoptimizer using the vcpkg dependency manager, [Getting Started](https://github.com/microsoft/vcpkg#getting-started).
The source files are organized in such a way that you don't need to change your build-system settings, and you only need to add the source files for the algorithms you use. They should build without warnings or special compilation options on all major compilers.

## Pipeline

Expand Down Expand Up @@ -255,18 +245,17 @@ All algorithms presented so far don't affect visual appearance at all, with the
This library provides two simplification algorithms that reduce the number of triangles in the mesh. Given a vertex and an index buffer, they generate a second index buffer that uses existing vertices in the vertex buffer. This index buffer can be used directly for rendering with the original vertex buffer (preferably after vertex cache optimization), or a new compact vertex/index buffer can be generated using `meshopt_optimizeVertexFetch` that uses the optimal number and order of vertices.
The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be possible to preprocess the index buffer (e.g. with `meshopt_generateShadowIndexBuffer`) to discard any vertex attributes that aren't critical and can be rebuilt later.
The first simplification algorithm, `meshopt_simplify`, follows the topology of the original mesh in an attempt to preserve attribute seams, borders and overall appearance. For meshes with inconsistent topology or many seams, such as faceted meshes, it can result in simplifier getting "stuck" and not being able to simplify the mesh fully. Therefore it's critical that identical vertices are "welded" together, that is, the input vertex buffer does not contain duplicates. Additionally, it may be possible to preprocess the index buffer (e.g. with `meshopt_generateShadowIndexBuffer`) to weld the vertices without taking into account vertex attributes that aren't critical and can be rebuilt later.
```c++
float threshold = 0.2f;
size_t target_index_count = size_t(index_count * threshold);
float target_error = 1e-2f;
unsigned int options = 0; // meshopt_SimplifyX flags, 0 is a safe default
std::vector<unsigned int> lod(index_count);
float lod_error = 0.f;
lod.resize(meshopt_simplify(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),
target_index_count, target_error, options, &lod_error));
target_index_count, target_error, /* options= */ 0, &lod_error));
```

Target error is an approximate measure of the deviation from the original mesh using distance normalized to `[0..1]` range (e.g. `1e-2f` means that simplifier will try to maintain the error to be below 1% of the mesh extents). Note that the simplifier attempts to produce the requested number of indices at minimal error, but because of topological restrictions and error limit it is not guaranteed to reach the target index count and can stop earlier.
Expand All @@ -290,6 +279,35 @@ When a sequence of LOD meshes is generated that all use the original vertex buff
Both algorithms can also return the resulting normalized deviation that can be used to choose the correct level of detail based on screen size or solid angle; the error can be converted to world space by multiplying by the scaling factor returned by `meshopt_simplifyScale`.
## Advanced simplification
The main simplification algorithm, `meshopt_simplify`, exposes additional options and functions that can be used to control the simplification process in more detail.
For basic customization, a number of options can be passed via `options` bitmask that adjust the behavior of the simplifier:
- `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.
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`:
```c++
const float nrm_weight = 0.5f;
const float attr_weights[3] = {nrm_weight, nrm_weight, nrm_weight};
std::vector<unsigned int> lod(index_count);
float lod_error = 0.f;
lod.resize(meshopt_simplifyWithAttributes(&lod[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex),
&vertices[0].nx, sizeof(Vertex), attr_weights, 3, /* vertex_lock= */ NULL,
target_index_count, target_error, /* options= */ 0, &lod_error));
```

The attributes are passed as a separate buffer (in the example above it's a subset of the same vertex buffer) and should be stored as consecutive floats; attribute weights are used to control the importance of each attribute in the simplification process.

When using `meshopt_simplifyWithAttributes`, it is also possible to lock certain vertices by providing a `vertex_lock` array that contains a boolean value for each vertex in the mesh. This can be useful to preserve certain vertices, such as the boundary of the mesh, with more control than `meshopt_SimplifyLockBorder` option provides.

Simplification currently assumes that the input mesh is using the same material for all triangles. If the mesh uses multiple materials, it is possible to split the mesh into subsets based on the material and simplify each subset independently, using `meshopt_SimplifyLockBorder` or `vertex_lock` to preserve material boundaries; however, this limits the collapses and as a result may reduce the resulting quality. An alternative approach is to encode information about the material into the vertex buffer, ensuring that all three vertices referencing the same triangle have the same material ID; this may require duplicating vertices on the boundary between materials. After this, simplification can be performed as usual, and after simplification per-triangle material information can be computed from the vertex material IDs. There is no need to inform the simplifier of the value of the material ID: the implicit boundaries created by duplicating vertices with conflicting material IDs will be preserved automatically.

## Mesh shading

Modern GPUs are beginning to deviate from the traditional rasterization model. NVidia GPUs starting from Turing and AMD GPUs starting from RDNA2 provide a new programmable geometry pipeline that, instead of being built around index buffers and vertex shaders, is built around mesh shaders - a new shader type that allows to provide a batch of work to the rasterizer.
Expand Down
1 change: 0 additions & 1 deletion lib/meshoptimizer/meshoptimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,6 @@ MESHOPTIMIZER_API size_t meshopt_simplify(unsigned int* destination, const unsig
* attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position. The recommended weight range is [1e-3..1e-1], assuming attribute data is in [0..1] range.
* attribute_count must be <= 32
* vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; 1 denotes vertices that can't be moved
* TODO target_error/result_error currently use combined distance+attribute error; this may change in the future
*/
MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error);

Expand Down
52 changes: 21 additions & 31 deletions lib/meshoptimizer/simplifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ static void quadricAdd(QuadricGrad* G, const QuadricGrad* R, size_t attribute_co
}
}

static float quadricError(const Quadric& Q, const Vector3& v)
static float quadricEval(const Quadric& Q, const Vector3& v)
{
float rx = Q.b0;
float ry = Q.b1;
Expand All @@ -621,48 +621,32 @@ static float quadricError(const Quadric& Q, const Vector3& v)
r += ry * v.y;
r += rz * v.z;

return r;
}

static float quadricError(const Quadric& Q, const Vector3& v)
{
float r = quadricEval(Q, v);
float s = Q.w == 0.f ? 0.f : 1.f / Q.w;

return fabsf(r) * s;
}

static float quadricError(const Quadric& Q, const QuadricGrad* G, size_t attribute_count, const Vector3& v, const float* va)
{
float rx = Q.b0;
float ry = Q.b1;
float rz = Q.b2;

rx += Q.a10 * v.y;
ry += Q.a21 * v.z;
rz += Q.a20 * v.x;

rx *= 2;
ry *= 2;
rz *= 2;

rx += Q.a00 * v.x;
ry += Q.a11 * v.y;
rz += Q.a22 * v.z;

float r = Q.c;
r += rx * v.x;
r += ry * v.y;
r += rz * v.z;
float r = quadricEval(Q, v);

// see quadricFromAttributes for general derivation; here we need to add the parts of (eval(pos) - attr)^2 that depend on attr
for (size_t k = 0; k < attribute_count; ++k)
{
float a = va[k];
float g = v.x * G[k].gx + v.y * G[k].gy + v.z * G[k].gz + G[k].gw;

r += a * a * Q.w;
r -= 2 * a * g;
r += a * (a * Q.w - 2 * g);
}

// TODO: weight normalization is breaking attribute error somehow
float s = 1; // Q.w == 0.f ? 0.f : 1.f / Q.w;

return fabsf(r) * s;
// note: unlike position error, we do not normalize by Q.w to retain edge scaling as described in quadricFromAttributes
return fabsf(r);
}

static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w)
Expand Down Expand Up @@ -729,16 +713,21 @@ static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0,
Vector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};
Vector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};

// weight is scaled linearly with edge length
// normal = cross(p1 - p0, p2 - p0)
Vector3 normal = {p10.y * p20.z - p10.z * p20.y, p10.z * p20.x - p10.x * p20.z, p10.x * p20.y - p10.y * p20.x};
float area = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
float w = sqrtf(area); // TODO this needs more experimentation
float area = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z) * 0.5f;

// quadric is weighted with the square of edge length (= area)
// this equalizes the units with the positional error (which, after normalization, is a square of distance)
// as a result, a change in weighted attribute of 1 along distance d is approximately equivalent to a change in position of d
float w = area;

// we compute gradients using barycentric coordinates; barycentric coordinates can be computed as follows:
// v = (d11 * d20 - d01 * d21) / denom
// w = (d00 * d21 - d01 * d20) / denom
// u = 1 - v - w
// here v0, v1 are triangle edge vectors, v2 is a vector from point to triangle corner, and dij = dot(vi, vj)
// note: v2 and d20/d21 can not be evaluated here as v2 is effectively an unknown variable; we need these only as variables for derivation of gradients
const Vector3& v0 = p10;
const Vector3& v1 = p20;
float d00 = v0.x * v0.x + v0.y * v0.y + v0.z * v0.z;
Expand All @@ -748,7 +737,7 @@ static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0,
float denomr = denom == 0 ? 0.f : 1.f / denom;

// precompute gradient factors
// these are derived by directly computing derivative of eval(pos) = a0 * u + a1 * v + a2 * w and factoring out common factors that are shared between attributes
// these are derived by directly computing derivative of eval(pos) = a0 * u + a1 * v + a2 * w and factoring out expressions that are shared between attributes
float gx1 = (d11 * v0.x - d01 * v1.x) * denomr;
float gx2 = (d00 * v1.x - d01 * v0.x) * denomr;
float gy1 = (d11 * v0.y - d01 * v1.y) * denomr;
Expand All @@ -773,6 +762,7 @@ static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0,

// quadric encodes (eval(pos)-attr)^2; this means that the resulting expansion needs to compute, for example, pos.x * pos.y * K
// since quadrics already encode factors for pos.x * pos.y, we can accumulate almost everything in basic quadric fields
// note: for simplicity we scale all factors by weight here instead of outside the loop
Q.a00 += w * (gx * gx);
Q.a11 += w * (gy * gy);
Q.a22 += w * (gz * gz);
Expand Down

0 comments on commit f9ff6fd

Please sign in to comment.