diff --git a/docs/components/gltf-model.md b/docs/components/gltf-model.md index fc8f9de400d..e0dc13afca0 100644 --- a/docs/components/gltf-model.md +++ b/docs/components/gltf-model.md @@ -22,6 +22,11 @@ Note that glTF is a fairly new specification and adoption is still growing. Work on the [three.js glTF loader][threejsgltf] and converters are still active. +> **NOTE:** A-Frame supports glTF 2.0. For models using older versions of the +> glTF format, use `gltf-model-legacy` from [donmccurdy/aframe-extras][extras]. + +[extras]: https://github.com/donmccurdy/aframe-extras/tree/master/src/loaders + ## Why use glTF? @@ -91,17 +96,18 @@ rendering. ## More Resources -The glTF format is fairly new, and few editors will export a `.gltf` file -directly. Instead, various converters are available or in progress: - -[fbx-converter]: http://gltf.autodesk.io/ -[collada-converter]: http://cesiumjs.org/convertmodel.html +[sketchfab]: https://sketchfab.com/models?features=downloadable&sort_by=-likeCount +[blender-exporter]: https://github.com/KhronosGroup/glTF-Blender-Exporter/ [obj-converter]: https://github.com/AnalyticalGraphicsInc/obj2gltf +[collada-converter]: http://cesiumjs.org/convertmodel.html -- [glTF Workflow for A Saturday Night](https://blog.mozvr.com/a-saturday-night-gltf-workflow/) -- [FBX → glTF][fbx-converter] - Coming soon. -- [COLLADA → glTF][collada-converter] +Over 100,000 glTF models are free for download on [Sketchfab][sketchfab], and +various exporters and converters converters are available: + +- [Blender Exporter](blender-exporter) - [OBJ → glTF][obj-converter] +- [COLLADA → glTF][collada-converter] +- [glTF Workflow for A Saturday Night](https://blog.mozvr.com/a-saturday-night-gltf-workflow/) [spec]: https://github.com/KhronosGroup/glTF diff --git a/src/components/gltf-model.js b/src/components/gltf-model.js index 382eaf1ed5f..70bfbafc5f6 100644 --- a/src/components/gltf-model.js +++ b/src/components/gltf-model.js @@ -9,7 +9,7 @@ module.exports.Component = registerComponent('gltf-model', { init: function () { this.model = null; - this.loader = new THREE.GLTFLoader(); + this.loader = new THREE.GLTF2Loader(); }, update: function () { @@ -24,7 +24,6 @@ module.exports.Component = registerComponent('gltf-model', { this.loader.load(src, function gltfLoaded (gltfModel) { self.model = gltfModel.scene || gltfModel.scenes[0]; self.model.animations = gltfModel.animations; - self.system.registerModel(self.model); el.setObject3D('mesh', self.model); el.emit('model-loaded', {format: 'gltf', model: self.model}); }); @@ -33,6 +32,5 @@ module.exports.Component = registerComponent('gltf-model', { remove: function () { if (!this.model) { return; } this.el.removeObject3D('mesh'); - this.system.unregisterModel(this.model); } }); diff --git a/src/lib/three.js b/src/lib/three.js index 25e33f03607..127f38e1405 100644 --- a/src/lib/three.js +++ b/src/lib/three.js @@ -19,7 +19,7 @@ if (THREE.Cache) { } // TODO: Eventually include these only if they are needed by a component. -require('three/examples/js/loaders/GLTFLoader'); // THREE.GLTFLoader +require('three/examples/js/loaders/GLTF2Loader'); // THREE.GLTF2Loader require('three/examples/js/loaders/OBJLoader'); // THREE.OBJLoader require('three/examples/js/loaders/MTLLoader'); // THREE.MTLLoader require('three/examples/js/loaders/ColladaLoader'); // THREE.ColladaLoader @@ -27,7 +27,7 @@ require('../../vendor/VRControls'); // THREE.VRControls require('../../vendor/VREffect'); // THREE.VREffect THREE.ColladaLoader.prototype.crossOrigin = 'anonymous'; -THREE.GLTFLoader.prototype.crossOrigin = 'anonymous'; +THREE.GLTF2Loader.prototype.crossOrigin = 'anonymous'; THREE.MTLLoader.prototype.crossOrigin = 'anonymous'; THREE.OBJLoader.prototype.crossOrigin = 'anonymous'; diff --git a/src/systems/gltf-model.js b/src/systems/gltf-model.js deleted file mode 100644 index 031e538f4b8..00000000000 --- a/src/systems/gltf-model.js +++ /dev/null @@ -1,41 +0,0 @@ -var registerSystem = require('../core/system').registerSystem; -var THREE = require('../lib/three'); - -/** - * glTF model system. - */ -module.exports.System = registerSystem('gltf-model', { - init: function () { - this.models = []; - }, - - /** - * Updates shaders for all glTF models in the system. - */ - tick: function () { - var sceneEl = this.sceneEl; - if (sceneEl.hasLoaded && this.models.length) { - THREE.GLTFLoader.Shaders.update(sceneEl.object3D, sceneEl.camera); - } - }, - - /** - * Registers a glTF asset. - * @param {object} gltf Asset containing a scene and (optional) animations and cameras. - */ - registerModel: function (gltf) { - this.models.push(gltf); - }, - - /** - * Unregisters a glTF asset. - * @param {object} gltf Asset containing a scene and (optional) animations and cameras. - */ - unregisterModel: function (gltf) { - var models = this.models; - var index = models.indexOf(gltf); - if (index >= 0) { - models.splice(index, 1); - } - } -}); diff --git a/src/systems/index.js b/src/systems/index.js index c7e7e0cdfd7..47eb84c5ac1 100755 --- a/src/systems/index.js +++ b/src/systems/index.js @@ -1,6 +1,5 @@ require('./camera'); require('./geometry'); -require('./gltf-model'); require('./light'); require('./material'); require('./shadow'); diff --git a/tests/assets/box/Box.bin b/tests/assets/box/Box.bin deleted file mode 100755 index 29a29e1385f..00000000000 Binary files a/tests/assets/box/Box.bin and /dev/null differ diff --git a/tests/assets/box/Box.gltf b/tests/assets/box/Box.gltf index 90df6f1b3da..7f603f07fcc 100755 --- a/tests/assets/box/Box.gltf +++ b/tests/assets/box/Box.gltf @@ -1,250 +1,142 @@ { - "accessors": { - "accessor_21": { - "bufferView": "bufferView_29", + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, "byteOffset": 0, - "byteStride": 0, "componentType": 5123, "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], "type": "SCALAR" }, - "accessor_23": { - "bufferView": "bufferView_30", + { + "bufferView": 1, "byteOffset": 0, - "byteStride": 12, "componentType": 5126, "count": 24, "max": [ - 0.5, - 0.5, - 0.5 + 1.0, + 1.0, + 1.0 ], "min": [ - -0.5, - -0.5, - -0.5 + -1.0, + -1.0, + -1.0 ], "type": "VEC3" }, - "accessor_25": { - "bufferView": "bufferView_30", + { + "bufferView": 1, "byteOffset": 288, - "byteStride": 12, "componentType": 5126, "count": 24, "max": [ - 1, - 1, - 1 + 0.5, + 0.5, + 0.5 ], "min": [ - -1, - -1, - -1 + -0.5, + -0.5, + -0.5 ], "type": "VEC3" } - }, - "animations": {}, - "asset": { - "generator": "collada2gltf@027f74366341d569dea42e9a68b7104cc3892054", - "premultipliedAlpha": true, - "profile": { - "api": "WebGL", - "version": "1.0.2" - }, - "version": "1.0" - }, - "bufferViews": { - "bufferView_29": { - "buffer": "Box", + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "name": "Red" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 576, "byteLength": 72, - "byteOffset": 0, "target": 34963 }, - "bufferView_30": { - "buffer": "Box", + { + "buffer": 0, + "byteOffset": 0, "byteLength": 576, - "byteOffset": 72, + "byteStride": 12, "target": 34962 } - }, - "buffers": { - "Box": { + ], + "buffers": [ + { "byteLength": 648, - "type": "arraybuffer", - "uri": "Box.bin" - } - }, - "materials": { - "Effect-Red": { - "name": "Red", - "technique": "technique0", - "values": { - "diffuse": [ - 0.8, - 0, - 0, - 1 - ], - "shininess": 256, - "specular": [ - 0.2, - 0.2, - 0.2, - 1 - ] - } - } - }, - "meshes": { - "Geometry-mesh002": { - "name": "Mesh", - "primitives": [ - { - "attributes": { - "NORMAL": "accessor_25", - "POSITION": "accessor_23" - }, - "indices": "accessor_21", - "material": "Effect-Red", - "mode": 4 - } - ] - } - }, - "nodes": { - "Geometry-mesh002Node": { - "children": [], - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "meshes": [ - "Geometry-mesh002" - ], - "name": "Mesh" - }, - "node_1": { - "children": [ - "Geometry-mesh002Node" - ], - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 0, - -1, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 1 - ], - "name": "Y_UP_Transform" - } - }, - "programs": { - "program_0": { - "attributes": [ - "a_normal", - "a_position" - ], - "fragmentShader": "Box0FS", - "vertexShader": "Box0VS" - } - }, - "scene": "defaultScene", - "scenes": { - "defaultScene": { - "nodes": [ - "node_1" - ] - } - }, - "shaders": { - "Box0FS": { - "type": 35632, - "uri": "Box0FS.glsl" - }, - "Box0VS": { - "type": 35633, - "uri": "Box0VS.glsl" - } - }, - "skins": {}, - "techniques": { - "technique0": { - "attributes": { - "a_normal": "normal", - "a_position": "position" - }, - "parameters": { - "diffuse": { - "type": 35666 - }, - "modelViewMatrix": { - "semantic": "MODELVIEW", - "type": 35676 - }, - "normal": { - "semantic": "NORMAL", - "type": 35665 - }, - "normalMatrix": { - "semantic": "MODELVIEWINVERSETRANSPOSE", - "type": 35675 - }, - "position": { - "semantic": "POSITION", - "type": 35665 - }, - "projectionMatrix": { - "semantic": "PROJECTION", - "type": 35676 - }, - "shininess": { - "type": 5126 - }, - "specular": { - "type": 35666 - } - }, - "program": "program_0", - "states": { - "enable": [ - 2929, - 2884 - ] - }, - "uniforms": { - "u_diffuse": "diffuse", - "u_modelViewMatrix": "modelViewMatrix", - "u_normalMatrix": "normalMatrix", - "u_projectionMatrix": "projectionMatrix", - "u_shininess": "shininess", - "u_specular": "specular" - } + "uri": "Box0.bin" } - } -} \ No newline at end of file + ] +} diff --git a/tests/assets/box/Box0.bin b/tests/assets/box/Box0.bin new file mode 100755 index 00000000000..d7798abb516 Binary files /dev/null and b/tests/assets/box/Box0.bin differ diff --git a/tests/assets/box/Box0FS.glsl b/tests/assets/box/Box0FS.glsl deleted file mode 100755 index 6e928dc7006..00000000000 --- a/tests/assets/box/Box0FS.glsl +++ /dev/null @@ -1,17 +0,0 @@ -precision highp float; -varying vec3 v_normal; -uniform vec4 u_diffuse; -uniform vec4 u_specular; -uniform float u_shininess; -void main(void) { -vec3 normal = normalize(v_normal); -vec4 color = vec4(0., 0., 0., 0.); -vec4 diffuse = vec4(0., 0., 0., 1.); -vec4 specular; -diffuse = u_diffuse; -specular = u_specular; -diffuse.xyz *= max(dot(normal,vec3(0.,0.,1.)), 0.); -color.xyz += diffuse.xyz; -color = vec4(color.rgb * diffuse.a, diffuse.a); -gl_FragColor = color; -} diff --git a/tests/assets/box/Box0VS.glsl b/tests/assets/box/Box0VS.glsl deleted file mode 100755 index 9e3592280a2..00000000000 --- a/tests/assets/box/Box0VS.glsl +++ /dev/null @@ -1,12 +0,0 @@ -precision highp float; -attribute vec3 a_position; -attribute vec3 a_normal; -varying vec3 v_normal; -uniform mat3 u_normalMatrix; -uniform mat4 u_modelViewMatrix; -uniform mat4 u_projectionMatrix; -void main(void) { -vec4 pos = u_modelViewMatrix * vec4(a_position,1.0); -v_normal = u_normalMatrix * a_normal; -gl_Position = u_projectionMatrix * pos; -} diff --git a/tests/assets/box/Box_no_default_scene.gltf b/tests/assets/box/Box_no_default_scene.gltf old mode 100644 new mode 100755 index ca98dc3cab0..71f527df33c --- a/tests/assets/box/Box_no_default_scene.gltf +++ b/tests/assets/box/Box_no_default_scene.gltf @@ -1,249 +1,141 @@ { - "accessors": { - "accessor_21": { - "bufferView": "bufferView_29", + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, "byteOffset": 0, - "byteStride": 0, "componentType": 5123, "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], "type": "SCALAR" }, - "accessor_23": { - "bufferView": "bufferView_30", + { + "bufferView": 1, "byteOffset": 0, - "byteStride": 12, "componentType": 5126, "count": 24, "max": [ - 0.5, - 0.5, - 0.5 + 1.0, + 1.0, + 1.0 ], "min": [ - -0.5, - -0.5, - -0.5 + -1.0, + -1.0, + -1.0 ], "type": "VEC3" }, - "accessor_25": { - "bufferView": "bufferView_30", + { + "bufferView": 1, "byteOffset": 288, - "byteStride": 12, "componentType": 5126, "count": 24, "max": [ - 1, - 1, - 1 + 0.5, + 0.5, + 0.5 ], "min": [ - -1, - -1, - -1 + -0.5, + -0.5, + -0.5 ], "type": "VEC3" } - }, - "animations": {}, - "asset": { - "generator": "collada2gltf@027f74366341d569dea42e9a68b7104cc3892054", - "premultipliedAlpha": true, - "profile": { - "api": "WebGL", - "version": "1.0.2" - }, - "version": "1.0" - }, - "bufferViews": { - "bufferView_29": { - "buffer": "Box", + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "name": "Red" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 576, "byteLength": 72, - "byteOffset": 0, "target": 34963 }, - "bufferView_30": { - "buffer": "Box", + { + "buffer": 0, + "byteOffset": 0, "byteLength": 576, - "byteOffset": 72, + "byteStride": 12, "target": 34962 } - }, - "buffers": { - "Box": { + ], + "buffers": [ + { "byteLength": 648, - "type": "arraybuffer", - "uri": "Box.bin" - } - }, - "materials": { - "Effect-Red": { - "name": "Red", - "technique": "technique0", - "values": { - "diffuse": [ - 0.8, - 0, - 0, - 1 - ], - "shininess": 256, - "specular": [ - 0.2, - 0.2, - 0.2, - 1 - ] - } - } - }, - "meshes": { - "Geometry-mesh002": { - "name": "Mesh", - "primitives": [ - { - "attributes": { - "NORMAL": "accessor_25", - "POSITION": "accessor_23" - }, - "indices": "accessor_21", - "material": "Effect-Red", - "mode": 4 - } - ] - } - }, - "nodes": { - "Geometry-mesh002Node": { - "children": [], - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 1 - ], - "meshes": [ - "Geometry-mesh002" - ], - "name": "Mesh" - }, - "node_1": { - "children": [ - "Geometry-mesh002Node" - ], - "matrix": [ - 1, - 0, - 0, - 0, - 0, - 0, - -1, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 1 - ], - "name": "Y_UP_Transform" - } - }, - "programs": { - "program_0": { - "attributes": [ - "a_normal", - "a_position" - ], - "fragmentShader": "Box0FS", - "vertexShader": "Box0VS" - } - }, - "scenes": { - "scene1": { - "nodes": [ - "node_1" - ] - } - }, - "shaders": { - "Box0FS": { - "type": 35632, - "uri": "Box0FS.glsl" - }, - "Box0VS": { - "type": 35633, - "uri": "Box0VS.glsl" - } - }, - "skins": {}, - "techniques": { - "technique0": { - "attributes": { - "a_normal": "normal", - "a_position": "position" - }, - "parameters": { - "diffuse": { - "type": 35666 - }, - "modelViewMatrix": { - "semantic": "MODELVIEW", - "type": 35676 - }, - "normal": { - "semantic": "NORMAL", - "type": 35665 - }, - "normalMatrix": { - "semantic": "MODELVIEWINVERSETRANSPOSE", - "type": 35675 - }, - "position": { - "semantic": "POSITION", - "type": 35665 - }, - "projectionMatrix": { - "semantic": "PROJECTION", - "type": 35676 - }, - "shininess": { - "type": 5126 - }, - "specular": { - "type": 35666 - } - }, - "program": "program_0", - "states": { - "enable": [ - 2929, - 2884 - ] - }, - "uniforms": { - "u_diffuse": "diffuse", - "u_modelViewMatrix": "modelViewMatrix", - "u_normalMatrix": "normalMatrix", - "u_projectionMatrix": "projectionMatrix", - "u_shininess": "shininess", - "u_specular": "specular" - } + "uri": "Box0.bin" } - } -} \ No newline at end of file + ] +} diff --git a/tests/components/gltf-model.test.js b/tests/components/gltf-model.test.js index f729f54c67d..460ff9c5fc7 100644 --- a/tests/components/gltf-model.test.js +++ b/tests/components/gltf-model.test.js @@ -71,7 +71,7 @@ suite('gltf-model', function () { animations: animations }; - this.sinon.stub(THREE, 'GLTFLoader', function MockGLTFLoader () { + this.sinon.stub(THREE, 'GLTF2Loader', function MockGLTFLoader () { this.load = function (url, onLoad) { process.nextTick(onLoad.bind(null, gltfMock)); }; diff --git a/tests/systems/gltf-model.test.js b/tests/systems/gltf-model.test.js deleted file mode 100644 index 2aa351c2bce..00000000000 --- a/tests/systems/gltf-model.test.js +++ /dev/null @@ -1,37 +0,0 @@ -/* global process, setup, suite, test, sinon, THREE */ -var entityFactory = require('../helpers').entityFactory; - -suite('glTF system', function () { - setup(function (done) { - var el = this.el = entityFactory(); - this.sinon.spy(THREE.GLTFLoader.Shaders, 'update'); - el.addEventListener('loaded', function () { - done(); - }); - }); - - suite('tick', function () { - test('does nothing when no models are present', function () { - var sceneEl = this.el.sceneEl; - var model = {cool: 'very'}; - - sceneEl.systems['gltf-model'].tick(100, 10); - sinon.assert.notCalled(THREE.GLTFLoader.Shaders.update); - - sceneEl.systems['gltf-model'].registerModel(model); - sceneEl.systems['gltf-model'].unregisterModel(model); - sceneEl.systems['gltf-model'].tick(110, 10); - sinon.assert.notCalled(THREE.GLTFLoader.Shaders.update); - }); - - test('updates shaders', function () { - var sceneEl = this.el.sceneEl; - var model = {cool: 'very'}; - - sceneEl.systems['gltf-model'].registerModel(model); - sceneEl.systems['gltf-model'].tick(110, 10); - sinon.assert.calledOnce(THREE.GLTFLoader.Shaders.update); - sinon.assert.calledWith(THREE.GLTFLoader.Shaders.update, sceneEl.object3D, sceneEl.camera); - }); - }); -});