Come implementare una trackball in OpenGL?


15

Dopo così tante letture sulle trasformazioni, è tempo di implementare una trackball per la mia app. Capisco di dover creare un vettore dall'origine a cui viene fatto clic sul mouse e quindi un altro dall'origine a dove viene rilasciato il mouse.

La mia domanda è: devo trasforare i pixel (x, y) pixel in coordinate del mondo o dovrei semplicemente fare tutto nello spazio dell'immagine (considerando che lo spazio dell'immagine è la proiezione 2D della scena misurata in pixel)?

MODIFICARE

La risposta di Richie Sams è ottima. Tuttavia, penso di seguire un approccio leggermente diverso, per favore correggimi se sbaglio o sto fraintendendo qualcosa.

Nella mia applicazione ho una SimplePerspectiveCameraclasse che riceve l' positiondella fotocamera, la position of the targetstiamo guardando, il upvettore, i fovy, aspectRatio, neare fardistanze.

Con quelli ho creato le mie matrici di visualizzazione e proiezione. Ora, se voglio ingrandire / ridurre, aggiorno il campo visivo e aggiorno la mia matrice di proiezione. Se voglio fare una panoramica muovo la posizione della telecamera e guardo dal delta che il mouse produce.

Infine, per le rotazioni posso usare la trasformazione dell'asse angolare o i quaternioni. Per questo, salvo i pixel-coords dove è stato premuto il mouse e poi quando il mouse si sposta, salvo i pixel-coords.

Per ogni coppia di corde posso calcolare il valore Z data la formula per una sfera, cioè sqrt (1-x ^ 2-y ^ 2), quindi calcolare i vettori che vanno da targeta PointMousePressede da targetaPointMouseMoved , attraversano il prodotto per ottenere l'asse di rotazione e utilizzare qualsiasi metodo per calcolare la nuova posizione della telecamera.

Tuttavia, il mio più grande dubbio è che i valori (x, y, z) siano dati in pixel-coords, e quando si calcolano i vettori che sto usando targetche è un punto in coordes del mondo. Questa miscelazione del sistema di coordinate non sta influenzando il risultato della rotazione che sto cercando di fare?


1
"Trackball" significa una videocamera che orbita attorno a un oggetto, come nelle app di modellazione 3D? In tal caso, penso che di solito venga fatto semplicemente tenendo traccia delle coordinate del mouse 2D e mappando x = yaw, y = pitch per la rotazione della telecamera.
Nathan Reed,

1
@NathanReed l'altra opzione è basata sull'angolo dell'asse , si proiettano 2 punti del mouse su una sfera (virtuale) e quindi si trova la rotazione da una all'altra.
maniaco del cricchetto

@NathanReed sì, questo è ciò che intendevo per trackball, pensavo fosse un nome comune nella comunità CG.
BRabbit27

@ratchetfreak sì, il mio approccio considera una rotazione basata sull'angolo dell'asse. Il mio dubbio è che se è necessario mappare le coordinate del mouse 2D su world-coord o no. So di poter usare (x, y) per calcolare il zvalore di una sfera di raggio r, tuttavia non sono sicuro che quella sfera viva nello spazio del mondo o nello spazio dell'immagine e quali siano le implicazioni. Forse sto pensando troppo al problema.
BRabbit27

2
Sulla tua modifica: Sì. Dovresti trasformare i tuoi valori (x, y, z) nello spazio mondiale usando la matrice Visualizza.
RichieSams,

Risposte:


16

Supponendo che intendi una videocamera che ruota in base al movimento del mouse:

Un modo per implementarlo è tenere traccia della posizione della telecamera e della sua rotazione nello spazio. Le coordinate sferiche sono utili per questo, poiché puoi rappresentare direttamente gli angoli.

Immagine di coordinate sferiche

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

La fotocamera si trova in P, che è definita da m_theta, m_phi e m_radius. Possiamo ruotare e muoverci liberamente dove vogliamo cambiando questi tre valori. Tuttavia, guardiamo sempre e ruotiamo intorno a m_target. m_target è l'origine locale della sfera. Tuttavia, siamo liberi di spostare questa origine ovunque desideriamo nello spazio mondiale.

Esistono tre funzioni principali della fotocamera:

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

Nelle loro forme più semplici, Rotate () e Zoom () sono banali. Il giusto modifica rispettivamente m_theta, m_phi e m_radius:

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

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

La panoramica è un po 'più complicata. Una panoramica della videocamera è definita come lo spostamento della videocamera verso sinistra / destra e / o verso l'alto / verso il basso rispetto alla vista corrente della videocamera. Il modo più semplice per raggiungere questo obiettivo è convertire la nostra attuale vista della telecamera da coordinate sferiche a coordinate cartesiane. Questo ci darà un up e di destra vettori.

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

Quindi, in primo luogo, convertiamo il nostro sistema di coordinate sferiche in cartesiano per ottenere il nostro aspetto vettoriale. Successivamente, realizziamo il vettore incrociato con il vettore world up , al fine di ottenere un vettore giusto . Questo è un vettore che punta direttamente a destra della vista della telecamera. Infine, facciamo un altro prodotto vettoriale incrociato per ottenere la fotocamera su vettore.

Per finire la panoramica, spostiamo m_target lungo i vettori su e destra .

Una domanda che potresti porci è: perché convertire continuamente tra cartesiano e sferico (dovrai anche convertire per creare la matrice Visualizza).

Buona domanda. Anch'io avevo questa domanda e ho provato ad usare esclusivamente cartesiano. Si finiscono con problemi con le rotazioni. Poiché le operazioni in virgola mobile non sono esattamente precise, più rotazioni finiscono per accumulare errori, che corrispondevano lentamente alla telecamera e rotolavano involontariamente.

inserisci qui la descrizione dell'immagine

Quindi, alla fine, mi sono bloccato con coordinate sferiche. Al fine di contrastare i calcoli aggiuntivi, ho finito per memorizzare nella cache la matrice di visualizzazione e calcolarla solo quando la telecamera si sposta.

L'ultimo passo è usare questa classe Camera. Basta chiamare la funzione membro appropriata all'interno delle funzioni MouseDown / Su / Scorrimento della tua app:

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

Le variabili del fattore m_camera * sono solo fattori di scala che cambiano la velocità di rotazione / panoramica / scorrimento della videocamera

Il codice che ho sopra è una versione semplificata del pseudo-codice del sistema di telecamere che ho realizzato per un progetto laterale: camera.h e camera.cpp . La fotocamera tenta di imitare il sistema di telecamere Maya. Il codice è gratuito e open source, quindi sentiti libero di usarlo nel tuo progetto.


1
Immagino che la divisione per 300 sia solo un parametro per la sensibilità della rotazione dato lo spostamento del mouse?
BRabbit27,

Corretta. È quello che è successo per funzionare bene con la mia risoluzione in quel momento.
RichieSams,

-2

Nel caso in cui volessi dare un'occhiata a una soluzione pronta, ho una porta per i controlli TrackBall di TRE.JS in C ++ e C #


Tendo a pensarlo, può imparare dal codice all'interno di come funziona la trackball.
Michele IV,

1
@MichaelIV Tuttavia, Alan Wolfe ha ragione. Potresti migliorare notevolmente la tua risposta includendo il codice pertinente nella risposta stessa per renderlo autonomo e a prova di futuro contro il link che un giorno sarebbe morto.
Martin Ender,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.