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?


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.


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 #


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.