Heim >Web-Frontend >js-Tutorial >Erstellen eines 3D -Motors mit JavaScript
Bilder und andere flache Formen auf Webseiten anzeigen ist ziemlich einfach. Wenn es jedoch darum geht, 3D -Formen zu zeigen, wird die Dinge weniger einfach, da die 3D -Geometrie komplexer ist als die 2D -Geometrie. Dazu können Sie dedizierte Technologien und Bibliotheken wie WebGL und drei.Js verwenden.
Diese Technologien sind jedoch nicht erforderlich, wenn Sie nur einige grundlegende Formen wie einen Würfel anzeigen möchten. Darüber hinaus helfen sie Ihnen nicht, zu verstehen, wie sie funktionieren und wie wir 3D -Formen auf einem Flachbildschirm anzeigen können.
Ziel dieses Tutorials ist es zu erklären, wie wir eine einfache 3D -Engine für das Web erstellen können, ohne WebGL. Wir werden zuerst sehen, wie wir 3D -Formen speichern können. Dann werden wir sehen, wie diese Formen in zwei verschiedenen Ansichten angezeigt werden.
Die virtuelle Welt unterscheidet sich auf eine große Art und Weise von der realen: Nichts ist kontinuierlich und alles ist diskret. Sie können beispielsweise keinen perfekten Kreis auf einem Bildschirm anzeigen. Sie können es nähern, indem Sie ein reguläres Polygon mit vielen Kanten zeichnen: Je mehr Kanten Sie haben, desto „perfekter“ ist Ihr Kreis.
In 3D ist es dasselbe und jede Form muss mit dem 3D -Äquivalent eines Polygons angesprochen werden: einem Polyeder (eine 3D -Form, in der wir nur flache Gesichter nicht wie in einer Kugel finden). Es ist nicht überraschend, wenn wir über eine Form sprechen, die bereits ein Polyeder ist, wie ein Würfel, aber es ist etwas, das wir beachten sollten, wenn wir andere Formen wie eine Kugel anzeigen wollen.
Um zu erraten, wie man ein Polyeder speichert, müssen wir uns daran erinnern, wie so etwas in Mathematik identifiziert werden kann. Sie haben in Ihren Schuljahren sicherlich bereits einige grundlegende Geometrie gemacht. Um ein Quadrat zu identifizieren, nennen Sie es beispielsweise ABCD mit A, B, C und D, die sich auf Eckpunkte beziehen, die jede Ecke des Quadrats ausmachen.
Für unseren 3D -Motor wird es gleich sein. Wir werden zunächst jeden Scheitelpunkt unserer Form speichern. Dann listet diese Form ihre Gesichter auf, und jedes Gesicht listet seine Eckpunkte auf.
Um einen Scheitelpunkt darzustellen, brauchen wir die richtige Struktur. Hier erstellen wir eine Klasse, um die Koordinaten des Scheitelpunkts zu speichern.
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>
Jetzt kann ein Scheitelpunkt wie jedes andere Objekt erstellt werden:
<span>var A = new Vertex(10, 20, 0.5); </span>
Als nächstes erstellen wir eine Klasse, die unser Polyeder darstellt. Nehmen wir einen Würfel als Beispiel. Die Definition der Klasse ist unten, mit der Erklärung direkt danach.
<span>var <span>Cube</span> = function(center<span>, size</span>) { </span> <span>// Generate the vertices </span> <span>var d = size / 2; </span> <span>this.vertices = [ </span> <span>new Vertex(center.x - d, center.y - d, center.z + d), </span> <span>new Vertex(center.x - d, center.y - d, center.z - d), </span> <span>new Vertex(center.x + d, center.y - d, center.z - d), </span> <span>new Vertex(center.x + d, center.y - d, center.z + d), </span> <span>new Vertex(center.x + d, center.y + d, center.z + d), </span> <span>new Vertex(center.x + d, center.y + d, center.z - d), </span> <span>new Vertex(center.x - d, center.y + d, center.z - d), </span> <span>new Vertex(center.x - d, center.y + d, center.z + d) </span> <span>]; </span> <span>// Generate the faces </span> <span>this.faces = [ </span> <span>[this.vertices[0], this.vertices[1], this.vertices[2], this.vertices[3]], </span> <span>[this.vertices[3], this.vertices[2], this.vertices[5], this.vertices[4]], </span> <span>[this.vertices[4], this.vertices[5], this.vertices[6], this.vertices[7]], </span> <span>[this.vertices[7], this.vertices[6], this.vertices[1], this.vertices[0]], </span> <span>[this.vertices[7], this.vertices[0], this.vertices[3], this.vertices[4]], </span> <span>[this.vertices[1], this.vertices[6], this.vertices[5], this.vertices[2]] </span> <span>]; </span><span>}; </span>
Verwenden dieser Klasse können wir einen virtuellen Würfel erstellen, indem wir sein Zentrum und die Länge seiner Kanten angeben.
<span>var cube = new Cube(new Vertex(0, 0, 0), 200); </span>
Der Konstruktor der Würfelklasse beginnt mit der Erzeugung der Scheitelpunkte des Würfels, berechnet aus der Position des angegebenen Zentrums. Ein Schema wird klarer sein. Siehe also unter den Positionen der acht Scheitelpunkte, die wir generieren:
Dann listen wir die Gesichter auf. Jedes Gesicht ist ein Quadrat, daher müssen wir vier Eckpunkte für jedes Gesicht angeben. Hier habe ich mich entschlossen, ein Gesicht mit einem Array darzustellen, aber wenn Sie es benötigen, können Sie dafür eine spezielle Klasse erstellen.
Wenn wir ein Gesicht erstellen, verwenden wir vier Eckpunkte. Wir müssen ihre Position nicht wieder angeben, da sie in dem Objekt in diesem. Es ist praktisch, aber es gibt einen anderen Grund, warum wir das getan haben.
standardmäßig versucht JavaScript, die geringste Menge an Speicher zu verwenden. Um dies zu erreichen, kopiert es nicht die Objekte, die als Funktionsargumente übergeben oder sogar in Arrays gespeichert sind. Für unseren Fall ist es ein perfektes Verhalten.
In der Tat enthält jeder Scheitelpunkt drei Zahlen (ihre Koordinaten) sowie verschiedene Methoden, wenn wir sie hinzufügen müssen. Wenn wir für jedes Gesicht eine Kopie des Scheitelpunkts speichern, werden wir viel Speicher verwenden, was nutzlos ist. Hier sind nur Referenzen: Die Koordinaten (und andere Methoden) werden einmal und nur einmal gespeichert. Da jeder Scheitelpunkt von drei verschiedenen Gesichtern verwendet wird, indem Referenzen und nicht Kopien gespeichert werden, teilen wir den erforderlichen Speicher um drei (mehr oder weniger)!
Wenn Sie bereits mit 3D (zum Beispiel mit Software wie Blender oder mit Bibliotheken wie WebGL) gespielt haben, haben Sie vielleicht gehört, dass wir Dreiecke verwenden sollten. Hier habe ich mich dafür entschieden, keine Dreiecke zu verwenden.
Der Grund für diese Entscheidung ist, dass dieser Artikel eine Einführung in das Thema ist und wir grundlegende Formen wie Würfel anzeigen werden. Die Verwendung von Dreikeln zur Anzeige von Quadraten wäre in unserem Fall eher eine Komplikation als alles andere.
Wenn Sie jedoch vorhaben, einen vollständigeren Renderer zu erstellen, müssen Sie wissen, dass Dreiecke im Allgemeinen bevorzugt werden. Dafür gibt es zwei Hauptgründe:
Es gibt einen weiteren Vorteil beim Speichern von Referenzen anstelle von Kopien. Wenn wir ein Polyeder modifizieren möchten, teilt die Verwendung eines solchen Systems auch die erforderliche Anzahl von Vorgängen um drei.
, um zu verstehen, warum, erinnern wir uns noch einmal an unseren Mathe -Kurs. Wenn Sie ein Quadrat übersetzen möchten, übersetzen Sie es nicht wirklich. Tatsächlich übersetzen Sie die vier Eckpunkte und beitreten den Übersetzungen.
Hier werden wir das Gleiche tun: Wir werden die Gesichter nicht berühren. Wir wenden den gesuchten Betrieb auf jedem Scheitelpunkt an und sind fertig. Wenn Gesichter Referenzen verwenden, werden die Koordinaten der Gesichter automatisch aktualisiert. Sehen Sie zum Beispiel, wie wir unseren zuvor erstellten Würfel übersetzen können:
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>
Wir wissen, wie man 3D -Objekte speichert und wie man auf sie reagiert. Jetzt ist es Zeit zu sehen, wie man sie sieht! Aber zuerst brauchen wir einen kleinen Hintergrund in der Theorie, um zu verstehen, was wir tun werden.
Derzeit speichern wir 3D -Koordinaten. Ein Bildschirm kann jedoch nur 2D -Koordinaten anzeigen, sodass wir eine Möglichkeit benötigen, unsere 3D -Koordinaten in 2D zu verwandeln: So nennen wir eine Projektion in Mathematik. Die 3D -bis 2D -Projektion ist eine abstrakte Operation, die von einem neuen Objekt, der als virtuelle Kamera bezeichnet wird, durchgeführt wird. Diese Kamera nimmt ein 3D -Objekt an und wandelt ihre Koordinaten in 2D um, um sie an den Renderer zu senden, wodurch sie auf dem Bildschirm angezeigt werden. Wir werden hier annehmen, dass unsere Kamera am Ursprung unseres 3D -Raums platziert ist (so sind die Koordinaten (0,0,0)).
Seit Beginn dieses Artikels haben wir über Koordinaten gesprochen, dargestellt durch drei Zahlen: x, y und z. Aber um Koordinaten zu definieren, brauchen wir eine Grundlage: Ist z die vertikale Koordinate? Geht es nach oben oder nach unten? Es gibt keine universelle Antwort und keine Konvention, da Sie auswählen können, was Sie wollen. Das einzige, was Sie beachten müssen, ist, dass Sie, wenn Sie auf 3D -Objekte handeln, konsistent sein müssen, da sich die Formeln je nach IT ändern. In diesem Artikel habe ich die Grundlage ausgewählt, die Sie im Schema des Würfels oben sehen können: x von links nach rechts, y von hinten nach vorne und z von unten nach oben.
Nun wissen wir, was zu tun ist: Wir haben Koordinaten in der Basis (x, y, z) und um sie anzuzeigen, müssen wir sie in (x, z) in Koordinaten umwandeln: Da es sich um eine Ebene handelt Wir werden sie anzeigen.
Es gibt nicht nur eine Projektion. Schlimmer noch, es gibt eine unendliche Anzahl verschiedener Projektionen! In diesem Artikel werden wir zwei verschiedene Arten von Projektion sehen, die in der Praxis die am häufigsten verwendeten sind.
Bevor wir unsere Objekte projizieren, schreiben wir die Funktion, die sie anzeigt. Diese Funktion akzeptiert als Parameter ein Array, das die zu rendernden Objekte auflistet, den Kontext der Leinwand, mit dem die Objekte angezeigt werden müssen, und andere Details, die zum Zeichnen der Objekte am richtigen Ort erforderlich sind.
Das Array kann mehrere Objekte enthalten, die zum Rendern sind. Diese Objekte müssen eine Sache respektieren: Ein öffentliches Eigentum mit dem Namen Faces, das ein Array ist, das alle Gesichter des Objekts (wie unser zuvor erstellter Würfel) auflistet. Diese Gesichter können alles sein (Quadrat, Dreieck oder sogar ein Dodecagon, wenn Sie möchten): Sie müssen nur Arrays sein, die ihre Eckpunkte auflisten.
Schauen wir uns den Code für die Funktion an, gefolgt von der Erläuterung:
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>
Diese Funktion verdient eine Erklärung. Genauer gesagt müssen wir erklären, was diese Projekt () () -Funktion ist und was diese DX- und DY -Argumente sind. Der Rest ist im Grunde nichts anderes als die Auflistung der Objekte und das Zeichnen jedes Gesichts.
Wie der Name schon sagt, ist die Funktion von Project () hier, um 3D -Koordinaten in 2D umzuwandeln. Es akzeptiert einen Scheitelpunkt im 3D -Raum und gibt einen Scheitelpunkt in der 2D -Ebene zurück, die wir wie unten definieren können.
<span>var A = new Vertex(10, 20, 0.5); </span>
Anstatt die Koordinaten x und z zu benennen.
Der genaue Inhalt von Project () ist das, was wir im nächsten Abschnitt sehen werden: Es hängt von der Art der von Ihnen gewählten Projektion ab. Aber was auch immer dieser Typ ist, die Funktion render () kann wie jetzt so gehalten werden.Sobald wir Koordinaten in der Ebene haben, können wir sie auf der Leinwand anzeigen, und das tun wir ... mit einem kleinen Trick: Wir zeichnen die tatsächlichen Koordinaten nicht wirklich, die von der Projekt (Projekt ()) zurückgegeben wurden.
In der Tat gibt die Funktion project () die Koordinaten in einer virtuellen 2D -Ebene zurück, jedoch mit dem gleichen Ursprung als die, die wir für unseren 3D -Raum definiert haben. Wir möchten jedoch, dass der Ursprung im Zentrum unserer Leinwand steht. Deshalb übersetzen wir die Koordinaten: Der Scheitelpunkt (0,0) steht nicht in der Mitte der Leinwand, sondern (0 dx, 0 dy), wenn wir Wählen Sie DX und DY mit Bedacht. Wie wir wollen (DX, DY), die in der Mitte der Leinwand stehen, haben wir nicht wirklich die Wahl und definieren dx = canvas.
Schließlich das letzte Detail: Warum verwenden wir -y und nicht y direkt? Die Antwort liegt in unserer Wahl der Basis: Die Z -Achse ist nach oben gerichtet. Dann wird sich in unserer Szene ein Eckel mit einem positiven Z-Koordinaten nach oben bewegen. Auf der Leinwand ist die Y-Achse jedoch nach unten gerichtet: Ein Eckel mit einer positiven Y-Koordinaten bewegt sich nach unten. Deshalb müssen wir die Leinwand auf der Leinwand als Umkehrung der Z-Koordinate unserer Szene definieren.Jetzt, da die Funktion render () klar ist, ist es an der Zeit, das Projekt () zu betrachten.
orthographische Ansicht
Wir haben drei Koordinaten und wollen nur zwei. Was ist in einem solchen Fall am einfachsten? Entfernen Sie eine der Koordinaten. Und das tun wir in orthografischer Sicht. Wir werden die Koordinate entfernen, die die Tiefe darstellt: die y -Koordinate.
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>Sie können jetzt den gesamten Code testen, den wir seit Beginn dieses Artikels geschrieben haben: Es funktioniert! Herzlichen Glückwunsch, Sie haben gerade ein 3D -Objekt auf einem Flachbildschirm angezeigt!
Diese Funktion wird im folgenden Live -Beispiel implementiert, in dem Sie mit dem Würfel interagieren können, indem Sie ihn mit Ihrer Maus drehen.
Siehe die orthografische Sichtweise von Pen 3D von SitePoint (@sinepoint) auf CodePen.
Manchmal ist eine orthografische Sichtweise das, was wir wollen, da sie den Vorteil hat, die Parallelen zu bewahren. Es ist jedoch nicht die natürlichste Ansicht: Unsere Augen sehen nicht so. Deshalb werden wir eine zweite Projektion sehen: die Perspektivansicht.
Perspektivansicht
Um zu verstehen, warum, schauen wir uns ein Schema an, das die orthografische Sichtweise darstellt. Wir haben unsere Punkte orthogonal in einem Flugzeug projiziert.
Im Gegensatz zur orthografischen Ansicht ist der genaue Ort der Ebene hier wichtig: Wenn Sie das Flugzeug weit von der Kamera entfernt platzieren, erhalten Sie nicht den gleichen Effekt, als wenn Sie sie in die Nähe deshalb platzieren. Hier platzieren wir es in der Ferne D von der Kamera.
Ausgehend von einem Scheitelpunkt M (x, y, z) im 3D -Raum möchten wir die Koordinaten (x ', z') der Projektion m in der Ebene berechnen.
Um zu erraten, wie wir diese Koordinaten berechnen werden, nehmen wir einen weiteren Standpunkt ein und sehen das gleiche Schema wie oben, aber von oben gesehen.
Wir können eine im Intercept -Theorem verwendete Konfiguration erkennen. Auf dem obigen Schema kennen wir einige Werte: x, y und d unter anderem. Wir wollen x 'also berechnen
Wenn Sie nun die gleiche Szene von einer Seite von einer Seite betrachten, erhalten Sie ein ähnliches Schema, sodass Sie den Wert von z 'dank Z, y und d: z' = d / y * z. Wir können jetzt die Funktion project () mit der Perspektivansicht schreiben:
<span>var <span>Vertex</span> = function(x<span>, y, z</span>) { </span> <span>this.x = parseFloat(x); </span> <span>this.y = parseFloat(y); </span> <span>this.z = parseFloat(z); </span><span>}; </span>Diese Funktion kann im folgenden Live -Beispiel getestet werden. Noch einmal können Sie mit dem Würfel interagieren.
Siehe die Pen 3D -Perspektive -Ansicht von SitePoint (@sitepoint) auf CodePen.
Wörter schließen
Wir haben auch nicht über Texturen gesprochen. Hier haben alle unsere Formen die gleiche Farbe. Sie können dies ändern, indem Sie beispielsweise eine Farbeigenschaft in Ihren Objekten hinzufügen, um zu wissen, wie Sie sie zeichnen. Sie können sogar eine Farbe pro Gesicht auswählen, ohne viele Dinge zu ändern. Sie können auch versuchen, Bilder auf den Gesichtern anzuzeigen. Es ist jedoch schwieriger und die Detaillierung, wie man so etwas tut, würde einen ganzen Artikel annehmen.
Andere Dinge können geändert werden. Wir haben die Kamera auf den Ursprung des Raums gelegt, aber Sie können sie verschieben (eine Änderung der Basis wird benötigt, bevor Sie die Scheitelpunkte projizieren). Außerdem werden hier hinter der Kamera gezogene Scheitelpunkte gezeichnet, und das wollen wir nicht. Eine Ausschnittebene kann das beheben (leicht zu verstehen, weniger einfach zu implementieren).
Wie Sie sehen, ist der 3D -Motor, den wir hier erstellt haben, weit, um vollständig zu sein, und es ist auch meine eigene Interpretation. Sie können Ihren eigenen Klassen Ihren eigenen Treffer hinzufügen: Zum Beispiel verwendet drei.js eine dedizierte Klasse, um die Kamera und die Projektion zu verwalten. Außerdem haben wir grundlegende Mathematik verwendet, um die Koordinaten zu speichern. Wenn Sie jedoch eine komplexere Anwendung erstellen möchten und beispielsweise während eines Rahmens viele Eckpunkte drehen möchten, haben Sie keine reibungslose Erfahrung. Um es zu optimieren, benötigen Sie einige komplexere Mathematik: Homogene Koordinaten (projektive Geometrie) und Quaternionen.
Wenn Sie Ideen für Ihre eigenen Verbesserungen an der Engine haben oder basierend auf diesem Code etwas Cooles aufgebaut haben, lassen Sie es mich bitte in den Kommentaren unten wissen!
Das obige ist der detaillierte Inhalt vonErstellen eines 3D -Motors mit JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!