为什么我必须除以Z?

时间:2014-01-08 22:08:07

标签: c++ opengl matrix-multiplication

我需要在3D环境中实现“选择对象”。因此,我决定采用简单的方法,而不是采用强大,准确的方法,例如光线投射。首先,我将对象世界位置转换为屏幕坐标:

glm::mat4 modelView, projection, accum;
glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat*)&projection);
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&modelView);
accum = projection * modelView;
glm::mat4 transformed = accum * glm::vec4(objectLocation, 1);

接下来是一些简单的代码,将opengl坐标系转换为普通窗口坐标,并与鼠标检查做一个简单的距离。 但是不太有效。为了从世界空间转换到屏幕空间,我需要在上面显示的函数末尾添加一个计算:

transformed.x /= transformed.z;
transformed.y /= transformed.z;

我不明白我为什么要这样做。我的印象是,一旦你的顶点乘以累积的modelViewProjection矩阵,你就得到了你的屏幕坐标。但我必须除以Z才能让它正常工作。在我的openGL 3.3着色器中,我永远不必除以Z.为什么会这样?

编辑:从opengl坐标系转换为屏幕坐标的代码如下:

int screenX = (int)((trans.x + 1.f)*640.f); //640 = 1280/2
int screenY = (int)((-trans.y + 1.f)*360.f); //360 = 720/2

然后我通过执行以下操作测试鼠标是否接近该点:

float length = glm::distance(glm::vec2(screenX, screenY), glm::vec2(mouseX, mouseY));
if(length < 50) {//you can guess the rest

编辑#2
在鼠标单击事件时调用此方法:

glm::mat4 modelView;
glm::mat4 projection;
glm::mat4 accum;
glGetFloatv(GL_PROJECTION_MATRIX, (GLfloat*)&projection);
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&modelView);
accum = projection * modelView;
float nearestDistance = 1000.f;
gameObject* nearest = NULL;
for(uint i = 0; i < objects.size(); i++) {
    gameObject* o = objects[i];
    o->selected = false;
    glm::vec4 trans = accum * glm::vec4(o->location,1);
    trans.x /= trans.z;
    trans.y /= trans.z;
    int clipX = (int)((trans.x+1.f)*640.f);
    int clipY = (int)((-trans.y+1.f)*360.f);
    float length = glm::distance(glm::vec2(clipX,clipY), glm::vec2(mouseX, mouseY));
    if(length<50) {
        nearestDistance = trans.z;
        nearest = o;
    }
}
if(nearest) {
    nearest->selected = true;
}

mouseRightPressed = true;

整个代码不完整,但与我的问题相关的部分工作正常。 'objects'向量只包含一个用于我的测试的元素,因此循环根本不会妨碍。

2 个答案:

答案 0 :(得分:3)

Z分频步骤有效地应用了透视变换。没有它,你就有了一个iso视图。想象一下两个视空间顶点:A(-1,0,1)B(-1,0,100)

没有除以Z步,屏幕坐标等于(-1,0)

使用除以Z,它们是不同的:A(-1,0)B(-0.01,0)。因此,远离视空间原点(相机)的东西在屏幕空间中比距离更近的东西更小。 IE,透视。

那说:如果您的投影矩阵(和矩阵乘法码)是正确的,那么这应该已经发生,因为投影矩阵将包含1/Z缩放组件来执行此操作。所以,有些问题:

  1. 你真的在使用投影变换的输出,还是只使用视图变换?

  2. 您是在像素/片段着色器中执行此操作吗?屏幕坐标有标准化(-1,-1)到(+ 1,+ 1),而不是像素坐标,原点位于视口的中间。通常在这种情况下,您除以.w而不是.z来获得有用的东西。

  3. 如果您在CPU上执行此操作,如何将此信息反馈给主机?

答案 1 :(得分:2)

我已经弄清楚了。正如David Lively先生指出的那样,

  

通常在这种情况下,您除以.w而不是.z来获得有用的东西。

我的.w值非常接近我的.z值,所以在我的代码中我更改了语句:

transformed.x /= transformed.z;
transformed.y /= transformed.z;

为:

transformed.x /= transformed.w;
transformed.y /= transformed.w;

它仍然像以前一样工作。

https://stackoverflow.com/a/10354368/2159051解释说,w的划分将在管道中稍后完成。显然,因为我的代码只是将矩阵相乘,所以没有'后来的管道'。从某种意义上说,我只是变得幸运,因为我的.z值非常接近我的.w值,因此有一种错觉认为它正在发挥作用。