14

3D 空間内のポイントの 2D スクリーン座標を取得しようとしています。つまり、カメラのパン、チルト、ロールの位置を知っており、投影したいポイントの 3D x、y、z 座標を持っています。

私は変換/射影行列を理解するのに苦労しています。ここの知的な人々が私を助けてくれることを望んでいました;)

これまでにまとめたテストコードは次のとおりです。

public class TransformTest {

public static void main(String[] args) {

    // set up a world point (Point to Project)
    double[] wp = {100, 100, 1};
    // set up the projection centre (Camera Location)
    double[] pc = {90, 90, 1};

    double roll = 0;
    double tilt = 0;
    double pan = 0;

    // translate the point
    vSub(wp, pc, wp);

    // create roll matrix
    double[][] rollMat = {
            {1, 0, 0},
            {0, Math.cos(roll), -Math.sin(roll)},
            {0, Math.sin(roll), Math.cos(roll)},
    };
    // create tilt matrix
    double[][] tiltMat = {
            {Math.cos(tilt), 0, Math.sin(tilt)},
            {0, 1, 0},
            {-Math.sin(tilt), 0, Math.cos(tilt)},
    };
    // create pan matrix
    double[][] panMat = {
            {Math.cos(pan), -Math.sin(pan), 0},
            {Math.sin(pan), Math.cos(pan), 0},
            {0, 0, 1},
    };

    // roll it
    mvMul(rollMat, wp, wp);
    // tilt it
    mvMul(tiltMat, wp, wp);
    // pan it
    mvMul(panMat, wp, wp);

}

public static void vAdd(double[] a, double[] b, double[] c) {
    for (int i=0; i<a.length; i++) {
        c[i] = a[i] + b[i];
    }
}

public static void vSub(double[] a, double[] b, double[] c) {
    for (int i=0; i<a.length; i++) {
        c[i] = a[i] - b[i];
    }      
}

public static void mvMul(double[][] m, double[] v, double[] w) {

    // How to multiply matrices?
} }

基本的に、必要なのは、3D ポイントが交差する特定の画面の 2D XY 座標を取得することです。ロール、チルト、パン マトリックスを使用してワールド ポイント (wp) を変換する方法がわかりません。

これに関するヘルプは大歓迎です!

4

3 に答える 3

32

これは複雑なことです。このトピックに関する本を読んで、すべての数学と核心を理解してください。このようなもので長く遊ぶ予定がある場合は、これらのことを知っておく必要があります. この答えは、足を濡らしてハッキングできるようにするためのものです。

乗算行列

まず最初に。行列の乗算はかなり単純な作業です。

AB = Cである行列AB、およびCがあるとします。行列Cの行 3、列 2の値を求めたいとしましょう。

  • Aの 3 行目とBの 2 列目を取得します。これで、 ABから同じ数の値が得られるはずです。(これらの 2 つの行列に対して行列の乗算が定義されていない場合、実行できません。) 両方とも 4×4 行列の場合、A (行 3) からの 4 つの値とB (行 3) からの 4 つの値が必要です(列 2)。
  • A の各値にBの各値を掛けます。4 つの新しい値で終了する必要があります。
  • これらの値を追加します。

これで行列Cの値が行 3、列 2 に表示されました。もちろん、課題はこれをプログラムで行うことです。

/* AB = C

Row-major ordering
a[0][0] a[0][2] a[0][3]...
a[1][0] a[1][4] ...
a[2][0] ...
...*/
public static mmMul(double[][] a, double[][] b, double[][] c) {
    c_height = b.length; // Height of b
    c_width = a[0].length; // Width of a
    common_side = a.length; // Height of a, width of b

    for (int i = 0; i < c_height; i++) {
        for (int j = 0; j < c_width; j++) {
            // Ready to calculate value of c[i][j]
            c[i][j] = 0;

            // Iterate through ith row of a, jth col of b in lockstep
            for (int k = 0; k < common_side; k++) {
                c[i][j] += a[i][k] * b[k][j];
            }
        }
    }
}

同次座標

3D座標があります。(5, 2, 1) があるとしましょう。これらはデカルト座標です。それらを ( xyz )と呼びましょう。

同次座標とは、デカルト座標の最後に余分な 1 を書き込むことを意味します。(5, 2, 1) は (5, 2, 1, 1) になります。それらを ( xyzw )と呼びましょう。

w ≠ 1 になる変換を行うときはいつでも、座標のすべてのコンポーネントをwで除算します。これにより、xy、およびzが変更され、w = 1 に戻ります。(変換がwに変わらない場合でも、これを実行しても害はありません。すべてを 1 で除算するだけで、何もしません。)

それらの背後にある数学が完全に意味をなさない場合でも、同次座標で実行できるいくつかの非常に優れた機能があります。この時点で、この回答の上部にあるアドバイスをもう一度見てください。


ポイントの変換

このセクションと次のセクションでは、OpenGL の用語とアプローチを使用します。不明な点や目標と矛盾する点がある場合 (これは漠然と宿題のように思えるため :P)、コメントを残してください。

また、ロール、チルト、およびパンのマトリックスが正しいと仮定することから始めます。

変換行列を使用してポイントを変換する場合は、その行列にポイントを表す列ベクトルを右乗算します。(5, 2, 1) を変換行列Aで変換したいとします。最初にv = [5, 2, 1, 1] Tを定義します。([ x , y , z , w ] T小さな Tで書き、列ベクトルとして記述する必要があることを意味します。)

// Your point in 3D
double v[4][5] = {{5}, {2}, {1}, {1}}

この場合、Av = v 1で、v 1は変換されたポイントです。この乗算は、 Aが 4×4 でvが 4×1の行列乗算のように行います。最終的に 4×1 の行列 (別の列ベクトル) になります。

// Transforming a single point with a roll
double v_1[4][6];
mmMul(rollMat, v, v_1);

ここで、適用する変換行列が複数ある場合は、まずそれらを 1 つの変換行列に結合します。これを行うには、適用する順序で行列を乗算します。

プログラムでは、単位行列から始めて、各変換行列を右乗算する必要があります。I 44×4 単位行列とし、A 1A 2A 3、... を変換行列とします。最終的な変換行列をA finalにします

AファイナルI 4
AファイナルAファイナル A 1
AファイナルAファイナル A 2
AファイナルAファイナル A 3

割り当てを表すためにその矢印を使用していることに注意してください。これを実装するときは、行列の乗算計算で使用している間にA final を上書きしないように注意してください。コピーを作成します。

// A composite transformation matrix (roll, then tilt)

double a_final[4][4] =
{
    {1, 0, 0, 0},
    {0, 1, 0, 0},
    {0, 0, 1, 0},
    {0, 0, 0, 1}
}; // the 4 x 4 identity matrix

double a_final_copy[4][4];
mCopy(a_final, a_final_copy); // make a copy of a_final
mmMul(rollMat, a_final_copy, a_final);
mCopy(a_final, a_final_copy); // update the copy
mmMul(tiltMat, a_final_copy, a_final);

最後に、上記と同じ乗算を行います: A final v = v 1

// Use the above matrix to transform v
mmMul(a_final, v, v_1);

最初から最後まで

カメラの変換は、ビュー マトリックスとして表す必要があります。ここでAビュー v = v 1操作を実行します。( vは世界座標を 4×1 の列ベクトルとして表します。A finalAビューです。)

// World coordinates to eye coordinates
// A_view is a_final from above
mmMult(a_view, v_world, v_view);

射影変換は、透視変換を表します。これにより、近くのオブジェクトが大きくなり、遠くのオブジェクトが小さくなります。これは、カメラの変換後に実行されます。パースペクティブがまだ必要ない場合は、射影行列に単位行列を使用してください。とにかく、ここでA v 1 = v 2を実行します。

// Eye coordinates to clip coordinates
// If you don't care about perspective, SKIP THIS STEP
mmMult(a_projection, v_view, v_eye);

次に、パースペクティブ分割を行う必要があります。ここでは、まだ説明していない同次座標について詳しく説明します。とにかく、 v 2 のすべてのコンポーネントをv 2最後のコンポーネントで割ります。v 2 = [ x , y , z , w ] Tの場合、各成分をw ( w自体を含む)で除算します。最終的にw = 1 になるはずです (前述のように射影行列が恒等行列の場合、この手順では何も実行されません)。

// Clip coordinates to normalized device coordinates
// If you skipped the previous step, SKIP THIS STEP
for (int i = 0; i < 4; i++) {
    v_ndc[i] = v_eye[i] / v[3];
}

最後に、あなたのv 2を取ります。最初の 2 つの座標は、x座標とy座標です。3 番目はzで、これは破棄できます。(後で、非常に高度になったら、このz値を使用して、他の点の前または後ろにある点を把握できます。) この時点で、最後のコンポーネントはw = 1 であるため、必要はありません。それはもうまったく。

x = v_ndc[0]
y = v_ndc[1]
z = v_ndc[2]  // unused; your screen is 2D

遠近法と遠近法の分割の手順をスキップした場合は、上記v_viewの代わりに使用しv_ndcます。

これは、 OpenGL 座標系のセットに非常に似ています。違いは、ワールド座標から開始するのに対し、OpenGL はオブジェクト座標から開始することです。違いは次のとおりです。

  • 世界座標から始めます
    • OpenGL はオブジェクト座標から始まります
  • ビュー マトリックスを使用して、ワールド座標を目の座標に変換します
    • OpenGL は ModelView マトリックスを使用してオブジェクト座標を目の座標に変換します

そこから先は、すべて同じです。

于 2009-05-11T21:42:35.153 に答える
3

ここで適切な回答を得るには範囲が広すぎます。このトピックに関する適切なリファレンスを読むことをお勧めします。私はいつもフォーリーとヴァンダムが好きでした...

于 2009-05-11T21:37:40.327 に答える
1

必要なことの多くを実行するコードをここに投稿しました。

OpenGLgluPerspective()gluLookAt()関数の Java 実装が含まれています。

Camera camera = new Camera();

Point3d eye = new Point3d(3, 4, 8);
Point3d center = new Point3d(0, 0, 0);
Vector3d up = new Vector3d(0, 1, 0);

camera.perspective(60.0, 1.6, 0.1, 20); // vertical fov, aspect ratio, znear, zfar
camera.lookAt(eye, center, up);

project()その関数を使用するには、次を使用します。

void plot(Camera camera, Point4d p) {
    Point4d q = Camera.project(p);
    float x = q.x / q.w;
    float y = q.y / q.w;
    ...
}

返される値xy値は、-0.5 ... 0.5 の範囲になります。

于 2009-05-12T16:42:52.373 に答える