THREEjs: Objekt und Material laden

Canvas WEBGL mit 3D Animationen

THREEjs importiert Modelle und Materialien aus 3D-Programmen wie Blender mithilfe von Loadern. Blender wiederum ist ein 3D-Programm für die Konstruktion und Animation von 3D-Modellen.

Damit haben wir die Zutaten für 3D-Animationen in Webseiten zusammen.

18-12-15 SITEMAP TUTORIALS

Material und Objekte aus Blender exportieren

POV-Ray erschien in den 90er Jahren als Freeware-Raytracer, basierte auf mathematischen Formeln und die 3D-Szene wurde mit SDL, einer C++-ähnlichen Szenen-Beschreibungssprache eingestellt.

Maustaste drücken und Ziehen, um die Kamera zu rotieren; Scrollen zum Zoomen
Touchscreen: Ziehen zum Rotieren; Zoomen: zwei Finger-Geste

Karikatur-Mühle Auf Der Insel Niedrig Poly 3D-Modell lowpolylab

Wenn wir heute 3D-Szenen für die Darstellung in einem Browser erstellen, stehen wir vor einer ähnlichen Situation: Szenen werden mit Javascript sozusagen im Blindflug beschrieben, eingerichtet und animiert. Wie in den 90ern werden die 3D-Modelle am besten in einem grafischen 3D-Programm erstellt und z.B. mit der WEBGL-Library THREEjs importiert.

Blender Export für THREEjs

Blender ist ein freies 3D-Programm für die Modellierung, Rendern und Animation von Objekten und Szenen und kommt bereits mit den nötigen Export-Funktionen: Wavefront OBJ oder GLTF (GL Transmission Format).

Blender 2.8 Wavefront OBJ Export
Blender 2.8 Export-Optionen

Der Export als Wavefront OBJ oder GLFT ist zuverlässig und einfach und ist in Blender 2.8 bereits ohne die Installation von Addons implementiert, während der Export als JSON, der sich über ein Blender-Addon realisieren lässt, heute als deprecated gilt.

Der Export als OBJ erzeugt zwei Dateien: modell.obj (Objekt beschrieben durch v für Vertex, vn für Normale, f für Faces) und modell.mtl, die Materialdatei. Wavefront-OBJ ist ein altes Austauschformat zwischen 3D-Anwendungen und ausgesprochen einfach: Alle Objekte der Szene werden als ein großes Mesh exportiert.

gLTF hingegen ist ein neues Austauschformat, das einen großen Teil der Daten binär speichert, so dass sie direkt in die GPU geladen werden können, statt sie wie VRML und OBJ zuvor als Text zu parsen. Daten, die nicht für das Render erforderlich sind, sind bereits entfernt, Polygone in Dreiecke umgewandelt, Materialien müssen nicht wie bei Wavefront in einer gesonderten mtl-Datei geladen werden.

THREEjs Module importieren

Zwei Module sind für den Import von 3D-Dateien für Objekte und Material zuständig: MTLLoader und OBJLoader. Für die Animation ist OrbitControls zuständig.

<script type="module">	
import * as THREE from '/threejs/r109/build/three.module.js';
import {OrbitControls} from '/threejs/r109/examples/jsm/controls/OrbitControls.js';
import {OBJLoader2} from '/threejs/r109/examples/jsm/loaders/OBJLoader2.js';
import {MTLLoader} from '/threejs/r109/examples/jsm/loaders/MTLLoader.js';
import {MtlObjBridge} from '/threejs/r109/examples/jsm/loaders/obj2/bridge/MtlObjBridge.js';

const canvas = document.querySelector('#canvas');
const renderer = new THREE.WebGLRenderer({canvas});
…

Javascript Import

Die Kamera hat genauso wie in Blender und anderen grafischen 3D-Programmen einen Bildwinkel (Field of View). Eine perspektivische Kamera kommt unserem räumlichen Sehen am nächsten.

const fov = 40;
const aspect = 2;  // the canvas default
const near = 10;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(-50, 50, 50);

Objekt und Material importieren

Bevor ein Objekt in THREEjs importiert wird, muss das Material mit mtlLoader.load () angelegt sein.

{
   const mtlLoader = new MTLLoader();
   mtlLoader.load("/objects/mill.mtl", (mtlParseResult) => {
      const objLoader = new OBJLoader2();
      const materials =  MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult);
      objLoader.addMaterials(materials);
      objLoader.load("/objects/mill.obj", (root) => {
         scene.add(root);
      });
   });
}

Das komplette Script

Das Script muss mit type="module" geladen werden, auch wenn es mit einem script-Tag aus der externen Javascript-Datei geladen wird. Module importieren und exportieren selber u.U. die Scripte, die sie selber brauchen. Das erspart das händische Einbinden von weiteren Scripten, die benötigt werden.

<script type="module">	
import * as THREE from "/threejs/r109/build/three.module.js";
import {OrbitControls} from "/threejs/r109/examples/jsm/controls/OrbitControls.js";
import {OBJLoader2} from "/threejs/r109/examples/jsm/loaders/OBJLoader2.js";
import {MTLLoader} from "/threejs/r109/examples/jsm/loaders/MTLLoader.js";
import {MtlObjBridge} from "/threejs/r109/examples/jsm/loaders/obj2/bridge/MtlObjBridge.js";

function main() {
   const canvas = document.querySelector("#canvas");
   const renderer = new THREE.WebGLRenderer({canvas});

   const fov = 40;
   const aspect = 2;  // Default-Seitenverhältnis des Canvas
   const near = 10;
   const far = 200;
   const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
   camera.position.set(-50, 20, 50);

   const controls = new OrbitControls(camera, canvas);
   controls.target.set(0, 5, 0);
   controls.update();

   const scene = new THREE.Scene();
   scene.background = new THREE.Color("lavender");
  
   const axesHelper = new THREE.AxesHelper( 80 ); 
   scene.add( axesHelper );

   {
      const skyColor = "azur";          // Himmelblau
      const groundColor = "burlywood";  // Blasses Orange
      const intensity = 0.5;
      const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
      scene.add(light);
   }
  
   {
      const spotLight = new THREE.SpotLight(0xcccccc);
      spotLight.position.set (100,500,300);
      scene.add (spotLight);
   }
	
   {
      const spotLight = new THREE.SpotLight(0xcccccc);
      spotLight.position.set (100,100,-300);
      scene.add (spotLight);
   }
	
   {
      const mtlLoader = new MTLLoader();
      mtlLoader.load("/objects/mill.mtl", (mtlParseResult) => {
         const objLoader = new OBJLoader2();
         const materials =  MtlObjBridge.addMaterialsFromMtlLoader(mtlParseResult);
         objLoader.addMaterials(materials);
         objLoader.load("/objects/mill.obj", (root) => {
            scene.add(root);
            scene.scale.set(20,20,20);
         });
      });
   }

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render() {
    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }
    renderer.render(scene, camera);
    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
</script>

Die immergrünen Browser haben kein Problem mit ES6 Javascript. Wenn die Anwendungen auch den ganz alten Browsern zugänglich gemacht werden sollen, kann ein Transpiler (Babel) eingesetzt werden.