加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

OpenGLNDC:左手还是右手?

(2023-03-27 12:26:23)
标签:

it

分类: 技术
之前看到一篇文章说默认情况下 OpenGL NDC 是基于左手坐标系。我在我之前的博文中也提到过。之后我在想为什么 OpenGL NDC 是基于左手坐标系?真的是基于左手坐标系吗?所以这篇博客就是来找到答案。

再谈 OpenGL 坐标变换(Coordinate Transformation)
红宝书中第五章很清楚的描述了坐标变换,这里的图示也是引用那里,具体内容可以查看红宝书,我在这里仅简单提一下。

在坐标系中指定坐标点,将坐标点连接起来绘制几何形状。绘制复杂的场景时,不同的物体,静态的或是动态的,必然涉及到坐标变换。如下图,用户指定物体的坐标,然后在 user transform 阶段变换物体,之后就由 OpenGl 进行 clipping 和 viewport transform 操作,然后到了 Opengl 管线中光栅化阶段,最终由片段着色器(fragment shader)输出片段颜色,显示在屏幕上。

Coordinate System
现代 OpenGL 特地有给用户预留 user transform 这一些阶段。在这一阶段用户自己根据需求进行 scale 、rotate 、translate 、project (缩放,旋转,平移,投影)等操作。如下图在 user transform 中就可以看见我们熟悉的 MVP 变换。MVP 变换分别对应着 model matrix 、view matrix 、projection matrix (model 矩阵,view 矩阵,投影矩阵)。假设只用到顶点着色器(vertex shader)和片段着色器(fragment shader)。在顶点着色器中为 gl_Position 赋值后,user transform 就结束了,接下来 OpenGL 根据输入的顶点,最终在屏幕上绘制出形状。

其中通常用 model matrix 把物体变换到 world coordinate space (世界坐标空间)。通常用 view matrix 再世界坐标空间中的物体变换到 eye/camera coordinate space (眼睛坐标空间或摄像机坐标空间)中。物体间的交互通常在世界坐标空间中计算,这是所有物体共用的坐标空间。而有时会根据需要在摄像机坐标空间中进行计算,比如光照计算。在世界坐标空间中 ,(0, 0, 0) 是坐标系原点。而摄像机坐标空间中,摄像机所在位置则是坐标系的原点位置。

User Transforms

视口变换(Viewport Transformation)
如上图,顶点坐标经过 perspective division(OpenGL divide by w)后,要在标准化设备坐标空间(Normalized Device CoordinatesNDC)中对顶点进行剪裁(clipping)。NDC 坐标空间范围在 X 轴,Y 轴和 Z 轴都是 [-1, 1] 。处于 NDC 范围外的顶点会被忽略掉
经过剪裁,余下的顶点是如何显示在窗口上的呢?这时就涉及到了视口变换。glViewport 和 glDepthRange 用于控制视口变换。glViewport 用于将 NDC 中的 x 和 y 坐标变换到窗口坐标空间中(window coordinate)。窗口坐标 x 表示水平方向的像素,y 表示竖直方向的像素。而 glDepthRange 则把 NDC 的 z 坐标映射到窗口深度坐标(window depth)窗口深度坐标的范围是 [0, 1]

需要调用 glEnable 启用 GL_DEPTH_TEST 深度测试,glDepthRange 设置才会起作用,毕竟默认的渲染顺序是后面的绘制的像素覆盖前面的绘制的像素。开启深度测试后,不同深度值显示的先后顺序则是通过 glDepthFunc 设置。

介绍了坐标变换和视口变换后后,你应该对于物体从你指定坐标到最终显示在屏幕上经历了哪些变换,有了初步的认识。下面就通过问题和小例子来感受下。

问题,NDC Z 轴范围真的是 [-1, 1] ?
蓝宝书和红宝书都提到 NDC Z 轴范围是从 [0, 1] ,但是其他资料文章都是写着 [-1, 1] 。不知道为什么蓝宝书和红宝书出了这么多版本后未修改,应该是在的角度不同吧。先不管这些文字上给的范围,经过上面的介绍,我们完全能计算出 Z 轴的实际范围,于是来在代码中验证吧。

代码中忽略了 MV 变换,仅仅进行投影变换,并且在透视投影和正交投影中分别计算 Z 轴范围。要计算 Z 轴范围,就是在近平面(near plane)和远平面(far plane)各取一点,进行投影变换和透视除法,这时得到的坐标的 z 值就是实际 Z 轴的范围。

注意,默认情况下 glm::perspective 和 glm::ortho 函数中指定的近平面和远平面距离是基于右手坐标系来实现的,并且距离是到原点 (0, 0, 0) 的距离(glm::perspective 有左手坐标系实现)。

我们的代码中采用的是默认方式。具体代码如下。
void
test_perspective_proj() {
    float neardist = 0.1f, fardist = 100.0f;
    glm::vec4 nearpoint(0.0f, 0.0f, -neardist, 1.0f);
    glm::vec4 farpoint(0.0f, 0.0f, -fardist, 1.0f);
    glm::mat4 persmat = glm::perspective(glm::radians(45.0f), 4.0f/3.0f, neardist, fardist);
    
    glm::vec4 nearpoint_clip = persmat * nearpoint;
    glm::vec4 farpoint_clip = persmat * farpoint;
    nearpoint_clip /= nearpoint_clip.w;
    farpoint_clip /= farpoint_clip.w;

    glm::vec3 ndc_near(nearpoint_clip);
    glm::vec3 ndc_far(farpoint_clip);
    printf("one point in near plane:

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有