OpenGL에서 트랙볼을 구현하는 방법?


15

변환에 대해 너무 많이 읽은 후에는 앱에 트랙볼을 구현할 차례입니다. 나는 원점에서 마우스를 클릭 한 곳에서 원점에서 마우스가 놓인 곳으로 벡터를 만들어야한다는 것을 이해합니다.

내 질문은 (x, y) 픽셀 좌표를 월드 좌표로 변환해야합니까, 아니면 이미지 공간에서 모든 것을해야합니까 (이미지 공간을 고려하면 장면의 2D 투영이 픽셀로 측정됩니다)?

편집하다

Richie Sams의 답변은 매우 좋습니다. 그러나 약간 다른 접근 방식을 따르고 있다고 생각합니다. 잘못되었거나 잘못 이해하면 수정하십시오.

내 응용 프로그램에서 나는이 SimplePerspectiveCamera받는 클래스 position의 카메라를 position of the target우리는,보고있는 up벡터는 fovy, aspectRatio, nearfar거리.

이것들을 사용하여 View 및 Projection 행렬을 만듭니다. 이제 확대 / 축소하려는 경우 시야를 업데이트하고 투영 매트릭스를 업데이트합니다. 이동하려면 카메라의 위치를 ​​이동하고 마우스가 생성하는 델타로 봅니다.

마지막으로 회전을 위해 각도 축 변환 또는 쿼터니언을 사용할 수 있습니다. 이를 위해 마우스를 누른 위치의 픽셀 코드를 저장 한 다음 마우스가 움직일 때 픽셀 코드도 저장합니다.

각 좌표 쌍에 대해 구의 공식, 즉 sqrt (1-x ^ 2-y ^ 2)에 대해 주어진 Z- 값을 계산 한 다음 targetto PointMousePressed에서 targetto 로 이동하는 벡터로 계산하고 PointMouseMoved교차 곱을 수행 할 수 있습니다. 회전축을 얻고 새로운 카메라 위치를 계산하는 방법을 사용합니다.

그러나 가장 큰 의구심은 (x, y, z) 값이 픽셀 좌표로 제공되며 사용 target중인 벡터를 계산할 때 세계 좌표의 포인트입니다. 이 좌표계 혼합이 내가 시도하는 회전 결과에 영향을 미치지 않습니까?


1
"트랙볼"은 3D 모델링 앱과 같이 물체 주위를 공전하는 카메라를 의미합니까? 그렇다면 일반적으로 2D 마우스 좌표를 추적하고 카메라 회전을 위해 x = yaw, y = pitch를 매핑하면된다고 생각합니다.
Nathan Reed

1
@NathanReed 다른 옵션은 축 각도 기반입니다 . 두 개의 마우스 포인트를 (가상) 구에 투영 한 다음 회전을 찾습니다.
ratchet freak

@NathanReed 네, 이것이 트랙볼의 의미입니다. 저는 이것이 CG 커뮤니티에서 일반적인 이름이라고 생각했습니다.
BRabbit27

@ratchetfreak 예 내 접근 방식은 축 각도 기반 회전을 고려합니다. 내 의심은 2D 마우스 좌표를 세계 좌표에 매핑 해야하는지 여부입니다. (x, y)를 사용하여 z반지름 구의 값 을 계산할 수 r있지만 그 구가 월드 공간 또는 이미지 공간에 있는지와 그 의미가 무엇인지 확실하지 않습니다. 아마도 나는 문제를 지나치게 생각하고 있습니다.
BRabbit27

2
편집시 : 예. View 행렬을 사용하여 (x, y, z) 값을 월드 공간으로 변환해야합니다.
RichieSams

답변:


16

마우스 움직임을 기준으로 회전하는 카메라를 의미한다고 가정합니다.

이를 구현하는 한 가지 방법은 카메라 위치와 공간에서의 회전을 추적하는 것입니다. 구면 좌표는 각도를 직접 나타낼 수 있기 때문에 편리합니다.

구면 좌표 이미지

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

카메라는 m에 위치하며 P 는 m_theta, m_phi 및 m_radius로 정의됩니다. 이 세 가지 값을 변경하여 원하는 곳 어디든 자유롭게 회전하고 이동할 수 있습니다. 그러나 우리는 항상 m_target을보고 회전합니다. m_target은 구의 로컬 원점입니다. 그러나 우리는이 원점을 세계 공간에서 원하는 곳으로 자유롭게 이동할 수 있습니다.

세 가지 주요 카메라 기능이 있습니다.

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

가장 간단한 형태로 Rotate () 및 Zoom ()은 사소한 것입니다. m_theta, m_phi 및 m_radius를 각각 수정하십시오.

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

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

패닝은 조금 더 복잡합니다. 카메라 팬은 카메라를 현재 카메라 뷰에 따라 왼쪽 / 오른쪽 및 / 또는 위 / 아래로 이동하는 것으로 정의됩니다. 가장 쉬운 방법은 현재 카메라 뷰를 구형 좌표에서 직교 좌표로 변환하는 것입니다. 이것은 우리에게 줄 것이다 최대권리 벡터를.

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

먼저 구형 좌표계를 직교 좌표계로 변환하여 모양 벡터 를 얻습니다 . 다음 으로 올바른 벡터 를 얻기 위해 월드 벡터 와 벡터 교차 곱을 수행합니다 . 카메라 뷰의 바로 오른쪽을 가리키는 벡터입니다. 마지막으로, 우리는 카메라를 얻을 수있는 또 다른 벡터 외적 할 최대 벡터.

팬을 완료하기 위해, 우리는 함께 m_target 이동 위로 하고 바로 벡터.

묻는 질문 중 하나는 항상 데카르트와 구형을 변환해야하는 이유입니다 (뷰 매트릭스를 만들려면 변환해야 함).

좋은 질문. 나도이 질문을 가지고 독점적으로 데카르트를 사용하려고했습니다. 회전 문제가 생깁니다. 부동 소수점 연산이 정확하게 정확하지 않기 때문에 다중 회전으로 인해 누적 오류가 발생하여 카메라에 느리게 대응하고 의도하지 않게 롤링됩니다.

여기에 이미지 설명을 입력하십시오

결국, 나는 구형 좌표를 고수했습니다. 추가 계산에 대응하기 위해 뷰 매트릭스를 캐싱하고 카메라가 움직일 때만 계산했습니다.

마지막 단계는 이 카메라 클래스 를 사용 하는 것입니다. 앱의 MouseDown / Up / Scroll 함수 내에서 적절한 멤버 함수를 호출하십시오.

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

m_camera * Factor 변수는 카메라의 회전 / 팬 / 스크롤 속도를 변경하는 스케일 요소입니다.

위의 코드는 보조 프로젝트를 위해 만든 카메라 시스템의 단순화 된 의사 코드 버전입니다 : camera.hcamera.cpp . 카메라가 Maya 카메라 시스템을 모방하려고합니다. 코드는 무료이며 오픈 소스이므로 자신의 프로젝트에서 자유롭게 사용할 수 있습니다.


1
300으로 나눈 값은 마우스의 변위를 고려한 회전 감도의 매개 변수 일뿐입니다.
BRabbit27

옳은. 당시 내 결의안에서 잘 작동했습니다.
RichieSams

-2

경우 당신은 준비가 해결책이다로 살펴 갖고 싶어 포트 C ++와 C #으로 three.js를 트랙볼 controlls의를


트랙볼이 어떻게 작동하는지 내부 코드에서 배울 수 있습니다.
Michael IV

1
@MichaelIV 그럼에도 불구하고 Alan Wolfe는 지적했다. 응답 자체에 관련 코드를 포함시켜 언젠가는 연결이 끊어지는 링크에 대해 자체적으로 포함되어 있고 미래에 대비할 수 있도록하여 답변을 크게 향상시킬 수 있습니다.
Martin Ender
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.