OpenGL Kamera Problem
-
Hi,
ich habe ein Problem mit meiner OpenGL Kamera. Eigentlich läuft alles, aber wenn ich die Y-Achse bei einem großen X-Winkel rotiere (und umgekehrt), rotiert die Z-Achse mit. Das kann man sich wahrscheinlich schlecht vorstellen, also habe ich mal ein Video gemacht:
http://www.youtube.com/watch?v=L8wRVM19ZQg
Ihr seht also, die Anordnung der Würfel ändert sich, obwohl ich "eigentlich" nur nach links und rechts gucke. Ich fürchte irgendwie, dass das an der Ungenaugkeit der Gleitkommazahlen liegt.
Hat irgendjemand eine Idee, wo ich da ansetzen könnte? (PS: Ich nutze keine fertige Mathe-Lib, also die Funktionen sind nicht "fest" und könnten durchaus verändert werden.)Edit: Ich glaube mittlerweile nicht mehr wirklich an einen Rundungsfehler, das muss eigentlich gravierender sein. Hier noch mal ein YouTube-Video mit einem charakteristischerem Verhalten:
http://www.youtube.com/watch?v=nJCvhy267XgEdit:
Die Rotation der Kamera wird über eine 4x4 Matrix dargestellt. Die Rotationsfunktionen sehen so aus:void rotateY(f32 angle) { rotation_ *= lin::rotateY(-angle); }
Und die Rotationsmatritzen werden so erstellt: ( http://de.wikipedia.org/wiki/Drehmatrix )
template<typename T> inline BlockMatrix4<T> rotateY(T angle) { T rad = make_rad(angle); T crot = cos(rad); T srot = sin(rad); BlockMatrix4<T> mat (GeometricVector4<T>(crot, 0.0, srot, 0.0), GeometricVector4<T>(0.0, 1.0, 0.0, 0.0), GeometricVector4<T>(-srot, 0.0, crot, 0.0), GeometricVector4<T>(0.0, 0.0, 0.0, 1.0)); return mat; }
-
Deine Projektionsmatrix scheint kaputt zu sein. An der Matrix die du uns zeigst liegt es auf jeden Flal nicht. Das Schlimmste was da passieren kann ist, dass du in die falsche Richtung rotierst. So einen Effekt kannst du aber kriegen, wenn du row major/column major in der Projektionsmatrix vertauschst.
-
sieht irgendwie aus als ob du die transformation immer weiter auf die vorherigen transformationen draufrechnest. dann wuerde das verhalten durchaus normal sein, esseiden ich missverstehe was das problem sein soll.
-
rapso schrieb:
sieht irgendwie aus als ob du die transformation immer weiter auf die vorherigen transformationen draufrechnest. dann wuerde das verhalten durchaus normal sein, esseiden ich missverstehe was das problem sein soll.
Ja, genau das mache ich, gut erkannt, denn das könnte der Fehler sein. Ich dachte, man könnte das so machen. (Ich kann mir die sinus/cosinus Wertefolge bei den Rotationen leider nicht so gut vorstellen, ist halt einfach von Wikipedia abgeschrieben.)
Mir fällt allerdings kein Weg ein wie ich das sonst machen sollte. Also momentan ist das in vereinfacht so:
class Camera { mat4 rotation_; public: rotateX(f32 angle) { .. }
Ich muss ja die aktuelle Rotation irgendwie speichern. Übersehe ich da einen Weg? (Klar, die Vektoren könnte ich auch einzeln speichern, aber das ist doch jetzt nicht so der Unterschied?)
Wie mache ich das also? (Im Netz findet man leider meist nur Kameras die in zwei oder sogar nur in einer Dimension gedreht werden können.)
-
Hi,
halte ein lokales Koordinatensystem der Kamera vor und manipuliere das bei jeder Drehung oder Translation.
Hier mal ein wenig Code den ich neulich zusammengefrickelt habe
using OpenTK; namespace Test { class Camera { private float translationSpeed = 0.08f; private float rotationSpeed = 0.50f; private Vector3 eye; private Vector3 forward; private Vector3 right; private Vector3 up; public Vector3 Eye { get { return eye; } set { eye = value;}} public Vector3 LookAt { get { return eye + forward; } } public Vector3 Up { get { return up; } } public Camera() { Reset(); } public void Reset() { eye = new Vector3(0.5f, 0.04f, -0.3f); forward = new Vector3(0, 0, -1); right = new Vector3(1, 0, 0); up = new Vector3(0, 1, 0); } private void Roll(float delta) { Matrix4 matrix; Matrix4.CreateFromAxisAngle(forward, (float)delta, out matrix); right = Vector3.Transform(right, matrix); up = Vector3.Transform(up, matrix); } private void Pitch(float delta) { Matrix4 matrix; Matrix4.CreateFromAxisAngle(right, (float)delta, out matrix); forward = Vector3.Transform(forward, matrix); up = Vector3.Transform(up, matrix); } private void Yaw(float delta) { Matrix4 matrix; Matrix4.CreateFromAxisAngle(up, (float)delta, out matrix); forward = Vector3.Transform(forward, matrix); right = Vector3.Transform(right, matrix); } public void RotateWorldY(float delta) { Matrix4 matrix; Matrix4.CreateFromAxisAngle(Vector3.UnitY, (float)(rotationSpeed * delta), out matrix); forward = Vector3.Transform(forward, matrix); right = Vector3.Transform(right, matrix); up = Vector3.Transform(up, matrix); } public void SpeedRotationUp() { rotationSpeed += 0.05f; } public void SpeedRotationDown() { if (rotationSpeed > 0.1) rotationSpeed -= 0.05f; } public void SpeedTranslationUp() { translationSpeed += 0.05f; } public void SpeedTranslationDown() { if (translationSpeed > 0.1) translationSpeed -= 0.05f; } public void RollRight(float delta) { Roll(rotationSpeed * delta); } public void RollLeft(float delta) { Roll(-rotationSpeed * delta); } public void YawLeft(float delta) { Yaw(rotationSpeed * delta); } public void YawRight(float delta) { Yaw(-rotationSpeed * delta); } public void PitchForward(float delta) { Pitch(rotationSpeed * delta); } public void PitchBackward(float delta) { Pitch(-rotationSpeed * delta); } public void MoveForward(float delta, bool adjustY) { eye.X += (float)(translationSpeed * delta * forward.X); if(adjustY) eye.Y += (float)(translationSpeed * delta * forward.Y); eye.Z += (float)(translationSpeed * delta * forward.Z); } public void MoveBackward(float delta, bool adjustY) { eye.X -= (float)(translationSpeed * delta * forward.X); if(adjustY) eye.Y -= (float)(translationSpeed * delta * forward.Y); eye.Z -= (float)(translationSpeed * delta * forward.Z); } public void MoveRight(float delta) { eye.X += (float)(translationSpeed * delta * right.X); eye.Y += (float)(translationSpeed * delta * right.Y); eye.Z += (float)(translationSpeed * delta * right.Z); } public void MoveLeft(float delta) { eye.X -= (float)(translationSpeed * delta * right.X); eye.Y -= (float)(translationSpeed * delta * right.Y); eye.Z -= (float)(translationSpeed * delta * right.Z); } public void MoveUp(float delta) { eye.X += (float)(translationSpeed * delta * up.X); eye.Y += (float)(translationSpeed * delta * up.Y); eye.Z += (float)(translationSpeed * delta * up.Z); } public void MoveDown(float delta) { eye.X -= (float)(translationSpeed * delta * up.X); eye.Y -= (float)(translationSpeed * delta * up.Y); eye.Z -= (float)(translationSpeed * delta * up.Z); } } }
(Matrix4.CreateFromAxisAngle und sonstiges stammt aus OpenTK. Musst eine alternative für C++ finden oder implementieren)
Und dann:
GL.MatrixMode(MatrixMode.Modelview); Vector3 eye = Camera.Eye; Vector3 lookat = Camera.LookAt; Vector3 up = Camera.Up; Matrix4 modelview = Matrix4.LookAt( eye.X, eye.Y, eye.Z, lookat.X, lookat.Y, lookat.Z, up.X, up.Y, up.Z); GL.LoadMatrix(ref modelview);
-
Ja, mit Vektoren hatte ich es auch schon mal, ich dachte nur es sei so einfacher, da ich die Matritzen dann direkt GLSL übergeben kann. Ich gucke mir deinen Code mal an, vielleicht verstehe ich dann ja, was ich bei der Matrix falsch mache.
µ schrieb:
halte ein lokales Koordinatensystem der Kamera vor und manipuliere das bei jeder Drehung oder Translation.
Ich dachte eigentlich, dass ich genau das machen würde.
-
Ich vermute Du drehst nicht um die lokalen Achsen, sondern um die globalen. Kann das sein?
Matrix4.CreateFromAxisAngle generiert eine Drehmatrix um einen beliebigen Vektor, damit man um die lokalen Kameraachsen rotieren kann.
-
µ schrieb:
Ich vermute Du drehst nicht um die lokalen Achsen, sondern um die globalen. Kann das sein?
Ja. Ich habe schon probiert um die lokalen Achsen zu rotieren, aber das funktionierte gar nicht, auch wenn es eigentlich "logisch" sein müsste. Irgendwie habe ich bei Matritzen aufgegeben mir das vorzustellen, mit Vektoren ging das noch irgendwie, aber so oO.
Wie auch immer, das sah dann so aus:template<typename T> inline BlockMatrix4<T> rotate(GeometricVector3<T> n, T angle) { T rad = make_rad(angle); T crot = cos(rad); T srot = sin(rad); BlockMatrix4<T> mat (GeometricVector4<T> (crot + (n[0] * n[0]) * (1 - crot), n[0] * n[1] * (1 - crot) - n[2] * srot, n[0] * n[2] * (1 - crot) + n[1] * srot, 0.f), GeometricVector4<T> (n[1] * n[0] * (1 - crot) + n[2] * srot, crot + (n[1] * n[1]) * (1 - crot), n[1] * n[2] * (1 - crot) - n[0] * srot, 0.f), GeometricVector4<T> (n[2] * n[1] * (1 - crot) - n[1] * srot, n[2] * n[1] * (1 - crot) + n[0] * srot, crot + (n[2] * n[2]) * (1 - crot), 0.f), GeometricVector4<T>(0.f, 0.f, 0.f, 1.f)); mat.invert(); // Testweise return mat; } void rotateX(f32 angle) { rotation_ *= lin::rotate(vectorX(), -angle); } vec3 vectorX() { return vec3(rotation_[0][0], rotation_[1][0], rotation_[2][0]).normalized(); }
-
Das hier ist deine Matrix, wenn ich das richtig sehe:
http://upload.wikimedia.org/math/5/d/5/5d5a3e63e4a88919fa13d1b3a79ccba7.pngDamit hast Du doch schon alles zusammen, was für eine Vektorkamera notwendig ist. Eigentlich solltest Du meinen Code jetzt direkt übernehmen können.
(Anmerkung: Bei Roll, Pitch und Yaw sollte man den dritten Vektor des Kamerasystems vielleicht noch aus dem Kreuzprodukt der beiden anderen bilden. Als Vorsichtsmaßnahme gegen Rundungsfehler, damit die Vektoren stets orthogonal sind.)
-
µ schrieb:
Das hier ist deine Matrix, wenn ich das richtig sehe:
http://upload.wikimedia.org/math/5/d/5/5d5a3e63e4a88919fa13d1b3a79ccba7.pngBei genauem Hinsehen fällt auf, dass ich genau diese Matrix nutze. :p
µ schrieb:
Damit hast Du doch schon alles zusammen, was für eine Vektorkamera notwendig ist. Eigentlich solltest Du meinen Code jetzt direkt übernehmen können.
Ich würde eigentlich gerne bei der Matrix bleiben. Oder zumindest verstehen, warum das nicht funktioniert.
µ schrieb:
(Anmerkung: Bei Roll, Pitch und Yaw sollte man den dritten Vektor des Kamerasystems vielleicht noch aus dem Kreuzprodukt der beiden anderen bilden. Als Vorsichtsmaßnahme gegen Rundungsfehler, damit die Vektoren stets orthogonal sind.)
Ja, könnte helfen gegen Rundungsfehler. Werde ich vielleicht noch einbauen. Aber leider hilft es nicht gegen mein Problem.
Hier möchte ich noch einmal drauf eingehen:
rapso schrieb:
sieht irgendwie aus als ob du die transformation immer weiter auf die vorherigen transformationen draufrechnest. dann wuerde das verhalten durchaus normal sein, esseiden ich missverstehe was das problem sein soll.
Genau das ist der Fall. Wie könnte ich es anders machen? Ich muss das lokale Koordinatesystem ja irgendwie speichern? (Hier halt in der Matrix.)
-
Ich glaube Rapso meinte, dass Du die aktuelle Transformation (fälschlicherweise) immer zur Modelview-Matrix multiplizierst. Die muss aber in jedem Frame zurückgesetzt werden. Das lokale Koordinatensystem der Kamera wird aber natürlich bei jeder Bewegung verändert und gespeichert.
cooky451 schrieb:
Ich würde eigentlich gerne bei der Matrix bleiben. Oder zumindest verstehen, warum das nicht funktioniert.
Ob Du forward, right, up einzeln als Vektoren oder zusammen in einer Matrix speicherst ist wohl unerheblich, wenn am Ende das gleiche rauskommt. Versuchs mit den Vektoren, Code steht oben. Wenn das funktioniert, press alles in eine Matrix.
Eigentlich ist die Lösung hier im Thread. Du musst es nur umsetzen
-
µ schrieb:
Eigentlich ist die Lösung hier im Thread. Du musst es nur umsetzen
Ich mache ja eigentlich alles genau so, nur halt, dass ich um die globalen Achsen rotiere. Wenn ich wie beschrieben die lokalen nehme, sieht das so aus.
Interessant wäre halt noch wie CreateFromAxisAngle() und Transform() implementiert sind, denn wenn sie das so sind wie ich mir das vorstelle, dürfte sich nichts ändern.Allerdings bin ich so verzweifelt, dass ich es jetzt trotzdem mit "direkten" Vektoren versuche.
-
Ok, heute habe ich wieder etwas Zeit gefunden um zu basteln. Ich habe mal alles mit Vektoren aufgezogen, allerdings verhalten die sich (warum wusste ich das? :D) genau so wie die Matritzen.
Eine Sache ist mir allerdings noch aufgefallen. Wenn ich um die globalen Achsen rotiere, muss ich, damit es funktioniert, die Matrix am Ende so erstellen:mat4 rotation (vec4(right_[0], right_[1], right_[2], 0.f), vec4(up_[0], up_[1], up_[2], 0.f), vec4(forward_[0], forward_[1], forward_[2], 0.f), vec4(0.f, 0.f, 0.f, 1.f));
Wenn ich dagegen um die lokalen Achsen rotiere bzw. mich auf diesen fortbewege, muss ich die Matrix so erstellen:
mat4 rotation (vec4(right_[0], up_[0], forward_[0], 0.f), vec4(right_[1], up_[1], forward_[1], 0.f), vec4(right_[2], up_[2], forward_[2], 0.f), vec4(0.f, 0.f, 0.f, 1.f));
Vertausche ich das Ganze, bewege (und rotiere) ich mich unabhängig von der Rotation. Kann da vielleicht schon jemand sehen, was ich falsch mache?
(PS: Die Position wird im Shader dazugerechnet, da muss ich dann am Ende gucken, ob ich das noch vereinen kann.)
(PPS: Es geht immernoch um den anfangs beschriebenen Effekt. Der bleibt auch mit Vektoren genau so erhalten.)
-
Generierst Du am Ende auch eine Matrix mit Hilfe einer LookAt-Funktion, oder verwendest Du direkt diese als Modelview-Matrix?
Vielleicht solltest Du mal den ganzen Code der Kamera bis hin zum setzen der MV-Matrix hier posten.
-
µ schrieb:
Vielleicht solltest Du mal den ganzen Code der Kamera bis hin zum setzen der MV-Matrix hier posten.
Wollte ich vermeiden, da etwas unübersichtlich. Aber ich werde wohl nicht drum rum kommen.
Unwichtige Teile (Shader Aktivierung etc.) wurden entfernt.class Object { protected: vec3 forward_, right_, up_; vec3 position_; public: Object(ShaderProgram *shader, vec3 const& pos) : right_(1.f, 0.f, 0.f), up_(0.f, 1.f, 0.f), forward_(0.f, 0.f, 1.f), position_(pos) {} virtual ~Object() = 0 {} void moveX(f32 amount) { position_ -= vectorX() * amount; } void moveY(f32 amount) { position_ -= vectorY() * amount; } void moveZ(f32 amount) { position_ -= vectorZ() * amount; } void rotateX(f32 angle) { mat3 m = lin::rotate(vectorX(), angle); //mat3 m = lin::rotateX(angle); up_ = m * up_; up_.normalize(); forward_ = m * forward_; forward_.normalize(); } void rotateY(f32 angle) { mat3 m = lin::rotate(vectorY(), angle); //mat3 m = lin::rotateY(angle); right_ = m * right_; right_.normalize(); forward_ = m * forward_; forward_.normalize(); } void rotateZ(f32 angle) { mat3 m = lin::rotate(vectorZ(), angle); //mat3 m = lin::rotateZ(angle); right_ = m * right_; right_.normalize(); up_ = m * up_; up_.normalize(); } vec3 vectorX() { return right_; } vec3 vectorY() { return up_; } vec3 vectorZ() { return forward_; } virtual void render() = 0; };
Kamera:
class Camera : public Object { mat4 frustum_; public: Camera(ShaderProgram *shader, vec3 const& pos) : Object(shader, pos) { setPerspective(45.f, 16.f / 9.f, 1.f, 10000.f); } void setPerspective(f32 angle, f32 aspect, f32 min, f32 max) { using namespace std; f32 rad = lin::make_rad(angle); frustum_ = mat4 (vec4(1.f / tan(rad), 0.f, 0.f, 0.f), vec4(0.f, aspect / tan(rad), 0.f, 0.f), vec4(0.f, 0.f, (min + max) / (min - max), 1.f), vec4(0.f, 0.f, -2.f * min * max / (min - max), 0.f)); } void render() { glUniformMatrix4fv(shader_frustum_, 1, GL_FALSE, frustum_[0]); glUniform3fv(shader_cam_translation_, 1, &position_[0]); //mat4 rotation // (vec4(right_[0], right_[1], right_[2], 0.f), // vec4(up_[0], up_[1], up_[2], 0.f), // vec4(forward_[0], forward_[1], forward_[2], 0.f), // vec4(0.f, 0.f, 0.f, 1.f)); mat4 rotation (vec4(right_[0], up_[0], forward_[0], 0.f), vec4(right_[1], up_[1], forward_[1], 0.f), vec4(right_[2], up_[2], forward_[2], 0.f), vec4(0.f, 0.f, 0.f, 1.f)); glUniformMatrix4fv(shader_cam_rotation_, 1, GL_FALSE, rotation[0]); } };
GLSL-Code:
#version 110 uniform mat4 frustum; uniform vec3 cam_translation; uniform mat4 cam_rotation; uniform vec3 translation; uniform mat4 rotation; attribute vec4 position; mat4 translate(vec3 v) { return mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(v.x, v.y, v.z, 1.0)); } void main() { gl_Position = frustum * cam_rotation * translate(cam_translation) * translate(translation) * rotation * position; }
Falls die Vermutung besteht, dass die Mathe-Funktionen (rotate, operator * etc.) falsch sind, werde ich die auch noch posten.
-
Nur kurz überflogen weil ich jetzt in die Falle muss
cooky451 schrieb:
mat4 rotation (vec4(right_[0], up_[0], forward_[0], 0.f), vec4(right_[1], up_[1], forward_[1], 0.f), vec4(right_[2], up_[2], forward_[2], 0.f), vec4(0.f, 0.f, 0.f, 1.f)); glUniformMatrix4fv(shader_cam_rotation_, 1, GL_FALSE, rotation[0]);
Versuche mal aus dieser Umrechnung deine MV-Matrix zu erstellen, statt right, up und forward direkt zu verwenden:
http://wiki.delphigl.com/index.php/gluLookAt
-
Hm.. also damit bekomme ich sehr eigenartige Effekte.
template<typename T> inline BlockMatrix4<T> lookAt(GeometricVector3<T> const& eye, GeometricVector3<T> const& lookat, GeometricVector3<T> up) { vec3 forward = lookat - eye; forward.normalize(); up.normalize(); vec3 side = cross(forward, up); side.normalize(); up = cross(side, forward); // up.normalize(); ? BlockMatrix4<T> mat (GeometricVector4<T>(side[0], side[1], side[2], 0.0), GeometricVector4<T>(up[0], up[1], up[2], 0.0), GeometricVector4<T>(forward[0], forward[1], forward[2], 0.0), GeometricVector4<T>(0.f, 0.f, 0.f, 1.0)); return mat; }
-
Das:
mat4 translate(vec3 v) { return mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(v.x, v.y, v.z, 1.0)); }
ist keine Translation wenn du von Rechts multiplizierst, sondern eine verzerrung entlang der w-koordinate. transponier das.
Ich war also mit meinem ersten Kommatar ganz nah ran:
So einen Effekt kannst du aber kriegen, wenn du row major/column major in der Projektionsmatrix vertauschst.
-
otze schrieb:
ist keine Translation wenn du von Rechts multiplizierst, sondern eine verzerrung entlang der w-koordinate. transponier das.
OpenGL erwartet diese Anordnung. Ist leider alles etwas nervig, weil man bei jeder Matrix aufpassen muss, welcher Ordnung die jetzt folgt. Wie gesagt, ist diese Matrix aber korrekt, falls du allerdings im restlichen Code eine nicht dazu passende Matrix findest, zeig sie gerne auf.
(Zeile 29 - 33 passen natürlich nicht dazu, deshalb hatte es mich anfänglich ja auch so gewundert, dass das so auf einmal "funktioniert". Da muss also irgendwie vorher ein Haken sein, ich komme nur nicht drauf. Muss jetzt leider kurz ~2 Stunden weg.)
-
Den Effekt hatte ich auch mal. Der ist auch logisch, wenn man sich überlegt, dass man beim Rotieren um die x-Achse die lokalte Y-Koordinate der Kamera nach vorne kippt. Sobald man dann um die y-Achse rotiert, wird die Kamera dann schief und dreht sich relativ gesehen zur globalen z-Achse.
Was man machen muss ist, dass man nicht um die lokale y-Achse der Kamera rotiert, sondern um die globale. D.h. man muss erst die globale y-Achse (0, 1, 0) in das lokalte Koordinatensystem der Kamera transformieren (Kamera-Matrix * (0, 1, 0)), dann aus dieser Matrix die die "Position" herausfiltern (also die ersten 3 Elemente der letzten Spalte), welche dann die Achse darstellen, um die man dann die Kamera rotieren muss.