如何应用变换矩阵?

发布于 2024-07-19 19:34:02 字数 1672 浏览 4 评论 0原文

我试图获取 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?
} }

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

非常感谢任何帮助!

I am trying to get the 2D screen coordinates of a point in 3D space, i.e. I know the location of the camera its pan, tilt and roll and I have the 3D x,y,z coordinates of a point I wish to project.

I am having difficulty understanding transformation/projection matrices and I was hoping some intelligent people here could help me along ;)

Here is my test code I have thrown together thus far:

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

Basically, what I need is to get the 2D XY coordinates for a given screen where the 3D point intersects. I am not sure how to use the roll, tilt and pan matrices to transform the world point (wp).

Any help with this is greatly appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

夏天碎花小短裙 2024-07-26 19:34:02

这是很复杂的事情。 请阅读一本有关该主题的书,以了解所有数学和基本细节。 如果你打算长期使用这些东西,你需要了解这些事情。 这个答案只是为了让你可以先实践一下。

矩阵相乘

首先要做的事情。 矩阵相乘是一件相当简单的事情

假设您有矩阵 ABC,其中 AB = C >。 假设您想计算出矩阵 C 第 3 行第 2 列的值。

  • A 的第三行和 B 的第二列嗯>。 现在,AB 中的值数量应该相同。 (如果你没有为这两个矩阵定义矩阵乘法。你不能这样做。)如果两个都是 4×4 矩阵,你应该有来自 A 的 4 个值(第 3 行) )和来自 B 的 4 个值(第 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)。 这些是笛卡尔坐标。 我们称它们为(xyz)。

齐次坐标意味着您在笛卡尔坐标末尾额外写入 1。 (5,2,1) 变为 (5,2,1,1)。 我们称它们为(xyzw)。

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

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


转变一点

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

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

当您想要使用变换矩阵来变换点时,可以将该矩阵与表示该点的列向量右乘。 假设您想通过某个变换矩阵 A 来平移 (5, 2, 1)。 您首先定义v = [5, 2, 1, 1]T。 (我用 [xyzw]T小 T 表示应该将其写为列向量。)

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

在这种情况下,Av = v 1,其中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

AfinalI 4
A最终A最终 A1< br>
A最终A最终 A2< br>
A最终A最终 A3< br>

请注意,我使用该箭头来表示赋值。 当您实现此操作时,请确保在矩阵乘法计算中仍在使用 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 = v 1

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

从开始到结束

相机变换应该表示为视图矩阵。 在此执行 Aview v = v1 操作。 (v 将您的世界坐标表示为 4×1 列向量,Afinal 是您的 A 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 = [xyzw ]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。 前两个坐标是您的 xy 坐标。 第三个是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 矩阵将对象坐标转换为眼睛坐标

从那时起,一切都是一样的。

This is complicated stuff. Please read a book about this topic to get all the math and nitty gritty details. If you plan on playing with this stuff at length, you need to know these things. This answer is just so you can get your feet wet and hack around.

Multiplying matrices

First things first. Multiplying matrices is a reasonably simple affair.

Let's say you have matrices A, B, and C, where AB = C. Let's say you want to figure out the value of matrix C at row 3, column 2.

  • Take the third row of A and the second column of B. You should have the same number of values from A and B now. (If you don't matrix multiplication isn't defined for those two matrices. You can't do it.) If both are 4×4 matrices, you should have 4 values from A (row 3) and 4 values from B (column 2).
  • Multiply each value of A with each value of B. You should end up with 4 new values.
  • Add these values.

You now have the value of matrix C at row 3, column 2. The challenge is, of course, to do this programmatically.

/* 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];
            }
        }
    }
}

Homogenous coordinates

You have 3D coordinates. Let's say you have (5, 2, 1). These are Cartesian coordinates. Let's call them (x, y, z).

Homogenous coordinates mean that you write an extra 1 at the end of your Cartesian coordinates. (5, 2, 1) becomes (5, 2, 1, 1). Let's call them (x, y, z, w).

Whenever you do a transformation that makes w ≠ 1, you divide every component of your coordinates by w. This changes your x, y, and z, and it makes w = 1 again. (There is no harm in doing this even when your transformation doesn't change w. It just divides everything by 1, which does nothing.)

There is some majorly cool stuff you can do with homogenous coordinates, even if the math behind them doesn't make total sense. It is at this point that I ask you to look again at the advice at the top of this answer.


Transforming a point

I'll be using OpenGL terminology and approaches in this and following sections. If anything is unclear or seems to conflict with your goals (because this seems vaguely homework-like to me :P), please leave a comment.

I'll also start by assuming that your roll, tilt, and pan matrices are correct.

When you want to transform a point using a transformation matrix, you right-multiply that matrix with a column vector representing your point. Say you want to translate (5, 2, 1) by some transformation matrix A. You first define v = [5, 2, 1, 1]T. (I write [x, y, z, w]T with the little T to mean that you should write it as a column vector.)

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

In this case, Av = v1, where v1 is your transformed point. Do this multiplication like a matrix multiplication, where A is 4×4 and v is 4×1. You will end up with a 4×1 matrix (which is another column vector).

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

Now, if you have several transformation matrices to apply, first combine them into one transformation matrix. Do this by multiplying the matrices together in the order that you want them applied.

Programmatically, you should start with the identity matrix and right-multiply each transformation matrix. Let I4 be 4×4 identity matrix, and let A1, A2, A3, ... be your transformation matrices. Let your final transformation matrix be Afinal

AfinalI4
AfinalAfinal A1
AfinalAfinal A2
AfinalAfinal A3

Note that I'm using that arrow to represent assignment. When you implement this, make sure not to overwrite Afinal while you're still using it in the matrix multiplication calculation! Make a copy.

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

Finally, do the same multiplication as above: Afinal v = v1

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

From start to finish

Camera transformations should be represented as a view matrix. Perform your Aview v = v1 operation here. (v represents your world coordinates as a 4×1 column vector, Afinal is your Aview.)

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

Projection transformations describe a perspective transform. This is what makes nearer objects bigger and farther objects smaller. This is performed after the camera transformation. If you don't want perspective yet, just use the identity matrix for the projection matrix. Anyway, perform A v1 = v2 here.

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

Next, you need to do a perspective divide. This delves deeper into homogenous coordinates, which I haven't described yet. Anyway, divide every component of v2 by the last component of v2. If v2 = [x, y, z, w]T, then divide each component by w (including w itself). You should end up with w = 1. (If your projection matrix is the identity matrix, like I described earlier, this step should do nothing.)

// 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];
}

Finally, take your v2. The first two coordinates are your x and y coordinates. The third is z, which you can throw away. (Later, once you get very advanced, you can use this z value to figure out which point is in front of or behind some other point.) And at this point, the last component is w = 1, so you don't need that at all anymore.

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

If you skipped the perspective and perspective divide steps, use v_view instead of v_ndc above.

This is very similar to the set of OpenGL coordinate systems. The difference is that you start with world coordinates, while OpenGL starts with object coordinates. The difference is as follows:

  • You start with world coordinates
    • OpenGL starts with object coordinates
  • You use the view matrix to transform world coordinates to eye coordinates
    • OpenGL uses the ModelView matrix to transform object coordinates to eye coordinates

From there on, everything is the same.

梦情居士 2024-07-26 19:34:02

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

The scope of this is way too large to get a good answer here: I'd recommend reading a good reference on the topic. I've always liked the Foley and VanDam...

夏至、离别 2024-07-26 19:34:02

我在此处发布了一些代码,可以满足您的大部分需求。

它包含 OpenGL gluPerspective()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;
    ...
}

:返回的 code>x 和 y 值在 -0.5 ... 0.5 范围内

I've posted some code here that does much of what you need.

It contains Java implementations of the OpenGL gluPerspective() and gluLookAt() functions:

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

To use the project() function therein, use:

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

The x and y values returned fall in the range -0.5 ... 0.5

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文