Comment implémenter un trackball en OpenGL?

15

Après tant de lecture sur les transformations, il est temps d'implémenter une trackball pour mon application. Je comprends que je dois créer un vecteur de l'origine à l'endroit où la souris est cliquée, puis un autre de l'origine à l'endroit où la souris est relâchée.

Ma question est la suivante: dois-je convertir les cordons de pixels (x, y) en cordes du monde ou dois-je tout faire dans l'espace image (étant donné que l'espace image est la projection 2D de la scène mesurée en pixels)?

ÉDITER

La réponse de Richie Sams est très bonne. Cependant, je pense que je suis en train de suivre une approche légèrement différente, veuillez me corriger si je me trompe ou si je me méprends sur quelque chose.

Dans ma demande , j'ai une SimplePerspectiveCameraclasse qui reçoit positionde la caméra, le position of the targetnous regardons, le upvecteur, les fovy, aspectRatio, nearet fardistances.

Avec celles-ci, je construis mes matrices de vue et de projection. Maintenant, si je veux faire un zoom avant / arrière, je mets à jour le champ de vision et mets à jour ma matrice de projection. Si je veux faire un panoramique, je déplace la position de la caméra et regarde par le delta produit par la souris.

Enfin, pour les rotations, je peux utiliser la transformation angle-axe ou les quaternions. Pour cela, j'enregistre les pixels-coords où la souris a été enfoncée, puis lorsque la souris se déplace, j'enregistre également les pixels-coords.

Pour chaque paire de coords je peux calculer la valeur Z compte tenu de la formule pour une sphère, à savoir, sqrt (1-x ^ 2-y ^ 2), puis calculer des vecteurs qui vont de l' targetau PointMousePressedet à partir targetde PointMouseMoved, faire produit vectoriel pour obtenir l'axe de rotation et utiliser n'importe quelle méthode pour calculer la nouvelle position de la caméra.

Cependant, mon plus grand doute est que les valeurs (x, y, z) sont données en pixels-coords, et lors du calcul des vecteurs que j'utilise, targetce qui est un point en monde-coords. Ce mélange de système de coordonnées n'affecte-t-il pas le résultat de la rotation que j'essaie de faire?

BRabbit27
la source
1
Est-ce que «trackball» signifie une caméra qui tourne autour d'un objet, comme dans les applications de modélisation 3D? Si c'est le cas, je pense que cela se fait généralement en gardant simplement la trace des coordonnées 2D de la souris et en mappant x = lacet, y = pas pour la rotation de la caméra.
Nathan Reed
1
@NathanReed l'autre option est basée sur l'angle-angle , vous projetez 2 points de souris sur une sphère (virtuelle) puis trouvez la rotation de l'un à l'autre.
monstre à cliquet
@NathanReed oui c'est ce que je voulais dire par trackball, je pensais que c'était un nom commun dans la communauté CG.
BRabbit27
@ratchetfreak yes mon approche considère une rotation basée sur l'axe-angle. Mon doute est que s'il est nécessaire de mapper les coordonnées de la souris 2D en coordonnées mondiales ou non. Je sais que je peux utiliser le (x, y) pour calculer la zvaleur d'une sphère de rayon r, mais je ne sais pas si cette sphère vit dans l'espace-monde ou l'espace-image et quelles sont les implications. Je réfléchis peut-être trop au problème.
BRabbit27
2
Sur votre montage: oui. Vous auriez besoin de transformer vos valeurs (x, y, z) en espace mondial à l'aide de la matrice de vue.
RichieSams

Réponses:

16

En supposant que vous voulez dire une caméra qui tourne en fonction du mouvement de la souris:

Une façon de l'implémenter est de garder une trace de la position de la caméra et de sa rotation dans l'espace. Les coordonnées sphériques sont pratiques pour cela, car vous pouvez représenter directement les angles.

Image de coordonnées sphériques

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

La caméra est située à P qui est défini par m_theta, m_phi et m_radius. Nous pouvons tourner et nous déplacer librement où nous voulons en modifiant ces trois valeurs. Cependant, nous regardons et tournons toujours autour de m_target. m_target est l'origine locale de la sphère. Cependant, nous sommes libres de déplacer cette origine où nous voulons dans l'espace mondial.

Il existe trois fonctions principales de l'appareil photo:

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

Dans leurs formes les plus simples, Rotate () et Zoom () sont triviaux. Il suffit de modifier respectivement m_theta, m_phi et m_radius:

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

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

Le panoramique est un peu plus compliqué. Un panoramique de la caméra est défini comme le déplacement de la caméra vers la gauche / droite et / ou vers le haut / bas par rapport à la vue de caméra actuelle. La façon la plus simple de le faire est de convertir notre vue de caméra actuelle des coordonnées sphériques en coordonnées cartésiennes. Cela nous donnera des vecteurs ascendants et droits .

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

Donc, tout d'abord, nous convertissons notre système de coordonnées sphériques en cartésien pour obtenir notre vecteur de look . Ensuite, nous faisons le produit vectoriel croisé avec le vecteur monde vers le haut , afin d'obtenir un bon vecteur. Il s'agit d'un vecteur qui pointe directement à droite de la vue de la caméra. Enfin, nous faisons un autre vecteur produit croisé pour obtenir la caméra jusqu'à vecteur.

Pour terminer le panoramique, nous déplaçons m_target le long des vecteurs haut et droit .

Une question que vous pourriez vous poser est la suivante: pourquoi convertir entre cartésien et sphérique tout le temps (vous devrez également convertir pour créer la matrice de vue).

Bonne question. Moi aussi, j'ai eu cette question et j'ai essayé d'utiliser exclusivement la carte cartésienne. Vous vous retrouvez avec des problèmes de rotation. Étant donné que les opérations en virgule flottante ne sont pas exactement précises, plusieurs rotations finissent par accumuler des erreurs, qui correspondaient à la caméra lentement et roulaient accidentellement.

entrez la description de l'image ici

Donc, à la fin, je suis resté avec des coordonnées sphériques. Afin de contrer les calculs supplémentaires, j'ai fini par mettre en cache la matrice de vue et à la calculer uniquement lorsque la caméra se déplace.

La dernière étape consiste à utiliser cette classe Camera. Appelez simplement la fonction membre appropriée dans les fonctions MouseDown / Up / Scroll de votre application:

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

Les variables de facteur m_camera * ne sont que des facteurs d'échelle qui changent la vitesse de rotation / panoramique / défilement de votre appareil photo

Le code que j'ai ci-dessus est une version pseudo-code simplifiée du système de caméra que j'ai fait pour un projet parallèle : camera.h et camera.cpp . La caméra essaie d'imiter le système de caméra Maya. Le code est gratuit et open source, alors n'hésitez pas à l'utiliser dans votre propre projet.

RichieSams
la source
1
Je suppose que la division par 300 n'est qu'un paramètre pour la sensibilité de la rotation compte tenu du déplacement de la souris?
BRabbit27
Correct. C'est ce qui s'est bien passé avec ma résolution à l'époque.
RichieSams
-2

Dans le cas où vous souhaitez jeter un œil à une solution prête, j'ai un port de THREE.JS TrackBall contrôlés en C ++ et C #

Michael IV
la source
J'ai tendance à penser que oui, il peut apprendre du code à l'intérieur du fonctionnement du trackball.
Michael IV
1
@MichaelIV Néanmoins, Alan Wolfe a raison. Vous pouvez grandement améliorer votre réponse en incluant le code pertinent dans la réponse elle-même pour la rendre autonome et à l'épreuve du jour contre la mort du lien un jour.
Martin Ender