Mesh simplification using meshoptimizer

Hello, I am trying to simplify the meshes in the threeJs scene using meshoptimizer library.

I am facing some difficulties while creating a new vertex attribute after the compactMesh function is called. I also took some references from gltf-transform library and wrote similar methods which does same functionality on typedArray instead of a mesh primitive.

Here is my code

import {MeshoptSimplifier} from  'meshoptimizer'

function createIndices(count: number, maxIndex = count): Uint16Array | Uint32Array {
	const array = maxIndex <= 65534 ? new Uint16Array(count) : new Uint32Array(count);
	for (let i = 0; i < array.length; i++) array[i] = i;
	return array;
}

function cleanIndexArray(indexArray : Uint16Array | Uint32Array, maxIndex : number){
    const srcIndicesArray = indexArray;
    const dstIndicesArray = []
    const component_type = maxIndex <= 65534 ? '5123' : '5125';
    let maxIndex1 = -Infinity;

    for (let i = 0, il = srcIndicesArray.length; i < il; i += 3) {
        const a = srcIndicesArray[i];
        const b = srcIndicesArray[i + 1];
        const c = srcIndicesArray[i + 2];

        if (a === b || a === c || b === c) continue;

        dstIndicesArray.push(a, b, c);
        maxIndex = Math.max(maxIndex1, a, b, c);
    }

    const TypedArray = ComponentTypeToTypedArray[component_type]

    return new TypedArray(dstIndicesArray)
}

function remapGeometries(geometry: THREE.BufferGeometry, remap : TypedArray, dstVertexCount: number){
    // Remap Indices
    const srcVertexCount = geometry.attributes.position.count
	const srcIndices = geometry.index
	const srcIndicesArray = srcIndices ? srcIndices.array : null;
	const dstIndicesCount = srcIndices ? srcIndices.count : srcVertexCount; 
	const dstIndicesArray = createIndices(dstIndicesCount, dstVertexCount);
	for (let i = 0; i < dstIndicesCount; i++) {
		dstIndicesArray[i] = remap[srcIndicesArray ? srcIndicesArray[i] : i];
	}
    // Remap Vertices
    const elementSize = geometry.attributes.position.itemSize
	const srcCount = srcVertexCount
	const srcArray = geometry.attributes.position.array
	const dstArray = srcArray.slice(0, dstVertexCount * elementSize)
	const done = new Uint8Array(dstVertexCount)

	for (let srcIndex = 0; srcIndex < srcCount; srcIndex++) {
		const dstIndex = remap[srcIndex];
		if (done[dstIndex]) continue;
		for (let j = 0; j < elementSize; j++) {
			dstArray[dstIndex * elementSize + j] = srcArray[srcIndex * elementSize + j];
		}
		done[dstIndex] = 1;
	}

    const cleanedIndexArray = cleanIndexArray(dstIndicesArray, dstVertexCount)
    return [cleanedIndexArray, dstArray]
}
simplify(m : THREE.Mesh){
        let uint32Array = new Uint32Array(m.geometry.index?.count as number)
        m.geometry.index?.array.map((value, index) => uint32Array[index] = value)
        const divisor = 2
        let target_index_count = 3
        if (m.geometry.index){
            target_index_count = (m.geometry.index.count / divisor % 3) == 0 ? m.geometry.index.count / divisor : m.geometry.index.count % 3 + m.geometry.index.count
        }
        // printing arrays before simplification
        console.log(uint32Array, "source Index Array", m.geometry.attributes.position.array as Float32Array, "source Vertex postions Array")
        const meshIndices = MeshoptSimplifier.simplify(uint32Array, m.geometry.attributes.position.array as Float32Array, 3, target_index_count, 1)
        const [remap, dstVertexCount] = MeshoptSimplifier.compactMesh(meshIndices[0])
        const [dstIndicesArray, dstVertexArray] = remapGeometries(m.geometry, remap, dstVertexCount)
        // printing arrays after simplification
        console.log(remap, "new vertex order", dstIndicesArray, "Destination Index array", dstVertexArray, "new Vertex Array")
        if(m.geometry.index){
            m.geometry.index.array = dstIndicesArray
            m.geometry.attributes.position.array.set(dstVertexArray)
        }
        return m
    }

simplify(mesh)

The destination index array contains some reduntant values like 65535 which is which is not referenced in the destination vertex attributes. Also post rendering I don’t see any differnece in the vram usage as it is same as the original unsimplified mesh

I am trying rosolve these inconsistency, can someone please let me know if I am doing anyting wrong with the implementation.

Thanks for helping me in advance

Are you very sure that you need to simplify these meshes after loading the model in three.js? In 99% of cases it’s better to simplify the model before the webpage ever downloads it, and easier too. Use gltfpack or gltf-transform directly for that. Or Blender’s Decimate modifier.

If you need to do simplification at runtime after loading, three.js also has a built-in simplify modifier:

https://threejs.org/examples/?q=simpl#webgl_modifier_simplifier

… which would probably be easier for you to use, and has existing examples.


If you really want to use meshoptimizer at runtime to do simplification, we can probably help you do that, but I just want to make sure you’re aware that you’re doing this the hard way. :wink:

3 Likes

Yes I am sure that I need to simplify the meshes in the runtime. I have already tried using SimplifyModifier from three.js examples. Which really does not perform well on high density meshes reducing the UX of the application. On the other hand meshoptimizer performs well even on high density meshes.

I am also aware that this is the hard way.

It would be of great, if you could help me.

Here’s an example using meshoptimizer to simplify three.js mesh geometry —

In this example, I’ve overwritten only the ‘index’ selecting which vertices to draw. If you’ll be drawing the unsimplified mesh again later (like for an LOD system) then this would be the more efficient way to go. If you are never going to use the unsimplified mesh again, then you could additionally the ‘compactMesh’ method to compute a remap table and get rid of the unused vertices from the vertex attributes.

Note that geometry should be indexed before simplification. Either with three.js mergeVertices or gltf-transform weld input.glb output.glb.

2 Likes

My apologies for the late reply. I was able to use Meshoptimizer in runtime also remove the redundant vertices from vertex position buffer. Thank you for helping :grinning:.

1 Like