Mit Three.js Programmieren – Ein Guide für Einsteiger

Mit WebGL lassen sich 3D-Anwendungen programmieren, die im Browser ausgeführt werden. Wie es der Name also schon verrät, bringt WebGL die Grafikprogrammierung ins Web! Three.js baut nun wiederum auf WebGL auf. Dabei ist das Programmieren mit Three.js fürs Web deutlich leichter. So ist hier die Syntax einfacher und das Ganze spielt sich auf einer etwas höheren Ebene ab. Wir müssen uns also nicht mit unnötig komplizierten Details herumschlagen. Three.js eignet sich insofern auch für Einsteiger in die Grafikprogrammierung, als dass die einfache Syntax es einem leichter macht, die Konzepte hinter dem Code besser zu verstehen. Wer generell lernen möchte, wie man programmiert, kann verschiedenen Apps nutzen! Ich habe mich mit Three.js mal etwas ausführlicher beschäftigt und hatte mit dem JavaScript-Framework ziemlich viel Spaß! Deswegen hier ein kleiner Three.js Guide für Einsteiger und Anfänger.

Was braucht man für Three.js?

Um mit Three.js programmieren zu können, braucht ihr zuallererst Three.js selber. Die dafür notwendigen Dateien könnt ihr euch hier herunterladen (Achtung: Der Download startet sofort). Hier kommt ihr zur Website von Three.js, wo ihr neben der Dokumentation auch diverse großartige Beispiele für das findet, was sich mit Three.js alles programmieren lässt. Ansonsten braucht ihr lediglich einen Browser, der mit WebGL umgehen kann (Chrome oder Firefox zum Beispiel) und einen Code-Editor. Ich persönlich bin ein Fan von VS-Code, jeder andere Editor tut es aber auch. Außerdem solltet ihr euer Three.js-Projekt nicht einfach über den Browser öffnen, sondern auf eurem Rechner einen Web-Server starten. Hier gibt es für viele Code-Editoren Plugins, die das gewaltig erleichtern. Ich benutze gerne Live Server für VS-Code. Hier kann ich mit einem Click in VS-Code den Server starten. Den gesamten Code könnt ihr euch am Ende des Artikels kopieren!

Ein Three.js Projekt anlegen

Bevor wir mit Three.js loslegen könne, müssen wir ein Projekt anlegen. Das geht über verschiedene Wege. Manche benutzen Package-Manager wie npm, was gerade bei größeren Projekten auch absolut empfehlenswert ist. In unserem Fall aber unnötig.

Die Struktur des Projektes

Als Erstes legen wir einen lib-Ordner an und entpacken die Three.js-Datein hierhinein. Als Nächstes legen wir einen src-Ordner an, in dem wir unseren eigenen JavaScript-Code speichern. Die erste Amtshandlung besteht darin, in src eine main.jsDatei anzulegen. In das Hauptverzeichnis kommt schließlich die index.html. Über diese wird die gesamte 3D-Anwendung, die wir mit Three.js programmieren, geladen. Euer Verzeichnis sollte jetzt also wie folgt aussehen:

Screenshot aus VSCode Projektstruktur
Die Struktur unseres Projektes.

Die index.html

Der Inhalt unserer index.htmlDatei ist relativ überschaubar. So müssen wir hier lediglich das übliche HTML-Gerüst einfügen sowie unsere main.jsDatei verlinken, in der unser Three.js-Code stehen wird.

Codeschnispel / Screenshot taken with carbon.now.sh

Die main.js

In unserer main.js-Datei müssen wir nun zuerst die three.js-Datei laden. Wir könnten das Skript auch in der index.html direkt verlinken, wie wir es zuvor getan haben. Ich finde es aber übersichtlicher, es zu Beginn in der main.js mit dem Befehl document.write() einzufügen. Dieser Befehl schreibt die Verlinkung eigentlich auch bloß in die index.html.

Codeschnispel / Screenshot taken with carbon.now.sh

Eine Szene in Three.js erstellen

In unserem Three.js Guide für Einsteiger wollen wir eine Szene mit einem Boden anlegen und darin ein Modell laden, das Schatten auf den Boden wirft. Dafür erstellen wir zuerst eine main()Funktion. Diese rufen wir mit window.onload allerdings erst auf, sobald unsere Anwendung geladen ist. So verhindern wir, während wir mit Three.js programmieren, dass auf Elemente zugegriffen wird, die vielleicht noch gar nicht geladen wurden.

Codeschnispel / Screenshot taken with carbon.now.sh

Szene, Kamera und Renderer

Nun müssen wir unserer main-Funktion eine Szene, eine Kamera und einen Renderer zuweisen. Der Szene können wir zusätzlich noch einen Hintergrund verpassen. Die Kamera ist dafür zuständig, die Objekte in der Szene abzubilden, während der Renderer die Szene rendert und ein Bild auf unserem Monitor erzeugt. Weiterhin können wir die shadowMap des Renderers aktivieren, um so später Schatten werfen zu können. Zusätzlich müssen wir die Position der Kamera verändern, da sie ansonsten im Zentrum der Anwendung sitzt. Über die Funktion lookAt() können wir bestimmen, auf welchen Punkt die Kamera guckt.

Codeschnispel / Screenshot taken with carbon.now.sh

Noch sehen wir allerdings nichts, wenn wir unseren Server starten. Das liegt daran, dass wir dem Renderer noch nicht mitgeteilt haben, dass er die Szene auch rendern soll. In der Funktion animate() können wir das nachholen. Über requestAnimationFrame() sagen wir außerdem, dass die animate-Funktion immer wieder aufgerufen und somit das Bild immer wieder gerendert werden soll.

Codeschnispel / Screenshot taken with carbon.now.sh

Zwar sehen wir in unserer Three.js 3D-Anwendung noch immer nichts, aber wir sind nicht mehr weit davon entfernt! Nun müssen wir das DOM-Element, das der Renderer erzeugt, lediglich an das Div-Objekt aus unserer index.html anhängen.

Codeschnispel / Screenshot taken with carbon.now.sh

Da Three.js bei Rotationen mit Radianten rechnet, ergibt es Sinn sich die Konstante DEG_TO_RAD anzulegen, wenn wir mit Three.js programmieren. Multiplizieren wir eine Grad-Zahl mit ihr, erhalten wir die Rotation in Radianten.

Codeschnispel / Screenshot taken with carbon.now.sh

Der Code sollte nun wie folgt aussehen:

Codeschnispel / Screenshot taken with carbon.now.sh

Boden und Licht

Nun können wir unserer 3D-Anwendung einen Boden hinzufügen. Dafür hat Three.js ein eigenes Objekt, das wir einfach erzeugen können und über scene.add() unserer Szene hinzufügen können. Den Boden müssen wir noch 90 Grad gegen den Uhrzeigersinn auf der X-Achse drehen und benutzen dafür die eben angelegte Konstante.

Codeschnispel / Screenshot taken with carbon.now.sh

Allerdings können wir den Boden jetzt noch nicht sehen, da es bis jetzt in unserer Szene keinerlei Licht gibt. Also fügen wir der Szene noch zwei weitere Lichter hinzu – ein AmbientLight und ein DirectionalLight. Letzteres hat eine konkrete Richtung und kann somit Schatten werfen. Der Schatten wird dabei mit einer Schattenkamera berechnet. Über das Objekt directionalObject können wir den Schatten und seine Attribute definieren.

Codeschnispel / Screenshot taken with carbon.now.sh

Nun sollte unser Code so aussehen:

Codeschnispel / Screenshot taken with carbon.now.sh

Und unsere Szene sollte nun so aussehen. Spektakulär ist sie noch nicht, aber immerhin ist etwas zu sehen.

Einfache Three.js Szene
Noch ist zwar nicht viel zu sehen, aber so langsam nimmt die Szene Gestalt an.

Ein 3D-Modell mit Three.js laden

Um 3D-Modelle in eine Szene zu laden, stellt Three.js verschiedene Loader zur Verfügung. Da wir ein FBX-Model laden wollen, brauchen wir den FBX-Loader. Um diesen in unserer 3D-Anwendung zu verwenden, müssen wir ihn, wie zuvor auch die three.js-Datei, einbinden. Außerdem benötigen wir das Script inflate.js, mit dem das FBX-Format verarbeitet werden kann.

Codeschnispel / Screenshot taken with carbon.now.sh

Der FBX-Loader

Nun können wir den Loader benutzen, wenn wir mit Three.js programmieren. Wir verwenden in diesem Beispiel ein eigenes 3D-Modell. Alternativ gibt es auf Sketchfab viele kostenlose und sehr gute Modelle. Das Modell speichert ihr am besten in einem Ordner model im Ordner src. Nun können wir das Model laden und für alle Child-Objekte festlegen, dass diese Schatten werfen und empfangen können.

Codeschnispel / Screenshot taken with carbon.now.sh

Unser Code sollte nun so aussehen:

Codeschnispel / Screenshot taken with carbon.now.shUnd unsere Szene sollte nun so aussehen:

Einfache Three.js Szene
Mittlerweile haben wir ein FBX-Modell geladen, das Schatten wirft.

Wir haben also mittlerweile ein 3D-Modell in unsere 3D-Anwendung geladen, das Schatten auf unseren Boden wirft. Zu guter Letzt wollen wir uns noch die Möglichkeit geben, mit der Kamera herumzufliegen.

OrbitControls

Um mit der Kamera in der Three.js-Szene navigieren zu können, stehen uns die OrbitControls aus Three.js zur Verfügung stellt.

Codeschnispel / Screenshot taken with carbon.now.sh

Nun können wir die OrbitControls der Szene hinzufügen. Dabei müssen wir dem Objekt die Kamera und das DOM-Element, an das wir die Szene gehängt haben, übergeben.

Codeschnispel / Screenshot taken with carbon.now.sh

Und… Tadaaaa! Fertig ist unsere Szene. Natürlich ist mit Three.js weitaus mehr möglich, aber dieser Artikel sollte ja auch nur einen kleinen Einblick in das Framework ermöglichen. Wer sich mehr vornehmen möchte, kann sich von den vielen Beispielen im Internet inspirieren lassen. Ich finde die Möglichkeiten, die Three.js bietet sehr spannend und hatte großen Spaß dabei, mit Three.js zu programmieren.

Der komplette Code:

document.write('');
document.write('');
document.write('');
document.write('');

const DEG_TO_RAD = Math.PI / 180;

function main () {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 150, 250);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor(new THREE.Color(0x000000));
renderer.shadowMap.enabled = true;

var ambientLight = new THREE.AmbientLight(0xffffff);
ambientLight.intensity = 0.4;

var directionaLight = new THREE.DirectionalLight(0xffffff);
directionaLight.position.set(0, 100, 100);
directionaLight.lookAt(scene.position);
directionaLight.intensity = 0.7;
directionaLight.castShadow = true;
directionaLight.shadow.radius = 2;
directionaLight.shadow.mapSize.width = 2048;
directionaLight.shadow.mapSize.height = 2048;
directionaLight.shadow.camera.top = 100;
directionaLight.shadow.camera.bottom = -100;
directionaLight.shadow.camera.left = -100;
directionaLight.shadow.camera.right = 100;

scene.add(ambientLight);
scene.add(directionaLight);

var floorGeometry = new THREE.PlaneGeometry(200, 200);
var floorMaterial = new THREE.MeshStandardMaterial({
color: 0xFFFFFF,
roughness: 0.4,
metalness: 0.0
});

var floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -90 * DEG_TO_RAD;
floor.receiveShadow = true;

scene.add(floor);

var npLogo = new THREE.Group();

var fbxloader = new THREE.FBXLoader();

fbxloader.load("src/model/NPLOGO.fbx", function (model) {
npLogo.add(model);

model.traverse( function ( child ) {
if ( child.isMesh ) {
child.castShadow = true;
child.receiveShadow = true;
}
});

});
npLogo.position.y = 2;
npLogo.scale.set(0.5, 0.5, 0.5);
scene.add(npLogo);

var orbitControls = new THREE.OrbitControls(camera, document.getElementById("3d_application"));
orbitControls.target = new THREE.Vector3(0, 80, 0);
orbitControls.update();

document.getElementById("3d_application").appendChild(renderer.domElement);

var animate = function () {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};

animate();
}

window.onload = main;


Screenshot aus VS-Code erstellt von Moritz Stoll mit carbon.now.sh

Titleimage by Денис Любезнов via stock.adobe.com

Moritz Stoll

studiert Medieninformatik an der HAW und ist Teil der Netzpiloten-Redaktion. Nebenbei arbeitet er als freiberuflicher Programmierer. Die Digitale Welt ist für ihn ein Ort voller Möglichkeiten und spannender Technologien, um damit Neues zu erschaffen und ganz viel auszuprobieren.


Artikel per E-Mail verschicken
Schlagwörter: ,