# Eine 'cameraLookAt' Matrix erstellen

• Hallo,
Meine aktuelle cameraLookAt Matrix habe ich in dieser Klasse untergebracht:

#pragma once
#include "VectorObject3D_T.h"
#include "Mat4x4_T.h"

namespace zp
{
template <typename T>
class ModelView
{
Vector3D<T> CameraPosition;
Vector3D<T> CameraRotation;
Mat4x4<T> ObjectMatrix;
Mat4x4<T> ViewMatrix;

Mat4x4<T> Matrix;

public:
ModelView() = default;
ModelView(const Mat4x4<T>& objMat) : ObjectMatrix(objMat) {}
ModelView(const Mat4x4<T>& objMat, const Vector3D<T>& camPos, const Vector3D<T>& camRotate)
: ObjectMatrix(objMat),
CameraPosition(camPos),
CameraRotation(camRotate) {}

const Mat4x4<T>& matrix() const { return Matrix; }
const Mat4x4<T>& viewMatrix() const { return ViewMatrix; }
const Mat4x4<T>& objectMatrix() const { return ObjectMatrix; }
const Vector3D<T>& cameraPosition() const { return CameraPosition; }
const Vector3D<T>& cameraRotation() const { return CameraRotation; }
Vector3D<T>& cameraPosition() { return CameraPosition; }
Vector3D<T>& cameraRotation() { return CameraRotation; }

void createMatrix()
{
const Vector3D<float> directonY = { 0, 1, 0 };
Vector3D<float> directonZ = { 0, 0, 1 };
const Vector3D<float> LookDir = directonZ * matrix::makeRotation(CameraRotation);
directonZ = CameraPosition + LookDir;
const Mat4x4<float> matCamera = cameraLookAt(directonZ, directonY);

ViewMatrix = matrix::quickInverse(matCamera);
Matrix = ObjectMatrix * ViewMatrix;
}

void transform(VectorShape3D<T>& shape)
{
createMatrix();
for (auto& point : shape.vertices())
point *= Matrix;
}

void transform(VectorObject3D<T>& object)
{
createMatrix();
std::vector<VectorShape3D<T>> shapes(object.shapes().size());
auto n = 0;
for (auto& shape : object.shapes())
{
for (auto& point : shape.vertices())
point *= Matrix;

shapes[n++] = shape.vertices();
}
object.getAttributesFrom(shapes);
}

private:
Mat4x4<T> cameraLookAt(const Vector3D<T>& dirZ, const Vector3D<T>& dirY) const
{
// calculate new Z direction;
const Vector3D<T> directonZ = (dirZ - CameraPosition).normalize();

// calculate new Y direction
const Vector3D<T> d = directonZ * vector::dotProduct(dirY, directonZ);
const Vector3D<T> directonY = (dirY - d).normalize();

// calculate new X direction
const Vector3D<T> directonX = vector::crossProduct(directonY, directonZ);

// construct dimensioning and translation Matrix
Mat4x4<T> mat;
mat.matrix[0][0] = directonX.x; mat.matrix[0][1] = directonX.y; mat.matrix[0][2] = directonX.z; mat.matrix[0][3] = 0;
mat.matrix[1][0] = directonY.x; mat.matrix[1][1] = directonY.y;	mat.matrix[1][2] = directonY.z; mat.matrix[1][3] = 0;
mat.matrix[2][0] = directonZ.x; mat.matrix[2][1] = directonZ.y; mat.matrix[2][2] = directonZ.z; mat.matrix[2][3] = 0;
mat.matrix[3][0] = CameraPosition.x;
mat.matrix[3][1] = CameraPosition.y;
mat.matrix[3][2] = CameraPosition.z;
mat.matrix[3][3] = 1;
return mat;
}
};
}



Diese LookAt Matrix habe ich mir aus anderen Codes zusammengesetzt, kann also nicht genau erklären, was ich da tue. Sie funktioniert auch nicht richtig, weil die Rotation scheinbar nicht berücksichtigt wird.
Denn um verdeckte Flächen (die sich im Gegensatz zur Kameraposition befinden) nicht zeichnen zu müssen, habe ich diese freien Funktionen:

        template <typename T>
inline static Vector3D<T> getCameraRay(const VectorShape3D<T>& shape, const Vector3D<T>& cameraPosition)
{
return shape.center() - cameraPosition;
}

template <typename T>
inline static const bool isShapeVisibleToCamera(const VectorShape3D<T>& shape, const Vector3D<T>& cameraPosition)
{
return vector::dotProduct<T>(getCameraRay(shape, cameraPosition), shape.normalVector()) < 0;
}



Und dies funktioniert nur, wenn die Kamera nicht rotiert wird, positionieren und Objekt drehen funktioniert.
Um zu zeigen, wie sich das auswirkt, habe ich zwei Beispiele hochgeladen. Beim ersten Beispiel, wo nur die Kamera bewegt/gedreht wird, erkennt man, das die vorderen Flächen ausgeblendet sind, beim zweiten Beispiel nicht:
https://ibb.co/28SrhKh
https://ibb.co/yh5vRdH

Nun aber zum Thema:
Bei der Suche nach einer korrekten Matrix bin ich auf diese Seite gestossen:
https://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/graphics_6_1_ger_web.html#1
genauer gesagt dreht es sich um diese Abbildung:
https://ibb.co/nswZ8VP
Um es kurz zu machen, ich versteh das nicht, schon alleine, das es es keine 4x4 Matrizen sind, ich weiß nicht wie ich das umsetzen soll?

Ich hoffe, ich habe mich einigermaßen verständlich ausgedrückt, es ist ein Thema, wo mir schon einige mathematische Grundlagen fehlen.

PS: Es könnte sein, das meine Antworten auf eventuelle Beiträge erst in ein paar Stunden oder morgen erscheinen, ich muss mir diese erst durch den Kopf gehen lassen.

• @zeropage ganz kurz, bin gerade nur am Smartphone. Das sind jeweils 4x4 Matrizen, die jeweiligen bs und das c beschreibt jeweils einen Vektor. Und die Untere Matrix ist oben links Rotation und rechts der Translationsvektor

• Aha, mhm. Die Vektoren b und C sind dann von oben nach unten geschrieben? Aber dann ohne das obligatorische w? Sonst wären es ja dann 4x5 Matrizen? Aber ich warte mal ab, das stimmt wahrscheinlich so nicht.

• @zeropage Ja, das sind jeweils Spaltenvektoren mit 3 Dimensionen. Hier geht es ja nur um Koordinaten Transformationen ohne perspektische Abbildung. Das macht man auch mit 4x4 Matrizen, damit man Rotation und Translation direkt in einer Matrix und einer Rechnung machen kann, die Perspektive (das w) setzt man dann einfach auf 1.

• Edit 2: Zur Notation, die ich hier verwende, falls das nicht ganz klar sein sollte:

$x$ - Kleinbuchstabe, normal: Skalar, also einzelne Zahlenwerte, hier reelle Zahlen.
$\bold x$ - Kleinbuchstabe, fett: Vektor, genauer Spaltenvektor aus $\mathbb{R}^4$ bzw. eine $4 \times 1$-Matrix, homogene Koordinaten für Punkte im $\mathbb{R}^3$, um auch Translationen als Matrixmultiplikation formulieren zu können.
$\bold X$ - Großbuchstabe, fett: Matrix, hier alles $4 \times 4$-Matrizen aus $\mathbb{R}^{4 \times 4}$.
$\bold X(...)$ - Großbuchstabe, fett, mit Funktionsargumenten: Funktion, die eine Matrix zurückgibt.

Edit: Sorry, hatte dein Problem falsch verstanden. Habe hier ner Erklärung geschrieben, wie man aus einem Kamera-"Weltobjekt" mit Position und Rotation eine View-Matrix berechnet. Aber das hast du ja bereits in deinem Code mit der invertierten Matrix.

Dein Problem scheint wohl eher zu sein, wie die Rotation des Kamera-Objekts korrekt aus Richtungsvektoren (dirZ und dirY-Parameter von cameraLookAt) ermittelt wird. Da hab ich jetzt gerade keine Zeit mehr, darüber nachzudenken. Irgendwas mit Skalarprodukt sieht schonmal nach ner brauchbaren Richtung aus, schliesslich gilt ja $\bold x^T \cdot \bold y = \left\lVert \bold x \right\rVert \left\lVert \bold y \right\rVert \cos \theta$.

Vielleicht finde ich ja im Laufe des Wochenendes nochmal Zeit, mir das genauer anzusehen

Das was ich eben geschrieben hatte, lass ich aber trotzdem mal stehen, vielleicht ist es ja doch irgendwie noch nützlich:

@zeropage Villeicht hilft dir so eine alternative Erklärung hier:

(Hinweis: Vektoren sind bei mir alle Spaltenvektoren. Davon hängt ab wie die Matrizen ausehen und in welcher Reihenfolge die Multipliziert werden)

Kurzer Refresher, damit wir auf dem selben Stand sind - bitte um Nachsicht, wenn ich hier Dinge schreibe, die du schon kennst:

Mit der Model-Matrix positionierst du ein Objekt in der 3D-Welt. Diese Matrix ist üblicherweise eine Komposition aus der Rotation ($\bold R = \bold R_z(\gamma) \cdot \bold R_y(\beta) \cdot \bold R_x(\alpha)$) und der Verschiebung ($\bold T = \bold T(t_x, t_y, t_z)$) des Objekts (und evtl. noch Skalierung und sowas, das ist aber hier nicht wichtig), also das Produkt aus diesen beiden Transformationsmatrizen: $\bold M = \bold T \cdot \bold R$.

Dann gibt es noch die View-Matrix ($\bold V$), mit der man die Welt-Koordinaten in lokale Kamerakoordinaten überführt (also Kamera um Nullpunkt, die in Richtung einer der Koordinatenachsen schaut) und eine Projektionsmatrix ($\bold P$), mit der man diese 3D-Kamerakordinaten auf den "Bildschirm" zur Darstellung projiziert.

Um einen Punkt von Objekt-Kordinaten auf den Bildschirm zu projizieren, führt man diese Transformationen einfach hintereinander aus:

$\bold x_{Bildschirm} = \bold P \cdot \bold V \cdot \bold M \cdot \bold x_{Objekt}$

Was du mit deinem cameraLookAt letztendlich berechnen willst, ist die View-Matrix ($\bold V$) für diese Tranformation.

Wenn du die Kamera nun als reguläres Weltobjekt betrachtest, dann stellst du fest, dass die gesuchte View Matrix die inverse der Model-Matrix der Kamera ist: Die Model-Matrix transformiert ja Objekt-Koordinaten in Welt-Koordinaten und die View-Matrix Welt-Koordinaten zu Kamera-Koordinaten, also genau das umgekehrte, was die Model-Matrix des Kameraobjekts macht. Es gilt also $\bold V = \bold M^{-1}$.

$\bold M^{-1}$ kannst du berechnen, indem du tatsächlich $\bold M$ mit einem Algorithmus deiner Wahl invertierst, oder aber du baust dir eine Matrix zusammen, bei der alle Transformationen in die entgegengesetzte Richtung durchgeführt werden.

$\bold M^{-1} = \bold R^{-1} \cdot \bold T^{-1}$

mit $\bold R^{-1} = \bold R_x(-\alpha) \cdot \bold R_y(-\beta) \cdot \bold R_z(-\gamma)=$matrix::makeRotation(-CameraRotation)
und $\bold T^{-1} = \bold T(-t_x, -t_y, -t_z)$

... also einfach in die entgegengesetzte Richtung verschieben und rotieren.

Ich hoffe das hilft weiter und verwirrt nicht noch mehr

• @zeropage Schwierig bei deinem Post ist, dass du nicht ein Problem mit einer klaren Frage hast, sondern dass das "alles irgendwie nicht funktioniert". Diese Posts sind immer besonders mühsam ordentlich zu beantworten, da man erstmal das oder (leider oft auch) mehrere (gerne auch rekursive) Probleme identifizieren und lösen muss, anstatt nur eine einzige Lösung zu finden

So wie ich das sehe, hast du folgenden Problemkomplex:

1. Du verstehst die Notation in den Vorlesungsfolien nicht: Ich denke das hat @Schlangenmensch geklärt, oder? Die Basisvektoren z.B. sind hier

$\bold{\tilde b_x} = \begin{bmatrix} 1 \\ 0 \\ 0\end{bmatrix}, \bold{\tilde b_y} = \begin{bmatrix} 0 \\ 1 \\ 0\end{bmatrix}, \bold{\tilde b_z} = \begin{bmatrix} 0 \\ 0 \\ 1\end{bmatrix}$

Damit sind $\bold T_{\bold{obj}}$ und $\bold T_{\bold{cam}}$ folglich $4 \times 4$-Matrizen.

2. Deine LookAt-Matrix "funktioniert nicht richtig": Das ist überhaupt nicht gesagt, da die gar nicht zur Anwendung kommt (siehe 3.). Zumindest beim groben Überfliegen sieht die Berechnung der View-Matrix erstmal nicht total falsch aus. Ich erkenne jedenfalls viele Teile, die ich auch so gemacht hätte (können natürlich trotzdem falsch "zusammengesteckt" sein )

Wie sieht es denn aus, wenn du die Szene mit dieser View-Matrix renderst und du die Sache mit den nicht-sichtbaren Flächen erstmal komplett ignorierst? Schaut die Kamera in die richtige Richtung? Vielleicht testest du erstmal das aus und bringst es in Ordnung, wenn da was nicht stimmt, bevor du dich Problem 3 zuwendest.

3. Dein Culling (herausfiltern von nicht-sichtbaren Flächen) funktioniert nicht richtig bezüglich Rotation der Kamera. Wie soll es auch? Ich sehe in den Funktionen getCameraRay und isShapeVisibleToCamera keinerlei Referenz auf irgendeine Form von Rotation. Du arbeitest lediglich mit cameraPosition.

Für einen Lösungsansatz möchte ich auch hier wieder das Bild der verschiedenen Koordinatensysteme bemühen, zwischen denen man transformiert (Objekt-, Welt-, Kamera-, Bildschirm-Koordianten, etc.). Mir persönlich hilft das recht gut beim Verständnis solcher Probleme:

Man kann getCameraRay so interpretieren, dass du mit der Berechung shape.center() - cameraPosition die Koordinaten des Shape in das Kamera-Koordinatensystem überführst (Rotation erstmal außen vor gelassen). Hier gilt für die Translationsmatrix:

$\bold T_{Kamera} = \begin{bmatrix} 1 & 0 & 0 & x_{Kamera}\\ 0 & 1 & 0 & y_{Kamera} \\ 0 & 0 & 1 & z_{Kamera} \\ 0 & 0 & 0 & 1\end{bmatrix}$ und $\bold T_{Kamera}^{-1} = \begin{bmatrix} 1 & 0 & 0 & -x_{Kamera}\\ 0 & 1 & 0 & -y_{Kamera} \\ 0 & 0 & 1 & -z_{Kamera} \\ 0 & 0 & 0 & 1\end{bmatrix}$

Siehe auch oben in meiner ersten Antwort. Die negativen Koordinaten verschieben in die entgegengesetzte Richtung und kehren die Translation damit um (inverse Matrix). Ich bezeichne den shape-Mittelpunkt hier mal als $s$, seine Welt-Koordinaten als $\bold s_{Welt}$ und seine Kamera-Koordinaten als $\bold s_{Kamera}$. Es gilt also :

$\bold s_{Kamera} =$shape.center() - cameraPosition$=\bold T_{Kamera}^{-1} \cdot \bold s_{Welt}$

shape.center() wird in also Kamera-Koordinaten transformiert, wobei alledings noch die Rotation fehlt. Dazu musst du eben nicht nur die Translation "umkehren", sondern auch noch die Rotation:

$\bold s_{Kamera} =$matrix::makeRotation(-CameraRotation) * (shape.center() - cameraPosition)$= \bold R_{Kamera}^{-1} \cdot \bold T_{Kamera}^{-1} \cdot \bold s_{Welt}$

Wobei $\bold R_{Kamera}^{-1}$ die Inverse Rotationsmatrix ist, die man u.a. erhalten kann, indem man alle Rotationen in die entgegengesetzte Richtung durchführt. Siehe z.B. oben:

$\bold R^{-1} = \bold R_x(-\alpha) \cdot \bold R_y(-\beta) \cdot \bold R_z(-\gamma)=$matrix::makeRotation(-CameraRotation)

Oder alternativ eben, wie du es auch schon in createMatrix machst um die gesamte Kameratransformation umzukehren, einfach die Matrix-Inverse direkt berechnen:

$\bold s_{Kamera} =$matrix::quickInverse(matCamera) * shape.center()$=(\bold M_{Kamera})^{-1} \cdot \bold s_{Welt}=\bold R_{Kamera}^{-1} \cdot \bold T_{Kamera}^{-1} \cdot \bold s_{Welt}$

So zumindest mein Verständnis. Ich würde es für den Anfang erstmal damit versuchen, das wird vermutlich eh nicht direkt reibungslos klappen - meiner Erfahrung nach braucht man bei solchen Sachen immer ein paar Versuche, bis mans richtig hinbekommt. Aber der Gedankengang ist glaube ich schon ganz gut erstmal

Bei vector::dotProduct<T>(getCameraRay(shape, cameraPosition), shape.normalVector()) < 0 bin ich mir allerdings etwas unsicher. Das Skalarprodukt ist $< 0$, wenn der Winkel zwischen den beiden Vektoren $\gt 90^\circ$ ist. Ich weiss nicht, wie du shape.normalVector() brechnest, aber nach meinem Verständnis müsstest du hier gegen $\begin{bmatrix} 0 & 0 & 1\end{bmatrix}^T$ prüfen, die Richtung, in die eine nicht-rotierte Kamera im Nullpunkt schaut (wir befinden uns ja in lokalen Kamera-Koordinaten) - also entlang der Z-Achse.

Wenn also der Winkel zwischen dem Vektor entlang dessen die Kamera schaut und dem Vektor, der von der Kamera aus auf den Mittelpunkt des shape zeigt (shape-Mittelpunkt in Kamera-Koordinaten) größer als 90° ist, dann liegt es "hinter" der Kamera-Bildebene.

4. Wenn das alles läuft, möchtest du vielleicht nicht nur nach der Kamera-Bildebene filtern, sondern gleich nach dem gesamten Pyramidenstumpf, den das Sichtfeld deiner Kamera bildet. Den nennt man auch "View Frustum" und ein Stichwort, das du hier suchst ist z.B. View Frustum Culling. Ich empfehle aber erstmal die simplere Sache mit der Bildebene hinzubekommen, ich denke da wird das mit dem Frustum dann einfacher, wenn du das erstmal alles verstanden hast.

So, das war jetzt erstmal ne Menge Holz, ich hoffe ich habe selbst nichts verbockt und es hilft weiter

• Moment, nun lasst mich doch erst mal die Antworten durchgehen Ich bin noch mit Deinem ersten Beitrag beshäftigt Hätte vielleicht sofort Rückmeldung geben sollen.

• Hallo Leute, Hallo Finnegan

Ich kann mich im Moment nicht mit meiner Anfrage beschäftigen. Ich sehe und lese, wie ausführlich Du geantwortet hast. Leider kann ich trotzdem hier gerade nicht weitermachen.

Ich lass das aber so stehen, weil ich darauf nochmal kommen werde. Auf jeden Fall sind in Deinen Antworten soviel Infos enthalten, das das auch nicht vergeblich dort steht.

Danke nochmal für die ausführlichen Antworten Ich komm wieder drauf zurück

• @zeropage Kein Problem. Der erste Kommentar dazu war schon echt höflich und nicht wirklich nötig. Einfach irgendwann Antworten wenn du wieder an dem Problem sitzt und noch Fragen offen sein sollten. Echt egal ob jetzt, in ein paar Monaten oder gar nicht mehr

• ich habe da ein buch "computergrafik" von alfred nischwitz, in dem das alles wunderbar erklärt wird und bei meinem private project sieht die funktion für die berechnung der matrix dann so aus:

int Viewing(float *viewingmatrix, float *eyevector, float *lookvector, float *upvector)
{
float nvector[VECTORSIZE];
float uvector[VECTORSIZE];
float vvector[VECTORSIZE];

SubtractVectors(nvector, eyevector, lookvector);
CrossProduct(uvector, upvector, nvector);
CrossProduct(vvector, nvector, uvector);

NormalizeVector(nvector);
NormalizeVector(uvector);
NormalizeVector(vvector);

viewingmatrix[0] = uvector[0];
viewingmatrix[1] = uvector[1];
viewingmatrix[2] = uvector[2];
viewingmatrix[3] = uvector[0] * -eyevector[0] + uvector[1] * -eyevector[1] + uvector[2] * -eyevector[2];

viewingmatrix[4] = vvector[0];
viewingmatrix[5] = vvector[1];
viewingmatrix[6] = vvector[2];
viewingmatrix[7] = vvector[0] * -eyevector[0] + vvector[1] * -eyevector[1] + vvector[2] * -eyevector[2];

viewingmatrix[8] = nvector[0];
viewingmatrix[9] = nvector[1];
viewingmatrix[10] = nvector[2];
viewingmatrix[11] = nvector[0] * -eyevector[0] + nvector[1] * -eyevector[1] + nvector[2] * -eyevector[2];

viewingmatrix[12] = 0.0f;
viewingmatrix[13] = 0.0f;
viewingmatrix[14] = 0.0f;
viewingmatrix[15] = 1.0f;

return 0;
}


wobei
eyevector: position der kamera
lookvector: zielpunkt der kamera
upvector: da wo "oben" ist