Malen mit Zahlen, Teil RS1 – Kamera und Punkte

Das ist der erste Teil der Serie über 3D-Computergraphik, der sich speziell mit Rasterung/Schattierung beschäftigt. Obwohl es natürlich Überschneidungen gibt, möchte ich hier nicht auf spezielle Bibliotheken wie OpenGL oder Direct3D eingehen. Es wird aber wahrscheinlich noch eine Unterserie zu WebGL geben.

Die Standardkamera

Bevor wir etwas rastern können, müssen wir zunächst Punkte im Raum auf unsere Kamera projizieren. Fürs Erste verwenden wir dazu die in Abb. 1 gezeigte inverse Lochkamera (s. Teil 0).

Abb. 1: Unsere Standard-Lochkamera. Der »eye point« (das Loch) ist im Ursprung und der Schirm liegt in der Ebene z = -1. Die Kreuze auf den Achsen haben jeweils eine Längeneinheit Abstand.

Das Loch unserer inversen Lochkamera – der eye point – liegt im Ursprung unseres Koordinatensystems. Der Schirm ist ein Quadrat der Seitenlänge 2 und parallel zur xy-Ebene. Er schneidet die z-Achse bei z = -1. Die z-Achse geht genau durch seinen Mittelpunkt und zeigt aus dem Schirm heraus. Mit dieser Kamera schauen wir gegen die z-Richtung. Das entspricht dem Standard von OpenGL, mit Direct3D schaut man in z-Richtung.

Ich habe mir kurz überlegt, die z-Achse wie aus der Schule gewohnt nach oben zeigen zu lassen. Dann würde aber die y-Achse in die Tiefe der Szene hinein zeigen und der Begriff z-Buffer für den Speicher der Tiefenwerte wenig Sinn ergeben. Wer seine Punkte lieber mit der z-Koordinate nach oben angibt, kann sie durch eine Drehung um -\tau/4=-90^\circ um die x-Achse in unser Koordinatensystem überführen (s. ein späterer Teil).

Die Wahl unserer Standardkamera ist natürlich völlig willkürlich. Wir werden später noch sehen, wie wir sie anders positionieren können … Also, eigentlich wird sie immer gleich bleiben; wir werden den Rest der Szene nur so manipulieren, dass es aussieht, als hätten wir die Kamera verändert.

Kamerakoordinaten

Ein Lichtstrahl, der vom Punkt P(x|y|z) im Raum ausgeht, muss durch das Loch unserer Lochkamera, also den Ursprung gehen (dicke grüne Linie in Abb. 2). Dabei schneidet er den Schirm unserer Kamera im Punkt P_C(x_C|y_C|{-1}), weil sich der Schirm in der Ebene z = -1 befindet.

Abb. 2: Ein Punkt P(x|y|z) wird auf den Schirm unserer Kamera projiziert, was den Punkt P_C(x_C|y_C|{-1}) ergibt.

Dieser Schnittpunkt P_C ist die Projektion unseres Punktes P. Wie erhalten wir aber dessen Kamerakoordinaten (camera bzw. view coordinates) (x_C|y_C)?

Die x– und y-Koordinaten des Punktes P liegen in der Ebene z = const; die Koordinaten des Punktes P_C liegen in der Ebene z = -1. Weil zwei Ebenen mit konstanten z-Werten parallel zueinander sind, und zwei Punkte eine Gerade eindeutig festlegen, sind auch die roten Linien zueinander parallel und die blauen Linien ebenfalls. Daher können wir den Strahlensatz anwenden und erhalten

\displaystyle\frac{x}{x_C}=\frac{y}{y_C}=\frac{z}{-1} .

Umformen liefert dann die camera coordinates

\displaystyle\begin{aligned}x_C&=-\frac{x}{z}\,,\\[1ex]y_C&=-\frac{y}{z}\,.\end{aligned}

(Um vor der Kamera zu liegen, muss die z-Koordinate negativ, ja sogar kleiner als -1 sein. Zur Erinnerung: -2 ist kleiner als -1!)

Die Kamerakoordinaten des projizierten Punktes P_C sind also im Wesentlichen die x– und y-Koordinaten des ursprünglichen Punktes P, dividiert durch dessen z-Koordinate. Je weiter ein Punkt vor der Kamera liegt, desto näher zur Mitte des Schirms wandern die Kamerakoordinaten (bei sonst gleichem x und y).

Das ist für die typischen perspektivischen Effekte verantwortlich, z.B. parallele Schienen, die Richtung Horizont einander immer näher zu kommen scheinen. Tatsächlich bleibt ihr Abstand immer gleich (hoffentlich!), und nur die Bildpunkte weiter entfernter Schienenteile sind näher zusammen.

Durch unsere Projektion haben wir aus drei Koordinaten nur noch zwei gemacht. Durchs Projizieren geht immer Information verloren. De facto werden alle Punkte auf der grünen Geraden in Abb. 2 auf denselben Punkt P_C abgebildet. Unsere Zentralprojektion macht also aus jeder Geraden durch den Ursprung einen Punkt am Schirm. Der Zweig der Mathematik, der sich damit beschäftigt, heißt projektive Geometrie.

Bildschirmkoordinaten

Wenn wir einen Bildschirm mit unendlich feiner Auflösung hätten, wären wir jetzt fertig – haben wir aber nicht. Jeder Bildschirm besteht aus einem Raster endlich großer Pixel wie in Abb. 3 gezeigt, und wir müssen die camera coordinates (x_C|y_C) noch in die Pixelkoordinaten (screen coordinates) (x_S|y_S) am Bildschirm umrechnen.

Abb. 3: Unser Schirm besteht aus vielen Pixeln (z.B. 8×8). Die Kamerakoordinaten (camera coordinates) sind in orange gezeigt, die Bildschirmkoordinaten (screen coordinates) in blau. Das Pixel, in dem P_C liegt, wurde eingefärbt.

Nachdem unser Bildschirm ein Quadrat der Seitenlänge 2 ist, sollten wir das Pixelraster auch quadratisch wählen, mit n Pixeln pro Seite. Ein Pixel hat also die Breite bzw. Höhe 2/n (in willkürlichen Längeneinheiten). In der Computergraphik liegt der Ursprung des Pixelrasters in der linken oberen Ecke. Die x_S-Achse zeigt dabei in dieselbe Richtung wie die x_C-Achse, die y_S-Achse entgegen der Richtung der y_C-Achse.

Wenn wir die x_C– bzw. y_C-Koordinate durch die Breite bzw. Höhe eines Pixels dividieren, wissen wir, wie viele Pixel wir vom Ursprung entfernt sind. Dieses Ergebnis müssen wir noch zur Anzahl der Pixel bis zum Ursprung des Kamerakoordinatensystems addieren (x_S) bzw. davon subtrahieren (y_S):

\displaystyle\begin{aligned}x_S&=\left\lfloor\frac{n}{2}+\frac{x_C}{\frac{2}{n}}\right\rfloor=\left\lfloor\frac{n}{2}+x_C\cdot\frac{n}{2}\right\rfloor=\left\lfloor(1+x_C)\cdot\frac{n}{2}\right\rfloor\,,\\y_S&=\left\lfloor\frac{n}{2}-\frac{y_C}{\frac{2}{n}}\right\rfloor=\left\lfloor\frac{n}{2}-y_C\cdot\frac{n}{2}\right\rfloor=\left\lfloor(1-y_C)\cdot\frac{n}{2}\right\rfloor\,.\end{aligned}

Die floor-Funktion \lfloor x\rfloor liefert die größte ganze Zahl nicht größer als x, weil nur ganze Zahlen Pixelkoordinaten sein können.

Setzen wir jetzt noch unsere Kamerakoordinaten ein, erhalten wir schließlich

\displaystyle\begin{aligned}x_S&=\left\lfloor\left(1-\frac{x}{z}\right)\cdot\frac{n}{2}\right\rfloor\,,\\[1ex]y_S&=\left\lfloor\left(1+\frac{y}{z}\right)\cdot\frac{n}{2}\right\rfloor\,.\end{aligned}

Weil die screen coordinates nicht negativ sein können, braucht man die floor-Funktion eigentlich nicht. Eine einfache Typumwandlung von Dezimalzahl in Ganzzahl leistet in den meisten Programmiersprachen dasselbe.

Punkte am linken bzw. oberen Rand eines Pixels gehören zu diesem Pixel. Punkte am rechten bzw. unteren Rand gehören zu den Nachbarpixeln. Wenn die Anzahl der Pixel wie üblich gerade ist, liegt der Ursprung des Kamerakoordinatensystems am Rand eines Pixels. Der Punkt (0|0) in camera coordinates würde also das Pixel rechts unterhalb des Ursprungs aufleuchten lassen (Pixel (4|4) in Abb. 3).

Fragmente

Durch die endliche Größe eines Pixels gibt es einen ganzen Raumbereich, aus dem Punkte auf dieses eine Pixel abgebildet werden. Alle Punkte einer Oberfläche, die auf dasselbe Pixel abgebildet werden, nennt man ein Fragment (s. Abb. 4). So ein Fragment kann viel größer als ein Pixel selber sein.

Abb. 4: Ein Fragment (rot) sind alle Punkte einer Oberfläche, die auf dasselbe Pixel (grün) abgebildet werden.

Beispiel

Nehmen wir als Beispiel die Eckpunkte eines Würfels der Seitenlänge 1 (blaue Kreise in Abb. 5). Die Vorderseite ist bei z = -1.5 und die Hinterseite bei z = -2.5. In x-/y-Richtung sind die Koordinaten ±0.5. Projizieren wir diese Eckpunkte mit unserer Standardkamera, ergeben sich die grünen Pixel auf dem 32×32-Pixel Schirm. Man erkennt die typische perspektivische Verzerrung, weil die Punkte der Hinterseite näher zur Bildschirmmitte hin abgebildet werden.

Abb. 5: Die Eckpunkte eines Würfels (blaue Kreise) und ihre Projektion auf einen 32×32-Pixel Schirm (grün). Die Kreuze auf den Achsen haben wieder eine Längeneinheit Abstand.

Diskussion

Das richtige Einfärben der Pixel des Pixelrasters je nach Szene hat dieser Methode den Namen Rasterung gegeben (die Schattierung wird noch etwas dauern).

Die Projektion erfolgt im Wesentlichen mittels Division durch die z-Koordinate eines Punktes (später werden wir die w-Koordinate verwenden). Das ist eine nichtlineare Operation. Obwohl z.B. OpenGL Projektionsmatrizen kennt (und wir die auch noch verwenden werden), die tatsächliche Zentralprojektion kann nicht durch eine Matrix-Punkt-Multiplikation erreicht werden.

Eigentlich wollte ich auch noch den entsprechenden Java-Code besprechen, aber das verschiebe ich auf den nächsten Teil.

Benötigte Mathematik bisher:

Strahlensatz, Grundrechnungsarten, Abrunden von Dezimalzahlen zu Ganzzahlen.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..