Bladeren bron

GLTFLoader: Implement support for EXT_meshopt_compression

EXT_meshopt_compression is a recent extension that supports compressing
glTF data - geometry, animation, instance transforms - in a generic
fashion.

Specification:
https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression

It is possible to produce files that use the extension using gltfpack
(https://github.com/zeux/meshoptimizer/tree/master/gltf). To decompress
the files, loader needs to decode the bufferView data - the extension
specification details the compressed format, but meshoptimizer library
provides a portable decoder that can be readily consumed from JS; the
decoder requires WebAssembly and will automatically dispatch to Wasm SIMD
when available, or use MVP otherwise.

meshopt_decoder.js is a drop-in copy of
https://github.com/zeux/meshoptimizer/blob/master/js/meshopt_decoder.js;
since it implements the specified format, it can be updated at any point
without loss of backwards compatibility.

The PR for EXT_meshopt_compression in glTF repository
(https://github.com/KhronosGroup/glTF/pull/1830) contains file sizes for
a lot of example models - the benefit of using this extension is that it
supports compressing every possible type of glTF binary data except for
images, and can decode at breakneck speeds (~1 GB/s when using Wasm SIMD
on a recent desktop Intel CPU) while only requiring a very small decoder
binary (meshopt_decoder.js is at 21 KB raw, 6 KB deflated, while
including *two* builds of the decoder, scalar and SIMD).

As an example, I've tested this PR on BrainStem model from
glTF-Sample-Models repository that contains animation data. The original
file size is 3120 KB, Draco-compressed version is 1942 KB, and
meshopt-compressed version is 362 KB, produced by gltfpack with `-cc`
option. The large size delta is due to animation data being compressed
with the extension; the loader extension decompresses bufferViews upon
request and the rest of the loader pipeline works without further
changes.

To integrate the extension after this change the user simply needs to
include the module, and pass the exported MeshoptDecoder global to the
loader:

	import './js/libs/meshopt_decoder.js';

	...

	loader.setMeshoptDecoder( MeshoptDecoder );
Arseny Kapoulkine 4 jaren geleden
bovenliggende
commit
75f4c14eaa
3 gewijzigde bestanden met toevoegingen van 149 en 6 verwijderingen
  1. 7 0
      examples/js/libs/meshopt_decoder.js
  2. 71 3
      examples/js/loaders/GLTFLoader.js
  3. 71 3
      examples/jsm/loaders/GLTFLoader.js

File diff suppressed because it is too large
+ 7 - 0
examples/js/libs/meshopt_decoder.js


+ 71 - 3
examples/js/loaders/GLTFLoader.js

@@ -9,6 +9,7 @@ THREE.GLTFLoader = ( function () {
 		this.dracoLoader = null;
 		this.ddsLoader = null;
 		this.ktx2Loader = null;
+		this.meshoptDecoder = null;
 
 		this.pluginCallbacks = [];
 
@@ -36,6 +37,12 @@ THREE.GLTFLoader = ( function () {
 
 		} );
 
+		this.register( function ( parser ) {
+
+			return new GLTFMeshoptCompression( parser );
+
+		} );
+
 	}
 
 	GLTFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
@@ -134,6 +141,13 @@ THREE.GLTFLoader = ( function () {
 
 		},
 
+		setMeshoptDecoder: function ( meshoptDecoder ) {
+
+			this.meshoptDecoder = meshoptDecoder;
+			return this;
+
+		},
+
 		register: function ( callback ) {
 
 			if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {
@@ -209,7 +223,8 @@ THREE.GLTFLoader = ( function () {
 				path: path || this.resourcePath || '',
 				crossOrigin: this.crossOrigin,
 				manager: this.manager,
-				ktx2Loader: this.ktx2Loader
+				ktx2Loader: this.ktx2Loader,
+				meshoptDecoder: this.meshoptDecoder
 
 			} );
 
@@ -334,6 +349,7 @@ THREE.GLTFLoader = ( function () {
 		KHR_TEXTURE_BASISU: 'KHR_texture_basisu',
 		KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
 		KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
+		EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression',
 		MSFT_TEXTURE_DDS: 'MSFT_texture_dds'
 	};
 
@@ -668,7 +684,6 @@ THREE.GLTFLoader = ( function () {
 	 * BasisU Texture Extension
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu
-	 * (draft PR https://github.com/KhronosGroup/glTF/pull/1751)
 	 */
 	function GLTFTextureBasisUExtension( parser ) {
 
@@ -704,6 +719,59 @@ THREE.GLTFLoader = ( function () {
 
 	};
 
+	/**
+	* meshopt BufferView Compression Extension
+	*
+	* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression
+	*/
+	function GLTFMeshoptCompression( parser ) {
+
+		this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION;
+		this.parser = parser;
+
+	}
+
+	GLTFMeshoptCompression.prototype.loadBufferView = function ( index ) {
+
+		var bufferView = this.parser.json.bufferViews[ index ];
+
+		if ( bufferView.extensions && bufferView.extensions[ this.name ] ) {
+
+			var extensionDef = bufferView.extensions[ this.name ];
+
+			var buffer = this.parser.getDependency( 'buffer', extensionDef.buffer );
+			var decoder = this.parser.options.meshoptDecoder;
+
+			if ( !decoder ) {
+
+				throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' );
+
+			}
+
+			return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) {
+
+				var byteOffset = extensionDef.byteOffset || 0;
+				var byteLength = extensionDef.byteLength || 0;
+
+				var count = extensionDef.count;
+				var stride = extensionDef.byteStride;
+
+				var result = new ArrayBuffer( count * stride );
+				var source = new Uint8Array( res[ 0 ], byteOffset, byteLength );
+
+				decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter );
+				return result;
+
+			} );
+
+		} else {
+
+			return null;
+
+		}
+
+	};
+
 	/* BINARY EXTENSION */
 	var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
 	var BINARY_EXTENSION_HEADER_LENGTH = 12;
@@ -1397,7 +1465,7 @@ THREE.GLTFLoader = ( function () {
 
 	var INTERPOLATION = {
 		CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
-		                        // keyframe track will be initialized with a default interpolation type, then modified.
+								// keyframe track will be initialized with a default interpolation type, then modified.
 		LINEAR: THREE.InterpolateLinear,
 		STEP: THREE.InterpolateDiscrete
 	};

+ 71 - 3
examples/jsm/loaders/GLTFLoader.js

@@ -72,6 +72,7 @@ var GLTFLoader = ( function () {
 		this.dracoLoader = null;
 		this.ddsLoader = null;
 		this.ktx2Loader = null;
+		this.meshoptDecoder = null;
 
 		this.pluginCallbacks = [];
 
@@ -99,6 +100,12 @@ var GLTFLoader = ( function () {
 
 		} );
 
+		this.register( function ( parser ) {
+
+			return new GLTFMeshoptCompression( parser );
+
+		} );
+
 	}
 
 	GLTFLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
@@ -197,6 +204,13 @@ var GLTFLoader = ( function () {
 
 		},
 
+		setMeshoptDecoder: function ( meshoptDecoder ) {
+
+			this.meshoptDecoder = meshoptDecoder;
+			return this;
+
+		},
+
 		register: function ( callback ) {
 
 			if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {
@@ -272,7 +286,8 @@ var GLTFLoader = ( function () {
 				path: path || this.resourcePath || '',
 				crossOrigin: this.crossOrigin,
 				manager: this.manager,
-				ktx2Loader: this.ktx2Loader
+				ktx2Loader: this.ktx2Loader,
+				meshoptDecoder: this.meshoptDecoder
 
 			} );
 
@@ -397,6 +412,7 @@ var GLTFLoader = ( function () {
 		KHR_TEXTURE_BASISU: 'KHR_texture_basisu',
 		KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
 		KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
+		EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression',
 		MSFT_TEXTURE_DDS: 'MSFT_texture_dds'
 	};
 
@@ -731,7 +747,6 @@ var GLTFLoader = ( function () {
 	 * BasisU Texture Extension
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu
-	 * (draft PR https://github.com/KhronosGroup/glTF/pull/1751)
 	 */
 	function GLTFTextureBasisUExtension( parser ) {
 
@@ -767,6 +782,59 @@ var GLTFLoader = ( function () {
 
 	};
 
+	/**
+	* meshopt BufferView Compression Extension
+	*
+	* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression
+	*/
+	function GLTFMeshoptCompression( parser ) {
+
+		this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION;
+		this.parser = parser;
+
+	}
+
+	GLTFMeshoptCompression.prototype.loadBufferView = function ( index ) {
+
+		var bufferView = this.parser.json.bufferViews[ index ];
+
+		if ( bufferView.extensions && bufferView.extensions[ this.name ] ) {
+
+			var extensionDef = bufferView.extensions[ this.name ];
+
+			var buffer = this.parser.getDependency( 'buffer', extensionDef.buffer );
+			var decoder = this.parser.options.meshoptDecoder;
+
+			if ( !decoder ) {
+
+				throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' );
+
+			}
+
+			return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) {
+
+				var byteOffset = extensionDef.byteOffset || 0;
+				var byteLength = extensionDef.byteLength || 0;
+
+				var count = extensionDef.count;
+				var stride = extensionDef.byteStride;
+
+				var result = new ArrayBuffer( count * stride );
+				var source = new Uint8Array( res[ 0 ], byteOffset, byteLength );
+
+				decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter );
+				return result;
+
+			} );
+
+		} else {
+
+			return null;
+
+		}
+
+	};
+
 	/* BINARY EXTENSION */
 	var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
 	var BINARY_EXTENSION_HEADER_LENGTH = 12;
@@ -1460,7 +1528,7 @@ var GLTFLoader = ( function () {
 
 	var INTERPOLATION = {
 		CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
-		                        // keyframe track will be initialized with a default interpolation type, then modified.
+								// keyframe track will be initialized with a default interpolation type, then modified.
 		LINEAR: InterpolateLinear,
 		STEP: InterpolateDiscrete
 	};

Some files were not shown because too many files changed in this diff