demo: Add auto-LOD testing mode to simplify demo

Instead of using a controlled ratio with a fixed threshold, we can now
use the distance to the object's surface to scale the expected error and
simplify as much as we can with that limit.

For now we don't carefully account for multi-mesh scenes where the
difference in submesh scale and size would require adjusting the allowed
error, and the scene radius is assumed to be 1 because that's what we
resize it to. This is sufficient for testing this but may improve later.
This commit is contained in:
Arseny Kapoulkine
2024-09-06 20:03:11 -07:00
parent e91fdfe1b0
commit 9fdcdc4de6
+29 -8
View File
@@ -71,6 +71,8 @@
normalWeight: 0.5,
colorWeight: 1.0,
textureWeight: 0.0,
autoLod: false,
autoLodFactor: 1.0,
stats: {
triangles: 0,
@@ -116,6 +118,10 @@
guiAttributes.push(guiSimplify.add(settings, 'colorWeight', 0, 2, 0.01).onChange(simplify));
guiAttributes.push(guiSimplify.add(settings, 'textureWeight', 0, 100, 0.1).onChange(simplify));
var guiLod = gui.addFolder('LOD');
guiLod.add(settings, 'autoLod').onChange(simplify);
guiLod.add(settings, 'autoLodFactor', 0, 10, 0.01).onChange(simplify);
var guiLoad = gui.addFolder('Load');
guiLoad.add(settings, 'loadFile');
guiLoad.add(settings, 'updateModule');
@@ -138,7 +144,7 @@
}
}
function simplifyMesh(geo) {
function simplifyMesh(geo, threshold) {
if (settings.weldVertices) {
// pre-welding is important for some meshes that have very close normals in adjacent face corners
// for now we use the welder from three.js; this is not as performant as it could be
@@ -149,7 +155,7 @@
var positions = new Float32Array(geo.attributes.position.array);
var indices = new Uint32Array(geo.index.array); // needed for _InternalDebug to work
var target = Math.floor((indices.length * settings.ratio) / 3) * 3;
var target = settings.autoLod ? 0 : Math.floor((indices.length * settings.ratio) / 3) * 3;
if (settings.useAttributes) {
var attrib = new Float32Array(geo.attributes.position.count * attributes);
@@ -190,7 +196,6 @@
flags.push('LockBorder');
}
var threshold = Math.pow(10, -settings.errorThresholdLog10);
var stride = geo.attributes.position instanceof THREE.InterleavedBufferAttribute ? geo.attributes.position.data.stride : 3;
console.time('simplify');
@@ -297,6 +302,21 @@
var vertices = 0;
var error = 0;
var threshold = Math.pow(10, -settings.errorThresholdLog10);
if (settings.autoLod) {
// subtract 1 because we assume model radius is 1.0, so this is a distance-to-sphere
// ideally this should actually use the real radius :) but we rescale the model to fit a unit box
var distance = Math.max(camera.position.distanceTo(model.position) - 1, 0);
var loderrortarget = 1e-3; // ~1 pixel at 1k x 1k
// note: we are currently cutting corners wrt handling the actual mesh scale
// since we rescale the entire scene to fit a unit box, we can directly feed the threshold
// this works correctly if there's just one mesh or scales match; ideally we tweak this to compute
// threshold per mesh based on relative scale between mesh and scene.
threshold = distance * loderrortarget * settings.autoLodFactor;
}
scene.traverse(function (object) {
if (object.isMesh) {
if (!object.original) {
@@ -316,7 +336,7 @@
];
}
var [geo, tri, err] = simplifyMesh(object.original);
var [geo, tri, err] = simplifyMesh(object.original, threshold);
object.geometry = geo;
@@ -405,11 +425,11 @@
model = gltf.scene;
var bbox = new THREE.Box3().setFromObject(model);
var scale = 2 / (bbox.max.y - bbox.min.y);
var offset = (-(bbox.min.y + bbox.max.y) / 2) * scale;
var scale = 2 / Math.max(bbox.max.x - bbox.min.x, bbox.max.y - bbox.min.y, bbox.max.z - bbox.min.z);
var offset = new THREE.Vector3().addVectors(bbox.max, bbox.min).multiplyScalar(scale / 2);
model.scale.set(scale, scale, scale);
model.position.set(0, offset, 0);
model.position.set(-offset.x, -offset.y, -offset.z);
scene.add(model);
@@ -435,11 +455,12 @@
window.addEventListener('resize', onWindowResize, false);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 100);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 1.0;
camera.position.z = 3.0;
controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', simplify);
scene = new THREE.Scene();
scene.background = new THREE.Color(0x300a24);