ソースを参照

Add support for glTF binary extension.

Don McCurdy 8 年 前
コミット
fef8cb07d7

+ 249 - 108
examples/js/loaders/GLTFLoader.js

@@ -24,9 +24,12 @@ THREE.GLTFLoader = ( function () {
 			var path = this.path && ( typeof this.path === "string" ) ? this.path : THREE.Loader.prototype.extractUrlBase( url );
 
 			var loader = new THREE.FileLoader( scope.manager );
-			loader.load( url, function ( text ) {
 
-				scope.parse( JSON.parse( text ), onLoad, path );
+			loader.setResponseType( 'arraybuffer' );
+
+			loader.load( url, function ( data ) {
+
+				scope.parse( data , onLoad, path );
 
 			}, onProgress, onError );
 
@@ -44,11 +47,37 @@ THREE.GLTFLoader = ( function () {
 
 		},
 
-		parse: function ( json, callback, path ) {
+		parse: function ( data, callback, path ) {
+
+			var json;
+
+			var extensions = {};
+
+			var magic = convertUint8ArrayToString( new Uint8Array( data, 0, 4 ) );
+
+			if ( magic === BINARY_EXTENSION_HEADER_DEFAULTS.magic ) {
+
+				extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
+
+				json = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
+
+			} else {
+
+				json = convertUint8ArrayToString( new Uint8Array( data ) );
+
+			}
+
+			json = JSON.parse( json );
+
+			if ( json.extensionsUsed && json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_COMMON ) >= 0 ) {
+
+				extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] = new GLTFMaterialsCommonExtension( json );
+
+			}
 
 			console.time( 'GLTFLoader' );
 
-			var parser = new GLTFParser( json, {
+			var parser = new GLTFParser( json, extensions, {
 
 				path: path || this.path,
 				crossOrigin: this.crossOrigin
@@ -255,6 +284,126 @@ THREE.GLTFLoader = ( function () {
 
 	};
 
+	/*********************************/
+	/********** EXTENSIONS ***********/
+	/*********************************/
+
+	var EXTENSIONS = {
+		KHR_BINARY_GLTF: 'KHR_binary_glTF',
+		KHR_MATERIALS_COMMON: 'KHR_materials_common'
+	};
+
+	/* MATERIALS COMMON EXTENSION */
+
+	function GLTFMaterialsCommonExtension ( json ) {
+
+		this.name = EXTENSIONS.KHR_MATERIALS_COMMON;
+
+		this.lights = {};
+
+		var lights = json.extensions && json.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ].lights;
+
+		for ( var lightId in lights ) {
+
+			var light = lights[ lightId ];
+			var lightNode;
+
+			var lightParams = light[ light.type ];
+			var color = new THREE.Color().fromArray( lightParams.color );
+
+			switch ( light.type ) {
+
+				case "directional":
+					lightNode = new THREE.DirectionalLight( color );
+					lightNode.position.set( 0, 0, 1 );
+					break;
+
+				case "point":
+					lightNode = new THREE.PointLight( color );
+					break;
+
+				case "spot":
+					lightNode = new THREE.SpotLight( color );
+					lightNode.position.set( 0, 0, 1 );
+					break;
+
+				case "ambient":
+					lightNode = new THREE.AmbientLight( color );
+					break;
+
+			}
+
+			if ( lightNode ) {
+
+				this.lights[ lightId ] = lightNode;
+
+			}
+
+		}
+
+	}
+
+	/* BINARY EXTENSION */
+
+	var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF';
+
+	var BINARY_EXTENSION_HEADER_DEFAULTS = { magic: 'glTF', version: 1, contentFormat: 0 };
+
+	var BINARY_EXTENSION_HEADER_LENGTH = 20;
+
+	function GLTFBinaryExtension ( data ) {
+
+		this.name = EXTENSIONS.KHR_BINARY_GLTF;
+
+		var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
+
+		var header = {
+			magic: convertUint8ArrayToString( new Uint8Array( data.slice( 0, 4 ) ) ),
+			version: headerView.getUint32( 4, true ),
+			length: headerView.getUint32( 8, true ),
+			contentLength: headerView.getUint32( 12, true ),
+			contentFormat: headerView.getUint32( 16, true )
+		};
+
+		for ( var key in BINARY_EXTENSION_HEADER_DEFAULTS ) {
+
+			var value = BINARY_EXTENSION_HEADER_DEFAULTS[ key ];
+
+			if ( header[ key ] !== value ) {
+
+				throw new Error( 'Unsupported glTF-Binary header: Expected "%s" to be "%s".', key, value );
+
+			}
+
+		}
+
+		var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH, header.contentLength );
+
+		this.header = header;
+		this.content = convertUint8ArrayToString( contentArray );
+		this.body = data.slice( BINARY_EXTENSION_HEADER_LENGTH + header.contentLength, header.length );
+
+	}
+
+	GLTFBinaryExtension.prototype.loadShader = function ( shader, bufferViews ) {
+
+		var bufferView = bufferViews[ shader.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].bufferView ];
+		var array = new Uint8Array( bufferView );
+
+		return convertUint8ArrayToString( array );
+
+	};
+
+	GLTFBinaryExtension.prototype.loadTextureSourceUri = function ( source, bufferViews ) {
+
+		var metadata = source.extensions[ EXTENSIONS.KHR_BINARY_GLTF ];
+		var bufferView = bufferViews[ metadata.bufferView ];
+		var stringData = convertUint8ArrayToString( new Uint8Array( bufferView ) );
+
+		return 'data:' + metadata.mimeType + ';base64,' + btoa( stringData );
+
+	};
+
 	/*********************************/
 	/********** INTERNALS ************/
 	/*********************************/
@@ -471,6 +620,20 @@ THREE.GLTFLoader = ( function () {
 
 	}
 
+	// Avoid the String.fromCharCode.apply(null, array) shortcut, which
+	// throws a "maximum call stack size exceeded" error for large arrays.
+	function convertUint8ArrayToString ( array ) {
+		var s = '';
+
+		for ( var i = 0; i < array.length; i++ ) {
+
+			s += String.fromCharCode( array[ i ] );
+
+		}
+
+		return s;
+	}
+
 	// Three.js seems too dependent on attribute names so globally
 	// replace those in the shader code
 	function replaceTHREEShaderAttributes( shaderText, technique ) {
@@ -614,9 +777,10 @@ THREE.GLTFLoader = ( function () {
 
 	/* GLTF PARSER */
 
-	function GLTFParser( json, options ) {
+	function GLTFParser( json, extensions, options ) {
 
 		this.json = json || {};
+		this.extensions = extensions || {};
 		this.options = options || {};
 
 		// loader object cache
@@ -710,17 +874,32 @@ THREE.GLTFLoader = ( function () {
 	GLTFParser.prototype.loadShaders = function () {
 
 		var json = this.json;
+		var extensions = this.extensions;
 		var options = this.options;
 
-		return _each( json.shaders, function ( shader ) {
+		return this._withDependencies( [
+
+			"bufferViews"
+
+		] ).then( function ( dependencies ) {
+
+			return _each( json.shaders, function ( shader ) {
+
+				if ( shader.extensions && shader.extensions[ EXTENSIONS.KHR_BINARY_GLTF ] ) {
+
+					return extensions[ EXTENSIONS.KHR_BINARY_GLTF ].loadShader( shader, dependencies.bufferViews );
+
+				}
+
+				return new Promise( function ( resolve ) {
 
-			return new Promise( function ( resolve ) {
+					var loader = new THREE.FileLoader();
+					loader.setResponseType( 'text' );
+					loader.load( resolveURL( shader.uri, options.path ), function ( shaderText ) {
 
-				var loader = new THREE.FileLoader();
-				loader.setResponseType( 'text' );
-				loader.load( resolveURL( shader.uri, options.path ), function ( shaderText ) {
+						resolve( shaderText );
 
-					resolve( shaderText );
+					} );
 
 				} );
 
@@ -733,9 +912,16 @@ THREE.GLTFLoader = ( function () {
 	GLTFParser.prototype.loadBuffers = function () {
 
 		var json = this.json;
+		var extensions = this.extensions;
 		var options = this.options;
 
-		return _each( json.buffers, function ( buffer ) {
+		return _each( json.buffers, function ( buffer, name ) {
+
+			if ( name === BINARY_EXTENSION_BUFFER_NAME ) {
+
+				return extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body;
+
+			}
 
 			if ( buffer.type === 'arraybuffer' || buffer.type === undefined ) {
 
@@ -831,52 +1017,68 @@ THREE.GLTFLoader = ( function () {
 	GLTFParser.prototype.loadTextures = function () {
 
 		var json = this.json;
+		var extensions = this.extensions;
 		var options = this.options;
 
-		return _each( json.textures, function ( texture ) {
-
-			if ( texture.source ) {
+		return this._withDependencies( [
 
-				return new Promise( function ( resolve ) {
+			"bufferViews"
 
-					var source = json.images[ texture.source ];
+		] ).then( function ( dependencies ) {
 
-					var textureLoader = THREE.Loader.Handlers.get( source.uri );
+			return _each( json.textures, function ( texture ) {
 
-					if ( textureLoader === null ) {
+				if ( texture.source ) {
 
-						textureLoader = new THREE.TextureLoader();
+					return new Promise( function ( resolve ) {
 
-					}
+						var source = json.images[ texture.source ];
+						var sourceUri = source.uri;
 
-					textureLoader.setCrossOrigin( options.crossOrigin );
+						if (source.extensions && source.extensions[ EXTENSIONS.KHR_BINARY_GLTF ]) {
 
-					textureLoader.load( resolveURL( source.uri, options.path ), function ( _texture ) {
+							sourceUri = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].loadTextureSourceUri( source, dependencies.bufferViews );
 
-						_texture.flipY = false;
+						}
 
-						if ( texture.sampler ) {
+						var textureLoader = THREE.Loader.Handlers.get( sourceUri );
 
-							var sampler = json.samplers[ texture.sampler ];
+						if ( textureLoader === null ) {
 
-							_texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ];
-							_texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ];
-							_texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ];
-							_texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ];
+							textureLoader = new THREE.TextureLoader();
 
 						}
 
-						resolve( _texture );
+						textureLoader.setCrossOrigin( options.crossOrigin );
+
+						textureLoader.load( resolveURL( sourceUri, options.path ), function ( _texture ) {
+
+							_texture.flipY = false;
+
+							if ( texture.sampler ) {
+
+								var sampler = json.samplers[ texture.sampler ];
+
+								_texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ];
+								_texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ];
+								_texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ];
+								_texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ];
+
+							}
+
+							resolve( _texture );
+
+						}, undefined, function () {
 
-					}, undefined, function () {
+							resolve();
 
-						resolve();
+						} );
 
 					} );
 
-				} );
+				}
 
-			}
+			} );
 
 		} );
 
@@ -901,13 +1103,13 @@ THREE.GLTFLoader = ( function () {
 
 				var khr_material;
 
-				if ( material.extensions && material.extensions.KHR_materials_common ) {
+				if ( material.extensions && material.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] ) {
 
-					khr_material = material.extensions.KHR_materials_common;
+					khr_material = material.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ];
 
-				} else if ( json.extensions && json.extensions.KHR_materials_common ) {
+				} else if ( json.extensions && json.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] ) {
 
-					khr_material = json.extensions.KHR_materials_common;
+					khr_material = json.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ];
 
 				}
 
@@ -1413,7 +1615,7 @@ THREE.GLTFLoader = ( function () {
 								case 'POSITION':
 									geometry.addAttribute( 'position', bufferAttribute );
 									break;
-				
+
 								case 'COLOR_0':
 								case 'COLOR0':
 								case 'COLOR':
@@ -1599,6 +1801,7 @@ THREE.GLTFLoader = ( function () {
 	GLTFParser.prototype.loadNodes = function () {
 
 		var json = this.json;
+		var extensions = this.extensions;
 		var scope = this;
 
 		return _each( json.nodes, function ( node ) {
@@ -1657,8 +1860,7 @@ THREE.GLTFLoader = ( function () {
 
 				"meshes",
 				"skins",
-				"cameras",
-				"extensions"
+				"cameras"
 
 			] ).then( function ( dependencies ) {
 
@@ -1827,9 +2029,12 @@ THREE.GLTFLoader = ( function () {
 
 					}
 
-					if ( node.extensions && node.extensions.KHR_materials_common && node.extensions.KHR_materials_common.light ) {
+					if ( node.extensions
+							 && node.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ]
+							 && node.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ].light ) {
 
-						var light = dependencies.extensions.KHR_materials_common.lights[ node.extensions.KHR_materials_common.light ];
+						var extensionLights = extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ].lights;
+						var light = extensionLights[ node.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ].light ];
 
 						_node.add( light );
 
@@ -1845,70 +2050,6 @@ THREE.GLTFLoader = ( function () {
 
 	};
 
-	GLTFParser.prototype.loadExtensions = function () {
-
-		var json = this.json;
-
-		return _each( json.extensions, function ( extension, extensionId ) {
-
-			switch ( extensionId ) {
-
-				case "KHR_materials_common":
-
-					var extensionNode = {
-						lights: {}
-					};
-
-					var lights = extension.lights;
-
-					for ( var lightId in lights ) {
-
-						var light = lights[ lightId ];
-						var lightNode;
-
-						var lightParams = light[ light.type ];
-						var color = new THREE.Color().fromArray( lightParams.color );
-
-						switch ( light.type ) {
-
-							case "directional":
-								lightNode = new THREE.DirectionalLight( color );
-								lightNode.position.set( 0, 0, 1 );
-								break;
-
-							case "point":
-								lightNode = new THREE.PointLight( color );
-								break;
-
-							case "spot":
-								lightNode = new THREE.SpotLight( color );
-								lightNode.position.set( 0, 0, 1 );
-								break;
-
-							case "ambient":
-								lightNode = new THREE.AmbientLight( color );
-								break;
-
-						}
-
-						if ( lightNode ) {
-
-							extensionNode.lights[ lightId ] = lightNode;
-
-						}
-
-					}
-
-					return extensionNode;
-
-					break;
-
-			}
-
-		} );
-
-	};
-
 	GLTFParser.prototype.loadScenes = function () {
 
 		var json = this.json;

BIN
examples/models/gltf/CesiumMan/glTF-Binary/Cesium_Man.glb


BIN
examples/models/gltf/CesiumMilkTruck/glTF-Binary/CesiumMilkTruck.glb


BIN
examples/models/gltf/duck/glTF-Binary/duck.glb


BIN
examples/models/gltf/monster/glTF-Binary/Monster.glb


+ 38 - 17
examples/webgl_loader_gltf.html

@@ -79,7 +79,7 @@
 				top:72px;
 			}
 
-			#materials_extension_control {
+			#extensions_control {
 				position:absolute;
 				top:104px;
 			}
@@ -116,10 +116,14 @@
 		Animations
 		<div class="controlValue"><input type="checkbox" checked onclick="toggleAnimations();">Play</input></div>
 		</div>
-		<div class="control" id="materials_extension_control">
-			Shaders
+		<div class="control" id="extensions_control">
+			Extension
 			<div class="controlValue">
-			<input type="checkbox" id="materials_extension_checkbox" checked onclick="toggleMaterialsExtension();">Use built-in</input>
+				<select id="extensions_list" onchange="selectExtension();">
+					<option value="glTF">None</option>
+					<option value="glTF-MaterialsCommon">Built-in shaders</option>
+					<option value="glTF-Binary">Binary</option>
+				</select>
 			</div>
 		</div>
 
@@ -238,10 +242,22 @@
 				var loadStartTime = Date.now();
 				var status = document.getElementById("status");
 				status.innerHTML = "Loading...";
+
+				for (var i = 0; i < extensionSelect.children.length; i++) {
+					var child = extensionSelect.children[i];
+					child.disabled = sceneInfo.extensions.indexOf(child.value) === -1;
+					if (child.disabled && child.selected) {
+						extensionSelect.value = extension = 'glTF';
+					}
+				}
+
 				var url = sceneInfo.url;
 				var r = eval("/" + '\%s' + "/g");
-				var dir = useMaterialsExtension ? 'glTF-MaterialsCommon' :  'glTF';
-				url = url.replace(r, dir);
+				url = url.replace(r, extension);
+
+				if (extension === 'glTF-Binary') {
+					url = url.replace('.gltf', '.glb');
+				}
 
 				var loadStartTime = Date.now();
 				var status = document.getElementById("status");
@@ -405,14 +421,16 @@
 					animationTime: 3,
 					addLights:true,
 					shadows:true,
-					addGround:true
+					addGround:true,
+					extensions: ["glTF", "glTF-MaterialsCommon", "glTF-Binary"]
 				},
 				{
 					name : "Duck", url : "./models/gltf/duck/%s/duck.gltf",
 					cameraPos: new THREE.Vector3(0, 3, 5),
 					addLights:true,
 					addGround:true,
-					shadows:true
+					shadows:true,
+					extensions: ["glTF", "glTF-MaterialsCommon", "glTF-Binary"]
 				},
 				{
 					name : "Cesium Man", url : "./models/gltf/CesiumMan/%s/Cesium_Man.gltf",
@@ -420,7 +438,8 @@
 					 objectRotation: new THREE.Euler(0, 0, 0),
 					 addLights:true,
 					 addGround:true,
-					 shadows:true
+					 shadows:true,
+					 extensions: ["glTF", "glTF-MaterialsCommon", "glTF-Binary"]
 				},
 				{
 					name : "Cesium Milk Truck",
@@ -428,7 +447,8 @@
 					cameraPos: new THREE.Vector3(0, 3, 10),
 					addLights:true,
 					addGround:true,
-					shadows:true
+					shadows:true,
+					extensions: ["glTF", "glTF-MaterialsCommon", "glTF-Binary"]
 				},
 				{
 					name : "Snowflake",
@@ -438,7 +458,8 @@
 					objectPosition: new THREE.Vector3(0, 0, 0),
 					addLights:false,
 					addGround:false,
-					shadows:false
+					shadows:false,
+					extensions: ["glTF"]
 				},
 				{
 					name : "Snowflakes",
@@ -447,7 +468,8 @@
 					objectPosition: new THREE.Vector3(-1200, -1200, 0),
 					addLights:false,
 					addGround:false,
-					shadows:false
+					shadows:false,
+					extensions: ["glTF"]
 				}
 			];
 
@@ -558,15 +580,14 @@
 
 			}
 
-			var useextmaterials = document.getElementById("materials_extension_checkbox");
-			var useMaterialsExtension = useextmaterials.hasAttribute("checked");
-			function toggleMaterialsExtension()
+			var extensionSelect = document.getElementById("extensions_list");
+			var extension = extensionSelect.value;
+			function selectExtension()
 			{
-				useMaterialsExtension = !useMaterialsExtension;
+				extension = extensionSelect.value;
 				selectScene();
 			}
 
-
 			function cleanup() {
 
 				if (container && renderer) {