Skip to content

GLTFExporter: Support EXT_mesh_gpu_instancing to export InstancedMesh. #26854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion examples/jsm/exporters/GLTFExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
Source,
SRGBColorSpace,
CompressedTexture,
Vector3
Vector3,
Quaternion,
} from 'three';
import { decompress } from './../utils/TextureUtils.js';

Expand Down Expand Up @@ -134,6 +135,12 @@ class GLTFExporter {

} );

this.register( function ( writer ) {

return new GLTFMeshGpuInstancing( writer );

} );

}

register( callback ) {
Expand Down Expand Up @@ -2962,6 +2969,67 @@ class GLTFMaterialsEmissiveStrengthExtension {

}

/**
* GPU Instancing Extension
*
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing
*/
class GLTFMeshGpuInstancing {

constructor( writer ) {

this.writer = writer;
this.name = 'EXT_mesh_gpu_instancing';

}

writeNode( object, nodeDef ) {

if ( ! object.isInstancedMesh ) return;

const writer = this.writer;

const mesh = object;

const translationAttr = new Float32Array( mesh.count * 3 );
const rotationAttr = new Float32Array( mesh.count * 4 );
const scaleAttr = new Float32Array( mesh.count * 3 );

const matrix = new Matrix4();
const position = new Vector3();
const quaternion = new Quaternion();
const scale = new Vector3();

for ( let i = 0; i < mesh.count; i ++ ) {

mesh.getMatrixAt( i, matrix );
matrix.decompose( position, quaternion, scale );

position.toArray( translationAttr, i * 3 );
quaternion.toArray( rotationAttr, i * 4 );
scale.toArray( scaleAttr, i * 3 );

}

const attributes = {
TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ),
ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ),
SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ),
};
Copy link
Collaborator

@takahirox takahirox Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment for potential future optimization. (So no need to do in this PR.)

As commented at

https://github.com/takahirox/three-gltf-extensions/blob/00e4233e4fc6c086f13c1c2de80ecc8a3c7eec29/exporters/EXT_mesh_gpu_instancing/EXT_mesh_gpu_instancing_exporter.js#L55

the attributes are optional and we may be able to think of exporting an attribute only if it has non-default value. For example, it may not be too rare that all scales are default (1, 1, 1) and in such a case the exported file size can be reducted.

Another approach would be the exporter always exports all the attributes as this PR does, and the optimization can be done in external gltf post-processing pipeline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I have tried it here: PalashBansal96@85bac3d

If it looks fine I can push it in this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also merge the PR first and then apply the optimization. In this way, the changes presented in this PR are not blocked.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can make another PR later.


if ( mesh.instanceColor )
attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor );

nodeDef.extensions = nodeDef.extensions || {};
nodeDef.extensions[ this.name ] = { attributes };

writer.extensionsUsed[ this.name ] = true;
writer.extensionsRequired[ this.name ] = true;

}

}

/**
* Static utility functions
*/
Expand Down
12 changes: 9 additions & 3 deletions examples/jsm/loaders/GLTFLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ import {
Vector2,
Vector3,
VectorKeyframeTrack,
SRGBColorSpace
SRGBColorSpace,
InstancedBufferAttribute
} from 'three';
import { toTrianglesDrawMode } from '../utils/BufferGeometryUtils.js';

Expand Down Expand Up @@ -942,7 +943,7 @@ class GLTFMaterialsSheenExtension {
if ( extension.sheenColorFactor !== undefined ) {

const colorFactor = extension.sheenColorFactor;
materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor [ 2 ], LinearSRGBColorSpace );
materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], LinearSRGBColorSpace );

}

Expand Down Expand Up @@ -1679,7 +1680,12 @@ class GLTFMeshGpuInstancing {
// Add instance attributes to the geometry, excluding TRS.
for ( const attributeName in attributes ) {

if ( attributeName !== 'TRANSLATION' &&
if ( attributeName === '_COLOR_0' ) {

const attr = attributes[ attributeName ];
instancedMesh.instanceColor = new InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized );

} else if ( attributeName !== 'TRANSLATION' &&
attributeName !== 'ROTATION' &&
attributeName !== 'SCALE' ) {

Expand Down
21 changes: 21 additions & 0 deletions examples/misc_exporter_gltf.html
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,27 @@

} );

// ---------------------------------------------------------------------
// Model requiring KHR_mesh_quantization
// ---------------------------------------------------------------------

material = new THREE.MeshBasicMaterial( {
color: 0xffffff,
} );
object = new THREE.InstancedMesh( new THREE.BoxGeometry( 10, 10, 10, 2, 2, 2 ), material, 50 );
const matrix = new THREE.Matrix4();
const color = new THREE.Color();
for ( let i = 0; i < 50; i ++ ) {

matrix.setPosition( Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50 );
object.setMatrixAt( i, matrix );
object.setColorAt( i, color.setHSL( i / 50, 1, 0.5 ) );

}

object.position.set( 400, 0, 200 );
scene1.add( object );

// ---------------------------------------------------------------------
// 2nd THREE.Scene
// ---------------------------------------------------------------------
Expand Down
Binary file modified examples/screenshots/misc_exporter_gltf.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.