3d - 如何应用变换矩阵?

标签 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?
} }

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

非常感谢您对此的任何帮助!

最佳答案

这是很复杂的东西。请阅读有关此主题的书以获取所有数学和细节。如果你打算长期玩这些东西,你需要知道这些东西。这个答案只是为了让你可以弄湿脚并四处走动。

乘法矩阵

先说第一件事。乘法矩阵是 reasonably simple affair .

假设您有矩阵 A、B 和 C,其中 AB = C。假设您想计算矩阵 C 在第 3 行第 2 列的值。

  • 取 A 的第三行和 B 的第二列。您现在应该拥有 A 和 B 中相同数量的值。 (如果你没有为这两个矩阵定义矩阵乘法。你不能这样做。)如果两者都是 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];
                }
            }
        }
    }
    

    齐次坐标

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

    齐次坐标意味着您在笛卡尔坐标的末尾多写一个 1。 (5, 2, 1) 变成 (5, 2, 1, 1)。我们称它们为 (x, y, z, w)。

    每当您进行使 w ≠ 1 的变换时,您将坐标的每个分量除以 w。这会改变你的 x、y 和 z,它再次使 w = 1。 (即使您的转换没​​有改变 w,这样做也没有坏处。它只是将所有内容除以 1,什么也不做。)

    你可以用齐次坐标做一些非常酷的事情,即使它们背后的数学并不完全有意义。正是在这一点上,我请您再次查看此答案顶部的建议。

    变换一个点

    我将在本节和后续章节中使用 OpenGL 术语和方法。如果有任何不清楚或似乎与您的目标相冲突(因为这对我来说似乎有点像家庭作业:P),请发表评论。

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

    当您想使用转换矩阵转换一个点时,您可以将该矩阵与代表您的点的列向量右乘。假设您想通过某个变换矩阵 A 转换 (5, 2, 1)。您首先定义 v = [5, 2, 1, 1]T。 (我用 小 T 写 [x, y, z, w]T 表示你应该把它写成一个列向量。)
    // Your point in 3D
    double v[4][5] = {{5}, {2}, {1}, {1}}
    

    在这种情况下,Av = v1,其中 v1 是您的变换点。像矩阵乘法一样执行此乘法,其中 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);
    

    现在,如果您要应用多个变换矩阵,请先将它们组合成一个变换矩阵。通过按照您希望应用它们的顺序将矩阵相乘来完成此操作。

    以编程方式,您应该从单位矩阵开始并右乘每个变换矩阵。设 I4 为 4×4 单位矩阵,并设 A1、A2、A3、... 为您的变换矩阵。让你的最终变换矩阵是 Afinal

    Afinal ← I4
    Afinal ← Afinal A1
    Afinal ← Afinal A2
    Afinal ← Afinal A3

    请注意,我使用该箭头来表示分配。实现这一点时,请确保在矩阵乘法计算中仍在使用 Afinal 时不要覆盖它!复印一份。
    // 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);
    

    最后,做与上面相同的乘法:Afinal v = v1
    // Use the above matrix to transform v
    mmMul(a_final, v, v_1);
    

    从开始到结束

    相机变换应表示为 View 矩阵。在此处执行 Aview v = v1 操作。 (v 将您的世界坐标表示为 4×1 列向量,Afinal 是您的 View 。)
    // World coordinates to eye coordinates
    // A_view is a_final from above
    mmMult(a_view, v_world, v_view);
    

    投影变换描述了透视变换。这就是使较近的物体变大而较远的物体变小的原因。这是在相机变换之后执行的。如果您还不需要透视,只需将单位矩阵用于投影矩阵。无论如何,在这里执行 A v1 = v2。
    // Eye coordinates to clip coordinates
    // If you don't care about perspective, SKIP THIS STEP
    mmMult(a_projection, v_view, v_eye);
    

    接下来,您需要进行透视划分。这将深入研究同质坐标,我还没有描述过。无论如何,将 v2 的每个分量除以 v2 的最后一个分量。如果 v2 = [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];
    }
    

    最后,使用您的 v2。前两个坐标是您的 x 和 y 坐标。第三个是 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 coordinate systems的设置非常相似。 .不同之处在于您从世界坐标开始,而 OpenGL 从对象坐标开始。区别如下:
  • 你从世界坐标开始
  • OpenGL 从对象坐标开始
  • 您使用 View 矩阵将世界坐标转换为眼睛坐标
  • OpenGL 使用 ModelView 矩阵将对象坐标转换为眼睛坐标

  • 从那以后,一切都是一样的。

    关于3d - 如何应用变换矩阵?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/850179/

    相关文章:

    opencv - 使用已知的相机位置和矩阵查找 3D 对象位置

    spring - 如何在 Spring Data REST 项目中使用 DTO?

    matlab - 如何在 3D 中沿表面法线获得最大强度投影

    opengl - 对数深度缓冲区

    c# - A* 寻路在 3D Minecraft 环境中不起作用

    CSS 3D立方体-webkit-transition

    algorithm - 如何表示/修改 3d 实体

    NHibernate 投影帮助

    c# - SelectListItem 投影的 EFCore SqlException 对值和文本使用相同的属性

    3d - 关于 3D 投影的问题?