Wie implementiere ich einen Trackball in OpenGL?

15

Nachdem ich so viel über Transformationen gelesen habe, ist es Zeit, einen Trackball für meine App zu implementieren. Ich verstehe, dass ich einen Vektor vom Ursprung bis zum Klicken mit der Maus und dann einen anderen vom Ursprung bis zum Loslassen der Maus erstellen muss.

Meine Frage ist, muss ich die (x, y) Pixelkoordinaten in Weltkoordinaten umwandeln oder sollte ich einfach alles im Bildraum tun (wenn man bedenkt, dass der Bildraum die 2D-Projektion der Szene in Pixeln ist)?

BEARBEITEN

Die Antwort von Richie Sams ist sehr gut. Ich denke jedoch, dass ich einen etwas anderen Ansatz verfolge. Bitte korrigieren Sie mich, wenn ich falsch liege oder etwas missverstehe.

In meiner Anwendung habe ich eine SimplePerspectiveCameraKlasse , die das empfängt positionder Kamera, die position of the targetwir betrachten, den upVektor, der fovy, aspectRatio, nearund farDistanzen.

Mit diesen baue ich meine View- und Projektionsmatrizen. Wenn ich nun vergrößern / verkleinern möchte, aktualisiere ich das Sichtfeld und aktualisiere meine Projektionsmatrix. Wenn ich schwenken möchte, bewege ich die Position der Kamera und schaue nach dem Delta, das die Maus erzeugt.

Schließlich kann ich für Rotationen entweder eine Winkelachsentransformation oder Quaternionen verwenden. Dazu speichere ich die Pixelkoordinaten dort, wo die Maus gedrückt wurde und wenn sich die Maus bewegt, speichere ich auch die Pixelkoordinaten.

Für jedes Paar von Koordinaten kann ich den Z-Wert gegeben berechnen die Formel für eine Kugel, dh sqrt (1-x ^ 2-y ^ 2), dann nach Vektoren berechnen , die aus dem ich gehen targetzu PointMousePressedund von targetzu PointMouseMovedtun Kreuzprodukt Um die Rotationsachse zu ermitteln, verwenden Sie eine beliebige Methode, um die neue Kameraposition zu berechnen.

Mein größter Zweifel ist jedoch, dass die (x, y, z) -Werte in Pixelkoordinaten angegeben sind und bei der Berechnung der Vektoren, die ich verwende target, ein Punkt in Weltkoordinaten ist. Beeinflusst diese Vermischung des Koordinatensystems nicht das Ergebnis der Rotation, die ich versuchen möchte?

BRabbit27
quelle
1
Bedeutet "Trackball" eine Kamera, die ein Objekt umkreist, wie in 3D-Modellierungs-Apps? Wenn ja, denke ich, geschieht dies normalerweise, indem man nur die 2D-Mauskoordinaten verfolgt und x = yaw, y = pitch für die Kameradrehung abbildet.
Nathan Reed
1
@ NathanReed Die andere Option basiert auf dem Achsenwinkel . Sie projizieren 2 Mauspunkte auf eine (virtuelle) Kugel und ermitteln dann die Drehung von einer zur anderen.
Ratschenfreak
@ NathanReed Ja, das ist es, was ich mit Trackball gemeint habe. Ich dachte, es ist ein in der CG-Community üblicher Name.
BRabbit27
@ratchetfreak ja mein Ansatz berücksichtigt eine achswinkelbasierte Rotation. Mein Zweifel ist, ob es notwendig ist, die 2D-Mauskoordinaten der Weltkoordinate zuzuordnen oder nicht. Ich weiß, dass ich mit (x, y) den zWert einer Kugel mit Radius berechnen kann r, bin mir jedoch nicht sicher, ob diese Kugel im Welt- oder Bildraum lebt und welche Auswirkungen dies hat. Vielleicht überdenke ich das Problem.
BRabbit27
2
Bei deiner Bearbeitung: Ja. Sie müssten Ihre (x, y, z) -Werte mithilfe der View-Matrix in den Weltraum transformieren.
RichieSams

Antworten:

16

Angenommen, Sie meinen eine Kamera, die sich aufgrund der Mausbewegung dreht:

Eine Möglichkeit zur Implementierung besteht darin, die Kameraposition und ihre Drehung im Raum zu verfolgen. Hierfür bieten sich sphärische Koordinaten an, da Sie die Winkel direkt darstellen können.

Bild der sphärischen Koordinaten

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

Die Kamera befindet sich bei P, das durch m_theta, m_phi und m_radius definiert ist. Wir können uns frei drehen und bewegen, wo immer wir wollen, indem wir diese drei Werte ändern. Wir schauen uns jedoch immer m_target an und drehen es. m_target ist der lokale Ursprung der Kugel. Es steht uns jedoch frei, diesen Ursprung im Weltraum zu bewegen, wo immer wir wollen.

Es gibt drei Hauptfunktionen der Kamera:

void Rotate(float dTheta, float dPhi);
void Zoom(float distance);
void Pan(float dx, float dy);

In ihrer einfachsten Form sind Rotate () und Zoom () trivial. Die beiden Modifikationen m_theta, m_phi und m_radius lauten:

void Camera::Rotate(float dTheta, float dPhi) {
    m_theta += dTheta;
    m_phi += dPhi;
}

void Camera::Zoom(float distance) {
    m_radius -= distance;
}

Schwenken ist etwas komplizierter. Ein Kameraschwenk ist definiert als Bewegen der Kamera nach links / rechts und / oder nach oben / unten in Bezug auf die aktuelle Kameraansicht. Am einfachsten können wir dies erreichen, indem wir unsere aktuelle Kameraansicht von sphärischen Koordinaten in kartesische Koordinaten konvertieren. Dies gibt uns eine nach oben und rechts Vektoren.

void Camera::Pan(float dx, float dy) {
    float3 look = normalize(ToCartesian());
    float3 worldUp = float3(0.0f, 1.0f, 0.0f, 0.0f);

    float3 right = cross(look, worldUp);
    float3 up = cross(look, right);

    m_target = m_target + (right * dx) + (up * dy);
}

inline float3 ToCartesian() {
    float x = m_radius * sinf(m_phi) * sinf(m_theta);
    float y = m_radius * cosf(m_phi);
    float z = m_radius * sinf(m_phi) * cosf(m_theta);
    float w = 1.0f;

    return float3(x, y, z, w);
}

Also, zunächst wandeln wir unser sphärisches Koordinatensystem kartesisch zu unserem bekommen Look Vektor. Als nächstes machen wir das Vektorkreuzprodukt mit dem Welt- Up- Vektor, um einen richtigen Vektor zu erhalten. Dies ist ein Vektor, der direkt rechts von der Kameraansicht zeigt. Zuletzt machen wir ein anderes Vektor-Kreuzprodukt, um den Vektor der Kamera zu erzeugen.

Um die Pfanne zu beenden, wir bewegen m_target entlang der nach oben und rechts Vektoren.

Eine Frage, die Sie sich möglicherweise stellen, lautet: Warum immer zwischen kartesisch und sphärisch konvertieren? (Sie müssen auch konvertieren, um die View-Matrix zu erstellen.)

Gute Frage. Auch ich hatte diese Frage und versuchte ausschließlich kartesisch zu sprechen. Sie haben Probleme mit Rotationen. Da Gleitkommaoperationen nicht exakt sind, häufen sich bei mehreren Umdrehungen Fehler an, die der Kamera langsam entsprachen und ungewollt abrollen.

Bildbeschreibung hier eingeben

Also blieb ich am Ende bei sphärischen Koordinaten. Um den zusätzlichen Berechnungen entgegenzuwirken, habe ich die Ansichtsmatrix zwischengespeichert und erst berechnet, wenn sich die Kamera bewegt.

Der letzte Schritt ist die Verwendung dieser Kamera-Klasse. Rufen Sie einfach die entsprechende Member-Funktion in den MouseDown / Up / Scroll-Funktionen Ihrer App auf:

void MouseDown(WPARAM buttonState, int x, int y) {
    m_mouseLastPos.x = x;
    m_mouseLastPos.y = y;

    SetCapture(m_hwnd);
}

void MouseUp(WPARAM buttonState, int x, int y) {
    ReleaseCapture();
}

void MouseMove(WPARAM buttonState, int x, int y) {
    if ((buttonState & MK_LBUTTON) != 0) {
        if (GetKeyState(VK_MENU) & 0x8000) {
            // Calculate the new phi and theta based on mouse position relative to where the user clicked
            float dPhi = ((float)(m_mouseLastPos.y - y) / 300);
            float dTheta = ((float)(m_mouseLastPos.x - x) / 300);

            m_camera.Rotate(-dTheta, dPhi);
        }
    } else if ((buttonState & MK_MBUTTON) != 0) {
        if (GetKeyState(VK_MENU) & 0x8000) {
            float dx = ((float)(m_mouseLastPos.x - x));
            float dy = ((float)(m_mouseLastPos.y - y));

            m_camera.Pan(-dx * m_cameraPanFactor, dy * m_cameraPanFactor);
        }
    }

    m_mouseLastPos.x = x;
    m_mouseLastPos.y = y;
}

void MouseWheel(int zDelta) {
    // Make each wheel dedent correspond to a size based on the scene
    m_camera.Zoom((float)zDelta * m_cameraScrollFactor);
}

Die m_camera * -Faktorvariablen sind lediglich Skalierungsfaktoren, die ändern, wie schnell sich Ihre Kamera dreht / schwenkt / rollt

Der Code, den ich oben habe, ist eine vereinfachte Pseudocode-Version des Kamerasystems, das ich für ein Nebenprojekt erstellt habe: camera.h und camera.cpp . Die Kamera versucht das Maya-Kamerasystem zu imitieren. Der Code ist kostenlos und Open Source. Sie können ihn also auch in Ihrem eigenen Projekt verwenden.

RichieSams
quelle
1
Ich vermute, die Division durch 300 ist nur ein Parameter für die Empfindlichkeit der Drehung angesichts der Verschiebung der Maus?
BRabbit27
Richtig. Das ist es, was mit meiner damaligen Auflösung gut funktioniert hat.
RichieSams
-2

Für den Fall, dass Sie einen Blick auf eine fertige Lösung werfen möchten. Ich habe einen Port von THREE.JS TrackBall-Steuerelementen in C ++ und C #.

Michael IV
quelle
Ich neige dazu zu glauben, dass dies der Fall ist. Er kann aus dem Code im Trackball lernen, wie er funktioniert.
Michael IV
1
@MichaelIV Trotzdem hat Alan Wolfe einen Punkt. Sie können Ihre Antwort erheblich verbessern, indem Sie relevanten Code in die Antwort selbst aufnehmen, um sie in sich geschlossen und zukunftssicher zu machen, damit die Verbindung eines Tages nicht mehr funktioniert.
Martin Ender