Tutorial Kamera1

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Dreht sich das Universum um uns und andere philosophische Fragen

Einführung

Sicherlich standen schon einige von euch vor dem Problem, wie man denn eine Kamera in OpenGl implementieren könnte. Es stellen sich einem dabei viele Probleme, z.B. wie kann ich die Kamera auf ein Objekt ausrichten. Oder wie sieht es mit Billboarding aus? Die Liste kann fast unendlich erweitert werden. Ich werde versuchen, ein paar dieser Probleme zu lösen und vielleicht schaffe ich es sogar, den Einen oder Anderen dazu zu bringen, selbst eigene Kameratechnische Probleme zu lösen.

Wer SchodMC's Objekt xxx Tutorials gelesen hat, hat übrigens schon einiges an Vorwissen, das er hier prima wiederverwerten kann. Ich kann die Lektüre dieser gut gelungenen Werke vor dem Weiterlesen nur wärmstens empfehlen.

Die Kamera Analogie

Dreht sich die Erde um die Sonne oder die Sonne um die Erde? Ist euch bewusst, dass diese Frage eigentlich nicht zu beantworten ist? Tatsächlich macht es, wenn man richtig darüber nachdenkt keinen Unterschied. Das könnt ihr selber überprüfen: Geht vor eure Haustür und stellt euch hin (pfui, es regnet - nein, macht es doch lieber drinnen). Dreht euch einmal im Kreis. Was habt ihr gesehen? Habt ihr euch nun im Universum gedreht oder das ganze Unviversum um euch herum? Eine seltsame Frage? ... Vielleicht hat`s der Ein oder Andere schon bemerkt... Für den Mensch der sich da grad im Regen gedreht hat, wäre beides denkbar. Tatsächlich macht es für das Auge keinen Unterschied. Es handelt sich nur um ein philosophisches Problem, besonders eines der westlich geprägten Welt, das ihr zu überwinden habt. Alles dreht sich um euch, nicht ihr darin. Also: Dreht ihr euch nach rechts, ist das gleichbedeutend damit, dass sich das Univerum unter euren Füssen nach links gedreht hat. Noch Fragen?

In der Hoffnung euch jetzt alle komplett verwirrt zu haben, mache ich jetzt einfach mal weiter.

Darstellung von Objekten

Bevor wir irgendwelche Objekte zeichnen, müssen wir uns erst einmal eine Welt ausdenken. Der zweite Punkt ist dann, zu definieren, wie diese Objekte dargestellt werden sollen. Mir sind unterschiedliche Ideen gekommen, die sich alle mit mehr oder weniger Aufwand realisieren lassen.

Letztendlich habe ich mich an einer Weltraumszene festgehängt. Hier lässt sich so einiges zeigen. Besonderen dank an Sascha Willems, der - freundlich wie er ist - gestattete, einen Satz seiner Texturen einzusetzen und gleich noch ein Paar Orte verriet, an denen man gute Sonnensystem-Texturen finden kann.

Kommen wir nun zu den Objekten selbst. Wir wollen hier ein wenig OOP verwenden. Das bietet sich an, da es in unserem Fall vorteilhaft ist, einige Objekte voneinander abzuleiten zu können. Alle Objekte müssen 2 Eigenschaften besitzen: Position und Rotationsmatrix:

  type
    //Basis Objekt
    TObjekt = class
    private
    public
      ObjektName : String;
      Rotation  : THomogeneousMatrix;
      Position  : TVertex;
      constructor Create; virtual;
    end;

Um die Planeten darzustellen, werden unterschiedliche Nachfolger dieses Objektes verwendet. Ich will auf diese nicht weiter eingehen, da sie an sich nichts mit unserem eigentlichem Interessensgebiet zu tun haben - den Kameras.

Das Kameraobjekt

Bevor wir uns daran machen, über die Implementierung nachzudenken, sollten wir überlegen, was wir eigentlich haben wollen. Eine Kamera, das ist klar. Was soll sie können? Sie soll einmal aus der Sicht des Objektes schauen können, ein Objekt fest in den Blick nehmen und auch eine freie Ansicht bieten (Freie Bewegungen sind bislang nicht in den Beispielsource mit eingebaut. Eine freie Ansicht hingegen schon - steuerbar mit dem Joystick). Wir wollen aber keine stationäre Kamera, sondern eine die sich gleich noch mehr oder weniger automatisch bewegt.

Wir brauchen außerdem die Möglichkeit, unterschiedliche Modi einzustellen und Zielobjekte festzulegen. Nicht zu vergessen auch eine Funktion, die eine Kameramatrix erzeugt und diese auch noch gleich an OpenGL weitergibt.

Dies sollte uns für den Anfang erst einmal genügen. Fangen wir also an, uns die gewünschte Kamera zu bauen.

    TLookMode = (lmFree, lmObjektSight, lmLookAtObjekt);
    TMoveMode = (mmFree, mmAttatched);
    TKamera = class(TObjekt)
    private
    public
      LookMode : TLookMode; //Ansichtsmodus
      MoveMode : TMoveMode; //Bewegungsmodus

      LookTarget, MoveTarget : TKugel; //Ansichtsziel und Bewegungsziel

      procedure UseMatrix; virtual; //Kamera-Matrix einsetzen
      constructor Create; override;
      procedure ResetView; //Ansicht zurücksetzen
    end;

Implementation der Kameramodi

lmFree und lmObjektSight

Diese Modi haben einige Gemeinsamkeiten. lmObjektSight steht für die Blickrichtung, die ein Objekt hat, lmFree ist die frei wählbare Variante. Häufig gleichen sich diese Modi, wenn man z.B. das Objekt von dem aus man in die Welt schaut gleich auch vom Spieler gesteuert wird. Von der Implementierung her sind sie fast Identisch. Das Problem besteht darin, dass die Kameramatrix genau anders herum drehen muss, als wie das Objekt gedreht ist (siehe Kamera Analogie). Die Mathematiker haben das Problem für uns schon gelöst - mit invertierten Matrizen. Wir müssen also nicht einmal das Rad neu erfinden:

    procedure TKamera.UseMatrix;
    var
      RotMatrix : THomogeneousMatrix;
      Pos : TVertex;

    begin
      //Ansichten
      case LookMode of
        lmFree     : begin
                       RotMatrix := Rotation;
                       InvertMatrix(RotMatrix)
                     end;
        lmObjektSight: begin
                         Assert(Assigned(LookTarget));
                         Rotation := LookTarget.Rotation;
                         RotMatrix := Rotation;
                         InvertMatrix(RotMatrix)
                       end
      end;


      //Bewegungen
      case MoveMode of
        mmFree     : begin
                       Pos := Position
                     end;
        mmAttatched : begin
                       Assert(Assigned(MoveTarget));
                       Position := MoveTarget.Position;
                       Pos := Position;
                     end;
      end;

      //Matrix einlegen
      glLoadMatrixf(@RotMatrix[0,0]);
      glTranslatef(-Pos[0], -Pos[1], -Pos[2])
    end;


War doch schon mal gar nicht so schwer, oder?

lmLookAtObjekt

Nun wollen wir ein Objekt in den Fokus der Kamera nehmen. Voraussetzung ist, dass wir bereits die Koordinaten unserer Kamera kennen, denn sonst können wir nicht bestimmen, in welcher Richtung das Objekt liegt.

Wir müssen uns entscheiden: Wollen wir eine komplett neue Matrix erstellen, oder wollen wir die bereits bestehende Rotationsmatrix verwenden und sie so manipulieren, dass die Kamera auf das Objekt ausgerichtet ist? Ich habe mich für letztere Variante entschieden, da man dieses Wissen in Spielen auch anderorts wiederverwerten kann.

Kommen wir also zur Idee, die hinter unserem Problem steckt: Ich hab eine Rotationsmatrix und die Globale Richtung von unserem Objekt zu unserem Zielpunkt. Drehen wir nun diese Globale Richtung in das lokale Koordinatensystem unseres Objektes, so können wir die Winkel errechnen, die nötig sind, um unsere Kamera auf das Ziel auszurichten.

Das Problem: Ein Problem, das sich mir beim Nachdenken über diese Idee gestellt hat, ist, dass die Blickrichtung von OpenGl immer in die negative Z-Richtung geht. An sich wird das Problem nicht wesentlich schwieriger, man muss nur an ein paar geschickten Stellen ein Minus verteilen, bzw. Minuend und Subtrahend vertauschen.

Gehen wir einmal davon aus, dass wir die globale Richtung in die lokale transformiert haben. Wie kommen wir nun an unsere Rotationsmatrix? Bzw. wie kommen wir an die benötigten Kosinus- und Sinuswerte? Zuerst wollen wir um die y-Achse drehen. Deshalb können wir den y-Wert der lokalen Richung erst einmal außer Acht lassen. Setzen wir diesen auf 0 und normalisieren unseren Vektor. Wir erhalten dadurch einen Vektor auf dem Einheitskreis - Vorteil: wir müssen unseren Sinus und Cosinus nicht kompliziert errechnen, denn wir haben ihn bereits. Keine Arbeit mehr für uns. Matrix erzeugen und wir haben fertig.

Jetzt haben wir die Kamera um die y-Achse ausgerichtet. Es fehlt noch die x-Achse. Wir haben bereits einiges an Vorarbeit geleistet. Wir können ganz einfach den Winkel zwischen der lokalen Richtung und dem Vektor auf dem Einheitskreis von vorhin berechnen. Sind beides Einheitsvektoren, so ist das Skalarprodukt(zu Englisch Dotproduct) der Kosinus des gesuchten Winkels. Wie sicher Einigen bekannt ist, gilt:

Cos²(α) + Sin²(α) = 1

Wir kennen also auch den Wert des Sinus. Nur das Vorzeichen ist uns dann noch unbekannt. Dies ergibt sich aber daraus, ob der y-Wert der lokalen Richtung positiv oder negativ ist.

Ich würde sagen Problem gelöst, hier der passende Code:

    procedure LockTarget(Target : TObjekt);
    var
      InvMatrix, M1, M2 : THomogeneousMatrix;
      ObjektPosition : TVertex;

      Cos, Sin : Single;
      xzEbenenPosition : TVertex;
    begin
      try
        InvMatrix := Rotation;
        InvertMatrix(InvMatrix);

        //Globale Objekt Position
        ObjektPosition := VectorSubtract(Position, Target.Position);
        //In lokale Koordinaten drehen
        ObjektPosition := VectorTransform(ObjektPosition, InvMatrix);

        xzEbenenPosition[0] := ObjektPosition[0];
        xzEbenenPosition[1] := 0;
        xzEbenenPosition[2] := ObjektPosition[2];
        if not((xzEbenenPosition[0] = 0.0) and (xzEbenenPosition[2] = 0.0)) then
        begin
          NormalizeVector(xzEbenenPosition);
          M1 := CreateRotationMatrixY(xzEbenenPosition[0], xzEbenenPosition[2])
        end
        else
          M1 := IdentityHmgMatrix;

        NormalizeVector(ObjektPosition);
        Cos := VectorDotProduct(xzEbenenPosition, ObjektPosition);
        Sin := Sqrt(1- Cos*Cos);

        if (ObjektPosition[1] > 0.0) then
          Sin := - Sin;
        M2 := CreateRotationMatrixX(Sin, Cos);

        RotMatrix := MatrixMultiply(M1, M2);
        Rotation := MatrixMultiply(RotMatrix, Rotation);
        RotMatrix := Rotation;
        InvertMatrix(RotMatrix)
      except
        RotMatrix := IdentityHmgMatrix
      end
    end;


Vorwärts, Rückwärts, Links, Rechts, Hoch, Runter

Eine in Ego Shootern als Strafen bezeichnete Art sich zu bewegen, ist das Bewegen der Kamera in eine bestimmte Richtung, die von der Ausrichtung der Kamera abhängt. Dies beschreibt ein recht häufig auftretendes Problem im Zusammenhang mit Kameras. An sich ist es nicht weiter schwer, z.B. entspricht links Strafen dem Bewegen entlang der negativen x-Achse im lokalen Koordinatensystem der Kamera. Will man also die Position der Kamera in dieser Art manipulieren, so erstellt man einfach einen Vektor, der in der zur Kamera lokalen Richtung zeigt (im Beispiel also: [-1, 0,0]). Dann dreht man mit Hilfe der Rotationsmatrix der Kamera diesen Vektor ins globale Koordinatensystem und addiert ihn zum Positionsvektor der Kamera. (Allen denen es nicht aufgefallen ist: Das war eine Kurzfassung der Mathematik in SchodMCs "Objekt gedreht und dennoch nach vorne bewegt" Tutorial).


Billboarding

Eine weitere Kameraspezifische Sache ist das Billboarding. Das Problem stellt sich einem, wenn man eine Partikelengine schreibt und die Polygone immer parallel zur Kamera ausgerichtet sein sollen. Man muss hierfür einfach die Rotation aus der Matrix entfernen:

    procedure Billboard;
    const
      NewModelView : Array[0..11] of TGLFloat =
        (1,0,0,0,0,1,0,0,0,0,1,0);
    var
      Modelview : Array[0..15] of TGLFloat;

    begin
      // Modelview Matrix holen
      glGetFloatv(GL_MODELVIEW_MATRIX , @Modelview[0]);
      Move(NewModelView[0], ModelView[0], SizeOf(NewModelview));
      glLoadMatrixf(@Modelview[0])
    end;


Der Renderer des Kameratutorials könnte dann so abgeändert werden:

    ...
    //Kamera initialisieren
    glLoadIdentity;
    Kamera.UseMatrix;

    //Planeten rendern
    for I := Low(Planeten) to High(Planeten) do
      if i <> 3 then
      Planeten[I].Render;

    with Planeten[3] do
      glTranslatef(Position[0], Position[1], Position[2]);

    Billboard;

    glBegin(GL_QUADS);
      glVertex3f(-10, -10, 0);
      glVertex3f(10, -10, 0);
      glVertex3f(10, 10, 0);
      glVertex3f(-10, 10, 0);
    glEnd();
    ...


Ich habe jetzt beschlossen, dass ich hier einmal aufhöre. Das Geschreibe ist doch recht anstrengend und ich will euch doch nicht alle Ideen vorwegnehmen. Deshalb wünsch ich euch jetzt viel Spaß beim Rumprobieren

...have a lot of fun!

Delphic


Nachtrag

Weitere Hinweise zum erstellen von Kameraklassen findet ihr im Artikel Kamera (1).


Dateien


Vorhergehendes Tutorial:
Tutorial TexFilter
Nächstes Tutorial:
-

Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com.
Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen.