如何应用转换矩阵?

12 3d projection

我正在尝试获取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?
} }
Run Code Online (Sandbox Code Playgroud)

基本上,我需要的是获得3D点相交的给定屏幕的2D XY坐标.我不确定如何使用滚动,倾斜和平移矩阵来转换世界点(wp).

非常感谢任何帮助!

Wes*_*ley 28

这很复杂.请阅读有关此主题的书籍以获取所有数学和细节.如果你打算长时间玩这些东西,你需要知道这些事情.这个答案就是让你的脚湿透了.

乘以矩阵

首先要做的事情.乘法矩阵是一个相当简单的事情.

比方说,你有矩阵,,和Ç,其中AB = Ç.假设你想在第3行第2列找出矩阵C的值.

  • 采取的第三行和的第二列.您现在应该从AB获得相同数量的值.(如果没有为这两个矩阵定义矩阵乘法.你不能这样做.)如果两者都是4×4矩阵,你应该有4个值来自A(第3行)和4个来自B的值(第2栏).
  • A的每个值乘以B的每个值.你最终应该得到4个新值.
  • 添加这些值.

现在,您可以在第3行第2列获得矩阵C的值.当然,挑战是以编程方式执行此操作.

/* 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];
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

均匀坐标

你有3D坐标.假设你有(5,2,1).这些是笛卡尔坐标.我们称之为(x,y,z).

均匀坐标意味着您在笛卡尔坐标的末尾写入额外的1.(5,2,1)变为(5,2,1,1).我们称之为(x,y,z,w).

每当你进行使w ≠1 的变换时,你将坐标的每个分量除以w.这会改变你的x,yz,它会再次使w = 1.(有没有这样做,伤害,甚至当你变换不改变w ^.它只是除以1一切,这什么都不做.)

你可以用同质坐标做一些很酷的东西,即使它们背后的数学没有完全意义.在这一点上,我请你再看一下这个答案顶部的建议.


改变一点

我将在本节及以下各节中使用OpenGL术语和方法.如果有任何不清楚或似乎与你的目标相冲突(因为这似乎模糊地作业 - 就像我:P),请发表评论.

我还首先假设您的滚动,倾斜和平移矩阵是正确的.

当您想使用变换矩阵变换点时,您可以将该矩阵与表示您的点的列向量右移.假设你想通过一些变换矩阵A翻译(5,2,1).首先定义v = [5,2,1,1] Ť.(我用小T写[ x,y,z,w ] T表示应该把它写成列向量.)

// Your point in 3D
double v[4][5] = {{5}, {2}, {1}, {1}}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,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);
Run Code Online (Sandbox Code Playgroud)

现在,如果要应用多个转换矩阵,首先将它们组合成一个转换矩阵.通过将矩阵按您希望它们应用的顺序放在一起来完成此操作.

在编程上,您应该从单位矩阵开始,并右对乘每个变换矩阵.设I 4为4×4单位矩阵,让A 1,A 2,A 3 ......成为变换矩阵.让你的最终转换矩阵成为A final

最终4
最终最终 1
最终最终 2
最终最终 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);
Run Code Online (Sandbox Code Playgroud)

最后,做同样的乘法如上:最终 v = v 1

// Use the above matrix to transform v
mmMul(a_final, v, v_1);
Run Code Online (Sandbox Code Playgroud)

从开始到结束

相机变换应表示为视图矩阵.在此处执行A view v = v 1操作.(v表示您的世界坐标为4×1列向量,A final是您的A 视图.)

// World coordinates to eye coordinates
// A_view is a_final from above
mmMult(a_view, v_world, v_view);
Run Code Online (Sandbox Code Playgroud)

投影变换描述了透视变换.这使得较近的物体更大,更远的物体更小.这是在相机转换后执行的.如果您还不想要透视,只需使用单位矩阵作为投影矩阵.无论如何,在这里执行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);
Run Code Online (Sandbox Code Playgroud)

接下来,你需要做一个透视鸿沟.这更深入地研究了同源坐标,我还没有描述过.无论如何,划分的每个组件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];
}
Run Code Online (Sandbox Code Playgroud)

最后,拿你的v 2.前两个坐标是您的xy坐标.第三个是z,你可以扔掉.(稍后,一旦你变得非常高级,你可以使用这个z值来确定哪个点位于其他点之前或之后.)此时,最后一个组件是w = 1,所以你不需要那就是了.

x = v_ndc[0]
y = v_ndc[1]
z = v_ndc[2]  // unused; your screen is 2D
Run Code Online (Sandbox Code Playgroud)

如果您跳过透视图和透视图划分步骤,请使用v_view而不是v_ndc上面的步骤.

这与OpenGL坐标系的集合非常相似.不同之处在于您从世界坐标开始,而OpenGL以对象坐标开始.区别如下:

  • 你从世界坐标开始
    • OpenGL以对象坐标开始
  • 您可以使用视图矩阵将世界坐标转换为眼睛坐标
    • OpenGL使用ModelView矩阵将对象坐标转换为眼睛坐标

从那以后,一切都是一样的.

  • 你熟悉同质坐标吗?表示3D变换的4×4矩阵和4矢量? (2认同)
  • 阅读完您的最后一条评论并查看了PDF,我认为我在回答的问题与您所提出的问题不同。GPS坐标的形式是什么?(纬度,对数,海拔吗?x,y,z的0、0、0是地球的中心?还有别的吗?)照片是否“包裹”在整个地球上?(如果没有,它覆盖什么区域?它弯曲了吗?) (2认同)

Pau*_*ier 2

这个范围太大了,无法在这里得到一个好的答案:我建议阅读有关该主题的很好的参考资料。我一直很喜欢Foley 和 VanDam ...