为啥我必须除以 Z?

Posted

技术标签:

【中文标题】为啥我必须除以 Z?【英文标题】:Why do I have to divide by Z?为什么我必须除以 Z? 【发布时间】:2014-01-08 22:08:06 【问题描述】:

我需要在 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' 向量只包含一个用于我的测试的元素,因此循环根本不会妨碍。

【问题讨论】:

你能展示一下琐碎的代码吗? 【参考方案1】:

我想通了。正如 David Lively 先生所指出的,

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

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

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

到:

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

它仍然像以前一样工作。

https://***.com/a/10354368/2159051 解释说,除以 w 将在管道中稍后完成。显然,因为我的代码只是将矩阵相乘,所以没有“稍后的管道”。从某种意义上说,我只是走运了,因为我的.z 值与我的.w 值非常接近,因此产生了它正在工作的错觉。

【讨论】:

【参考方案2】:

除以 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,-1) 到 (+1,+1),而不是像素坐标,原点位于视口的中间。不过,通常在这种情况下,您会除以 .w 而不是 .z 以获得有用的东西。

    如果您在 CPU 上执行此操作,您如何将此信息返回给主机?

【讨论】:

1:我同时使用这两种方法,正如您所见,我将 GL_MODELVIEW_MATRIX 和 GL_PROJECTION_MATRIX 组合成一个 glm::mat4 2:我根本没有着色器,我认为我使用的是 gl 1.1?我会尝试除以 w,但它按原样工作,这让我感到困惑。 3:我不明白这个问题 @BWG 如果您发布整个方法,那将非常非常有用。假设它不是 500 行长。这将是另一个问题。 @BWG 好的,乍一看还不错。这可能很愚蠢,但是您如何创建和分配投影矩阵?我可以假设一切都正确绘制吗? 是的,一切都很好。我打电话给glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(75.f, 1280.f/720.f, 0.1f, 500.f);glMatrixMode(GL_MODELVIEW);,然后分配我的相机变换。 我已经弄清楚并回答了我自己的问题。感谢您对问题的洞察力!【参考方案3】:

我猜是因为您要从 3 维到 2 维,所以您正在将 3 维世界标准化为 2 维坐标。

3D 中的 P = (X,Y,Z) 将是 2D 中的 q = (x,y),其中 x=X/Z 和 y = Y/Z

所以 3D 中的圆不会是 2D 中的圆。

您可以观看此视频: https://www.youtube.com/watch?v=fVJeJMWZcq8

希望我能正确理解您的问题。

【讨论】:

你没有错,但这不是问题的本质。我知道除以 Z 是必要的,但我的问题是为什么除以 Z 不能正常工作。在图形管道中,除以 W 是正确的方法。这样就可以将 Z 值以及 X 和 Y 归一化为正确的范围。

以上是关于为啥我必须除以 Z?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能将一个 numpy 数组除以(或乘以)一个标量?

STM32F4 定时器 - 预分频器或周期值必须除以二才能得到我期望的结果

为啥我们需要透视分割?

使用elevateZoom在我的div上有z-index问题,必须是2或3

为啥我必须对注册令牌保密?

为啥我必须初始化一个数组?