Nach der kleinen Einführung in die Computergrafik möchte ich etwas zu dessen Grundlage schreiben. Nicht um die nicht Mathematikinterressierten zu verschrecken sondern um zu erläutern warum eigentlich so viel gerechnet werden muß. Ich will hier auch keine Einführung in die Vektoranalysis geben sondern die Verbindung von der Vektoranalysis zur Computergrafik bringen.
Der Hauptanteil der Mathematik die in der Computergrafik verwendet wird ist die Vektoranalysis. Wer in der Schule erstmals davon höhrt kann sich nicht so recht vorstellen was man nun damit berechnen kann.
Um mit einen Datenmodell ein Körper zu beschreiben gibt es verschiedene Möglichkeiten. Nehmen wir eine Kugel, was definiert eine Kugel? Der Mittelpunkt und der Radius, genauso kann die Oberfläche abgetastet werden und jeder einzelne Punkt wird gespeichert. Beide Möglichkeiten haben Vor- und Nachteile. In diesem Abschnitt betrachten wir die erste Möglichkeit.
Der Mittelpunkt ist ein typischer Fall für einen Ortsvektor, ein Vektor der aus drei Zahlen besteht x,y und z Wert im kartesischen Koordinatensystem. Der Radius ist ein einfacher Wert, also eine reele Zahl.
Die Beschreibung eines Polygons mit 3 Eckpunkten ist genauso einfach. 3 Ortsvektoren beschreiben ein Polygon. Etwas komplizierter wird es mit einem Polygon was mehr als 3 Eckpunkte hat, befindet sich ein Punkt nicht auf der Ebene die durch die anderen Punkte beschrieben wird ist es kein Polygon mehr. Das ist dann der Fall den nicht das Datenmodell allein sondern nur mit Hilfe eines geeigneten Algorithmus verarbeitet werden kann.
Grundlage dieser Algorithmen bietet die Vektorbibliothek. In dieser Bibliothek sind alle Mathematischen Routinen die bei der Berechnung von Vektoren benötigt werden zusammengestellt. Ich möchte hier ein paar Ausschnitte erläutern.
Ein Ortsvektor besteht aus 3 Koordinaten:
class vector { float x, y, z; };
Um mit diesen rechnen zu können braucht man Operatoren, für die Vektoraddition bietet sich hier ein + an:
vector operator + (vector b) { return vector (x + b.x, y + b.y, z + b.z); }
Neben Ortsvektoren gibt es auch Richtungsvektoren, diese sind von den Daten her die gleichen wie Ortsvektoren haben aber die Aufgabe eine Richtung zu zeigen, also die Richtung entlang der X-Achse kann mit dem Vektor 5,0,0 oder 9,0,0 beschrieben werden. Wobei der Zahlenwert von x egal ist solange er größer als 0 ist. Diesen Vektor kann man ,,normalisieren'' also auf die Länge 1 bringen damit ist der Vektor 1,0,0 die Formel dazu steht in fast jeder Formeltafel und lautet
und ein C++ Programmierer schreibt:
void operator |= (vector b) { float s = sqrt (b * b); if (fabs (s) <= eps8) { x = 0.0; y = 0.0; z = 0.0; } else { s = 1.0 / s; x = s * b.x; y = s * b.y; z = s * b.z; } }
Sehr wichtig ist das Kreuzprodukt:
vector operator && (vector b) { return vector (y * b.z - z * b.y, -x * b.z + z * b.x, x * b.y - y * b.x); }
Leider gibt es im ASCII Kode und in C++ keinen Operator der einem Kreuz ähnelt, deshalb habe ich mich für das && entschieden.
An diesem einfachen Beispielen sieht man das man die Formeln fast nur abtippen braucht, zusätzlich muß man beim Programmieren auf die Wertebereiche achten. Es steht in jeder guten Formelsammlung das die Formel x=a/b nur dann gilt wenn b ungleich 0 ist. Beim rechnen mit dem Taschenrechner darf man das getrost ignorieren und wird dann mit einem Error! bestraft, bei einem Programm wird man manchmal mit einem Numeric Error: Division by 0 bestraft und das Fenster schließt sich. (Es soll auch Betriebssysteme geben, wo man nach jedem Programmierfehler neu booten muß)
Für die Berechnung des Polygons brauchen wir den Normalvektor des Polygons um dessen Richtung zu bestimmen, wie berechnet man diesen? Das Polygon ist in unserem Datenmodell durch 3 Ortsvektoren definiert. Im Formelheft steht dazu folgende Formel:
Mit unserer Vektorbibliothek können wir folgendes schreiben:
n |= (p1 - p0) && (p2 - p0);
Abb. 2
Diese Beispiele sollten zeigen das es mit Hilfe der Sprache C++ sehr effizient und gut lesbar möglich ist, die Vektoranalysis für die Computergrafik zur Verfügung zu stellen.
Abbildung 2: Normalvektor eines Polygons
Hier noch eine Erläuterung der wichtigsten Funktionen, die hier gut aufbereitet sind:
float abs_punkt_ebene(vector p, vector e0, vector e1, vector e2);}berechnet den Abstand eines Punktes von einer Ebene
vector absgeradepunkt(vector pg, vector r, vector p);
berechnet den Vektor von einem Punkt zu einer Geraden
void rotationVN(vector *r, vector i);}
rotiert einen Vektor r nach einen Vektor i
lautet die Rotationsmatrix
Für den Fall, daß a und b gleich Null werden, ist nur eine Drehung
um die y-Achse um erforderlich;
ist
, wird folgende Rotationsmatrix
verwendet:
Die Funktion sign(a) ist .
Durch den Einsatz dieser Funktion kann eine Division gespart werden. Obige Rotation
sollte, wenn möglich, immer eingesetzt werden, denn es sind keine aufwendigen
Winkelfunktionen zu berechnen. Die Lösung in C++ sieht so aus:
void rotationVN(vector *r, vector i) { float l, V; vector R; l = sqrt(i.y*i.y + i.z*i.z); if (fabs(l) > eps12) { V = sqrt(i.x*i.x + i.y*i.y + i.z*i.z); // Av^-1 R = vector(r->x * l/V + r->z * i.x/V, r->x*(-i.x*i.y/(l*V))+r->y*i.z/l+ r->z * i.y/V, r->x*((-i.x*i.z)/(l*V))+r->y*-i.y/l+ r->z * i.z/V); } else // Av^-1 bei iy und ix = 0 R = vector(r->z*fsign(i.x), r->y, r->x*-fsign(i.x)); *r = R; }
void rotationVZ(vector *r, vector i);Bei dieser Funktion wird eine Vektor in Richtung i zur Z-Achse rotiert. Die Berechnung erfolgt wie oben. Hier kann nicht wie bei der folgenden Rotation ein negativer Vektor angegeben werden. Eine Rotation in die Gegenrichtung wird durch das Invertieren der Matrizen erreicht.
void rotation(vector *e, vector p, vector w);
Diese Funktion rotiert einen Vektor um einen bestimmten Punkt. Vor
der Rotation wird der Punkt in den Koordinatenursprung verschoben, , dannach wird rotiert, und nach der
Rotation wird der Punkt wieder zurückgeschoben,
. Die Rotationen erfolgen nach folgenden Matrizen:
Hier die Lösung in C++:
void rotation(vector *e, vector p, vector w) { float sw, cw; vector pv; if (w.x != 0.0) { sw = sin(w.x); cw = cos(w.x); // Ergebnis holen & Koord. verschieben pv = *e - p; // Drehung um x Achse *e = vector(pv.x, pv.y*cw-pv.z*sw, pv.y*sw+pv.z*cw) + p; } if (w.y != 0.0) { sw = sin(w.y); cw = cos(w.y); // Ergebnis holen & Koord. verschieben pv = *e - p; // Drehung um y Achse *e = vector(pv.x*cw+pv.z*sw, pv.y, pv.x*-sw+pv.z* cw) + p; } if (w.z != 0.0) { sw = sin(w.z); cw = cos(w.z); // Ergebnis holen & Koord. verschieben pv = *e - p; // Drehung um z Achse *e = vector(pv.x*cw-pv.y*sw, pv.x*sw+pv.y*cw, pv.z) + p; } }
Etwas auffällig ist der Test auf != 0.0, aber da an vielen Stellen nur um eine bestimmte Achse gedreht wird, ist die Zahl mit 0.0 vorinitialisiert und somit klappt der Test auf Null. Wenn eine Addition oder eine beliebige andere Operation ausgeführt wurde, ist das Ergebniss nie Null. Dies ist die einzige Stelle im Programm, wo ein solcher Test vorkommt.
Eine andere, ähnlich geartete Sache ist die Division. Eine Grundregel für die Programmierung von numerischen Problemen ist der Test auf Division durch 0. Immer, wenn die Division verwendet wird, ist vorher ein Test auf 0 durchzuführen! Es ist genau zu prüfen, ob bei einer Berechnung der Divisor 0 werden kann. Wenn ja ,ist immer mit if zu testen.
float abs(vector a);berechnet den Betrag eines Vektors:
![]()
int solve_quad(float a, float b, float c, float *t1, float *t2);berechnet die Lösung einer quadratischen Gleichung
![]()
int solve_cubic(float *x, float *y);berechnet kubische Gleichung
![]()
int solve_quartic(float *x, float *results);berechnet eine Gleichung 4. Grades nach einer Methode von Francois Vieta (Circa 1735)
x_{1,2,3} = x^4*a+x^3*b+x^2*c+x*d+e
Das soll für den Anfang erstmal reichen, wer in dieser Richtung weitermachen will oder diese Vektorbibliothek für seine Täglichen Aufgaben benötigt kann sich mit mir in Verbindung setzen und diese bekommen.