¿Cómo implementar un trackball en OpenGL?

15

Después de tanta lectura sobre las transformaciones, es hora de implementar un trackball para mi aplicación. Entiendo que tengo que crear un vector desde el origen hasta donde se hace clic con el mouse y luego otro desde el origen hasta donde se suelta el mouse.

Mi pregunta es, ¿tengo que transformar las coordenadas de píxel (x, y) en coordenadas mundiales o debería hacer todo en el espacio de la imagen (considerando que el espacio de la imagen es la proyección 2D de la escena medida en píxeles)?

EDITAR

La respuesta de Richie Sams es muy buena. Sin embargo, creo que estoy siguiendo un enfoque ligeramente diferente, corrígeme si me equivoco o si no entiendo algo.

En mi aplicación tengo una SimplePerspectiveCameraclase que recibe el positionde la cámara, la position of the targetestamos viendo, el upvector, las fovy, aspectRatio, neary fardistancias.

Con ellos construyo mis matrices de Vista y Proyección. Ahora, si quiero acercar / alejar, actualizo el campo de visión y actualizo mi matriz de proyección. Si quiero hacer una panorámica, muevo la posición de la cámara y miro por el delta que produce el mouse.

Finalmente, para las rotaciones puedo usar la transformación del eje angular o los cuaterniones. Para esto, guardo los píxeles-coords donde se presionó el mouse y luego, cuando el mouse se mueve, también guardo los píxeles-coords.

Para cada par de coordenadas puedo calcular la Z-valor dado la fórmula para una esfera, es decir, sqrt (1-x ^ 2-y ^ 2), a continuación, calcular a vectores que van desde la targeta PointMousePressedy desde targeta PointMouseMoved, hacer producto cruzado para obtener el eje de rotación y usar cualquier método para calcular la nueva posición de la cámara.

Sin embargo, mi mayor duda es que los valores (x, y, z) se dan en píxeles-coordenadas, y al calcular los vectores que estoy usando, targetes un punto en las coordenadas mundiales. ¿Esta mezcla de sistema de coordenadas no está afectando el resultado de la rotación que estoy tratando de hacer?

BRabbit27
fuente
1
¿"Trackball" significa una cámara que orbita alrededor de un objeto, como en las aplicaciones de modelado 3D? Si es así, creo que generalmente se realiza simplemente haciendo un seguimiento de las coordenadas 2D del mouse y mapeando x = yaw, y = pitch para la rotación de la cámara.
Nathan Reed
1
@NathanReed, la otra opción se basa en el ángulo del eje , proyecta 2 puntos del mouse en una esfera (virtual) y luego encuentra la rotación de uno a otro.
monstruo de trinquete
@NathanReed sí, eso es lo que quise decir con trackball, pensé que era un nombre común en la comunidad de CG.
BRabbit27
@ratchetfreak sí, mi enfoque considera una rotación basada en el eje-ángulo. Mi duda es que si es necesario mapear las coordenadas del mouse 2D a coord de mundo o no. Sé que puedo usar (x, y) para calcular el zvalor de una esfera de radio r, sin embargo, no estoy seguro de si esa esfera vive en el espacio mundial o en el espacio de la imagen y cuáles son las implicaciones. Quizás estoy pensando demasiado en el problema.
BRabbit27
2
En tu edición: Sí. Necesitaría transformar sus valores (x, y, z) en el espacio mundial utilizando la matriz Ver.
RichieSams

Respuestas:

16

Suponiendo que se refiere a una cámara que gira según el movimiento del mouse:

Una forma de implementarlo es hacer un seguimiento de la posición de la cámara y su rotación en el espacio. Las coordenadas esféricas resultan convenientes para esto, ya que puede representar los ángulos directamente.

Imagen de coordenadas esféricas

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

La cámara está ubicada en P, que está definida por m_theta, m_phi y m_radius. Podemos rotar y movernos libremente donde queramos cambiando esos tres valores. Sin embargo, siempre miramos y giramos alrededor de m_target. m_target es el origen local de la esfera. Sin embargo, somos libres de mover este origen a donde queramos en el espacio mundial.

Hay tres funciones principales de la cámara:

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

En sus formas más simples, Rotate () y Zoom () son triviales. El solo modifica m_theta, m_phi y m_radius respectivamente:

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

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

La panorámica es un poco más complicada. Una panorámica de la cámara se define como mover la cámara hacia la izquierda / derecha y / o arriba / abajo, respectivamente, a la vista de la cámara actual. La forma más fácil de lograr esto es convertir nuestra vista actual de la cámara de coordenadas esféricas a coordenadas cartesianas. Esto nos dará un vector ascendente y correcto .

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

Entonces, primero, convertimos nuestro sistema de coordenadas esféricas a cartesiano para obtener nuestro vector de aspecto . A continuación, se hace el producto vectorial con el mundo arriba vector, con el fin de obtener un derecho de vectores. Este es un vector que apunta directamente a la derecha de la vista de la cámara. Por último, hacemos otro producto vectorial para conseguir la cámara hasta vectorial.

Para terminar la panorámica, movemos m_target a lo largo de los vectores arriba y derecha .

Una pregunta que podría estar haciendo es: ¿Por qué convertir entre cartesiano y esférico todo el tiempo (también tendrá que convertir para crear la matriz de Vista).

Buena pregunta. Yo también tuve esta pregunta y traté de usar exclusivamente cartesian. Terminas con problemas con las rotaciones. Dado que las operaciones de coma flotante no son exactamente precisas, las rotaciones múltiples terminan acumulando errores, que correspondieron a la cámara lentamente y sin querer rodando.

ingrese la descripción de la imagen aquí

Entonces, al final, me quedé con las coordenadas esféricas. Para contrarrestar los cálculos adicionales, terminé almacenando en caché la matriz de vista y solo la calculé cuando la cámara se mueve.

El último paso es usar esta clase de cámara. Simplemente llame a la función de miembro apropiada dentro de las funciones MouseDown / Up / Scroll de su aplicación:

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

Las variables m_camera * Factor son solo factores de escala que cambian la rapidez con la que la cámara gira / gira / desplaza

El código que tengo arriba es una versión simplificada de pseudocódigo del sistema de cámara que hice para un proyecto paralelo: camera.h y camera.cpp . La cámara intenta imitar el sistema de cámara Maya. El código es gratuito y de código abierto, así que siéntase libre de usarlo en su propio proyecto.

RichieSams
fuente
1
¿Supongo que la división por 300 es solo un parámetro para la sensibilidad de la rotación dado el desplazamiento del mouse?
BRabbit27
Correcto. Es lo que funcionó bien con mi resolución en ese momento.
RichieSams
-2

En caso de que desee echar un vistazo a una solución lista, tengo un puerto de controles TRES.JS TrackBall en C ++ y C #

Miguel IV
fuente
Tiendo a pensar que sí. Puede aprender del código interno cómo funciona el trackball.
Michael IV
1
@MichaelIV Sin embargo, Alan Wolfe tiene un punto. Podría mejorar enormemente su respuesta al incluir código relevante en la respuesta misma para que sea autónoma y esté preparada para el futuro contra el enlace que algún día falle.
Martin Ender