Skip to content

Commit

Permalink
feature: implement removeUnnecessaryVertices
Browse files Browse the repository at this point in the history
Redo of: #880
  • Loading branch information
0b5vr committed Jan 14, 2022
1 parent 79b737d commit 0d15801
Show file tree
Hide file tree
Showing 15 changed files with 179 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/three-vrm/examples/animations.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/three-vrm/examples/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
// called when the resource is loaded
( gltf ) => {

// calling this function greatly improves the performance
// calling these functions greatly improves the performance
THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

// generate VRM instance from gltf
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/blendshapes.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/bones.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/dnd.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/encoding.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf, {
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/envmap.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
// called when the resource is loaded
( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

// generate VRM instance from gltf
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/firstperson.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/lookat-advanced.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

const lookAtImporter = new VRMSmoothLookAtImporter();
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/lookat.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/materials-debug.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/meta.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
// called when the resource is loaded
( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

// generate VRM instance from gltf
Expand Down
1 change: 1 addition & 0 deletions packages/three-vrm/examples/mouse.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

( gltf ) => {

THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene );
THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );

THREE.VRM.from( gltf ).then( ( vrm ) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/three-vrm/src/VRMUtils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { extractThumbnailBlob } from './extractThumbnailBlob';
import { removeUnnecessaryJoints } from './removeUnnecessaryJoints';
import { removeUnnecessaryVertices } from './removeUnnecessaryVertices';

export class VRMUtils {
private constructor() {
Expand All @@ -8,4 +9,5 @@ export class VRMUtils {

public static extractThumbnailBlob = extractThumbnailBlob;
public static removeUnnecessaryJoints = removeUnnecessaryJoints;
public static removeUnnecessaryVertices = removeUnnecessaryVertices;
}
163 changes: 163 additions & 0 deletions packages/three-vrm/src/VRMUtils/removeUnnecessaryVertices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import * as THREE from 'three';
import { BufferAttribute } from 'three';

/**
* Traverse given object and remove unnecessary vertices from every BufferGeometries.
* This only processes buffer geometries with index buffer.
*
* Three.js creates morph textures for each geometries and it sometimes consumes unnecessary amount of VRAM for certain models.
* This function will optimize geometries to reduce the size of morph texture.
* See: https://github.com/mrdoob/three.js/issues/23095
*
* @param root Root object that will be traversed
*/
export function removeUnnecessaryVertices(root: THREE.Object3D): void {
const geometryMap = new Map<THREE.BufferGeometry, THREE.BufferGeometry>();

// Traverse an entire tree
root.traverse((obj) => {
if (!(obj as any).isMesh) {
return;
}

const mesh = obj as THREE.Mesh;
const geometry = mesh.geometry;

// if the geometry does not have an index buffer it does not need to process
const origianlIndex = geometry.index;
if (origianlIndex == null) {
return;
}

// skip already processed geometry
const newGeometryAlreadyExisted = geometryMap.get(geometry);
if (newGeometryAlreadyExisted != null) {
mesh.geometry = newGeometryAlreadyExisted;
return;
}

const newGeometry = new THREE.BufferGeometry();

// copy various properties
// Ref: https://github.com/mrdoob/three.js/blob/1a241ef10048770d56e06d6cd6a64c76cc720f95/src/core/BufferGeometry.js#L1011
newGeometry.name = geometry.name;

newGeometry.morphTargetsRelative = geometry.morphTargetsRelative;

geometry.groups.forEach((group) => {
newGeometry.addGroup(group.start, group.count, group.materialIndex);
});

newGeometry.boundingBox = geometry.boundingBox?.clone() ?? null;
newGeometry.boundingSphere = geometry.boundingSphere?.clone() ?? null;

newGeometry.setDrawRange(geometry.drawRange.start, geometry.drawRange.count);

newGeometry.userData = geometry.userData;

// set to geometryMap
geometryMap.set(geometry, newGeometry);

/** from original index to new index */
const originalIndexNewIndexMap: number[] = [];

/** from new index to original index */
const newIndexOriginalIndexMap: number[] = [];

// reorganize indices
{
const originalIndexArray = origianlIndex.array;
const newIndexArray = new (originalIndexArray.constructor as any)(originalIndexArray.length);

let indexHead = 0;

for (let i = 0; i < originalIndexArray.length; i++) {
const originalIndex = originalIndexArray[i];

let newIndex = originalIndexNewIndexMap[originalIndex];
if (newIndex == null) {
originalIndexNewIndexMap[originalIndex] = indexHead;
newIndexOriginalIndexMap[indexHead] = originalIndex;
newIndex = indexHead;
indexHead++;
}
newIndexArray[i] = newIndex;
}

newGeometry.setIndex(new BufferAttribute(newIndexArray, 1, false));
}

// reorganize attributes
Object.keys(geometry.attributes).forEach((attributeName) => {
const originalAttribute = geometry.attributes[attributeName] as THREE.BufferAttribute;

if ((originalAttribute as any).isInterleavedBufferAttribute) {
throw new Error('removeUnnecessaryVertices: InterleavedBufferAttribute is not supported');
}

const originalAttributeArray = originalAttribute.array;
const { itemSize, normalized } = originalAttribute;

const newAttributeArray = new (originalAttributeArray.constructor as any)(
newIndexOriginalIndexMap.length * itemSize,
);

newIndexOriginalIndexMap.forEach((originalIndex, i) => {
for (let j = 0; j < itemSize; j++) {
newAttributeArray[i * itemSize + j] = originalAttributeArray[originalIndex * itemSize + j];
}
});

newGeometry.setAttribute(attributeName, new BufferAttribute(newAttributeArray, itemSize, normalized));
});

// reorganize morph attributes
/** True if all morphs are zero. */
let isNullMorph = true;

Object.keys(geometry.morphAttributes).forEach((attributeName) => {
newGeometry.morphAttributes[attributeName] = [];

const morphs = geometry.morphAttributes[attributeName];
for (let iMorph = 0; iMorph < morphs.length; iMorph++) {
const originalAttribute = morphs[iMorph] as THREE.BufferAttribute;

if ((originalAttribute as any).isInterleavedBufferAttribute) {
throw new Error('removeUnnecessaryVertices: InterleavedBufferAttribute is not supported');
}

const originalAttributeArray = originalAttribute.array;
const { itemSize, normalized } = originalAttribute;

const newAttributeArray = new (originalAttributeArray.constructor as any)(
newIndexOriginalIndexMap.length * itemSize,
);

newIndexOriginalIndexMap.forEach((originalIndex, i) => {
for (let j = 0; j < itemSize; j++) {
newAttributeArray[i * itemSize + j] = originalAttributeArray[originalIndex * itemSize + j];
}
});

isNullMorph = isNullMorph && newAttributeArray.every((v: number) => v === 0);

newGeometry.morphAttributes[attributeName][iMorph] = new BufferAttribute(
newAttributeArray,
itemSize,
normalized,
);
}
});

// If all morphs are zero, just discard the morph attributes we've just made
if (isNullMorph) {
newGeometry.morphAttributes = {};
}

mesh.geometry = newGeometry;
});

Array.from(geometryMap.keys()).forEach((originalGeometry) => {
originalGeometry.dispose();
});
}

0 comments on commit 0d15801

Please sign in to comment.