0t1 steckt noch in den Kinderschuhen.

Interaktive 3D-Objekte in p5.js

In diesem Tutorial lernst du wie du 3D-Objekte in p5.js erstellen kannst und diesen spielerisch Weise erforschen kannst. Von Tastaturkürzel bis zu Maus-Eingaben werden dir hier verschiedene Möglichkeiten geboten. Also schau doch einfach mal rein.

Die Idee für das Tutorial

Die Idee für unser Tutorial stammt von einem Processing-Tutorial (Mixtureby von Simon Greenwold), welches das erstellen von 3D-Formen erklärt.

Wir übersetzten dieses in p5.js und erweiterten den Code so, dass mit dem Objekt interagiert werden kann. Der Nutzer des Programms kann dann durch das experimentieren mit der Maus und seiner Tastatur die verschiedenen Änderungsmöglichkeiten des Objektes erforschen.

1. Erste Schritte

Zunächst muss der p5.js-Editor im Webbrowser geöffnet werden.

Bei jedem öffnen des Editors wird mit dem Programm ein Basis-Code geöffnet.

Alles was innerhalb der function setup( ) steht wird hierbei einmalig ausgeführt sobald das Programm gestartet wird.

Alles was innerhalb der function draw( ) steht wird immer wieder ausgeführt bis das Programm gestoppt wird.

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
}

2. Ein 3D-Objekt mit Beleuchtungen erstellen

Zunächst rekreieren wir das Processing Tutorial im p5.js-Editor.

Dafür erstellen wir zunächst in der function setup() eine Zeichenfläche in welcher unsere 3D-Objekte erstellt werden können und wir mit diesen interagieren können.

function setup() {
  createCanvas (640, 360, WEBGL); //Breite, Höhe, WebGraphicsLibrary
  noStroke();
}

WEBGL steht ausgeschrieben für Web Graphics Library.

Es ist eine JavaScript-Programmierschnittstelle, mit welcher es ohne Erweiterungen möglich ist 3D-Objekte im Webbrowser darzustellen. Dies wird benötigt damit wir unsere Objekte überhaupt erstellen und sehen können.

Außerdem definieren wir noStroke() um keine Umrandungen an den Objekten zu erhalten.

Innerhalb der function draw() definieren wir nun die Hintergrundfarbe, die Position des Objektes, die Beleuchtungen, die Rotation und das Objekt selbst.

function draw() {
  background(0); //Hintergrundfarbe

  // position des objektes
  translate(width / 50, height / 50);

Durch background() kann dem Hintergrund eine beliebige Farbe verliehen werden. Hier verwenden wir eine einzelne Zahl, welche als Abstufung von Grautönen interpretiert wird. 0 steht dabei für schwarz.

Um die Position des Objektes zu bestimmen nutzen wir translate() welches für das übersetzen in das Koordinatensystem verwendet wird. Die Anweisung innerhalb der Klammer bewegt das Objekt, durch das verwenden der x- und y-Koordinaten, an einen bestimmten Punkt. In unserem Fall in die Mitte unserer Zeichenfläche.

Als nächstes erstellen wir die Beleuchtung innerhalb unseres Programmes.

// position des objektes
  translate(width / 500, height / 500);
  
  // lila pointLight von rechts
  pointLight(184, 171, 236, // Farbe
             200, -150, 0); // Die x-, y-, z-Position

  // dunkel lila directionalLight von links
  directionalLight(91, 76, 152, // Farbe
                   1, 0, 0); // Die x-, y-, z-Position

  // hell lila spotLight von vorne
  spotLight(221, 213, 254, // Farbe
            0, 40, 200, // // Die x-, y-, z-Position
            0, -0.5, -0.5, // // Die x-, y-, z-Richtung
            PI / 2, 2); // Winkel, Konzentration

Als erstes setzen wir unser pointLight(), welches seine Beleuchtung von einem Punkt aus in alle Richtungen verstreut.

Da sich der p5.js-Editor automatisch im RGB-Modus befindet können durch das definieren von Zahlenwerten an den Stellen (Rot, Grün, Blau) eine Farbe bestimmen. Die Position des Lichtes definieren wir mit Koordinaten (x-, y-, z-Achse).

Dieselbe Vorgehensweise verwenden wir beim directionalLight(), welches eine gerichtete Beleuchtung von parallelen Strahlen ist.

Bei dem erstellen des spotLight(), welches sein Licht von einem Punkt in eine spezifische Richtung richtet, gibt es eine Abweichung von den anderen Beleuchtungen.

Beim spotLight() müssen wir sowohl die Richtung (x-, y- und z-Achse), als auch den Winkel und die Konzentration mit der beleuchtet werden soll definieren.

Als nächstes sorgen wir dafür, dass unser Objekt automatisch rotiert und unserer Maus mit seiner Rotation folgt.

// verfolgen der maus
  rotateY(map(mouseX, 0, width, 0, PI)); //Rotation folgt Maus in Y-Richtung
  rotateX(map(mouseY, 0, height, 0, PI)); //Rotation folgt Maus in X-Richtung

 // rotation des objektes
  rotateX(frameCount * 0.01); //Schnelligkeit der Rotation in X-Richtung
  rotateY(frameCount * 0.01); //Schnelligkeit der Rotation in X-Richtung

Mit rotateX() bestimmen wir die Rotation des Objektes um die x-Achse. Mit frameCount * 0.01 geben wir die Geschwindigkeit an, mit diesem Wert kann nach belieben gespielt werden um die perfekte Geschwindigkeit für das eigene Objekt zu kreieren.

Mit rotateX(map()) folgt die Rotation des Objektes in x-Richtung dem Mauszeiger sobald dieser über die Zeichenfläche fährt.

Dasselbe gilt ebenfalls für rotateY() und rotateY(map()), nur das diese mit der y-Achse verknüpft sind.

Im letzten Schritt erstellen wir nun unser 3D-Objekt.

// erstellen 3D-Objekt
  box(100, 100, 100); //Höhe, Breite, Länge der Box
}

Über die Anweisung box() erstellen wir eine Box. Wir definieren diese durch (Höhe, Breite, Länge), welche mit Zahlenwerten angegeben werden. Auch diese Werte sind frei wählbar und mit Ihnen kann experimentiert werden.

Das Programm kann nun über den Play-Button gestartet werden und die rotierende Box erscheint.

3. Ein komplexes interaktives 3D-Objekt erstellen

Wir erstellen nun ein neues Projekt im p5.js-Editor.

Zunächst definieren wir Variablen mit let. Durch let wird eine neue Variable erstellt und ihr ein Wert zugewiesen. Wir definieren nun also Variablen, welche wir an anderer Stelle nutzen können um diese nach unseren Vorstellungen zu verändern.

let a = 60;        //Länge wird gemessen durch x-Achse
let b = 30;        //Breite wird gemessen durch y-Achse 
let hue = 0;       //Farbton
let saturation = 0;  //Sättigung
let brightness = 0;  //Helligkeit
let segment = 16;    //Segmentanzahl
let currentShape = '7'; // Standardform ist Box

a und b verwenden wir um die Länge und Breite unseres Objektes zu definieren. Die Ausgangswerte sind hierbei frei wählbar und man kann mit ihnen experimentieren um ein gewünschtes Ergebnis zu erzielen.

Mit hue, saturation und brightness definieren wir die Ausgangswerte für den Farbton, die Sättigung und Helligkeit. Hierbei ist es sinnvoll den Wert 0 zu wählen. Wieso dieser bei 0 bleiben sollte wird an späterer Stelle genau erklärt.

segement dient dazu die Segmentanzahl des Objektes zu definieren. Hierbei empfiehlt es sich einen Ausgangswert von 12-20 zu wählen um ein detaillreiches Objekt bei der ersten Generierung zu erzielen, ohne dass damit gespielt werden muss.

Mit currentShape wird die Ausgangsform definiert wenn das Programm startet. Wieso hier die '7' für Box steht wird im weitern Verlauf des Tutorials noch genau erklärt.

Im function setup() erstellen wir wie bereits zuvor wieder unsere Zeichenfläche.

function setup() {
  createCanvas (640, 360, WEBGL);
  noStroke();
  colorMode(HSB);
  //Farbmodus wird ins HSB-Modell versetzt, 
  //damit die Werte: Farbton, Sättigung und Helligkeit bearbeitet werden können
}

Diese versetzen wir diesmal mit colorMode(HSB) vom RGB-Modell ins HSB-Modell. HSB steht für Hue, Saturation und Brightness oder auf deutsch: Farbton, Sättigung und Helligkeit. Diese Umwandlung ist wichtig damit wir mit diesen Werten am Ende interagieren können.

Als nächstes erstellen wir wieder unsere function draw() in der wir erneut die Hintergrundfarbe, die Position des Objektes, die Beleuchtungen, die Rotation und das Objekt selbst definieren.

//Funktion erstellt 3D-Objekt
function draw() {
  //Hintergrundfarbe, -Sättigung und -Helligkeit wird definiert
  background(hue, saturation, brightness);

  //Objekt wird in die Mitte vom Canvas gesetzt
  translate(width / 500, height / 500);

Durch background(hue, saturation, brightness) verleihen wir unserer Hintergrundfarbe zunächst die oben definierten Variablen. Der Hintergrund erscheint beim starten des Programmes erneut schwarz.

Mit translate() wird das Objekt wie zuvor zentriert.

So langsam wird es Zeit für etwas Licht im Dunkeln.

// Pointlight wird gesetzt
  pointLight(hue, saturation, 134, //Farbe, Sättigung und Helligkeit
             150, -150, 0); // Position des Lichtes

  // DirectionalLight wird gesetzt
  directionalLight(hue, saturation, 255, //Farbe, Sättigung und Helligkeit
                   5, 0, 0); // The x-, y-, z-axis direction

  // Spotlight wird gesetzt
  spotLight(hue, saturation, 203, //Farbe, Sättigung und Helligkeit
            0, 40, 200, // Position
            0, -0.5, -0.5, // Direction
            PI / 2, 2); // Angle, concentration

Wie definieren unser pointLight(), directionalLight() und spotLight() wie zuvor nur mit dem Unterschied, dass wir die ersten zwei Farbwerte mit den Variablen hue und saturation austauschen, um diese interaktiv verändern zu können. Nur den Helligkeitswert definieren wir explizit mit einem Zahlenwert. Der Grund dafür ist, dass ein zu hoher oder zu niedriger Helligkeitswert das Objekt entweder überbelichten oder verschwinden lassen würde. Mit den Helligkeitswerten kann trotzdem beliebig experimentiert werden um diese anzupassen.

Zuletzt definieren wir innerhalb unserer function draw() wieder die Veroflgung, Rotation und die Erstellung unseres Objektes.

//Verfolgung mit der Maus in y-Achse
  rotateY(map(mouseX, 0, width, 0, PI));

  //Verfolgung mit der Maus in x-Achse
  rotateX(map(mouseY, 0, height, 0, PI));

  ////Automatische Rotation in x-Achse 
  rotateX(frameCount * 0.01);

  //Automatische Rotation in y-Achse 
  rotateY(frameCount * 0.01);

  //3D-Objekt wird erstellt
  shapesetting(currentShape);
}

Die Verfolgung und Rotation des Objektes bleibt hierbei gleich wie im ersten Beispiel.

Jedoch erstellt sich unser Objekt hier durch shapesetting(currentShape). Der Grund dafür ist, dass wir es dem Nutzer ermöglichen wollen den Körper des Objektes zu verändern. Durch currentShape übernimmt das Objekt also die aktuell ausgewählte Form. Zu Beginn des Programmes wäre dies dann die von uns zu Beginn in der Variable definierte box.

Wir erstellen nun die function mouseDragged() um dem Nutzer zu ermöglichen die Größe unseres Objektes individuell und interaktiv zu verstellen.

//Die Länge und Breite wird durch drücken und bewegen der Maus berechnet
function mouseDragged() {

  //Längenwert wird geändert
  a = mouseX;
  if (a > 255) {
    a = 255;
  }
  //Breitenwert wird geändert
  b = mouseY;
  if (b > 255) {
    b = 255;
  }
}

Durch a = mouseX wird der Variable a die aktuelle x-Koordinate der Maus innerhalb der Zeichenfläche verliehen. Durch die if-Schleife wird definiert dass a sobald der Wert der x-Koordinate größer als 255 sein sollte auf 255 gesetzt wird. Damit soll verhindert werden, dass das Objekt zu groß für die Zeichenfläche wird.

Dasselbe gilt für b = mouseY nur das diese sich auf die y-Koordinaten beziehen.

In der function mouseWheel(event) ermöglichen wir es dem Nutzer die Segmentanzahl der Objekte durch das Scrollen des Mausrades zu verändern.

//Segmentzahl wird mit Drehen des Mausrades berechnet
function mouseWheel(event){

  //Segmente werden erhöht
  if (event.deltaY < 0) {
    segment += 1;
  }
  //Segmente werden 
  else if (event.deltaY > 0 && segment > 3) {
    segment -= 1;
  }
}

Durch mouseWheel(event) erstellen wir ein Event, welches ausgelöst wird sobald eine bestimmt Interaktion am Mausrad vollführt wird. event.deltaY wird ausgelöst, sobald das Mausrad betätigt wird und ist negativ wenn der Nutzer von sich hinweg scrolled, positiv wenn er zu sich scrolled. Wir erstellen also eine if-Schleife, welche besagt, sobald das event.deltaY kleiner 0 ist, wird die Segmentanzahl erhöht. Scrolled der User also von sich weg wird das Objekt detaillierter. Wir erstellen eine else if-Scleife, die besagt sobald event.deltaY größer 0 ist und die Segemntanzahl größer 3 ist, wird die Segmentanzahl reduziert. Scrolled der User also zu sich hin wird das Objekt abstrahiert. Die Segemntanzahl muss größer 3 bleiben, da das Objekt sonst nicht mehr 3-Dimensional ist.

Wir erweitern unseren Code mit der function keyPressed() um durch das drücken von Tastaturbefehlen die Farbwerte unseres Hintergrundes und unserer Beleuchtung interaktiv anpassen zu können.

//Funktion um Farbe, Helligkeit und Sättigung zu berechnen
function keyPressed() {

  //Steuerung rechts erhöht den Farbton
  if (keyCode === RIGHT_ARROW) {
    hue += 30;
    //Farbwert wird auf 0 gesetzt, um einen Loop zu erzeugen
    if(hue > 360){
      hue = 0;
    }
  }
  //Steuerung oben erhöht den Helligkeitswert
  else if (keyCode === UP_ARROW){
    brightness += 10;
    //Helligkeitswert wird auf 0 gesetzt, um einen Loop zu erzeugen
    if(brightness > 100){
      brightness = 0;
    }
  }
  else if (keyCode === LEFT_ARROW) {
    //Steuerung links erhöht den Sättigungswert
    saturation += 10;
    //Sättigungswert wird auf 0 gesetzt, um einen Loop zu erzeugen
    if (saturation > 100) {
      saturation = 0;
    }
  } 
  //currentshape wird überschrieben
  //ein neues 3D-Objekt wird erzeugt
  else {
    currentShape = key;
  }
}

keyPressed() ist eine Funktion die genau einmal ausgeführt wird wenn ein bestimmter Tastaturbefehl gedrückt wird. Wir verwenden dafür keyCode, welcher einen Code für die zuletzt gedrückten Tasten ausführt. Dieser besitzt auch eigene Variablen welche für bestimmte Tasten stehen. (Bspw. RIGHT_ARROW für die rechte Maustaste)

Wir erstellen zunächst eine if-Schleife, die den Farbton erhöht sobald die rechte Pfeiltaste gedrückt wird. Dieser erhöht sich dabei immer um genau 30. Wir implementieren eine weitere innere if-Schleife, welche dafür sorgt, dass sobald der sich auf addierende Wert 360 überschreitet wieder auf 0 zurückgesetzt wird.

Damit erstellen wir ein Loop. Dieses Loop wird benötigt, da der Farbton im HSB-Modell maximal den Wert 360 tragen kann. Würde dieser überschritten werden könnte keine Farbe mehr angezeigt werden und der Code würde einen Fehler anzeigen. Außerdem können damit die Farben immer wieder gewechselt werden und bleiben nicht an einem bestimmten Wert "stecken". Ebenfalls braucht man dadurch nur einen Tastaturbefehl um den Farbwert zu steuern und muss keinen Zweiten einführen um die Werte anschließend wieder zu subtrahieren. Dies ist die effizienteste Möglichkeit den Farbton zu wechseln.

Nach dem selben Prinzip verfahren wir bei dem Helligkeitswert mit UP_ARROW und dem Sättigungswert mit LEFT_ARROW. Der einzige Unterschied ist hierbei, dass die Werte 100 nicht überschreiten dürfen und sie in 10er Schritten addiert werden. Auch bei diesen Werten wird dabei ein Loop erzeugt um den Code möglichst kompakt und effizient zu halten.

Da wir aber im nächsten Schritt die Möglichkeit den Körper des Objektes über Tastenkürzel zu verändern einführen wollen, muss hier ein neues Objekt erstellt werden. Da das ausgewählte Objekt hier überschrieben wird und wieder zur Beginn definierten box werden würde. Um dies zu verhindern definieren wir mit einer else-Schleife die currentShape mit key. Die Form des Körpers bleibt somit bei dem dazu korrespondiernden zuletzt gedrückten Tastenkürzel.

Durch die function shapesetting(shape) ermöglichen wir es den Körper des Objektes durch das drücken verschiedener Tastenkürzel zu verändern.

//Das 3D-Objekt wird bestimmt mit Tastenkürzel 1,2,3,4,5,6
 function shapesetting(shape){
  if(shape === '1'){
    torus(a, b, segment);
  }
  else if (shape === '2'){
    box(a, b);
  }
  else if (shape === '3'){
    cone(a, b, segment + 1);
    //segment+1 ist dafür da, damit das 3D-Objekt nicht zu einer Plane wird
  }
  else if (shape === '4'){
    plane(a, b);
  }
  else if (shape === '5'){
    sphere(a, a, segment);
  }
  else if (shape === '6'){
    cylinder(a, b, segment + 1);
    //segment+1 ist dafür da, damit das 3D-Objekt nicht zu einer Plane wird
  }
  //muss im Code vorhanden sein, um das Anfangsobjekt zu erschaffen
  else{
    box(a);
  }
}

Durch eine if-Schleife definieren wir dass die shape des Objektes sobald das Tastenkürzel '1' gedrückt wurde zu einem torus wird der die Parameter (a, b, segement) beinhaltet. Diese Variablen haben wir ganz am Anfang definiert und werden hier von den verschiedenen Körpern übernommen.

Die anderen Körperformen werden nach demselben Schemata erstellt. Bei einer box und einer plane müssen jedoch nur die Parameter (a, b) definiert werden. Bei cone und cylinder muss bei den Paramtern noch (a, b, segement + 1) ergänzt werden, da diese sonst als plane angezeigt werden würden. Das +1 wird als benötigt um diesen Körpern die 3-Dimensionalität zu verleihen. sphere folgt genau demselben Schemata wie torus.

Zuletzt müssen wir noch ein Anfangsobjekt erschaffen. Dafür benutzen wir eine else-Schleife, welche box(a) ausführt. Das bedeutet sollte ein anderes Tastenkürzel gedrückt werden, zeigt das Programm wieder eine box an. Da durch die Variable let currentShape = '7' ein anderes Tastenkürzel ausgeführt wird und dadurch eine box erzeugt wird.

Danach kann das Programm durch das betätigen des Play-Buttons gestartet werden und mit dem 3D-Objekt interagiert werden.

Mit diesem Tutorial kann natürlich experimentiert werden. Viel Spaß beim erforschen.

Quelldateien

Hier findet ihr unsere Inspiration und hier ist das was wir daraus gemacht haben.

Schlagworte


© 0t1

Cookies

0t1 mag keine Kekse (und kein Tracking). Wir verwenden lediglich notwendige Cookies für essentielle Funktionen.

Wir verwenden Schriftarten von Adobe Fonts. Dafür stellt dein Browser eine Verbindung zu den Servern von Adobe in den USA her. Wenn du unsere Seite nutzen möchtest, musst du dich damit einverstanden erklären.

Weitere Informationen in unserer Datenschutzerklärung.