Преглед на файлове

GLTFLoader: Add basic example.

Don McCurdy преди 7 години
родител
ревизия
4e9fac6224

+ 1 - 0
examples/files.js

@@ -91,6 +91,7 @@ var files = {
 		"webgl_loader_fbx_nurbs",
 		"webgl_loader_gcode",
 		"webgl_loader_gltf",
+		"webgl_loader_gltf_extensions",
 		"webgl_loader_imagebitmap",
 		"webgl_loader_json_blender",
 		"webgl_loader_json_claraio",

+ 13 - 0
examples/models/gltf/DamagedHelmet/README.md

@@ -0,0 +1,13 @@
+# Damaged Helmet
+
+https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/DamagedHelmet
+
+## License Information
+
+Battle Damaged Sci-fi Helmet - PBR by [theblueturtle_](https://sketchfab.com/theblueturtle_), published under a Creative Commons Attribution-NonCommercial license
+
+https://sketchfab.com/models/b81008d513954189a063ff901f7abfe4
+
+## Modifications
+
+The original model was built on an early draft of glTF 2.0 that did not become final.  This new model has been imported and re-exported from Blender to bring it into alignment with the final release glTF 2.0 specification.

BIN
examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.bin


+ 204 - 0
examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf

@@ -0,0 +1,204 @@
+{
+    "accessors" : [
+        {
+            "bufferView" : 0,
+            "componentType" : 5123,
+            "count" : 46356,
+            "max" : [
+                14555
+            ],
+            "min" : [
+                0
+            ],
+            "type" : "SCALAR"
+        },
+        {
+            "bufferView" : 1,
+            "componentType" : 5126,
+            "count" : 14556,
+            "max" : [
+                0.9424954056739807,
+                0.8128451108932495,
+                0.900973916053772
+            ],
+            "min" : [
+                -0.9474585652351379,
+                -1.18715500831604,
+                -0.9009949564933777
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 2,
+            "componentType" : 5126,
+            "count" : 14556,
+            "max" : [
+                1.0,
+                1.0,
+                1.0
+            ],
+            "min" : [
+                -1.0,
+                -1.0,
+                -1.0
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 3,
+            "componentType" : 5126,
+            "count" : 14556,
+            "max" : [
+                0.9999759793281555,
+                1.998665988445282
+            ],
+            "min" : [
+                0.002448640065267682,
+                1.0005531199858524
+            ],
+            "type" : "VEC2"
+        }
+    ],
+    "asset" : {
+        "generator" : "Khronos Blender glTF 2.0 exporter",
+        "version" : "2.0"
+    },
+    "bufferViews" : [
+        {
+            "buffer" : 0,
+            "byteLength" : 92712,
+            "byteOffset" : 0,
+            "target" : 34963
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 174672,
+            "byteOffset" : 92712,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 174672,
+            "byteOffset" : 267384,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 116448,
+            "byteOffset" : 442056,
+            "target" : 34962
+        }
+    ],
+    "buffers" : [
+        {
+            "byteLength" : 558504,
+            "uri" : "DamagedHelmet.bin"
+        }
+    ],
+    "images" : [
+        {
+            "uri" : "Default_albedo.jpg"
+        },
+        {
+            "uri" : "Default_metalRoughness.jpg"
+        },
+        {
+            "uri" : "Default_emissive.jpg"
+        },
+        {
+            "uri" : "Default_AO.jpg"
+        },
+        {
+            "uri" : "Default_normal.jpg"
+        }
+    ],
+    "materials" : [
+        {
+            "emissiveFactor" : [
+                1.0,
+                1.0,
+                1.0
+            ],
+            "emissiveTexture" : {
+                "index" : 2
+            },
+            "name" : "Material_MR",
+            "normalTexture" : {
+                "index" : 4
+            },
+            "occlusionTexture" : {
+                "index" : 3
+            },
+            "pbrMetallicRoughness" : {
+                "baseColorTexture" : {
+                    "index" : 0
+                },
+                "metallicRoughnessTexture" : {
+                    "index" : 1
+                }
+            }
+        }
+    ],
+    "meshes" : [
+        {
+            "name" : "mesh_helmet_LP_13930damagedHelmet",
+            "primitives" : [
+                {
+                    "attributes" : {
+                        "NORMAL" : 2,
+                        "POSITION" : 1,
+                        "TEXCOORD_0" : 3
+                    },
+                    "indices" : 0,
+                    "material" : 0
+                }
+            ]
+        }
+    ],
+    "nodes" : [
+        {
+            "mesh" : 0,
+            "name" : "node_damagedHelmet_-6514",
+            "rotation" : [
+                0.7071068286895752,
+                0.0,
+                -0.0,
+                0.7071068286895752
+            ]
+        }
+    ],
+    "samplers" : [
+        {}
+    ],
+    "scene" : 0,
+    "scenes" : [
+        {
+            "name" : "Scene",
+            "nodes" : [
+                0
+            ]
+        }
+    ],
+    "textures" : [
+        {
+            "sampler" : 0,
+            "source" : 0
+        },
+        {
+            "sampler" : 0,
+            "source" : 1
+        },
+        {
+            "sampler" : 0,
+            "source" : 2
+        },
+        {
+            "sampler" : 0,
+            "source" : 3
+        },
+        {
+            "sampler" : 0,
+            "source" : 4
+        }
+    ]
+}

BIN
examples/models/gltf/DamagedHelmet/glTF/Default_AO.jpg


BIN
examples/models/gltf/DamagedHelmet/glTF/Default_albedo.jpg


BIN
examples/models/gltf/DamagedHelmet/glTF/Default_emissive.jpg


BIN
examples/models/gltf/DamagedHelmet/glTF/Default_metalRoughness.jpg


BIN
examples/models/gltf/DamagedHelmet/glTF/Default_normal.jpg


+ 88 - 567
examples/webgl_loader_gltf.html

@@ -1,19 +1,19 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<title>three.js webgl - glTF 2.0</title>
+		<title>three.js webgl - glTF loader</title>
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<style>
 			body {
 				font-family: Monospace;
-				background-color: #222222;
+				background-color: #000;
+				color: #fff;
 				margin: 0px;
 				overflow: hidden;
 			}
-
 			#info {
-				color: #808080;
+				color: #fff;
 				position: absolute;
 				top: 10px;
 				width: 100%;
@@ -21,622 +21,143 @@
 				z-index: 100;
 				display:block;
 			}
-
-			#container {
-				position: absolute;
-				top: 0px;
-				width:100%;
-				height:100%;
-				z-index: -1;
-			}
-
-			#controls {
-				position: absolute;
-				width: 200px;
-				bottom: 0px;
-				left: 0px;
-				padding: 10px;
-				background-color: White;
-				font: 13px "Lucida Grande", Lucida, Verdana, sans-serif;
-			}
-
-			#controls > div {
-				margin-bottom: 8px;
-			}
-
-			#controls hr {
-				border: 0px;
-				height: 1px;
-				margin-bottom: 10px;
-				background-color: #bbb;
-			}
-
-			#info a, .button {
-				color: #f00;
+			#info a {
+				color: #75ddc1;
 				font-weight: bold;
-				text-decoration: underline;
-				cursor: pointer
 			}
 		</style>
 	</head>
 
 	<body>
 		<div id="info">
-		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> -
-		<a href="https://github.com/KhronosGroup/glTF" target="_blank" rel="noopener">glTF</a> 2.0 loader
-		<br>
-		BoomBox by <a href="https://www.microsoft.com/" target="_blank" rel="noopener">Microsoft</a>
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - GLTFLoader<br />
+			Battle Damaged Sci-fi Helmet by
+			<a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br />
 		</div>
-	<div id="container"></div>
-	<div id="controls">
-		<div id="status">Loading...</div>
-		<hr />
-		<div>
-			Model
-			<select id="scenes_list" size="1" onchange="selectScene();" ondblclick="selectScene();"></select>
-		</div>
-		<div>
-			Camera
-			<select id="cameras_list" size="1" onchange="selectCamera();" ondblclick="selectCamera();"></select>
-		</div>
-		<div>
-			Animations
-			<input type="checkbox" checked onclick="toggleAnimations();">Play</input>
-		</div>
-		<div>
-			Extension
-			<select id="extensions_list" onchange="selectExtension();">
-				<option value="glTF">None (Default)</option>
-				<option value="glTF-Embedded">None (Embedded)</option>
-				<option value="glTF-Binary">None (Binary)</option>
-				<option value="glTF-MaterialsCommon">Common Materials</option>
-				<option value="glTF-pbrSpecularGlossiness">Specular-Glossiness (PBR)</option>
-			</select>
-		</div>
-	</div>
-		<script src="../build/three.js"></script>
-		<script src="js/controls/OrbitControls.js"></script>
-		<script src="js/loaders/GLTFLoader.js"></script>
-
-		<script>
-			var orbitControls = null;
-			var container, camera, scene, renderer, loader;
 
-			var cameraIndex = 0;
-			var cameras = [];
-			var cameraNames = [];
-			var defaultCamera = null;
-			var gltf = null;
-			var mixer = null;
-			var clock = new THREE.Clock();
-
-			function onload() {
+		<script src="../build/three.js"></script>
 
-				window.addEventListener( 'resize', onWindowResize, false );
-				document.addEventListener( 'keydown', function(e) { onKeyDown(e); }, false );
+		<script src="js/loaders/GLTFLoader.js"></script>
 
-				buildSceneList();
-				switchScene(0);
-				animate();
+		<script src="js/controls/OrbitControls.js"></script>
 
-			}
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
 
-			function initScene(index) {
+		<script>
 
-				container = document.getElementById( 'container' );
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
 
-				scene = new THREE.Scene();
-				scene.background = new THREE.Color( 0x222222 );
+			var container, stats, controls;
+			var camera, scene, renderer, light;
 
-				defaultCamera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 0.001, 1000 );
+			init();
+			animate();
 
-				//defaultCamera.up = new THREE.Vector3( 0, 1, 0 );
-				scene.add( defaultCamera );
-				camera = defaultCamera;
+			function init() {
 
-				var sceneInfo = sceneList[index];
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
 
-				var spot1 = null;
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.set( -1.8, 0.9, 2.7 );
 
-				if (sceneInfo.addLights) {
+				controls = new THREE.OrbitControls( camera );
+				controls.target.set( 0, -0.2, -0.2 );
+				controls.update();
 
-					var ambient = new THREE.AmbientLight( 0x222222 );
-					scene.add( ambient );
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x443333 );
+				scene.fog = new THREE.Fog( 0x443333, 2, 10 );
+
+				light = new THREE.HemisphereLight( 0xffffff, 0x444444 );
+				light.position.set( 0, 2, 0 );
+				scene.add( light );
+
+				light = new THREE.DirectionalLight( 0xffffff );
+				light.position.set( 0, 2, 1 );
+				light.castShadow = true;
+				light.shadow.camera.top = 1.8;
+				light.shadow.camera.bottom = -1.8;
+				light.shadow.camera.left = -1.2;
+				light.shadow.camera.right = 1.2;
+				scene.add( light );
+
+				// ground
+				var plane = new THREE.Mesh(
+					new THREE.PlaneBufferGeometry( 20, 20 ),
+					new THREE.MeshPhongMaterial( { color: 0x999999, specular: 0x101010 } )
+				);
+				plane.rotation.x = - Math.PI / 2;
+				plane.position.y = -1;
+				plane.receiveShadow = true;
+				scene.add(plane);
+
+				// envmap
+				var path = 'textures/cube/Park2/';
+				var format = '.jpg';
+				var envMap = new THREE.CubeTextureLoader().load( [
+					path + 'posx' + format, path + 'negx' + format,
+					path + 'posy' + format, path + 'negy' + format,
+					path + 'posz' + format, path + 'negz' + format
+				] );
 
-					var directionalLight = new THREE.DirectionalLight( 0xdddddd );
-					directionalLight.position.set( 0, 0, 1 ).normalize();
-					scene.add( directionalLight );
+				// model
+				var loader = new THREE.GLTFLoader();
+				loader.load( 'models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', function ( gltf ) {
 
-					spot1   = new THREE.SpotLight( 0xffffff, 1 );
-					spot1.position.set( 10, 20, 10 );
-					spot1.angle = 0.25;
-					spot1.distance = 1024;
-					spot1.penumbra = 0.75;
+					gltf.scene.traverse( function ( child ) {
 
-					if ( sceneInfo.shadows ) {
+						if ( child.isMesh ) {
 
-						spot1.castShadow = true;
-						spot1.shadow.bias = 0.0001;
-						spot1.shadow.mapSize.width = 2048;
-						spot1.shadow.mapSize.height = 2048;
+							child.material.envMap = envMap;
+							child.material.needsUpdate = true;
+							child.castShadow = true;
 
-					}
+						}
 
-					scene.add( spot1 );
+					} );
 
-				}
+					scene.add.apply( scene, gltf.scene.children );
 
-				// RENDERER
+				} );
 
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer = new THREE.WebGLRenderer();
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.gammaOutput = true;
-
-				if (sceneInfo.shadows) {
-					renderer.shadowMap.enabled = true;
-					renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-				}
-
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.PCFSoftShadowMap;
 				container.appendChild( renderer.domElement );
 
-				var ground = null;
-
-				if (sceneInfo.addGround) {
-					var groundMaterial = new THREE.MeshPhongMaterial({
-							color: 0xFFFFFF
-						});
-					ground = new THREE.Mesh( new THREE.PlaneBufferGeometry(512, 512), groundMaterial);
-
-					if (sceneInfo.shadows) {
-						ground.receiveShadow = true;
-					}
-
-					if (sceneInfo.groundPos) {
-						ground.position.copy(sceneInfo.groundPos);
-					} else {
-						ground.position.z = -70;
-					}
-
-					ground.rotation.x = -Math.PI / 2;
-
-					scene.add(ground);
-				}
-
-				loader = new THREE.GLTFLoader();
-
-				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");
-				url = url.replace(r, extension);
-
-				if (extension === 'glTF-Binary') {
-					url = url.replace('.gltf', '.glb');
-				}
-
-				var loadStartTime = performance.now();
-				var status = document.getElementById("status");
-				status.innerHTML = "Loading...";
-
-				loader.load( url, function(data) {
-
-					gltf = data;
-
-					var object = gltf.scene;
-
-					status.innerHTML = "Load time: " + ( performance.now() - loadStartTime ).toFixed( 2 ) + " ms.";
-
-					if (sceneInfo.cameraPos)
-						defaultCamera.position.copy(sceneInfo.cameraPos);
-
-					if (sceneInfo.center) {
-						orbitControls.target.copy(sceneInfo.center);
-					}
-
-					if (sceneInfo.objectPosition) {
-						object.position.copy(sceneInfo.objectPosition);
-
-						if (spot1) {
-							spot1.position.set(sceneInfo.objectPosition.x - 100, sceneInfo.objectPosition.y + 200, sceneInfo.objectPosition.z - 100 );
-							spot1.target.position.copy(sceneInfo.objectPosition);
-						}
-					}
-
-					if (sceneInfo.objectRotation)
-						object.rotation.copy(sceneInfo.objectRotation);
-
-					if (sceneInfo.objectScale)
-						object.scale.copy(sceneInfo.objectScale);
-
-					if ( sceneInfo.addEnvMap ) {
-
-						var envMap = getEnvMap();
-
-						object.traverse( function( node ) {
-
-							if ( node.material && ( node.material.isMeshStandardMaterial ||
-								 ( node.material.isShaderMaterial && node.material.envMap !== undefined ) ) ) {
-
-								node.material.envMap = envMap;
-								node.material.needsUpdate = true;
-
-							}
-
-						} );
-
-						scene.background = envMap;
-
-					}
-
-					object.traverse( function ( node ) {
-
-						if ( node.isMesh ) node.castShadow = true;
-
-					} );
-
-					cameraIndex = 0;
-					cameras = [];
-					cameraNames = [];
-
-					if (gltf.cameras && gltf.cameras.length) {
-
-						var i, len = gltf.cameras.length;
-
-						for (i = 0; i < len; i++) {
-
-							var addCamera = true;
-							var cameraName = gltf.cameras[i].parent.name || ('camera_' + i);
-
-							if (sceneInfo.cameras && !(cameraName in sceneInfo.cameras)) {
-									addCamera = false;
-							}
-
-							if (addCamera) {
-								cameraNames.push(cameraName);
-								cameras.push(gltf.cameras[i]);
-							}
-
-						}
-
-						updateCamerasList();
-						switchCamera(1);
-
-					} else {
-
-						updateCamerasList();
-						switchCamera(0);
-
-					}
-
-					var animations = gltf.animations;
-
-					if ( animations && animations.length ) {
-
-						mixer = new THREE.AnimationMixer( object );
-
-						for ( var i = 0; i < animations.length; i ++ ) {
-
-							var animation = animations[ i ];
-
-							// There's .3333 seconds junk at the tail of the Monster animation that
-							// keeps it from looping cleanly. Clip it at 3 seconds
-							if ( sceneInfo.animationTime )
-								animation.duration = sceneInfo.animationTime;
-
-							mixer.clipAction( animation ).play();
-
-						}
-
-					}
-
-					scene.add( object );
-					onWindowResize();
-
-				}, undefined, function ( error ) {
-
-					console.error( error );
-
-				} );
+				window.addEventListener( 'resize', onWindowResize, false );
 
-			orbitControls = new THREE.OrbitControls(defaultCamera, renderer.domElement);
+				// stats
+				stats = new Stats();
+				container.appendChild( stats.dom );
 
 			}
 
 			function onWindowResize() {
 
-				defaultCamera.aspect = container.offsetWidth / container.offsetHeight;
-				defaultCamera.updateProjectionMatrix();
-
-				var i, len = cameras.length;
-
-				for (i = 0; i < len; i++) { // just do it for default
-					cameras[i].aspect = container.offsetWidth / container.offsetHeight;
-					cameras[i].updateProjectionMatrix();
-				}
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
 
 				renderer.setSize( window.innerWidth, window.innerHeight );
 
 			}
 
+			//
+
 			function animate() {
+
 				requestAnimationFrame( animate );
-				if (mixer) mixer.update(clock.getDelta());
-				if (cameraIndex == 0)
-					orbitControls.update();
-				render();
-			}
 
-			function render() {
 				renderer.render( scene, camera );
-			}
-
-			function onKeyDown(event) {
-
-				var chr = String.fromCharCode(event.keyCode);
-
-				if (chr == ' ') {
-
-					index = cameraIndex + 1;
-					if (index > cameras.length)
-					index = 0;
-					switchCamera(index);
-
-				} else {
-
-					var index = parseInt(chr);
-					if (!isNaN(index)	&& (index <= cameras.length)) {
-						switchCamera(index);
-					}
-
-				}
-
-			}
-
-			var envMap;
-
-			function getEnvMap() {
-
-				if ( envMap ) {
-
-					return envMap;
-
-				}
-
-				var path = 'textures/cube/Park2/';
-				var format = '.jpg';
-				var urls = [
-					path + 'posx' + format, path + 'negx' + format,
-					path + 'posy' + format, path + 'negy' + format,
-					path + 'posz' + format, path + 'negz' + format
-				];
-
-				envMap = new THREE.CubeTextureLoader().load( urls );
-				envMap.format = THREE.RGBFormat;
-				return envMap;
-
-			}
-
-			var sceneList = [
-				{
-					name : 'BoomBox (PBR)', url : './models/gltf/BoomBox/%s/BoomBox.gltf',
-					cameraPos: new THREE.Vector3(0.02, 0.01, 0.03),
-					objectRotation: new THREE.Euler(0, Math.PI, 0),
-					addLights:true,
-					extensions: ['glTF', 'glTF-pbrSpecularGlossiness', 'glTF-Binary'],
-					addEnvMap: true
-				},
-				{
-					name : 'MetalRoughSpheres (PBR)', url : './models/gltf/MetalRoughSpheres/%s/MetalRoughSpheres.gltf',
-					cameraPos: new THREE.Vector3(2, 1, 15),
-					objectRotation: new THREE.Euler(0, 0, 0),
-					addLights:true,
-					extensions: ['glTF', 'glTF-Embedded'],
-					addEnvMap: true
-				},
-				{
-					name : 'Duck', url : './models/gltf/Duck/%s/Duck.gltf',
-					cameraPos: new THREE.Vector3(0, 3, 5),
-					addLights:true,
-					addGround:true,
-					shadows:true,
-					extensions: ['glTF', 'glTF-Embedded', 'glTF-MaterialsCommon', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
-				},
-				{
-					name : "Monster", url : "./models/gltf/Monster/%s/Monster.gltf",
-					cameraPos: new THREE.Vector3(30, 10, 70),
-					objectScale: new THREE.Vector3(0.4, 0.4, 0.4),
-					objectPosition: new THREE.Vector3(2, 1, 0),
-					objectRotation: new THREE.Euler(0, - 3 * Math.PI / 4, 0),
-					animationTime: 3,
-					addLights:true,
-					shadows:true,
-					addGround:true,
-					// TODO: 'glTF-MaterialsCommon'
-					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
-				},
-				{
-					name : "Cesium Man", url : "./models/gltf/CesiumMan/%s/CesiumMan.gltf",
-					 cameraPos: new THREE.Vector3(0, 3, 10),
-					 objectRotation: new THREE.Euler(0, 0, 0),
-					 addLights:true,
-					 addGround:true,
-					 shadows:true,
-					// TODO: 'glTF-MaterialsCommon'
-					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
-				},
-				{
-					name : "Cesium Milk Truck",
-					url : "./models/gltf/CesiumMilkTruck/%s/CesiumMilkTruck.gltf",
-					cameraPos: new THREE.Vector3(0, 3, 10),
-					addLights:true,
-					addGround:true,
-					shadows:true,
-					// TODO: 'glTF-MaterialsCommon'
-					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
-				},
-				{
-					name : "Rigged Simple",
-					url : "./models/gltf/RiggedSimple/%s/RiggedSimple.gltf",
-					cameraPos: new THREE.Vector3(0, 5, 15),
-					objectRotation: new THREE.Euler(0, 90, 0),
-					addLights:true,
-					shadows:true,
-					// TODO: 'glTF-MaterialsCommon'
-					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
-				},
-				{
-					name : 'Outlined Box',
-					url : './models/gltf/OutlinedBox/OutlinedBox.gltf',
-					cameraPos: new THREE.Vector3(0, 5, 15),
-					objectScale: new THREE.Vector3(0.01, 0.01, 0.01),
-					objectRotation: new THREE.Euler(0, 90, 0),
-					addLights:true,
-					shadows:true,
-					extensions: ['glTF']
-				},
-			];
-
-			function buildSceneList() {
-
-				var elt = document.getElementById('scenes_list');
-
-				while( elt.hasChildNodes() ){
-					elt.removeChild(elt.lastChild);
-				}
-
-				var i, len = sceneList.length;
-
-				for (i = 0; i < len; i++) {
-					var option = document.createElement("option");
-					option.text=sceneList[i].name;
-					elt.add(option);
-				}
-
-			}
-
-			function switchScene(index) {
-
-				cleanup();
-				initScene(index);
-				var elt = document.getElementById('scenes_list');
-				elt.selectedIndex = index;
-
-			}
-
-			function selectScene() {
-
-				var select = document.getElementById("scenes_list");
-				var index = select.selectedIndex;
-
-				if (index >= 0) {
-					switchScene(index);
-				}
-
-			}
-
-			function switchCamera(index) {
-
-				cameraIndex = index;
-
-				if (cameraIndex == 0) {
-					camera = defaultCamera;
-				}
-
-				if (cameraIndex >= 1 && cameraIndex <= cameras.length) {
-					camera = cameras[cameraIndex - 1];
-				}
-
-				var elt = document.getElementById('cameras_list');
-				elt.selectedIndex = cameraIndex;
-
-			}
-
-			function updateCamerasList() {
-
-				var elt = document.getElementById('cameras_list');
-
-				while( elt.hasChildNodes() ){
-					elt.removeChild(elt.lastChild);
-				}
-
-				var option = document.createElement("option");
-				option.text="[default]";
-				elt.add(option);
-
-				var i, len = cameraNames.length;
-
-				for (i = 0; i < len; i++) {
-					var option = document.createElement("option");
-					option.text=cameraNames[i];
-					elt.add(option);
-				}
-
-			}
-
-			function selectCamera() {
-
-				var select = document.getElementById("cameras_list");
-				var index = select.selectedIndex;
-
-				if (index >= 0) {
-					switchCamera(index);
-				}
-
-			}
-
-			function toggleAnimations() {
-
-				var i, len = gltf.animations.length;
-
-				for (i = 0; i < len; i++) {
-
-					var clip = gltf.animations[i];
-					var action = mixer.existingAction( clip );
-
-					if (action.isRunning()) {
-						action.stop();
-					} else {
-						action.play();
-					}
-
-				}
-
-			}
-
-			var extensionSelect = document.getElementById("extensions_list");
-			var extension = extensionSelect.value;
-			function selectExtension()
-			{
-				extension = extensionSelect.value;
-				selectScene();
-			}
-
-			function cleanup() {
-
-				if (container && renderer) {
-					container.removeChild(renderer.domElement);
-				}
-
-				cameraIndex = 0;
-				cameras = [];
-				cameraNames = [];
-				defaultCamera = null;
-
-				if (!loader || !mixer)
-					return;
 
-				mixer.stopAllAction();
+				stats.update();
 
 			}
 
-			onload();
 		</script>
 
 	</body>

+ 643 - 0
examples/webgl_loader_gltf_extensions.html

@@ -0,0 +1,643 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - glTF 2.0 - extensions</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #222222;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				color: #808080;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				z-index: 100;
+				display:block;
+			}
+
+			#container {
+				position: absolute;
+				top: 0px;
+				width:100%;
+				height:100%;
+				z-index: -1;
+			}
+
+			#controls {
+				position: absolute;
+				width: 200px;
+				bottom: 0px;
+				left: 0px;
+				padding: 10px;
+				background-color: White;
+				font: 13px "Lucida Grande", Lucida, Verdana, sans-serif;
+			}
+
+			#controls > div {
+				margin-bottom: 8px;
+			}
+
+			#controls hr {
+				border: 0px;
+				height: 1px;
+				margin-bottom: 10px;
+				background-color: #bbb;
+			}
+
+			#info a, .button {
+				color: #f00;
+				font-weight: bold;
+				text-decoration: underline;
+				cursor: pointer
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="info">
+		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> -
+		<a href="https://github.com/KhronosGroup/glTF" target="_blank" rel="noopener">glTF</a> 2.0 loader
+		<br>
+		BoomBox by <a href="https://www.microsoft.com/" target="_blank" rel="noopener">Microsoft</a>
+		</div>
+	<div id="container"></div>
+	<div id="controls">
+		<div id="status">Loading...</div>
+		<hr />
+		<div>
+			Model
+			<select id="scenes_list" size="1" onchange="selectScene();" ondblclick="selectScene();"></select>
+		</div>
+		<div>
+			Camera
+			<select id="cameras_list" size="1" onchange="selectCamera();" ondblclick="selectCamera();"></select>
+		</div>
+		<div>
+			Animations
+			<input type="checkbox" checked onclick="toggleAnimations();">Play</input>
+		</div>
+		<div>
+			Extension
+			<select id="extensions_list" onchange="selectExtension();">
+				<option value="glTF">None (Default)</option>
+				<option value="glTF-Embedded">None (Embedded)</option>
+				<option value="glTF-Binary">None (Binary)</option>
+				<option value="glTF-MaterialsCommon">Common Materials</option>
+				<option value="glTF-pbrSpecularGlossiness">Specular-Glossiness (PBR)</option>
+			</select>
+		</div>
+	</div>
+		<script src="../build/three.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/loaders/GLTFLoader.js"></script>
+
+		<script>
+			var orbitControls = null;
+			var container, camera, scene, renderer, loader;
+
+			var cameraIndex = 0;
+			var cameras = [];
+			var cameraNames = [];
+			var defaultCamera = null;
+			var gltf = null;
+			var mixer = null;
+			var clock = new THREE.Clock();
+
+			function onload() {
+
+				window.addEventListener( 'resize', onWindowResize, false );
+				document.addEventListener( 'keydown', function(e) { onKeyDown(e); }, false );
+
+				buildSceneList();
+				switchScene(0);
+				animate();
+
+			}
+
+			function initScene(index) {
+
+				container = document.getElementById( 'container' );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x222222 );
+
+				defaultCamera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 0.001, 1000 );
+
+				//defaultCamera.up = new THREE.Vector3( 0, 1, 0 );
+				scene.add( defaultCamera );
+				camera = defaultCamera;
+
+				var sceneInfo = sceneList[index];
+
+				var spot1 = null;
+
+				if (sceneInfo.addLights) {
+
+					var ambient = new THREE.AmbientLight( 0x222222 );
+					scene.add( ambient );
+
+					var directionalLight = new THREE.DirectionalLight( 0xdddddd );
+					directionalLight.position.set( 0, 0, 1 ).normalize();
+					scene.add( directionalLight );
+
+					spot1   = new THREE.SpotLight( 0xffffff, 1 );
+					spot1.position.set( 10, 20, 10 );
+					spot1.angle = 0.25;
+					spot1.distance = 1024;
+					spot1.penumbra = 0.75;
+
+					if ( sceneInfo.shadows ) {
+
+						spot1.castShadow = true;
+						spot1.shadow.bias = 0.0001;
+						spot1.shadow.mapSize.width = 2048;
+						spot1.shadow.mapSize.height = 2048;
+
+					}
+
+					scene.add( spot1 );
+
+				}
+
+				// RENDERER
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.gammaOutput = true;
+
+				if (sceneInfo.shadows) {
+					renderer.shadowMap.enabled = true;
+					renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+				}
+
+				container.appendChild( renderer.domElement );
+
+				var ground = null;
+
+				if (sceneInfo.addGround) {
+					var groundMaterial = new THREE.MeshPhongMaterial({
+							color: 0xFFFFFF
+						});
+					ground = new THREE.Mesh( new THREE.PlaneBufferGeometry(512, 512), groundMaterial);
+
+					if (sceneInfo.shadows) {
+						ground.receiveShadow = true;
+					}
+
+					if (sceneInfo.groundPos) {
+						ground.position.copy(sceneInfo.groundPos);
+					} else {
+						ground.position.z = -70;
+					}
+
+					ground.rotation.x = -Math.PI / 2;
+
+					scene.add(ground);
+				}
+
+				loader = new THREE.GLTFLoader();
+
+				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");
+				url = url.replace(r, extension);
+
+				if (extension === 'glTF-Binary') {
+					url = url.replace('.gltf', '.glb');
+				}
+
+				var loadStartTime = performance.now();
+				var status = document.getElementById("status");
+				status.innerHTML = "Loading...";
+
+				loader.load( url, function(data) {
+
+					gltf = data;
+
+					var object = gltf.scene;
+
+					status.innerHTML = "Load time: " + ( performance.now() - loadStartTime ).toFixed( 2 ) + " ms.";
+
+					if (sceneInfo.cameraPos)
+						defaultCamera.position.copy(sceneInfo.cameraPos);
+
+					if (sceneInfo.center) {
+						orbitControls.target.copy(sceneInfo.center);
+					}
+
+					if (sceneInfo.objectPosition) {
+						object.position.copy(sceneInfo.objectPosition);
+
+						if (spot1) {
+							spot1.position.set(sceneInfo.objectPosition.x - 100, sceneInfo.objectPosition.y + 200, sceneInfo.objectPosition.z - 100 );
+							spot1.target.position.copy(sceneInfo.objectPosition);
+						}
+					}
+
+					if (sceneInfo.objectRotation)
+						object.rotation.copy(sceneInfo.objectRotation);
+
+					if (sceneInfo.objectScale)
+						object.scale.copy(sceneInfo.objectScale);
+
+					if ( sceneInfo.addEnvMap ) {
+
+						var envMap = getEnvMap();
+
+						object.traverse( function( node ) {
+
+							if ( node.material && ( node.material.isMeshStandardMaterial ||
+								 ( node.material.isShaderMaterial && node.material.envMap !== undefined ) ) ) {
+
+								node.material.envMap = envMap;
+								node.material.needsUpdate = true;
+
+							}
+
+						} );
+
+						scene.background = envMap;
+
+					}
+
+					object.traverse( function ( node ) {
+
+						if ( node.isMesh ) node.castShadow = true;
+
+					} );
+
+					cameraIndex = 0;
+					cameras = [];
+					cameraNames = [];
+
+					if (gltf.cameras && gltf.cameras.length) {
+
+						var i, len = gltf.cameras.length;
+
+						for (i = 0; i < len; i++) {
+
+							var addCamera = true;
+							var cameraName = gltf.cameras[i].parent.name || ('camera_' + i);
+
+							if (sceneInfo.cameras && !(cameraName in sceneInfo.cameras)) {
+									addCamera = false;
+							}
+
+							if (addCamera) {
+								cameraNames.push(cameraName);
+								cameras.push(gltf.cameras[i]);
+							}
+
+						}
+
+						updateCamerasList();
+						switchCamera(1);
+
+					} else {
+
+						updateCamerasList();
+						switchCamera(0);
+
+					}
+
+					var animations = gltf.animations;
+
+					if ( animations && animations.length ) {
+
+						mixer = new THREE.AnimationMixer( object );
+
+						for ( var i = 0; i < animations.length; i ++ ) {
+
+							var animation = animations[ i ];
+
+							// There's .3333 seconds junk at the tail of the Monster animation that
+							// keeps it from looping cleanly. Clip it at 3 seconds
+							if ( sceneInfo.animationTime )
+								animation.duration = sceneInfo.animationTime;
+
+							mixer.clipAction( animation ).play();
+
+						}
+
+					}
+
+					scene.add( object );
+					onWindowResize();
+
+				}, undefined, function ( error ) {
+
+					console.error( error );
+
+				} );
+
+			orbitControls = new THREE.OrbitControls(defaultCamera, renderer.domElement);
+
+			}
+
+			function onWindowResize() {
+
+				defaultCamera.aspect = container.offsetWidth / container.offsetHeight;
+				defaultCamera.updateProjectionMatrix();
+
+				var i, len = cameras.length;
+
+				for (i = 0; i < len; i++) { // just do it for default
+					cameras[i].aspect = container.offsetWidth / container.offsetHeight;
+					cameras[i].updateProjectionMatrix();
+				}
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+				requestAnimationFrame( animate );
+				if (mixer) mixer.update(clock.getDelta());
+				if (cameraIndex == 0)
+					orbitControls.update();
+				render();
+			}
+
+			function render() {
+				renderer.render( scene, camera );
+			}
+
+			function onKeyDown(event) {
+
+				var chr = String.fromCharCode(event.keyCode);
+
+				if (chr == ' ') {
+
+					index = cameraIndex + 1;
+					if (index > cameras.length)
+					index = 0;
+					switchCamera(index);
+
+				} else {
+
+					var index = parseInt(chr);
+					if (!isNaN(index)	&& (index <= cameras.length)) {
+						switchCamera(index);
+					}
+
+				}
+
+			}
+
+			var envMap;
+
+			function getEnvMap() {
+
+				if ( envMap ) {
+
+					return envMap;
+
+				}
+
+				var path = 'textures/cube/Park2/';
+				var format = '.jpg';
+				var urls = [
+					path + 'posx' + format, path + 'negx' + format,
+					path + 'posy' + format, path + 'negy' + format,
+					path + 'posz' + format, path + 'negz' + format
+				];
+
+				envMap = new THREE.CubeTextureLoader().load( urls );
+				envMap.format = THREE.RGBFormat;
+				return envMap;
+
+			}
+
+			var sceneList = [
+				{
+					name : 'BoomBox (PBR)', url : './models/gltf/BoomBox/%s/BoomBox.gltf',
+					cameraPos: new THREE.Vector3(0.02, 0.01, 0.03),
+					objectRotation: new THREE.Euler(0, Math.PI, 0),
+					addLights:true,
+					extensions: ['glTF', 'glTF-pbrSpecularGlossiness', 'glTF-Binary'],
+					addEnvMap: true
+				},
+				{
+					name : 'MetalRoughSpheres (PBR)', url : './models/gltf/MetalRoughSpheres/%s/MetalRoughSpheres.gltf',
+					cameraPos: new THREE.Vector3(2, 1, 15),
+					objectRotation: new THREE.Euler(0, 0, 0),
+					addLights:true,
+					extensions: ['glTF', 'glTF-Embedded'],
+					addEnvMap: true
+				},
+				{
+					name : 'Duck', url : './models/gltf/Duck/%s/Duck.gltf',
+					cameraPos: new THREE.Vector3(0, 3, 5),
+					addLights:true,
+					addGround:true,
+					shadows:true,
+					extensions: ['glTF', 'glTF-Embedded', 'glTF-MaterialsCommon', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
+				},
+				{
+					name : "Monster", url : "./models/gltf/Monster/%s/Monster.gltf",
+					cameraPos: new THREE.Vector3(30, 10, 70),
+					objectScale: new THREE.Vector3(0.4, 0.4, 0.4),
+					objectPosition: new THREE.Vector3(2, 1, 0),
+					objectRotation: new THREE.Euler(0, - 3 * Math.PI / 4, 0),
+					animationTime: 3,
+					addLights:true,
+					shadows:true,
+					addGround:true,
+					// TODO: 'glTF-MaterialsCommon'
+					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
+				},
+				{
+					name : "Cesium Man", url : "./models/gltf/CesiumMan/%s/CesiumMan.gltf",
+					 cameraPos: new THREE.Vector3(0, 3, 10),
+					 objectRotation: new THREE.Euler(0, 0, 0),
+					 addLights:true,
+					 addGround:true,
+					 shadows:true,
+					// TODO: 'glTF-MaterialsCommon'
+					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
+				},
+				{
+					name : "Cesium Milk Truck",
+					url : "./models/gltf/CesiumMilkTruck/%s/CesiumMilkTruck.gltf",
+					cameraPos: new THREE.Vector3(0, 3, 10),
+					addLights:true,
+					addGround:true,
+					shadows:true,
+					// TODO: 'glTF-MaterialsCommon'
+					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
+				},
+				{
+					name : "Rigged Simple",
+					url : "./models/gltf/RiggedSimple/%s/RiggedSimple.gltf",
+					cameraPos: new THREE.Vector3(0, 5, 15),
+					objectRotation: new THREE.Euler(0, 90, 0),
+					addLights:true,
+					shadows:true,
+					// TODO: 'glTF-MaterialsCommon'
+					extensions: ['glTF', 'glTF-Embedded', 'glTF-pbrSpecularGlossiness', 'glTF-Binary']
+				},
+				{
+					name : 'Outlined Box',
+					url : './models/gltf/OutlinedBox/OutlinedBox.gltf',
+					cameraPos: new THREE.Vector3(0, 5, 15),
+					objectScale: new THREE.Vector3(0.01, 0.01, 0.01),
+					objectRotation: new THREE.Euler(0, 90, 0),
+					addLights:true,
+					shadows:true,
+					extensions: ['glTF']
+				},
+			];
+
+			function buildSceneList() {
+
+				var elt = document.getElementById('scenes_list');
+
+				while( elt.hasChildNodes() ){
+					elt.removeChild(elt.lastChild);
+				}
+
+				var i, len = sceneList.length;
+
+				for (i = 0; i < len; i++) {
+					var option = document.createElement("option");
+					option.text=sceneList[i].name;
+					elt.add(option);
+				}
+
+			}
+
+			function switchScene(index) {
+
+				cleanup();
+				initScene(index);
+				var elt = document.getElementById('scenes_list');
+				elt.selectedIndex = index;
+
+			}
+
+			function selectScene() {
+
+				var select = document.getElementById("scenes_list");
+				var index = select.selectedIndex;
+
+				if (index >= 0) {
+					switchScene(index);
+				}
+
+			}
+
+			function switchCamera(index) {
+
+				cameraIndex = index;
+
+				if (cameraIndex == 0) {
+					camera = defaultCamera;
+				}
+
+				if (cameraIndex >= 1 && cameraIndex <= cameras.length) {
+					camera = cameras[cameraIndex - 1];
+				}
+
+				var elt = document.getElementById('cameras_list');
+				elt.selectedIndex = cameraIndex;
+
+			}
+
+			function updateCamerasList() {
+
+				var elt = document.getElementById('cameras_list');
+
+				while( elt.hasChildNodes() ){
+					elt.removeChild(elt.lastChild);
+				}
+
+				var option = document.createElement("option");
+				option.text="[default]";
+				elt.add(option);
+
+				var i, len = cameraNames.length;
+
+				for (i = 0; i < len; i++) {
+					var option = document.createElement("option");
+					option.text=cameraNames[i];
+					elt.add(option);
+				}
+
+			}
+
+			function selectCamera() {
+
+				var select = document.getElementById("cameras_list");
+				var index = select.selectedIndex;
+
+				if (index >= 0) {
+					switchCamera(index);
+				}
+
+			}
+
+			function toggleAnimations() {
+
+				var i, len = gltf.animations.length;
+
+				for (i = 0; i < len; i++) {
+
+					var clip = gltf.animations[i];
+					var action = mixer.existingAction( clip );
+
+					if (action.isRunning()) {
+						action.stop();
+					} else {
+						action.play();
+					}
+
+				}
+
+			}
+
+			var extensionSelect = document.getElementById("extensions_list");
+			var extension = extensionSelect.value;
+			function selectExtension()
+			{
+				extension = extensionSelect.value;
+				selectScene();
+			}
+
+			function cleanup() {
+
+				if (container && renderer) {
+					container.removeChild(renderer.domElement);
+				}
+
+				cameraIndex = 0;
+				cameras = [];
+				cameraNames = [];
+				defaultCamera = null;
+
+				if (!loader || !mixer)
+					return;
+
+				mixer.stopAllAction();
+
+			}
+
+			onload();
+		</script>
+
+	</body>
+</html>