OpenGLでトラックボールを実装する方法は?


15

変換について多くのことを読んだ後、アプリにトラックボールを実装するときが来ました。原点からマウスがクリックされた場所までのベクトルを作成し、次に原点からマウスがクリックされた場所まで別のベクトルを作成する必要があることを理解しています。

私の質問は、(x、y)ピクセル座標を世界座標に変換する必要がありますか、または画像空間ですべてを行う必要がありますか(画像空間はピクセルで測定されたシーンの2D投影であると考えてください)?

編集

リッチー・サムズの答えはとても良いものです。しかし、私は少し異なるアプローチを取っていると思います。間違っているか何かを誤解している場合は修正してください。

私のアプリケーションでは、私は持ってSimplePerspectiveCamera受け取り、クラスpositionのカメラの、position of the target私たちは、見ているup、ベクトルfovyaspectRationearfarの距離を。

それらを使用して、ViewマトリックスとProjectionマトリックスを作成します。今、ズームイン/ズームアウトしたい場合、視野を更新し、投影マトリックスを更新します。パンしたい場合は、カメラの位置を移動して、マウスが生成するデルタを確認します。

最後に、回転には、角度軸変換またはクォータニオンのいずれかを使用できます。このために、マウスが押されたピクセル座標を保存し、マウスが移動したときにピクセル座標も保存します。

座標の各ペアについて、球の公式、すなわちsqrt(1-x ^ 2-y ^ 2)を与えられたZ値を計算し、targetto PointMousePressedからtargetto PointMouseMovedに行くベクトルに計算し、外積を行います。回転軸を取得し、任意の方法を使用して新しいカメラ位置を計算します。

しかし、私の最大の疑問は、(x、y、z)値がピクセル座標で与えられていることtargetであり、使用しているベクトルを計算するとき、これは世界座標のポイントです。この座標系の混合は、私がやろうとしている回転の結果に影響していませんか?


1
「トラックボール」とは、3Dモデリングアプリのように、オブジェクトの周りを周回するカメラのことですか?もしそうなら、2Dマウスの座標を追跡し、カメラの回転のためにx = yaw、y = pitchをマッピングするだけで通常は行われると思います。
ネイサンリード

1
@NathanReedもう1つのオプションは軸角度ベースです。2つのマウスポイントを(仮想)球体に投影し、一方から他方への回転を見つけます。
ラチェットフリーク

@NathanReedはい、それはトラックボールが意味するもので、CGコミュニティでは一般的な名前だと思いました。
BRabbit27

@ratchetfreakはい、私のアプローチでは軸角度ベースの回転を考慮しています。私の疑問は、2Dマウス座標を世界座標にマッピングする必要があるかどうかです。私は(x、y)を使用してzradiusの球の値を計算できることを知っていますが、rその球がワールド空間に存在するかイメージ空間に存在するか、およびその意味はわかりません。おそらく私は問題を考え直しているのでしょう。
BRabbit27

2
編集について:はい。ビューマトリックスを使用して、(x、y、z)値をワールド空間に変換する必要があります。
-RichieSams

回答:


16

マウスの動きに基づいて回転するカメラを意味すると仮定します。

これを実装する1つの方法は、カメラの位置と空間内での回転を追跡することです。角度を直接表現できるため、球座標が便利です。

球面座標画像

float m_theta;
float m_phi;
float m_radius;

float3 m_target;

カメラは、m_theta、m_phi、m_radiusで定義されるPにあります。これらの3つの値を変更することで、好きな場所を自由に回転および移動できます。ただし、m_targetを常に見て、回転します。m_targetは球のローカル原点です。ただし、ワールド空間のどこにでもこの原点を自由に移動できます。

3つの主要なカメラ機能があります。

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を移動アップし、のベクトル。

質問の1つは、なぜデカルト座標と球面座標を常に変換するのかということです(Viewマトリックスを作成するために変換する必要もあります)。

良い質問。私もこの質問をして、デカルトを排他的に使用しようとしました。回転に問題が生じます。浮動小数点演算は正確に正確ではないため、複数の回転により累積エラーが発生します。これは、カメラに対応し、意図せずにローリングします。

ここに画像の説明を入力してください

だから、結局、私は球座標に固執しました。追加の計算に対抗するために、ビューマトリックスをキャッシュし、カメラが移動したときにのみ計算するようにしました。

最後のステップは、このCameraクラスを使用することです。アプリの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.hおよびcamera.cppです。カメラはMayaカメラシステムを模倣しようとします。コードは無料でオープンソースですので、自分のプロジェクトで自由に使用してください。


1
300での除算は、マウスの変位を考慮した場合の回転の感度のパラメーターに過ぎないのでしょうか。
BRabbit27

正しい。それは当時の私の解像度でうまくいったことでした。
-RichieSams

-2

準備ができたソリューションをご覧になりたい場合は、C ++およびC#でTHREE.JS TrackBallを制御するポートを持っています


彼はトラックボールの仕組みの内側のコードから学ぶことができます。
マイケルIV

1
@MichaelIVそれでも、アラン・ウルフにはポイントがあります。関連するコードを回答自体に含めて、いつかはリンク切れにならないように自己完結型で将来性のあるものにすることで、回答を大幅に改善できます。
マーティン・エンダー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.