Willkommen im letzten und wahrscheinlich schwierigsten Kapitel dieser Serie. Hier werden wir uns die Engine selbst vornehmen, Informationen aus ihr lesen und sie damit verändern.Es wird damit möglich sein, auf einfache Art und Weise 2D und 3D miteinander zu kombinieren, um Effekte zu zaubern, die normalerweise nur sehr schwer möglich wären. Viel Spaß! |
Die ersten Befehle, mit denen wir uns beschäftigen wollen, hängen sehr stark mit der Kamera zusammen. Wenn man schon die Positionen und Drehungen von Objekten mit Matrizen so schön ausrechnen kann, ist es nur logisch, dass man die Kamera genau so behandelt. Die Kamera lässt sich grundsätzlich mit 3 verschiedenen Matrizen beschreiben.Die erste Matrix beschreibt die Welt um die Kamera herum, die zweite die Position der Kamera darin und die letzte setzt alles in Relation zur Bildschirmauflösung. Bevor wir damit jedoch anfangen, werfen wir einen kurzen Blick auf denRendervorgang in DirectX, es hilft uns nachher, die Bedeutung dieser drei Matrizen besser zu verstehen. |
Der Rendervorgang läuft zunächst so ab, dass bei allen Objekten die Transformationen per Matrix durchgeführt werden. Dazu wird die sogenannte Weltmatrix (= die Matrix, die die Welt beschreibt) der speziellen Transformationsmatrix des Objektes gleichgesetzt und dann werden alle Vertices des Objektes damit transformiert. Dadurch erhält man die Positionen in der virtuellen Welt.Danach werden alle diese Positionen in Relation zur Kamera gesetzt, mittels der ViewMatrix. Jetzt hat man alle Positionen relativ zur Kamera, d.h. die Kamera befindet sich genau im Ursprung der Welt und die Vertices sind drumherum angeordnet.Der nächste Schritt ist die Transformation dieser 3D-Koordinaten in 2-dimensionale Bildschirmkoordinaten. Dafür wird die sogenannte Projektionsmatrix verwendet. Nun haben wir normale Bildschirmkoordinaten, allerdings müssen diese nicht unbedingt auf dem Bildschirm sein (könnten z.B. negative Koordinaten sein).Deshalb wird zum Schluss noch eine Viewport Transformation durchgeführt, die die "überstehenden" Koordinaten abschneidet. Erst jetzt wird tatsächlich gerendert, alles andere war nur eine große Rechnung :).Der Vorteil? Diese Rechnung wird genau ein Mal pro Objekt durchgeführt und kann dann für alle Vertices verwendet werden; ein deutlicher Zeitvorteil. |
![]() |
Es ist also zu sehen, dass die drei Matrizen der Kamera ein integraler Bestandteil des Rendervorgangs selbst sind. Ich will mit der einfachsten Matrix anfangen, der Weltmatrix.Wie bereits erwähnt, enthält die Weltmatrix die Transformationen des aktuell betrachteten Objektes zur Renderzeit. Zu allen anderen Zeitpunkten enthält sie, na was wohl? Die Identitätsmatrix.Da es uns nicht möglich ist, in den Rendervorgang mit einzugreifen, ist der Befehl world matrix4 Matrixnummer mit dem man die aktuelle Weltmatrix in die Matrix mit der angebenen Nummer kopiert, nur eine andere Form, die Identitätsmatrix zu setzen.Wir können sie also nicht gebrauchen, um Positionen zu berechnen, da die Identität überhaupt nichts ändert, wenn wir sie mit anderen Matrizen multiplizieren, aber dieser Befehl ist für Shader wichtig, da diese während des Rendervorgangs Code ausführen und die Matrix zu diesem Zeitpunkt gesetzt ist. Da dies aber kein Shadertutorial ist, gehe ich nun nicht weiter darauf ein. |
![]() |
Kommen wir zum zweiten Schritt des Rendervorganges und damit zur zweiten Matrix, der Viewmatrix. Diese enthält nun wichtige Informationen zur Lage und Position der aktuellen Kamera. Diese Matrix ist die nützlichste für uns, da man durch eine Transformation eines beliebigen Vektors die neuen Koordinaten relativ zur Kamera ermitteln kann, was bedeutet, dass in diesem neuen Koordinatensystem der Ursprung nun in der Kamera liegt und diese eine Blickrichtung entlang der Z-Achse hat (alle Winkel sind hier 0°).Man kopiert sie mit dem Befehl view matrix4 Matrixnummer in die angegebene Matrix.Die Kameraposition definiert sich aus drei verschiedenen Richtungsvektoren, dem LookAt-Vektor, der die neue Z-Achse in die Tiefe ist, dem Up-Vektor, der anzeigt, wo genau oben ist (beschreibt im Prinzip die Drehung um die Z-Achse) und der Right-Vektor, der die neue X-Achse ist. Dieser letzte Vektor lässt sich aus den anderen beiden bestimmen, indem man das Kreuzprodukt berechnet. Der Ortsvektor ist die Position der Kamera im Raum, allerdings negiert, da man den Ursprung der Weltkoordinaten ja relativ zur Kamera beschreiben will. |
![]() |
Einer der wichtigsten Schritte beim Rendervorgang ist zweifellos die Umwandlung der 3D-View Koordinaten in 2D-Bildschirmkoordinaten. Da die Koordinaten zu diesem Zeitpunkt schon relativ zur Kamera berechnet wurden, kann man davon ausgehen, dass die Kamera in ihrem eigenen System im Ursprung liegt und nicht gedreht ist. Das vereinfacht die Rechnung enorm, weshalb die sogenannte Projektionsmatrix eine der einfachsten ist, wie ich finde. Was für Informationen sollten in so einer Matrix stecken?Na zunächst könnte uns erstmal interessieren, welche Art von Projektion wir überhaupt wollen. In diesem Zusammenhang fallen mir mindestens zwei verschiedene Arten ein und zwar perspektivische und orthogonale Projektion. Perspektivische Projektion hat als markantes Merkmal, dass die Objekte mit zunehmender Entfernung kleiner werden, wie man es auch aus dem täglichen Leben kennt.Bei orthogonaler Projektion ist das nicht der Fall. Hier bleiben die Objekte und Winkel genau gleich groß. Solche Projektionen werden häufig bei Ingenieursskizzen oder Isometrieengines eingesetzt (Diablo 1 & 2 verwenden das z.B.). Damit ist der Grundgedanke der Projektionsmatrix auch schon erfasst, alles, was man sonst noch so festlegen kann, ist "Luxus".Es lassen sich zusätzlich auch noch Sachen festlegen, wie Blickwinkel, linke, rechte, vordere und hintere Clipping Ebenen, aber eine 3D-Engine könnte auch ohne diese Sachen laufen, wenn auch nicht so schnell wie mit. Der Vorteil von Clipping Ebenen ist, dass alles, was außerhalb des aufgespannten Raumes liegt, von vorne herein direkt ignoriert werden kann, was die Applikation natürlich schneller macht. Trotzdem beinhalten Abbildung 3 und 4 nur die Rohvarianten der Projektionsmodelle, weil diese schon den Kerngedanken vermitteln.Die Idee bei orthogonaler Projektion ist es, dass das Sichtvolumen ein Quader ist (Abb.5). Auf diese Art und Weise ändert sich die Größe der Objekte, die weiter weg sind nicht, was darin resultiert, dass man bei der entsprechenden Matrix die Z-Koordinaten einfach ignoriert. Bei der perspektivischen Projektion hingegen, fließt die Z-Koordinate in die Berechnung von X- und Y-Koordinaten mit ein (es wird durch sie dividiert). Diese Berechnungsmethode folgt übrigens direkt aus dem Strahlensatz.In DBP kann man die aktuelle (perspektivische) Projektionsmatrix mittels des Befehls projection matrix4 Matrixnummer in die angegebene Matrix kopieren und erhält damit ein sehr mächtiges Werkzeug, um eigenständig 2D mit 3D zu kombinieren.Wenn man also einen Vektor damit transformiert und dieser relativ zur Kamera angegeben wurde, erhält man einen neuen Vektor, der die Position auf dem Bildschirm angibt (analog zum Rendervorgang oben). |
|
![]() |
Bei den Matrizen, die wir eben kennengelernt haben, fällt auf, dass sie eigentlich nur den Renderablauf nachvollziehen (die normalen Transformationsmatrizen bringen uns Weltkoordinaten, durch die View Matrix erhalten wir Kamerkoordinaten und die Projektionsmatrix berechnet uns die Bildschirmkoordinaten). Was aber nun, wenn wir den Weg anders herum beschreiten wollen?Was, wenn wir eigentlich einen Vektor relativ zur Kamera gegeben haben und daraus die Weltkoordinaten errechnen wollen? Das ist das schöne an Mathematik, zu den meisten Operationen gibt es Umkehroperationen, die man nur ausführen muss, um genau das Gegenteil zu erreichen :).So ist das auch bei den Matrizen, dort gibt es die sogenannte inverse Matrix. Mathematisch gesehen, ist das die Matrix, die mit der Ursprungsmatrix multipliziert die Identitätsmatrix ergibt.Wenn man diese Matrix auf eine bestimmte Matrix anwendet, bekommt man eine Matrix, die anstatt im Rendervorgang nach unten zu laufen, eine, die nach oben läuft (die Inverse der View Matrix berechnet Weltkoordinaten aus Kamerakoordinaten, die Inverse der Projektionsmatrix berechnet Koordinaten relativ zur Kamera aus Bildschirmkoordinaten, etc.). Man findet diese Matrix in DBP mittels des Befehls:r = inverse matrix(Resultat, Quelle) Diese Matrix muss normalerweise nicht unbedingt existieren, aber bei den 4x4-Matrizen, die wir bearbeiten, gibt es immer eine Lösung.Die Inverse von der inversen Matrix ist natürlich die Matrix selbst. |
Neben der inversen Matrix gibt es auch noch die transponierte Matrix. Diese sieht fast genauso aus, wie die Ursprungsmatrix selbst, nur Zeilen und Spalten wurden vertauscht. Ich habe sie allerdings noch nie gebraucht, weiß also nicht genau, was man mit ihr machen kann :/. Ich lasse mich da gerne aufklären, wenn also jemandem etwas dazu einfällt...Man kann sie in DBP mit Hilfe des Befehls transpose matrix4 Resultat, Quelle erzeugen und das Resultat ist von der Form, die in Abbildung 8 zu sehen ist. |
![]() |
In der vorletzten Lektion haben wir eine Möglichkeit kennengelernt, eine Rotation um eine beliebige Achse im Raum durchzufüren, ganz einfach, indem wir X, Y und Z-Rotationsmatrizen verknüpften, aber DarkBASIC Pro bietet uns noch bessere Möglichkeiten, die Rotationsachse frei zu wählen und zwar mit dem Befehl: build rotation axis matrix4 Resultat, Achse, Winkel Der Parameter "Achse" ist ein 3 dimensionaler Vektor, der die Achse im Raum angibt und der Winkel ist die Drehung in Radiant.Damit ist es nun wirklich ein Kinderspiel, beliebige Objekte um beliebige Achsen zu drehen, ohne vorher schwierige trigonometrische Berechnungen anzustellen. Wie man die Matrix berechnen kann, ist in Abbildung 9 sehen.Ich habe die Lösung absichtlich nicht hingeschrieben, weil das Prinzip, wie man vorgeht, schon durch diese Formel sichtbar ist. Zuerst dreht man das Objekt um die X-Achse, so dass der Achsenvektor in der xy-Ebene zu liegen kommt, dann dreht man die so um die Y-Achse, dass die Achse der Z-Vektor ist. Nun dreht man einfach um die Z-Achse und danach wieder alles zurück. |
![]() |
Stellen wir uns vor, wir möchten einen Spiegel in unser Programm einbauen. Die einfachste Möglichkeit ist sicher, einfach eine Plain zu nehmen und einen Reflectionshader darüber zu legen. Dummerweise hat diese Variante zwei Nachteile: 1. Braucht sie eine Shaderfähige Grafikkarte und2. hat man nur einen sehr geringen Grad der Kontrolle. Für solche Fälle gibt es eine Matrix (sonst würde ich auch nix darüber sagen :D) und zwar die sogenannte Reflektionsmatrix. Diese Matrix nimmt den Vektor, den man ihr gibt und spiegelt ihn.In DBP kann man diese Matrix mittels des Befehls build reflection matrix4 NormalX, NormalY, NormalZ, Distanz konstruieren, wobei NormalX, Y und Z die drei Komponenten des Normalenvektors sind, der senkrecht auf der Ebene, an der gespiegelt werden soll, steht und Distanz den Abstand zum Ursprung [0,0,0] dieser Ebene angibt.Damit lässt sich eine Ebene eindeutig beschreiben und man kann sie drehen und wenden, wie man will, ohne sich Gedanken über komplizierte Koordinatentransformationen machen zu müssen. So ein gefakter Spiegel ist im Beispielprogramm mal implementiert. |
Kommen wir nun schließlich und endlich zu den Befehlen mit den abenteuerlichsten Namen, doch vorher will ich noch einige Sachen erklären. Es gibt zwischen den Engines DirectX und OpenGL einige markante Unterschiede in der Handhabung der Weltkoordinaten. Während bei DirectX die Kamera standardmäßig in Richtung der Z-Achse schaut (die Z-Koordinaten nach vorne also positiv sind), schaut die Kamera in OpenGL entgegen der Z-Achse.Die beiden Koordinatensysteme unterscheiden sich daher in der Richtung ihrer Z-Achsen. Man nennt Systeme, die nach vorne die positiven Z-Werte haben, linkshändige Systeme, weil man die Lage der Achsen mit der linken Hand nachvollziehen kann (Daumen->X-Achse, Zeigefinger->Y-Achse, Mittelfinger->Z-Achse). Das andere System mit negativer Z-Achse heißt dementsprechend rechtshändiges Koordinatensystem, denn man kann es mit der rechten Hand nachvollziehen (bei selber Benutzung der Finger).Mit der Erklärung habe ich uns eben die Hälfte der noch folgenden Befehle erspart, denn sie haben alle "LH" oder "RH" im Namen, was bedeutet, dass sie dasselbe machen, nur in Right-Handed, bzw. Left-Handed Systemen. Bei meinen Erklärungen werde ich mich natürlich auf die linkshändigen Befehle konzentrieren, denn diese werden ja in DirectX und damit DBP verwendet. |
![]() |
Manchmal wollen wir einen Vektor relativ von einem anderen Koordinatensystem betrachten, wie wir es auch schon bei der View Matrix der Kamera getan haben. Nun möchte man aber nicht ständig die Kamera verschieben, deren View Matrix kopieren und sie dann wieder zurück setzen deswegen. Glücklicherweise bietet uns DarkBasic Pro hier die Möglichkeit, eine eigene View Matrix zu konstruieren. Damit sind die Probleme der Transformation in andere Koordinatensysteme schlagartig gelöst, da man nun einfach so eine View Matrix konstruiert und dann auf Weltpositionen anwenden kann.Der Befehl, mit dem das geht, lautet build lookat lhmatrix4 Resultat, Position, Blickrichtung, Oben bzw.build lookat rhmatrix4 Resultat, Position, Blickrichtung, Oben In die Matrix mit der Nummer Resultat wird diese neue View Matrix kopiert. Position steht für die Weltposition des neuen Ursprungs, die Blickrichtung zeigt in Richtung Z-Achse des neuen Systems und Oben verläft parallel zur neuen Y-Achse. Alle Parameter sind Vektoren, bis auf die resultierende Matrix natürlich.Wo bleibt eigentlich die X-Achse? Ganz klar, die X-Achse lässt sich aus den anderen beiden Richtungsvektoren via Kreuzprodukt berechnen, deshalb müssen wir sie auch nicht angeben.Diese Matrix hat genau den selben Aufbau, wie die View Matrix vorhin (es ist ja auch eine View Matrix), den man in Abbildung 9 nochmal sehen kann. |
Stellen wir uns einen Moment lang vor, wir wollten eine Isometrieengine in DBP basteln, vielleicht um den Überblick in einem Leveleditor oder ähnlichem zu behalten. Wie können wir bestimmen, wo die Bilder, die wir haben, positioniert werden sollen, wenn wir schräg darauf schauen (von oben ist es ja kein Problem)? Am einfachsten (und korrektesten) geht das mittels einer Projektionsmatrix!Wir überlegen uns dafür einfach ein eigenes Weltkoordinatensystem, nach dem wir unsere Objekte ausrichten wollen und wenden darauf dann eine orthogonale Projektionsmtrix an, um die Positionen Figuren auf dem Bildschirm zu ermitteln. DBP unterstützt uns dabei, mit dem Befehl build ortho lhmatrix4 Resultat, Weite, Höhe, Vorne, Hinten bzw.build ortho rhmatrix4 Resultat, Weite, Höhe, Vorne, Hinten Hier lernen wir nun das Konzept der Clippingebenen kennen, denn alles, was sich außerhalb der Box, die wir mit Weite, Höhe, Vorne und Hinten beschreiben, wird automatisch hinter den Bildschirm positioniert, d.h. es muss von uns nicht gezeichnet werden.Die Matrix, die davon erstellt wird, funktioniert selbstverständlich genauso wie die orthogonale Projektionsmatrix von oben. |
![]() |
![]() |
Die gleiche Möglichkeit gibt es auch für die perspektivische Variante der Projektionsmatrix, aber wozu soll man sowas gebrauchen? Ich meine, die Projektionsmatrix der DBP-Kamera ist doch schon perspektivisch!Natürlich macht diese Matrix schon Sinn, wenn man bedenkt, dass es dazu kommen könnte, dass man mal ein etwas größeres Bild rendern wollen könnte. Man kann die Auflösung nicht beliebig groß einstellen und das ist mit diesem Befehl auch nicht mehr nötig, denn man kann sich damit einfach eine eigene Projektionsmatrix generieren und selbst rendern - so groß wie man will. Erzeugen kann man diese Matrix mit dem Befehl build perspective lhmatrix4 Resultat, Weite, Höhe, Vorne, Hinten bzw.build perspective rhmatrix4 Resultat, Weite, Höhe, Vorne, Hinten Wieder werden hier die Clippingebenen verwendet und erneut hat man komplette Kontrolle, auf welche Größe der Raum projeziert werden soll, einem eigenen Rendersystem steht somit nichts mehr im Wege :). |
Der letzte Befehl, den ich vorstellen möchte, ist einer der einfachsten
und gleichzeitig einer der mächtigsten. project vector3 Resultat, Quelle, Projektion, View, Welt Mit diesem Befehl kann man den gesamten Renderzyklus einmal manuell durchlaufen und nur damit lassen sich "Fantasien", wie die Iso-engine auch wirklich realisieren, da man die freie Auswahl hat, welche Matrizen man da einsetzt (man kann auch die selbstgebauten benutzen).Der Befehl will als Quellvektor eine Position in der 3D-Welt haben und gibt einen 3D-Vektor zurück, dessen erste zwei Komponenten Bildschirmkoordinaten sind. Die Z-Komponente gibt dabei an, ob der Vektor zu sehen ist, oder nicht. Er ist genau dann zu sehen, wenn der Betrag dieser Komponente kleiner gleich 1 ist. Damit kann man jetzt natürlich so ziemlich alles machen, von der Iso-engine, bis hin zur Kombination von 2D mit 3D! |
Wenn du es jetzt bis hier geschafft hast, Herzlichen Glückwunsch! Du kennst jetzt alle Befehle und das ist die Voraussetzung dafür, mit ihnen zu jonglieren und geniale Effekte mit einer Einfachheit zu erreichen, die DBP zu eigen ist. Solltest du in dieser Einführung Fehler entdeckt haben, oder sollte etwas unklar sein, maile mir bitte, damit ich mich damit beschäftigen kann.Ansonsten wünsche ich dir nun viel Spaß mit den neuen Möglichkeiten, die sich dir jetzt aufgetan haben. |