投影 3D 指向 2D 屏幕坐标



我正在尝试将几个 3d 点投影到屏幕坐标上,以确定触摸是否发生在大致相同的区域。应该注意的是,我在Kivy中这样做,这是Python和OpenGL。我见过这样的问题,但我仍然没有解决方案。我已经尝试了以下内容,但数字与屏幕坐标不接近。

def to2D(self, pos, width, height, modelview, projection):
    p = modelview*[pos[0], pos[1], pos[2], 0])
    p = projection*p
    a = p[0]
    b = p[1]
    c = p[2]
    a /= c
    b /= c
    a = (a+1)*width/2.
    b = (b+1)*height/2.
    return (a, b)

为了说明这不会产生良好的结果,请采用以下参数

modelview = [[-0.831470, 0.553001, 0.053372, 0.000000],
             [0.000000, 0.096068, -0.995375, 0.000000],
             [-0.555570, -0.827624, -0.079878, 0.000000],
             [-0.000000, -0.772988, -2.898705, 1.000000]]
projection = [[ 15.763722, 0.000000, 0.000000, 0.000000],
              [ 0.000000, 15.257052, 0.000000, 0.000000],
              [ 0.000000, 0.000000, -1.002002, -2.002002],
              [ 0.000000, 0.000000, -1.000000, 0.000000]]
pos = [0.523355213060808, -0.528964010275341, -0.668054187020413] #I'm working on a unit sphere, so these are more meaningful in spherical coordinates
width = 800
height = 600

使用这些参数,to2D给出的屏幕坐标为 (1383, -274)

我认为问题与OpenGL和python无关,而是与从3D到屏幕坐标所涉及的操作有关。我想做什么:发生触摸时,将 3d 点投影到 2d 屏幕坐标。

我的想法:获取相机的模型视图和投影矩阵、我感兴趣的点和触摸位置,然后制定从该点到触摸位置的方法。通过将gluProject的源代码转换为Python来获取该方法

我是如何做到的:

  1. 将所有数学对象放入 Sage 中,以实现计算简单性。

  2. 我的触摸位置是 (150, 114.1)

  3. modelview = matrix([[ -0.862734, 0.503319, 0.048577, 0.000000 ], [ 0.000000, 0.096068, -0.995375, 0.000000 ], [ -0.505657, -0.858744, -0.082881, 0.000000 ], [ 0.000000, -0.772988, -2.898705, 1.000000 ]])

  4. projection = matrix([[ 15.763722, 0.000000, 0.000000, 0.000000 ], [ 0.000000, 15.257052, 0.000000, 0.000000 ], [ 0.000000, 0.000000, -1.002002, -2.002002 ], [ 0.000000, 0.000000, -1.000000, 0.000000 ]])

  5. width = 800.

  6. height = 600.

  7. v4 = vector(QQ, [0.52324, -0.65021, -0.55086, 1.])

  8. p = modelview*v4

  9. p = projection*p

  10. x = p[0] y = p[1] z = p[2] w = p[3]

  11. x /= w y /= w z /= w

  12. x = x*0.5 + 0.5 y = y*0.5 + 0.5 z = z*0.5 + 0.5

  13. x = x*width y = y*height #There's no term added because the widget is located at (0, 0)

结果:

x = 15362.18
y = -6251.43
z = 10.14

修订:由于这甚至还没有接近,我回到步骤 8 和 9 并切换乘法顺序以查看会发生什么。所以现在8。为 p = v4*modelview 和 9。是p = p*projection.在这种情况下,向量是行向量。另一种看待这个问题的方法是p = modelviewTranspose*v4p = projectionTranspose*p,其中向量是列向量。

结果第 2 部分:

x = 150.29
y = 196.15
z = 0.6357

回想一下,目标是(150,114.1)。x 坐标非常好,但 y 坐标不是。所以我看了看y*z,也就是124.69。我可以接受这个答案,尽管我不确定看y*z是否是我真正应该做的

第一个问题在这里:

p = modelview*[pos[0], pos[1], pos[2], 0])

当您以矩阵为4分量向量的多个向量时,最后一个分量(w必须是1.0

另一个在这里:

c = p[2]
a /= c
b /= c
与其将 x 和 y 除以 z

,不如将 x、y 和 z 除以 w.w 是 p[4]。

除此之外:

如有疑问,请找到gluProjectgluUnproject的源代码,将其拆开并转换为python。

据我所知,手动将矢量投影到屏幕时,您应该执行以下操作:

  1. 将"位置"转换为 4 个分量向量,将 .w 分量设置为 1。

    v4.x = v3.x
    v4.y = v3.y
    v4.z = v3.z
    v4.w = 1.0
    
  2. 将 4 分量乘以矩阵。

  3. 然后将所有分量除以 w。

    v4.x /= v4.w
    v4.y /= v4.w
    v4.z /= v4.w
    

然后,您将获得 x 和 y 的 +-1.0 范围内的屏幕坐标(z 将在 0.0..1.0 或 0.0...-1.0 范围内,我忘记了在 OpenGL 的情况下哪个)。

w之所以发挥作用,是因为你不能通过矩阵乘法除法,

所以当你需要将x/y/z除以某物时,你把它放入w分量中,在所有矩阵乘法之后执行除法。任何 w == 0 的向量都不能使用平移矩阵进行平移,只能围绕原点旋转并用仿射变换变形("原点"表示坐标空间的零点 - (

0.0, 0.0, 0.0) 点)

附言另外,我不知道 python 如何处理整数到浮点转换,但我会用 a = (a+1.0)*width/2.0 替换a = (a+1)*width/2以明确指定您在此处使用浮点数。

如果你有权访问它,pyopengl 的gluProject(x, y, z)返回当前上下文的屏幕空间 x、y、z 坐标。(其他实现可能需要更多参数和设置指针,而不是返回!

最新更新