由于渲染 3D 图形的工作方式,这个问题有点模糊。
您需要一个参考框架来描述任何职位,这就是事情变得有趣的地方。 OpenGL 有六种不同的坐标空间来处理大多数操作,并且每个坐标空间都有自己独特的特征。
您所讨论的行为仅适用于一个非常特定的坐标空间。即使这样,通过更改您在绘制场景时使用的矩阵,您也可以有效地匹配您想要的任何其他 API 的坐标行为。
让我们考虑一下固定函数 OpenGL 绘图中的传统变换序列。
* Object-space {VertexPos (also RasterPos)}
* World-space (Skipped in GL by operation below)
惯用右手(+X点右,+Y点上,+Z点向后):
* Eye-space [Result of ModelView * VertexPos]
惯用左手(+X点右,+Y点上,+Z点前锋):
* Clip-space [Result of Projection * EyePos]
* Normalized Device-space [Result of ClipPos / ClipPos.w]
* Window-space [Result of Viewport and Depth Range mapping]
如您所见,GL 按照惯例从右手坐标系开始,然后传统的投影矩阵翻转 Z 轴以在投影后生成左手坐标空间。假设您不翻转任何其他轴,那么这种惯用手改变真正做的唯一一件事就是将所有东西放在相机的正确一侧(诸如缠绕方向之类的东西实际上不受影响)。投影矩阵是一个非常强大的工具。
在这些坐标空间中,NDC 是我们讨论的最重要的部分。它具有 Z 轴,因此 +Z 指向 屏幕。它还将 (-1,-1,...) 定义为左下角的一个点,将 (1,1,...) 定义为视口右上角的一个点。所有这些一起使 NDC 空间左撇子,这需要投影矩阵在 GL 中做一些额外的工作。在这个坐标空间中,你绝对可以说正 Y 轴指向上方。但是,正如您刚刚看到的,可以通过摆弄 Projection 或 ModelView 矩阵来改变您的图像在 NDC 空间中的表示方式,并且在 NDC 之前位于坐标空间中的点不一定同意Y 轴的方向。
最后,由于您正在讨论使用坐标 (0,0) 来描述窗口左下角的情况,我们知道您不是在讨论NDC 空间(在 NDC 中左下角是 (-1,-1) 并且该点相对于视口而不是窗口)。给定正确的变换矩阵,您可以描述其他 5 个矩阵中的任何一个。然而,唯一一次 保证 点 (0,0) 是窗口左下角的正数当您使用窗口空间坐标时,OpenGL 中的 Y 轴指向上方。
我为什么要费心引导您完成 OpenGL 的转换?
因为大多数时候,当您给 OpenGL 一个位置(无论是顶点还是光栅)时,它将在 object-space 中定义,并且必须经过所有那些转变。在 NDC->窗口映射之前,您有很多机会重新定义投影图像的原点。
Window-space 和 NDC-space 的正 Y 轴总是朝上,但是因为您用于实际绘制事物的位置是在 object-space 中定义的,您只需将场景倒置即可解决此问题。那么窗口空间坐标 (0,0) 实际上将对应于渲染图像的左上角而不是左下角。
当然,如果您尝试以这种方式显示,图像也会出现颠倒。为了克服这个问题,您可以绘制到 FBO,然后将 FBO 附加的颜色缓冲区也颠倒到您的窗口中。 Wine 项目具有执行此操作的代码,以补偿 D3D 的原始约定,其中 (0,0) = 左上角。在大多数情况下,这比它更值得的麻烦,但如果您必须与其他不兼容的 API 兼容,它会派上用场。
您可以对所有 OpenGL 坐标执行此操作,包括纹理坐标。图像的约定也使得 (0,0) 表示左下角,但没有理由不能首先使用矩阵转换 输入 坐标。固定功能 GL 已经使用按纹理单元存储的纹理矩阵(默认为同一性)来执行此操作。
TL;DR:使用 (0,0) 表示窗口空间左下角的一个有趣的点是,视口可以很容易地从 NDC 映射到窗口-空间而不翻转 Y 轴。 D3D 的视口变换必须这样做,因为 OpenGL 和 D3D 实际上都同意(令人震惊,正确)NDC 是左撇子(他们不同意 NDC 中 Z 坐标的范围或其他尽管)。在 D3D 中,(-1,-1,...) 在 NDC 中仍然是视口的左下角,但它变成了 (ViewportX,ViewportY + 视口高度)在窗口空间中。
我个人喜欢剪裁后的所有内容都同意坐标轴的大致方向,不管它是否提供了任何历史性能优势。