3D-Austauschformat glTF
Das jüngste Austauschformat ist glTF (Graphics Layer Transport Format) von der Kronos Group, das sich anschickt, zum Standard 3D-Format für Webseiten zu werden. glTF hält die Dateigröße so klein wie möglich und lädt Szenen besonders schnell. Alle Elemente der Szene werden in einer Datei exportiert – keine mtl-Datei wie beim Export der 3D-Elemente als Wavefront OBJ.
GLTFLoader
Der GLTFLoader ist noch kein Teil des three.js-Kerns, muss also genauso wie OrbitControls.js separat geladen werden.
<script type="module"> import * as THREE from '/threejs/r108/build/three.module.js'; import {OrbitControls} from '/threejs/r108/examples/jsm/controls/OrbitControls.js'; import {GLTFLoader} from '/threejs/r108/examples/jsm/loaders/GLTFLoader.js';
Maustaste drücken und Ziehen, um die Kamera zu rotieren; Scrollen zum Zoomen
Touchscreen: Ziehen zum Rotieren; Zoomen: zwei Finger-Geste
Parrot.glb und Flamingo.glb Modell von mrdoob / three.js auf Github
Im Script-Tag muss das Attribut type="module" gesetzt werden, damit die Scripte als Module geladen werden. Die Scripte können zwar auch auf dem klassischen Weg geladen werden, aber three.js empfiehlt das Laden als Modul seit R106.
Während beim Import als Wavefront obj neben der Modelldatei modell.obj auch noch die Materialdatei modell.mtl geladen werden muss, und Animationen nur durch eine Serie von Modell- und Materialdatei übernommen werden, ist in einer glb-Datei alles vorhanden, was für das Rendern, für Materialien und die Animation erforderlich ist.
Wenn in der glb-Datei Animationen enthalten sind, werden sie in Form eines Arrays von AnimationClips gespeichert. Die Funktion loadModells kann also gleich mehrere Modelle mit ihren jeweiligen Animationen verarbeiten.
function loadModels() { const loader = new GLTFLoader(); const onLoad = ( gltf, position ) => { const model = gltf.scene.children[ 0 ]; model.position.copy( position ); const animation = gltf.animations[ 0 ]; const mixer = new THREE.AnimationMixer( model ); mixers.push( mixer ); const action = mixer.clipAction( animation ); action.play(); scene.add( model ); }; // Ladefortschritt const onProgress = (message) => {console.log( "loading models" );}; // Fehlermeldung an Console const onError = ( errorMessage ) => { console.log( errorMessage ); }; // Modell asynchron laden. const parrotPosition = new THREE.Vector3( -20, 0, 2.5 ); loader.load( 'dist/objects/Parrot.glb', gltf => onLoad( gltf, parrotPosition ), onProgress, onError ); }
Das Laden der glb-Datei läuft asynchron ab, damit weitere Scriptanweisungen ausgeführt werden, während der onLoad auf das vollständige Laden wartet.
onLoad ist eine Callback-Funktion und wird aufgerufen, sobald das Modell fertig geladen ist.
onProgress ist ebenfalls ein Callback, das während des Ladevorgangs aufgerufen wird und wird geleert, wenn das Script wie gewünscht funktioniert.
const onProgress = () => {};
Zugriff auf Eigenschaften des importieren Objekts
Obwohl das Objekt mitsamt Texturen und Animationen in der glb-Datei importiert wird, lassen sich alle Elemente des Objekts erreichen.
castShadow : false children : [] (0) drawMode : 0 frustumCulled: true geometry : BufferGeometry {id: 9, uuid: "2611772B-5811-4779-8793-93DBDB0620CE", name: "", type: "BufferGeometry", index: BufferAttribute, …} id : 17 layers : Layers {mask: 1, set: function, enable: function, enableAll: function, toggle: function, …} material : MeshStandardMaterial {id: 14, uuid: "337C5D50-F089-4895-8F33-E1CE4BE84E40", name: "", type: "MeshStandardMaterial", fog: true, …} matrix : Matrix4 {elements: Array, isMatrix4: true, set: function, identity: function, clone: function, …} matrixAutoUpdate: true matrixWorld : Matrix4 {elements: Array, isMatrix4: true, set: function, identity: function, clone: function, …} matrixWorldNeedsUpdate: false modelViewMatrix: Matrix4 {elements: Array, isMatrix4: true, set: function, identity: function, clone: function, …} morphTargetDictionary: {flamingo_flyA_000: 0, flamingo_flyA_001: 1, flamingo_flyA_002: 2, flamingo_flyA_003: 3, flamingo_flyA_004: 4, …} morphTargetInfluences: [0, 0, 0, 0, 0, 0.7797618723507571, 0.22023812764924292, 0, 0, 0, …] (14) name : "mesh_0" normalMatrix : Matrix3 {elements: Array, isMatrix3: true, set: function, identity: function, clone: function, …} parent : Scene {id: 8, uuid: "2F44433C-79C1-4298-91AA-CC9EAA8ADF2E", name: "", type: "Scene", parent: null, …} position : Vector3 {x: -50, y: -50, z: -80, isVector3: true, set: function, …} quaternion : Quaternion {_x: 0, _y: 0, _z: 0, _w: 1, _onChangeCallback: function, …} receiveShadow: false renderOrder : 0 rotation : Euler {_x: 0, _y: 0, _z: 0, _order: "XYZ", _onChangeCallback: function, …} scale : Vector3 {x: 1, y: 1, z: 1, isVector3: true, set: function, …} type : "Mesh" up : Vector3 {x: 0, y: 1, z: 0, isVector3: true, set: function, …} userData : {targetNames: Array} uuid : "20ECB58E-2265-4209-9C3D-D988B8693A11" visible : true
Der gltf-Mixer steuert das Playback der Animation:
const mixer = new THREE.AnimationMixer( model ); action.setLoop( );
Die Animation nach einem Durchlauf anhalten
action.setLoop( THREE.LoopOnce );
Die Dauer der Animation bestimmen – z.b. 22 Sekunden
action.setDuration(22).play();
three.js Clock – die Stopuhr
Für die Steuerung der Animation wird die three.js Clock eingesetzt.
Das vollständige Script
Wenn kein canvas-Tag an three.js übergeben wird, erzeugt three.js das Canvas-Element selber. Ebensogut könnte als ein div benutzt werden. Für den renderer müsste dann nur eine Script-Zeile ergänzt werden: container.appendChild(renderer.domElement).
Ein direktes Canvas-Element ist besser lesbar und lässt sich flexibel an jede beliebige Stelle innerhalb des HTML-Markups einsetzen.
<canvas id="canvas"></canvas> … <script type="module"> import * as THREE from '/threejs/dist/threejs/build/three.module.js'; import {OrbitControls} from '/threejs/dist/threejs/examples/jsm/controls/OrbitControls.js'; import {GLTFLoader} from '/threejs/dist/threejs/examples/jsm/loaders/GLTFLoader.js'; const canvas = document.querySelector( '#canvas' ); let renderer = new THREE.WebGLRenderer({canvas}); const camera = new THREE.PerspectiveCamera( 86, 2, 1, 100 ); camera.position.set( 60, 1.5, 6.5 ); const controls = new OrbitControls( camera, canvas ); const scene = new THREE.Scene(); const mixers = []; const clock = new THREE.Clock(); function main() { scene.background = new THREE.Color('skyblue'); { const ambientLight = new THREE.HemisphereLight( 0xddeeff, 0x0f0e0d, 5 ); const mainLight = new THREE.DirectionalLight( 0xffffff, 5 ); mainLight.position.set( 10, 10, 10 ); scene.add( ambientLight, mainLight ); } { // WebGLRenderer Breite und Höhe setzen renderer.setSize( canvas.clientWidth, canvas.clientHeight ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.gammaFactor = 2.2; renderer.gammaOutput = true; renderer.physicallyCorrectLights = true; } loadModels(); function render() { renderer.render( scene, camera ); } function update() { const delta = clock.getDelta(); for ( const mixer of mixers ) { mixer.update( delta ); } } function onWindowResize() { camera.aspect = canvas.clientWidth / canvas.clientHeight; // Update für das Frustum der Kamera camera.updateProjectionMatrix(); renderer.setSize( canvas.clientWidth, canvas.clientHeight ); } window.addEventListener( 'resize', onWindowResize ); renderer.setAnimationLoop( () => { update(); render(); }); } function loadModels() { const loader = new GLTFLoader(); const onLoad = ( gltf, position ) => { const model = gltf.scene.children[ 0 ]; model.position.copy( position ); const animation = gltf.animations[ 0 ]; const mixer = new THREE.AnimationMixer( model ); mixers.push( mixer ); const action = mixer.clipAction( animation ); action.play(); scene.add( model ); }; // Ladefortschritt const onProgress = (message) => {console.log( "loading models" );}; // Fehlermeldung an Conole const onError = ( errorMessage ) => { console.log( errorMessage ); }; // Modell asynchron laden. const parrotPosition = new THREE.Vector3( -20, 0, 2.5 ); loader.load( 'dist/objects/Parrot.glb', gltf => onLoad( gltf, parrotPosition ), onProgress, onError ); } main(); </script>