【问题标题】:Why is origin usually top left corner in painting APIs when logically and native GL is at bottom left corner?为什么在逻辑上和原生 GL 位于左下角时,绘制 API 的原点通常位于左上角?
【发布时间】:2014-05-27 06:54:31
【问题描述】:

我注意到许多绘图 API 的 0,0 原点位于左上角,因此 y 实际上会随着它的增加而下降。我想知道这是为什么?不在我个人认为更合乎逻辑的左下角(常规 x/y 网格的原点)工作有什么特别的优势,这也恰好是硬件渲染 API 中坐标的本机表示?

或者它可能与扫描线渲染甚至显示刷新的方式有关?

【问题讨论】:

    标签: opengl graphics coordinates coordinate-systems representation


    【解决方案1】:

    由于渲染 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 + 视口高度)在窗口空间中。

    我个人喜欢剪裁后的所有内容都同意坐标轴的大致方向,不管它是否提供了任何历史性能优势。

    【讨论】:

      【解决方案2】:

      CRT 扫描早于计算机。为什么那些第一批电视工程师选择从左上角开始我不确定。可能是不考虑数学用途和成为从左上角开始的英语读者/作家的结合。

      因此,当计算机开始连接到 CRT 显示器时,使其工作的最简单方法是将帧缓冲存储器的起始点放在帧开始的左上角。

      将坐标原点 (0, 0) 放在左上角可以简化坐标到内存地址的计算。在 C 中,每个像素 1 个字节,它会是

      char * pixel  = (y * rowBytes) + x;
      

      其中 rowBytes 是水平像素数(包括任何填充字节以获得更好的对齐)。

      如果 rowBytes 是 2 的幂,或者说两个 POT 的总和(例如 y * 320 = (y * 256) + (y * 64)),那么您可以只用整数移位来进行计算。

      如果原点在左下角,则需要对 Y 进行额外的减法。今天没人关心,但当 CPU 速度以 Mhz 为单位测量时,这是一个显着的性能回击。

      【讨论】:

        【解决方案3】:

        具有左上角原点、向右 x 轴和向下 y 轴的 2D 光栅绘图很可能反映了 CRT 扫描模式。以这种方式组织的帧缓冲区(行主要像素从上到下)可以线性顺序馈送到 RAMDAC,从而为 CRT 生成信号。其他组织会要求图形系统执行更多的算术运算,但实际上并没有什么好处。

        【讨论】:

          【解决方案4】:

          由于历史原因,我认为是这样(从左上角开始)。在旧的文本屏幕上,屏幕上的第一个位置将是 0,0,因为您从左上角开始书写。然后在实现图形时,他们就保持这样。

          【讨论】:

          • 嗯,从历史上看,网格已经以很多方式旋转,例如,很多文化都是从右到左甚至从上到下书写。如果你问数学家,原点的变换甚至都无关紧要,我怀疑是数学家参与了建立计算机图形学,所以他们没有逻辑符合乔对空间的普通理解。此外,回到基础学校,我们了解到在 +/-n 的 2d 网格中,如果我们放大表示从 0 到最大 x/y 范围的部分,原点将在左下角,没有网格变换
          • @user2341104:在这种情况下,早期计算机文本模式的基向量设置优先级。这种扫描模式反映了英文文本的行/列,因为这是发明和开发计算机的文化。
          猜你喜欢
          • 2016-10-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多