3D-Programmierung (Mathematik) Teil 10: Kameradrehungen im 3D-Raum

Im 2D-Raum kann die Kamera immer nur um die Blickrichtungsachse gedreht werden. Für die Simulation von Kameradrehungen im 3D-Raum definiert man darüber hinaus zwei weitere Drehachsen, die ihrerseits senkrecht zur Blickrichtungsachse sowie senkrecht zueinander orientiert sind:

  • Drehung um die Blickrichtungsachse (der Kamera)
  • Drehung um die horizontale Achse (der Kamera)
  • Drehung um die vertikale Achse (der Kamera)


// Blickrichtungsachse der Kamera:
D3DXVECTOR3 g_CameraViewDirection (0.0f, 0.0f, 1.0f);
// vertikale Achse der Kamera:
D3DXVECTOR3 g_CameraVertical (0.0f, 1.0f, 0.0f);
// horizontale Achse der Kamera:
D3DXVECTOR3 g_CameraHorizontal (1.0f, 0.0f, 0.0f);

// Kameradrehwinkel um die Blickrichtungsachse, wird für die
// Billboard-Ausrichtung benötigt

float g_ViewRotationAngle = 0.0f;


// Sichtmatrix (Kameramatrix) "Das Auge der Kamera"
D3DXMATRIXA16 g_matView;

// inverse Sichtmatrix für die Beschreibung der
// Ausrichtung (Orientierung) der Kamera:
D3DXMATRIXA16 g_CameraTransformationMatrix;

// Drehachsen der Kamera in ihrer originalen Ausrichtung
// vor etwaigen Kameradrehungen (Ausgangslage):

D3DXVECTOR3 g_CameraViewDirectionOriginal (0.0f, 0.0f, 1.0f);
D3DXVECTOR3 g_CameraVerticalOriginal (0.0f, 1.0f, 0.0f);
D3DXVECTOR3 g_CameraHorizontalOriginal (1.0f, 0.0f, 0.0f);

D3DXVECTOR3 g_CameraViewDirectionOriginalNeg (0.0f, 0.0f, -1.0f);
D3DXVECTOR3 g_CameraVerticalOriginalNeg (0.0f, -1.0f, 0.0f);
D3DXVECTOR3 g_CameraHorizontalOriginalNeg (-1.0f, 0.0f, 0.0f);

Die Arbeitsweise unserer Funktionen für die Durchführung der Kameradrehungen (Camera_HorizontalRotation(), Camera_VerticalRotation() und Camera_ViewRotation()) ist denkbar einfach. Zunächst wird eine Rotationsmatrix berechnet, die die Drehung für das aktuelle Frame beschreibt. Man spricht daher auch von einer Frame-Rotationsmatrix. Im zweiten Schritt wird diese Matrix mit der Gesamtrotationsmatrix g_CameraTransformationMatrix multipliziert, die alle bisherigen Drehungen der Kamera aus ihrer Ausgangslage (Blickrichtung: positive z-Richtung, Horizontale Achse: positive x-Richtung, Vertikale Achse: positive y-Richtung) beschreibt. Hierbei ist auf die richtige Multiplikationsreihenfolge zu achten:

Drehung um die Blickrichtungsachse:

Gesamtrotationsmatrix(neues Frame) = Gesamtrotationsmatrix(altes Frame)*
                                     FrameRotationsmatrix

Schritt 1: Kameradrehung aus der Ausgangslage in die vorherige Blickrichtung.
Schritt 2: Kameradrehung um die aktuelle Blickrichtungsachse (Frame-Drehung).

Schritt 1 beschreibt also die vorherige Ausrichtung der Kamera und Schritt 2 die zusätzlich auszuführende Drehung.

Horizontale und vertikale Drehung:

Gesamtrotationsmatrix(neues Frame) = FrameRotationsmatrix*
                                     Gesamtrotationsmatrix(altes Frame)

Bei der horizontalen bzw. bei der vertikalen Drehung verändern wir die Multiplikationsreihenfolge. Fassen wir also die zweite Matrixgleichung in Worte:

Schritt 1: Kameradrehung aus der Ausgangslage entweder um die anfängliche horizontale bzw. um die anfängliche vertikale Achse (Frame-Drehung).
Schritt2: Kameradrehung mithilfe der bisherigen Gesamtrotationsmatrix in die endgültige Blickrichtung.

Genial an dieser Multiplikationsreihenfolge ist die Tatsache, dass wir für die Durchführung der Frame-Drehungen weder die aktuelle horizontale noch die aktuelle vertikale Drehachse benötigen – die originalen Drehachsen reichen völlig aus!

Hinweis:
Die Gesamtrotationsmatrix aller durchgeführten Kameradrehungen  g_CameraTransformationMatrix kommt in unseren Demoprogrammen u.a. bei der Ausrichtung von 2D-Objekten (Billboards) zum Einsatz. Vom mathematischen Standpunkt aus betrachtet entspricht diese Matrix der inversen bzw. transponierten Sichtmatrix (View Matrix).

Berechnung der neuen Kameraachsen nach einer Drehung
Unter der Voraussetzung, dass wir anfangs die positive z-Richtung als Blickrichtung, die positive x-Richtung als horizontale und die positive y-Richtung als vertikale Kameraachse gewählt haben, entspricht die erste Zeile der Kamera-Gesamtrotationsmatrix der horizontalen Kameraachse, die zweite Zeile der vertikalen Kameraachse und die dritte Zeile der Blickrichtung.

Erklärung:
Befinden sich die Kameraachsen noch in ihrer Ausgangslage, dann entspricht die Kamera-Gesamtrotationsmatrix der Einheitsmatrix. Die erste Zeile dieser Matrix (1, 0, 0) entspricht der horizontalen Kameraachse, die zweite Zeile (0, 1, 0) entspricht der vertikalen Kameraachse und die dritte Zeile (0, 0, 1) entspricht der Blickrichtung.

Alternative Berechnung der neuen Kameraachsen:
Zusätzlich zur Blickrichtung ändert sich bei einer horizontalen Drehung die horizontale Achse bzw. bei einer vertikalen Drehung die vertikale Achse. Mit der Hilfe des Kreuzprodukts (Vektorprodukts) aus der neuen Blickrichtung und der unverändert gebliebenen Drehachse könnte man jetzt auf alternative Weise die neue horizontale Achse bzw. vertikale Achse bestimmen.
Im Fall einer Drehung um die Blickrichtungsachse ändern sich sowohl die horizontale als auch die vertikale Achse. Die neuen Achsen lassen sich alternativ durch Multiplikation der alten Achsen mit der Frame-Rotationsmatrix berechnen.

Nachdem die neuen Drehachsen berechnet worden sind, müssen diese gegebenenfalls noch normiert werden. Bedenken Sie, dass es aufgrund der begrenzten Genauigkeit der Variablen immer mal zu Rundungsfehlern kommen kann. Unter Verwendung der aktualisierten Drehachsen wird im letzten Schritt mithilfe der Funktion D3DXMatrixLookAtLH() die neue Sichtmatrix berechnet.
Hier nun die zugehörigen Funktionen:

horizontale Kameradrehung

inline void Camera_HorizontalRotation(float rotationAngle)
{
    rotationAngle = rotationAngle*D3DX_PI/180.0f;

    D3DXMATRIXA16 tempMatrix1;

    D3DXMatrixRotationY(&tempMatrix1, rotationAngle);

    g_CameraTransformationMatrix = tempMatrix1*
                                   g_CameraTransformationMatrix;

    g_CameraHorizontal.x = g_CameraTransformationMatrix._11;
    g_CameraHorizontal.y = g_CameraTransformationMatrix._12;
    g_CameraHorizontal.z = g_CameraTransformationMatrix._13;

    g_CameraViewDirection.x = g_CameraTransformationMatrix._31;
    g_CameraViewDirection.y = g_CameraTransformationMatrix._32;
    g_CameraViewDirection.z = g_CameraTransformationMatrix._33;

    D3DXVec3Normalize(&g_CameraHorizontal, &g_CameraHorizontal);
    D3DXVec3Normalize(&g_CameraViewDirection, &g_CameraViewDirection);

    D3DXMatrixLookAtLH(&g_matView, &g_NullVector, &g_CameraViewDirection,
                       &g_CameraVertical);
}

vertikale Kameradrehung

inline void Camera_VerticalRotation(float rotationAngle)
{
    rotationAngle = rotationAngle*D3DX_PI/180.0f;

    D3DXMATRIXA16 tempMatrix1;

    D3DXMatrixRotationX(&tempMatrix1, rotationAngle);

    g_CameraTransformationMatrix = tempMatrix1*
                                   g_CameraTransformationMatrix;

    g_CameraVertical.x = g_CameraTransformationMatrix._21;
    g_CameraVertical.y = g_CameraTransformationMatrix._22;
    g_CameraVertical.z = g_CameraTransformationMatrix._23;

    g_CameraViewDirection.x = g_CameraTransformationMatrix._31;
    g_CameraViewDirection.y = g_CameraTransformationMatrix._32;
    g_CameraViewDirection.z = g_CameraTransformationMatrix._33;

    D3DXVec3Normalize(&g_CameraVertical, &g_CameraVertical);
    D3DXVec3Normalize(&g_CameraViewDirection, &g_CameraViewDirection);

    D3DXMatrixLookAtLH(&g_matView, &g_NullVector, &g_CameraViewDirection,
                       &g_CameraVertical);
}

Kameradrehung um die Blickrichtungsachse

inline void Camera_ViewRotation(float rotationAngle)
{
    rotationAngle = rotationAngle*D3DX_PI/180.0f;

    g_ViewRotationAngle += rotationAngle;

    if(g_ViewRotationAngle > D3DX_PI)
        g_ViewRotationAngle -= 6.283185308f;
    else if(g_ViewRotationAngle < -D3DX_PI)
        g_ViewRotationAngle += 6.283185308f;

    D3DXMATRIXA16 tempMatrix1;

    D3DXMatrixRotationAxis(&tempMatrix1, &g_CameraViewDirection,
                           rotationAngle);

    g_CameraTransformationMatrix = g_CameraTransformationMatrix*
                                   tempMatrix1;

    g_CameraHorizontal.x = g_CameraTransformationMatrix._11;
    g_CameraHorizontal.y = g_CameraTransformationMatrix._12;
    g_CameraHorizontal.z = g_CameraTransformationMatrix._13;

    g_CameraVertical.x = g_CameraTransformationMatrix._21;
    g_CameraVertical.y = g_CameraTransformationMatrix._22;
    g_CameraVertical.z = g_CameraTransformationMatrix._23;

    D3DXVec3Normalize(&g_CameraHorizontal, &g_CameraHorizontal);
    D3DXVec3Normalize(&g_CameraVertical, &g_CameraVertical);

    D3DXMatrixLookAtLH(&g_matView, &g_NullVector, &g_CameraViewDirection,
                       &g_CameraVertical);
}

Kameradrehungen zurücksetzen

inline void Reset_CameraRotation(void)
{
    g_ViewRotationAngle = 0.0f;

    g_CameraViewDirection = g_CameraViewDirectionOriginal;
    g_CameraVertical      = g_CameraVerticalOriginal;
    g_CameraHorizontal    = g_CameraHorizontalOriginal;

    D3DXMatrixLookAtLH(&g_matView, &g_NullVector, &g_CameraViewDirection,
                       &g_CameraVertical);

    D3DXMatrixInverse(&g_CameraTransformationMatrix, NULL,
                      &g_matView);
}