Created
March 16, 2025 16:52
-
-
Save Ctrlmonster/ba448e28108cc7a9cdd6741292ece6e2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {MeshoptSimplifier, MeshoptEncoder} from "meshoptimizer"; | |
import {BufferAttribute, BufferGeometry, Mesh} from "three"; | |
await MeshoptSimplifier.ready; // await wasm promise | |
await MeshoptEncoder.ready; // await wasm promise | |
MeshoptSimplifier.useExperimentalFeatures = true; | |
export function createLOD_Levels(originalMesh: Mesh, simplificationLevel: number[]) { | |
const lodMeshes: {meshes: Mesh[], errors: number[]} = {errors:[], meshes: []}; | |
for (let i = 0; i < simplificationLevel.length; i++) { | |
const errorLevel = simplificationLevel[i]; | |
const {error, mesh} = simplifyMesh(originalMesh, errorLevel); | |
lodMeshes.meshes.push(mesh); | |
lodMeshes.errors.push(error); | |
} | |
return lodMeshes; | |
} | |
export function simplifyMesh(mesh: Mesh, error=0.005) { | |
const indexedGeometry = mesh.geometry; | |
// Ensure the geometry is indexed | |
if (indexedGeometry.index === null) { | |
throw new Error(`LOD generation failed: Geometry needs an index buffer`); | |
} | |
// Extract position attribute and index array | |
const positionAttribute = indexedGeometry.getAttribute("position"); | |
if (!positionAttribute) { | |
throw new Error("Geometry must have a position attribute."); | |
} | |
const normalAttribute = indexedGeometry.getAttribute("normal"); | |
const uvAttribute = indexedGeometry.getAttribute("uv"); | |
const positions = indexedGeometry.getAttribute("position").array as Float32Array; | |
//const reductionPercent = 0.5; | |
//const targetIndexCount = indexedGeometry.index.count * reductionPercent + (indexedGeometry.index.count * reductionPercent % 3); | |
const [simplifiedIdxBuffer, _visibleErr] = MeshoptSimplifier.simplify( | |
indexedGeometry.index!.array as Uint32Array, | |
positions, | |
positionAttribute.itemSize, | |
0, // just set to 0 for max reduction while keeping the error boundary | |
error, // error boundary | |
["LockBorder", "Prune"] | |
); | |
const scaleFactor = MeshoptSimplifier.getScale(positions, 3); | |
const absError = scaleFactor * error; | |
//console.log(`Absolute Error: ${} - Relative Error: ${_visibleErr}`); | |
const newBuffer = new Uint32Array<ArrayBufferLike>(simplifiedIdxBuffer); | |
const [remap, uniqueVerts] = MeshoptEncoder.reorderMesh(newBuffer, true, false); | |
//console.log(`${(1 - uniqueVerts / indexedGeometry.index.array.length).toFixed(4)} vertex reduction with ${_visibleErr.toFixed(4)} visual error.`); | |
//const [remap, uniqueVerts] = MeshoptSimplifier.compactMesh(newBuffer); | |
const finalIdxBuffer = (uniqueVerts <= 256) | |
? new Uint8Array(simplifiedIdxBuffer.length) | |
: (uniqueVerts <= 65536) | |
? new Uint16Array(simplifiedIdxBuffer.length) | |
: new Uint32Array(simplifiedIdxBuffer.length); | |
for (let i = 0; i < simplifiedIdxBuffer.length; i++) { | |
const oldVertexIndex = simplifiedIdxBuffer[i]; | |
const newVertexIndex = remap[oldVertexIndex]; | |
finalIdxBuffer[i] = newVertexIndex; | |
} | |
const newGeo = new BufferGeometry(); | |
newGeo.setIndex(new BufferAttribute(finalIdxBuffer, 1)); | |
// we need to update all existing attributes | |
const newPositionAttribute = new BufferAttribute( | |
new Float32Array(uniqueVerts * positionAttribute.itemSize), | |
positionAttribute.itemSize | |
); | |
const newUvAttribute = uvAttribute | |
? new BufferAttribute(new Float32Array(uniqueVerts * uvAttribute.itemSize), uvAttribute.itemSize) | |
: null; | |
const newNormalAttribute = normalAttribute | |
? new BufferAttribute(new Float32Array(uniqueVerts * normalAttribute.itemSize), normalAttribute.itemSize) | |
: null; | |
for (let oldIdx = 0; oldIdx < remap.length; oldIdx++) { | |
const newIdx = remap[oldIdx]; | |
if (newIdx === 0xffffffff) continue; | |
// Remap positions | |
newPositionAttribute.setXYZ( | |
newIdx, | |
positionAttribute.getX(oldIdx), | |
positionAttribute.getY(oldIdx), | |
positionAttribute.getZ(oldIdx) | |
); | |
// Remap UVs | |
if (newUvAttribute && uvAttribute) { | |
newUvAttribute.setXY( | |
newIdx, | |
uvAttribute.getX(oldIdx), | |
uvAttribute.getY(oldIdx) | |
); | |
} | |
// Remap normals | |
if (newNormalAttribute && normalAttribute) { | |
newNormalAttribute.setXYZ( | |
newIdx, | |
normalAttribute.getX(oldIdx), | |
normalAttribute.getY(oldIdx), | |
normalAttribute.getZ(oldIdx) | |
); | |
} | |
} | |
newGeo.setAttribute("position", newPositionAttribute); | |
newGeo.applyMatrix4(mesh.matrixWorld); | |
if (newUvAttribute) newGeo.setAttribute("uv", newUvAttribute); | |
if (newNormalAttribute) newGeo.setAttribute("normal", newNormalAttribute); | |
return {mesh: new Mesh(newGeo, mesh.material), error: absError}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Lod selection system