计算机图形学(OPENGL):坐标系

本文同时发布在我的个人博客上:https://dragon_boy.gitee.io

坐标系

  在之前的章节我们学习了使用矩阵变换来操作顶点。在OpenGL中,要求所有的可见顶点的坐标在通过顶点着色器阶段后都是标准化设备坐标。我们的做法往往是自定义一片坐标空间,然后进行标准化操作。但在将顶点坐标标准化之前,我们往往会先将坐标转化为其它的一些中间坐标系的坐标,因为在进行一些特定操作的时候,在特定的坐标空间下会非常方便。这里列出5种重要的坐标系统:

  • 局部空间(local space or object space)
  • 世界空间(world space)
  • 视图空间(view space or eye space)
  • 切割空间(clip space)
  • 屏幕空间(screen space)

流程

  为了将坐标从一个空间转化到另一个空间,我们使用model,view,projection三个矩阵。我们的顶点坐标首先定义在局部空间,称为局部坐标,之后转到世界坐标,视图坐标,切割坐标,最终结束在屏幕坐标。下图是这段流程的介绍:


  1. 一个物体以自己的局部坐标作为开始。
  2. 经过model矩阵变换,获取在世界空间中的坐标。
  3. 接着通过view矩阵,获取视图空间,即摄像机视角的坐标。
  4. 然后通过projection矩阵,投影变换,获得在切割空间的坐标,如果不在-1到1范围内的点将被舍弃。
  5. 最后通过ViewPort中定义的分辨率参数将-1到1的坐标进行转换,最终的坐标将输入到光栅化阶段进行片元操作。
      就像之前说的,定义这些流程操作是因为在特定的空间进行某些操作会很方便。当然我们也可以自定义矩阵来进行任意的空间转换,只是可能缺少灵活性。

局部空间

  想象一下自己在类似于maya的三维建模软件中建立一个单独的模型,坐标往往在原点位置,这里就类似与局部空间坐标,之后我们就会进行场景摆放的时候就会放在模型应在的位置。假设所有建立的模型初始都在原点,那么模型就在局部空间。

世界空间

  在制作动画或游戏时,我们需要将特定的模型先放在特定的位置,这里的位置就不一定是局部空间的原点了。为了进行这种变换,我们会使用上一章节的平移、旋转、缩放等操作,组合成一个名为model的矩阵。

视图空间

  视图空间是我们使用摄像头进行观察到的世界。从世界空间转化到视图空间我们使用与摄像头相关的平移和旋转矩阵进行操作,我们将这些信息存储在名为view的矩阵中。

切割空间

  在每个顶点着色器运行结束时,OpenGL会剔除位于特定范围外的点,这往往是通过NDC的范围来实现的。由于手动将所有的坐标都定义在-1到1的范围内不太聪明,所以我们会随意定义坐标并通过特定的变化转化到NDC的范围内,这个矩阵被称为projection。这个矩阵会将最终在屏幕上显示的顶点坐标转化到NDC的范围内,其它的将被剔除。(注意,比如一个三角形的一个角定义在屏幕显示区域外,OpenGL会自己沿边缘进行重建。)投影矩阵会创立一个视锥体的模型,不在视锥体范围内的将不会显示。在转换结束后,在切割空间中我们会进行透视除法的操作,通过将每个顶点向量的xyz与w相除。用projection矩阵进行变换时,有两种构成projection矩阵的方式,一种是正交投影,一种是透视投影。

正交投影

  正交投影定义的视锥体如下,我们定义的近平面和远平面大小一样:



  正交投影不会影响顶点向量的w组件。
  我们可以使用GLM定义一个正交投影矩阵:

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

  前两个参数代表视锥体左和右的位置(并定义宽度),第三个第四个参数代表下和上的位置(并定义高度),第五个参数代表视锥体近平面的距离,第六个参数代表视锥体远平面的距离。

透视投影

  透视投影会操作顶点向量的w组件,离摄像机越远,w组件越大。下面是一个透视投影的视锥体:



  我们可以使用GLM定义第一个透视投影矩阵:

glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.f);

  第一个参数定义上图的FOV量,即透视角度。一般设为45度。第二个参数代表屏幕比,通过viewport的参数进行设置。最后两个参数同样代表近平面和远平面的距离。
  作为补充,这篇文章
详细讲解了正交投影和透视投影矩阵的计算方法。

将所有变换组合起来

  一个切割空间的顶点向量如下:
V_{clip} = M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}
  注意,我们从右往左阅读矩阵的乘法。最终的结果将会在顶点着色器中赋予给gl_Position,OpenGL会自动进行透视除法和切割。

渲染3D物体

  我们以上一章的带纹理的平面为基础进行以下操作。首先定义一个model矩阵,。我们让平面绕x轴旋转一下,让它看起来躺在地面上:

glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm:;radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));

  接着我们创建一个view矩阵。我们将物体向屏幕外移动一下,,由于OpenGL是右手坐标系,所以我们向+z轴移动。但在视图空间中,我们可以将摄像机向屏幕内移动来实现,也就是向-z移动:

glm::mat4 view  = glm::mat4(1.0f);
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

  最后创建一个projection矩阵,上面已经给出例子里,这里为了模拟立体效果,我们使用透视投影:

glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

  下面是在顶点着色器中的针对gl_Position的修改:

#version 330 core
layout (location = 0) in vec3 aPos;
...
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
  gl_Position = projection * view * model * vec4(aPos, 1.0);
  ...
}

  同样,我们要对uniform变量进行赋值:

int modelLoc = glGetUniformLocation(ourShader.ID, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
...// 其余同理

  运行修改后的程序结果应该如下:


绘制立方体

  接下来我们绘制立方体,我们暂时不使用EBO,这里使用36个顶点来绘制立方体。
  我们让立方体随时间旋转:

model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));

  使用glDrawArrays绘制立方体:

glDrawArrays(GL_TRIANGLES, 0, 36);

  结果是这样:结果
  我们会发现一个问题,在某些时候,前后面会重叠,无法判断谁在前谁在后。应对这种情况,我们可以使用OpenGL存储深度信息的z-buffer进行深度检测。

Z-bufer

  z-buffer是OpenGL自动创建的,它存储深度信息。在渲染时,OpenGL会通过z-buffer来比较片元的深度。如果当前的片元在另一个片元后面,它就会被忽略,在前面的话的就会覆盖后面的片元。这种操作被称为深度检测,OpenGL可以自动执行这一操作。当然,深度检测默认关闭,所以我们通过glEnable开启深度检测:

glEnable(GL_DEPTH_TEST);

  和颜色缓冲一样,我们也需要每帧清除一次深度缓冲来更新深度:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  运行程序结果应该就正常了:结果

最后,请多多参考原文:https://learnopengl.com/Getting-started/Coordinate-Systems

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343