Como implementar um trackball no OpenGL?

15

Depois de tanta leitura sobre transformações, é hora de implementar um trackball para o meu aplicativo. Entendo que preciso criar um vetor da origem para onde o mouse é clicado e depois outro da origem para onde o mouse é liberado.

Minha pergunta é: eu tenho que transformar as cordas de pixel (x, y) em cordas do mundo ou devo fazer tudo no espaço da imagem (considerando o espaço da imagem, a projeção 2D da cena é medida em pixels)?

EDITAR

A resposta de Richie Sams é muito boa. No entanto, acho que estou seguindo uma abordagem um pouco diferente. Por favor, corrija-me se estiver errado ou se estiver entendendo algo errado.

Na minha aplicação eu tenho uma SimplePerspectiveCameraclasse que recebe a positionda câmera, o position of the targetque estamos olhando, o upvetor, o fovy, aspectRatio, neare fardistâncias.

Com eles, construo minhas matrizes de exibição e projeção. Agora, se eu quiser aumentar / diminuir o zoom, atualizo o campo de visão e atualizo minha matriz de projeção. Se eu quiser fazer uma panorâmica, movo a posição da câmera e observo o delta que o mouse produz.

Finalmente, para rotações, posso usar transformação de eixo angular ou quaterniões. Para isso, salvei as cordas de pixel onde o mouse foi pressionado e, quando o mouse se move, também salvei as cordas de pixel.

Para cada par de coords I pode calcular o valor de Z determinado a fórmula para uma esfera, ou seja, sqrt (1-x ^ 2-y ^ 2), em seguida, calcular a vectores que vão desde o targetde PointMousePressede a partir targetde PointMouseMoved, fazer produto cruzado para obter o eixo de rotação e usar qualquer método para calcular a nova posição da câmera.

No entanto, minha maior dúvida é que os valores (x, y, z) são dados em cordas de pixel e, ao calcular os vetores que estou usando, targetesse é um ponto nas cordas do mundo. Essa mistura de sistema de coordenadas não está afetando o resultado da rotação que estou tentando fazer?

BRabbit27
fonte
11
"Trackball" significa uma câmera que orbita em torno de um objeto, como nos aplicativos de modelagem 3D? Nesse caso, acho que geralmente é feito apenas acompanhando as coordenadas 2D do mouse e mapeando x = guinada, y = afinação para a rotação da câmera.
Nathan Reed
11
@NathanReed a outra opção é baseada no ângulo do eixo . Você projeta 2 pontos do mouse em uma esfera (virtual) e encontra a rotação de um para o outro.
catraca aberração
@NathanReed sim, é isso que eu quis dizer com trackball, achei que era um nome comum na comunidade de CG.
BRBbit27 /
@ratchetfreak sim, minha abordagem considera uma rotação baseada no eixo-ângulo. Minha dúvida é que, se for necessário mapear as cordas do mouse 2D para a coordenação mundial ou não. Eu sei que posso usar o (x, y) para calcular o zvalor de uma esfera de raio r, no entanto, não tenho certeza se essa esfera vive no espaço do mundo ou no espaço da imagem e quais são as implicações. Talvez eu esteja pensando demais no problema.
precisa saber é o seguinte
2
Na sua edição: Sim. Você precisaria transformar seus valores (x, y, z) em espaço no mundo usando a matriz View.
RichieSams

Respostas:

16

Supondo que você queira dizer uma câmera que gira com base no movimento do mouse:

Uma maneira de implementá-lo é acompanhar a posição da câmera e sua rotação no espaço. As coordenadas esféricas são convenientes para isso, pois você pode representar os ângulos diretamente.

Imagem de coordenadas esféricas

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

A câmera está localizada em P, definida por m_theta, m_phi e m_radius. Podemos girar e mover-nos livremente para onde quisermos, alterando esses três valores. No entanto, sempre olhamos e giramos em torno de m_target. m_target é a origem local da esfera. No entanto, somos livres para mudar essa origem para onde quisermos no espaço do mundo.

Existem três funções principais da câmera:

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

Em suas formas mais simples, Rotate () e Zoom () são triviais. Basta modificar m_theta, m_phi e m_radius, respectivamente:

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

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

O movimento panorâmico é um pouco mais complicado. Um pan da câmera é definido como mover a câmera para a esquerda / direita e / ou para cima / para baixo, respectivamente, para a visualização atual da câmera. A maneira mais fácil de conseguir isso é converter nossa visão atual da câmera de coordenadas esféricas em coordenadas cartesianas. Isso nos dará um up e certos vetores.

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

Então, primeiro, convertemos nosso sistema de coordenadas esféricas em cartesiano para obter nosso vetor de aparência . Em seguida, fazemos o produto cruzado do vetor com o mundo até vetor, a fim de obter um certo vetor. Este é um vetor que aponta diretamente para a direita da visualização da câmera. Por fim, fazemos outro produto cruzado de vetor para obter a câmera de vetor.

Para finalizar o pan, movemos m_target pelos vetores para cima e para a direita .

Uma pergunta que você pode estar perguntando é: Por que converter entre cartesiano e esférico o tempo todo (você também precisará converter para criar a matriz View).

Boa pergunta. Eu também tive essa pergunta e tentei usar exclusivamente cartesiano. Você acaba com problemas com rotações. Como as operações de ponto flutuante não são exatamente precisas, várias rotações acabam acumulando erros, que correspondiam à câmera lenta e involuntariamente.

insira a descrição da imagem aqui

Então, no final, eu fiquei com coordenadas esféricas. A fim de combater os cálculos extras, acabei armazenando em cache a matriz de visualização e só a calculo quando a câmera se move.

O último passo é usar esta classe de câmera. Basta chamar a função de membro apropriada nas funções MouseDown / Up / Scroll do seu aplicativo:

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

As variáveis ​​m_camera * Factor são apenas fatores de escala que alteram a rapidez com que sua câmera gira / desloca / rola

O código que eu tenho acima é uma versão pseudo-código simplificado do sistema de câmera que eu fiz para um projeto paralelo: camera.h e camera.cpp . A câmera tenta imitar o sistema de câmera Maya. O código é gratuito e de código aberto, portanto, fique à vontade para usá-lo em seu próprio projeto.

RichieSams
fonte
11
Eu acho que a divisão por 300 é apenas um parâmetro para a sensibilidade da rotação, devido ao deslocamento do mouse?
precisa saber é o seguinte
Corrigir. Foi o que aconteceu com a minha resolução na época.
precisa saber é o seguinte
-2

Caso você queira dar uma olhada em uma solução pronta, eu tenho uma porta de THREE.JS TrackBall controla em C ++ e C #

Michael IV
fonte
Eu acredito que sim. Ele pode aprender com o código de como o trackball funciona.
Michael IV
11
@ MichaelIV No entanto, Alan Wolfe tem razão. Você pode melhorar bastante sua resposta, incluindo o código relevante na própria resposta, para torná-la independente e à prova de futuro, caso o link fique inoperante algum dia.
Martin Ender