在 python 中手动投影类似于 gluLookAt 的坐标

Manually project coordinates similar to gluLookAt in python

我正在尝试实现观察矩阵和投影,类似于 gluLookAt 来获取每个 3D 坐标的观察位置。我已经实现了一些看起来接近工作但被逆转的东西。 例如 - 下面的代码得到了正确的位置(当我实际上不改变坐标时。但是如果我改变向上矢量指向 X 而不是 Y,我得到颠倒的坐标。

import numpy as np

def normalize_vector(vector):
    return vector / (np.linalg.norm(vector))

def get_lookat_matrix(position_vector, front_vector, up_vector):
    m1 = np.zeros([4, 4], dtype=np.float32)
    m2 = np.zeros([4, 4], dtype=np.float32)

    z = normalize_vector(-front_vector)
    x = normalize_vector(np.cross(up_vector, z))
    y = np.cross(z, x)

    m1[:3, 0] = x
    m1[:3, 1] = y
    m1[:3, 2] = z
    m1[3, 3] = 1.0

    m2[0, 0] = m2[1, 1] = m2[2, 2] = 1.0
    m2[:3, 3] = -position_vector
    m2[3, 3] = 1.0

    return np.matmul(m1, m2)

def get_projection_matrix(near, far):
    aspect = 1.0
    fov = 1.0  # 90 Degrees
    m = np.zeros([4, 4], dtype=np.float32)

    m[0, 0] = fov/aspect
    m[1, 1] = fov
    m[2, 2] = (-far)/(far-near)
    m[2, 3] = (-near*far)/(far-near)
    m[3, 2] = -1.0
    return m

position_vector = np.array([0, 0, 0], dtype=np.float32)
front_vector = np.array([0, 0, -1], dtype=np.float32)
up_vector = np.array([0, 1, 0], dtype=np.float32)

viewing_matrix = get_lookat_matrix(position_vector=position_vector, front_vector=front_vector, up_vector=up_vector)
print("viewing_matrix\n", viewing_matrix, "\n\n")
projection_matrix = get_projection_matrix(near=0.1, far=100.0)
point = np.array([1, 0, -10, 1], dtype=np.float32)

projected_point = projection_matrix.dot(viewing_matrix.dot(point))
# Normalize
projected_point /= projected_point[3]
print(projected_point)

它发生在坐标的许多变化中。我不确定我哪里错了。

gluLookAt定义了一个4*4的观察变换矩阵,供OpenGL使用。

一个"mathematical" 4*4 矩阵如下所示:

  c0  c1  c2  c3            c0  c1  c2  c3
[ Xx  Yx  Zx  Tx ]        [  0   4   8  12 ]     
[ Xy  Yy  Zy  Ty ]        [  1   5   9  13 ]     
[ Xz  Yz  Zz  Tz ]        [  2   6  10  14 ]     
[  0   0   0   1 ]        [  3   7  11  15 ] 

但是一个4*4的OpenGL矩阵的内存图像是这样的:

[ Xx, Xy, Xz, 0, Yx, Yy, Yz, 0, Zx, Zy, Zz, 0, Tx, Ty, Tz, 1 ]

The OpenGL Shading Language 4.6, 5.4.2 Vector and Matrix Constructors, page 101
OpenGL ES Shading Language 3.20 Specification, 5.4.2 Vector and Matrix Constructors, page 100:

To initialize a matrix by specifying vectors or scalars, the components are assigned to the matrix elements in column-major order.

mat4(float, float, float, float,  // first column
     float, float, float, float,  // second column
     float, float, float, float,  // third column
     float, float, float, float); // fourth column

请注意,与从上到下书写列的数学矩阵相比,感觉自然,在 OpenGL 矩阵的初始化时,列是从左向右书写的。这带来了好处,即轴或平移的 x、y、z 分量在内存中直接连续。这在访问轴向量或矩阵的平移向量时是一个很大的优势。
另见 Data Type (GLSL) - Matrix constructors

这意味着您必须 "swap" 矩阵的列和行(转置):

def get_lookat_matrix(position_vector, front_vector, up_vector):
    m1 = np.zeros([4, 4], dtype=np.float32)
    m2 = np.zeros([4, 4], dtype=np.float32)

    z = normalize_vector(-front_vector)
    x = normalize_vector(np.cross(up_vector, z))
    y = np.cross(z, x)

    m1[0, :3] = x
    m1[1, :3] = y
    m1[2, :3] = z
    m1[3, 3] = 1.0

    m2[0, 0] = m2[1, 1] = m2[2, 2] = 1.0
    m2[3, :3] = -position_vector
    m2[3, 3] = 1.0

    return np.matmul(m1, m2)

def get_projection_matrix(near, far):
    aspect = 1.0
    fov = 1.0  # 90 Degrees
    m = np.zeros([4, 4], dtype=np.float32)

    m[0, 0] = fov/aspect
    m[1, 1] = fov
    m[2, 2] = (-far+near)/(far-near)
    m[3, 2] = (-2.0*near*far)/(far-near)
    m[2, 3] = -1.0
    return m

您必须做一个小改动:

m[2, 2] = -(far+near)/(far-near)       //instead of m[2, 2] = (-far)/(far-near)
m[2, 3] = (-2.0*near*far)/(far-near)   //instead of m[2, 3] = (-near*far)/(far-near)

重要的是矩阵的 row/column 顺序。

正如@Rabbid76 所指出的,市长专栏顺序是首选。 GLSL 提供了一个转置矩阵的函数。您还可以在使用 glUniformMatrix 系列命令将矩阵传递给 GPU 时转置矩阵。

让我们看看如何像您的代码那样使用行长阶矩阵。

目前 CPU 的目标是获得:finalPoint = matrixMultiply(C, P)C 组合矩阵以及 P 点坐标。 matrixMultiply 是你用来做矩阵乘法的任何函数。记住顺序很重要,A·B 和 B·A 不一样

因为C是4x4矩阵而P是1x4,所以C·P是不可能的,一定是P·C。 请注意,使用列顺序 P 是 4x1,然后 C·P 是正确的操作。

我们称L观察矩阵(正确名称是观察矩阵)。它由方向矩阵 O 和平移矩阵 T 组成。列顺序为 L= O·T.

A属性的转置矩阵为(A·B)t = Bt·At

所以,按行顺序你得到 O·T = Oct · Tct = (Tc · Oc)t 其中 c 用于列顺序。嘿!我们想要的是 (Oc · Tc)t 注意到乘法顺序的变化了吗?

因此,如果您使用行长阶矩阵,它们相乘的顺序将被交换。
视图和投影组合矩阵也必须交换。

因此替换:

return np.matmul(m2, m1)   //was return np.matmul(m1, m2)

//was projected_point = projection_matrix.dot(viewing_matrix.dot(point))
projected_point = point.dot(viewing_matrix.dot(projection_matrix))

尽管有上述所有情况,我还是建议使用专栏市长命令。这对 OpenGL 来说是最好的。而且您会更好地理解您在 OpenGL 上找到的任何数学和教程。