Jak zaimplementować trackball w OpenGL?


15

Po tak wielu lekturach dotyczących transformacji nadszedł czas, aby zaimplementować trackball dla mojej aplikacji. Rozumiem, że muszę utworzyć wektor od początku do miejsca kliknięcia myszy, a następnie inny od początku do miejsca zwolnienia myszy.

Moje pytanie brzmi: czy muszę przekształcić współrzędne pikseli (x, y) na współrzędne świata, czy powinienem po prostu zrobić wszystko w przestrzeni obrazu (biorąc pod uwagę, że przestrzeń obrazu to projekcja 2D sceny mierzona w pikselach)?

EDYTOWAĆ

Odpowiedź Richiego Samsa jest bardzo dobra. Myślę jednak, że stosuję nieco inne podejście, proszę mnie poprawić, jeśli się mylę lub coś źle rozumiem.

W mojej aplikacji mam SimplePerspectiveCameraklasy, która odbiera positionod aparatu, position of the targetpatrzymy, w upwektorze fovy, aspectRatio, neari farodległościach.

Za pomocą tych tworzę moje matryce Widok i Projekcja. Teraz, jeśli chcę powiększyć / pomniejszyć, aktualizuję pole widzenia i aktualizuję matrycę projekcji. Jeśli chcę przesuwać, przesuwam pozycję kamery i patrzę na deltę, którą wytwarza mysz.

Wreszcie, do rotacji mogę użyć transformacji kąt-oś lub czwartorzędów. W tym celu zapisuję współrzędne pikseli w miejscu naciśnięcia myszy, a następnie, gdy mysz się porusza, zapisuję również współrzędne pikseli.

Dla każdej pary coords można obliczyć Z wartość podana formuła kuli, czyli sqrt (1-x ^ 2-y ^ 2), a następnie obliczyć z wektorami, które go od targetdo PointMousePressedi od targetcelu PointMouseMoved, do iloczynu aby uzyskać oś obrotu i użyć dowolnej metody do obliczenia nowej pozycji kamery.

Jednak moje największe wątpliwości to to, że wartości (x, y, z) podane są w współrzędnych pikselowych, a przy obliczaniu wektorów używam tego, targetco jest punktem w światowych współrzędnych. Czy to mieszanie układu współrzędnych nie wpływa na wynik obrotu, który próbuję wykonać?


1
Czy „trackball” oznacza kamerę, która krąży wokół obiektu, jak w aplikacjach do modelowania 3D? Jeśli tak, myślę, że zwykle odbywa się to poprzez śledzenie współrzędnych myszy 2D i odwzorowanie x = odchylenie, y = wysokość obrotu kamery.
Nathan Reed,

1
@NathanReed druga opcja opiera się na kącie osi, rzutujesz 2 punkty myszy na (wirtualną) kulę, a następnie znajdujesz obrót od jednego do drugiego.
maniak zapadkowy

@NathanReed tak, właśnie to miałem na myśli przez trackball, myślałem, że to popularna nazwa w społeczności CG.
BRabbit27,

@ratchetfreak tak moje podejście uwzględnia obrót oparty na kącie osi. Wątpię, czy konieczne jest odwzorowanie współrzędnych myszy 2D na koordynację światową, czy nie. Wiem, że mogę użyć (x, y) do obliczenia zwartości kuli o promieniu r, jednak nie jestem pewien, czy ta kula żyje w przestrzeni świata czy przestrzeni obrazu i jakie są tego konsekwencje. Być może przesadzam z problemem.
BRabbit27,

2
W Twojej edycji: Tak. Musisz przekształcić swoje wartości (x, y, z) w przestrzeń świata za pomocą macierzy View.
RichieSams,

Odpowiedzi:


16

Zakładając, że masz na myśli kamerę obracającą się w oparciu o ruch myszy:

Jednym ze sposobów jego realizacji jest śledzenie położenia kamery i jej obrotu w przestrzeni. Współrzędne sferyczne są do tego wygodne, ponieważ można bezpośrednio przedstawić kąty.

Obraz współrzędnych sferycznych

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

Kamera znajduje się w punkcie P zdefiniowanym przez m_theta, m_phi i m_radius. Możemy obracać i swobodnie poruszać się, gdziekolwiek chcemy, zmieniając te trzy wartości. Zawsze jednak patrzymy na m_target i obracamy się wokół niego. m_target to lokalne pochodzenie kuli. Możemy jednak swobodnie przenosić to pochodzenie w dowolne miejsce na świecie.

Istnieją trzy główne funkcje aparatu:

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

W najprostszych postaciach Rotate () i Zoom () są trywialne. Wystarczy zmodyfikować odpowiednio m_theta, m_phi i m_radius:

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

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

Panoramowanie jest nieco bardziej skomplikowane. Panoramowanie kamery definiuje się jako przesuwanie kamery w lewo / prawo i / lub góra / dół odpowiednio do bieżącego widoku kamery. Najłatwiejszym sposobem na to jest konwersja naszego obecnego widoku kamery ze współrzędnych sferycznych na współrzędne kartezjańskie. To daje nam do góry i odpowiednie wektory.

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);
}

Najpierw przekształcamy nasz sferyczny układ współrzędnych na kartezjański, aby uzyskać wektor wyglądu . Następnie wykonujemy iloczyn wektorowy z wektorem świat do góry , aby uzyskać właściwy wektor. Jest to wektor wskazujący bezpośrednio na prawo od widoku z kamery. Na koniec robimy kolejny krzyżowy produkt wektorowy, aby ustawić kamerę w górę .

Aby zakończyć przesuwanie, przesuwamy m_target wzdłuż wektorów w górę i w prawo .

Jedno z pytań, które możesz zadać, brzmi: po co cały czas konwertować między kartezjańskim i sferycznym (musisz także przekonwertować, aby utworzyć macierz Widoku).

Dobre pytanie. Ja też miałem to pytanie i starałem się używać wyłącznie kartezjańskich. Masz problemy z obrotami. Ponieważ operacje zmiennoprzecinkowe nie są dokładnie precyzyjne, wielokrotne obroty powodują kumulowanie się błędów, które odpowiadały kamerze powoli i niezamierzenie toczyły się.

wprowadź opis zdjęcia tutaj

W końcu utknąłem ze współrzędnymi sferycznymi. Aby przeciwdziałać dodatkowym obliczeniom, skończyłem buforowanie macierzy widoku i obliczam ją tylko wtedy, gdy kamera się porusza.

Ostatnim krokiem jest użycie tej klasy aparatu. Wystarczy wywołać odpowiednią funkcję członka w funkcjach MouseDown / Up / Scroll aplikacji:

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);
}

Zmienne m_camera * Factor są tylko czynnikami skalującymi, które zmieniają szybkość, z jaką kamera obraca się / przesuwa / przewija

Kod, który mam powyżej, jest uproszczoną pseudokodową wersją systemu kamer, który stworzyłem dla pobocznego projektu: camera.h i camera.cpp . Aparat próbuje naśladować system kamer Maya. Kod jest darmowy i open source, więc możesz go używać we własnym projekcie.


1
Wydaje mi się, że podział przez 300 jest tylko parametrem dla czułości obrotu biorąc pod uwagę przemieszczenie myszy?
BRabbit27,

Poprawny. To właśnie wtedy działało dobrze z moją rozdzielczością.
RichieSams,

-2

Jeśli chcesz zajrzeć do gotowego rozwiązania, mam port z odpowiednio steruje Three.js trackball w C ++ i C #


Myślę, że tak. Może uczyć się na podstawie kodu wewnątrz działania trackballa.
Michael IV

1
@MichaelIV Niemniej jednak Alan Wolfe ma rację. Możesz znacznie poprawić swoją odpowiedź, umieszczając odpowiedni kod w samej odpowiedzi, aby uczynić go samodzielnym i zabezpieczonym na przyszłość przed utratą łącza pewnego dnia.
Martin Ender
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.