GL中的坐标系是标准设备坐标,即他的每个坐标轴的取值范围都是[-1.0,1.0]。通常,我们输入到顶点着色器中的顶点坐标都会被转换为标准化设备坐标,然后进行光栅化,转变成屏幕坐标。然而事实上,从顶点坐标到屏幕坐标是一个较为复杂的过程。总体来讲为了某些计算更加方便,会经过5个坐标系统的变换:

  • 局部空间(Local Space,或者称为物体空间(Object Space))
  • 世界空间(World Space)
  • 观察空间(View Space,或者称为视觉空间(Eye Space))
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)

下面就是坐标系统的具体意义。

1. 概述

经过了这么久的介绍,我们都一直在绘制屏幕中的一个矩形。而事实上,之前的一切过程,我们还只是停留在建造模型的过程。这时候我们的画布里只有这一个模型,我们把当前的坐标系统称作是局部空间

当然,我们的屏幕中大多数情况下不可能只有一个模型,而是千千万万个模型。我们应该通过一系列矩阵变换将模型变换到一个更大的画布中。当然,我们的屏幕是不可能变大的,所以我们是通过一系列的矩阵缩放我们的模型然后放到原始画布中来模拟把模型放入大画布中这个过程。这里,我们把变换到大画布后的坐标系统称为是世界空间。而从局部空间变换到世界空间转换所需要的这个矩阵,我们成为模型矩阵

我们知道,OpenGL是一个3维的世界,然而我们屏幕是一个2维的画面。就好像我们生活在3维空间中但是我们所观察的世界实际是以我们的眼睛作为起始点获取的3维空间在我们的视网膜上投影在将信息传给我们的大脑。那么OpenGL模拟了这个过程,首先我们需要一个眼睛,再其次我们需要将世界投影到我们的屏幕上。

在OpenGL中我们把这个眼睛称作是摄像机。而摄像机针对的坐标系统称为观察空间。我们从世界空间转换到观察空间所经过的矩阵为观察矩阵。经过观察矩阵转换后,实际上我们看到的就是3维世界在我们摄像机所面对的方向上的一个投影了。

生活中我们有一个常识,物体*大远小。物体距离我们越远,他看起来将会越小,我们把这种现象称为透视现象。然而在观察空间我们看到的投影缺不具备这种特点,我们把这种按照物体原比例显示的投影称为正投影。然而这样的投影却与我们*常所观察到的世界不一样,为了让事物看起来更加真实,我们要给物体加上透视效果。经过透视的投影,就是透视投影。GL中,我们为了使物体具有透视效果,我们要将物体经过一个透视投影矩阵进行转换,转换至的空间我们称为裁剪空间。之所以称为裁剪空间,是因为除了投食之外,我们还要把超出视野的地方裁减掉。而GL中就是把超出屏幕空间的物体裁减掉。所以称之为裁剪空间。

最后,我们要把裁剪空间中的物体转换到我们的屏幕上进行输出。屏幕输出的空间我们叫做屏幕空间。这个过程呢,就不用我们费心了,因为到了裁剪空间之后我们已经完全完成了模型到透视投影的转换。接下来只需要将这部分物体展示在屏幕上就好,所以这部分工作由GL替我们完成。这个过程,我们叫视口变换。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。

到这里我们已经大概清楚了这些空间的作用,那么在对应空间中的坐标就分别称为局部坐标(Local Coordinate)世界坐标(World Coordinate)观察坐标(View Coordinate)裁剪坐标(Clip Coordinate)屏幕坐标(Screen Coordinate)

2. 组合

我们知道,从我们的局部空间到屏幕空间需要我们先把局部坐标转换至裁剪坐标,再交由GL转换为屏幕坐标。所以我们应该经过的过程就是:

顺序一定不要搞错,记住矩阵乘法是从右向左的。

 3. 进入3D

这里我就不放全部代码了,先放一段模型构建的代码

 1 void configVAO(unsigned int * VAO,unsigned int * VBO,unsigned int * EBO) {
2 ///顶点数据
3 float vertices[] = {
4 0.5,0.5,0.5,0.0,0.0,0.0,
5 0.5,-0.5,0.5,1.0,0.0,0.0,
6 -0.5,-0.5,0.5,0.0,1.0,0.0,
7 -0.5,0.5,0.5,0.0,0.0,1.0,
8 0.5,0.5,-0.5,1,1,1,
9 0.5,-0.5,-0.5,0,1,1,
10 -0.5,-0.5,-0.5,1,0,1,
11 -0.5,0.5,-0.5,1,1,0
12 };
13
14 ///索引数据
15 unsigned int indices[] = {
16 0,1,2,
17 0,2,3,
18 1,4,5,
19 0,1,4,
20 5,6,7,
21 4,5,7,
22 2,3,6,
23 3,6,7,
24 0,3,4,
25 3,4,7,
26 1,5,6,
27 1,2,6,
28 };
29
30 ///创建顶点数组对象
31 glGenVertexArrays(1, VAO);
32
33 ///创建顶点缓冲对象
34 glGenBuffers(1, VBO);
35 ///创建索引缓冲对象
36 glGenBuffers(1, EBO);
37
38 ///绑定定点数组对象至上下文
39 glBindVertexArray(*VAO);
40
41 ///绑定定点缓冲对象至上下文
42 glBindBuffer(GL_ARRAY_BUFFER, *VBO);
43 ///把顶点数组复制到顶点缓冲对象中
44 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
45 ///设置顶点属性并激活属性
46 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
47 glEnableVertexAttribArray(0);
48 glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6 * sizeof(float), (void*)(3 * sizeof(float)));
49 glEnableVertexAttribArray(1);
50 ///绑定索引缓冲对象至上下文
51 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *EBO);
52 ///把索引数据复制到索引缓冲对象中
53 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
54
55 ///解除顶点数组对象的绑定
56 glBindVertexArray(0);
57 ///解除顶点缓冲对象的绑定
58 glBindBuffer(GL_ARRAY_BUFFER, 0);
59 ///解除索引缓冲对象的绑定
60 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
61 }

上面我们建立了一个正方体,八个顶点分别有八个颜色。目前,他还在局部空间内。

接下来我们来将他们转换到裁剪空间内:

 1 glm::vec3 postions[] = {
2 glm::vec3(0.0,0.0,0.0),
3 glm::vec3( 2.0f, 5.0f, -15.0f),
4 glm::vec3(-1.5f, -2.2f, -2.5f),
5 glm::vec3(-3.8f, -2.0f, -12.3f),
6 glm::vec3( 2.4f, -0.4f, -3.5f),
7 glm::vec3(-1.7f, 3.0f, -7.5f),
8 glm::vec3( 1.3f, -2.0f, -2.5f),
9 glm::vec3( 1.5f, 2.0f, -2.5f),
10 glm::vec3( 1.5f, 0.2f, -1.5f),
11 glm::vec3(-1.3f, 1.0f, -1.5f)
12 };
13
14 glm::mat4 view = glm::mat4(1.0f);
15 view = glm::translate(view, glm::vec3(0.f, 0.f, -3.f));
16 glm::mat4 projection = glm::mat4(1.0f);
17 projection = glm::perspective(glm::radians(45.0f), (float)(SCR_WIDTH * 1.0 / SCR_HEIGHT), 0.1f, 100.0f);
18 ourShader.setMtx4fv("view", view);
19 ourShader.setMtx4fv("projection", projection);
20
21 while (!glfwWindowShouldClose(window))
22 {
23 processInput(window);
24
25 ///设置清屏颜色
26 glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
27 ///清屏
28 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
29
30 ///绑定定点数组对象
31 glBindVertexArray(VAO);
32
33 for (int i = 0; i < 10; ++i) {
34 glm::mat4 model = glm::mat4(1.0f);
35 model = glm::translate(model, postions[i]);
36 float angle = 20.0f * i;
37 model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
38 ourShader.setMtx4fv("model", model);
39
40 ///以索引绘制顶点数据
41 // glDrawArrays(GL_TRIANGLES, 0, 3);
42 glDrawElements(GL_TRIANGLES,36,GL_UNSIGNED_INT,0);
43 }
44
45
46 ///交换颜色缓冲
47 glfwSwapBuffers(window);
48 ///拉取用户事件
49 glfwPollEvents();
50 }

我们看到,我们为每个物体定单独定义了一个模型矩阵,这样,我们每个模型在世界空间中的状态都不同,然后在定义了唯一一个观察矩阵和透视投影矩阵,这样就模拟出我看眼睛看到物体的一个过程。

这里我们只对glm为我们提供的几个新出现的函数做一下简单讲解:

这是用来指定透视投影矩阵的函数。

  • 第一个参数radians指的是Fov,它表示的是视野(Field of View),并且设置了观察空间的大小。如果想要一个真实的观察效果,它的值通常设置为45.0f。他就是图中两个蓝色实线的空间夹角。

  • 第二个参数scale设置了宽高比,由视口的宽除以高所得。

  • 第三和第四个参数设置了*截头体的*和远*面。我们通常设置*距离为0.1f,而远距离设为100.0f。所有在**面和远*面内且处于*截头体内的顶点都会被渲染。图中粉色截面即为**面,蓝色截面即为远*面。

接下来虽然代码中没有,我们还是提一下正投影矩阵的创建方法:

当你把透视矩阵的 near 值设置太大时(如10.0f),OpenGL会将靠*摄像机的坐标(在0.0f和10.0f之间)都裁剪掉,这会导致一个你在游戏中很熟悉的视觉效果:在太过靠*一个物体的时候你的视线会直接穿过去。

OpenGL 坐标系统详解的更多相关文章

  1. Qt的Graphics-View框架和OpenGL结合详解

    Qt的Graphics-View框架和OpenGL结合详解 演示程序下载地址:这里 程序源代码下载地址:这里 这是一篇纯技术文,介绍了这一个月来我抽时间研究的成果. Qt中有一个非常炫的例子:Boxe ...

  2. 【OpenGL】详解第一个OpenGL程序

    写在前面 OpenGL能做的事情太多了!很多程序也看起来很复杂.很多人感觉OpenGL晦涩难懂,原因大多是被OpenGL里面各种语句搞得头大,一会gen一下,一会bind一下,一会又active一下. ...

  3. OpenGL ES 详解纹理生成和纹理映射步骤以及函数

    通常一个纹理映射的步骤是: 创建纹理对象.就是获得一个新的纹理句柄 ID. 指定纹理.就是将数据赋值给 ID 的纹理对象,在这一步,图像数据正式加载到了 ID 的纹理对象中. 设定过滤器.定义了ope ...

  4. OpenGL一些函数详解(二)

    OpenGL ES顶点数据绘制技巧 在OpenGL中,绘制一个长方体,需要将每个顶点的坐标放在一个数组中.保存坐标时有一些技巧(由于字母下标不好表示,因此将下标表示为单引号,如A1将在后文中表示为A' ...

  5. OpenGL ES一些函数详解(一)

    glLoadIdentity和glMultMatrix   glLoadIdentity的作用是将当前模型视图矩阵转换为单位矩阵(行数和列数相同的矩阵,并且矩阵的左上角至右下角的连线上的元素都为1,其 ...

  6. view坐标_ _ Android应用坐标系统全面详解

    转:http://blog.csdn.net/yanbober/article/details/50419117 1 背景 去年有很多人私信告诉我让说说自定义控件,其实通观网络上的很多博客都在讲各种自 ...

  7. OpenGL的glTranslatef平移变换函数详解

    OpenGL的glTranslatef平移变换函数详解 glTranslated()和glTranslatef()这两个函数是定义一个平移矩阵,该矩阵与当前矩阵相乘,使后续的图形进行平移变换. 我们先 ...

  8. OpenGL的glRotatef旋转变换函数详解

    OpenGL的glRotatef旋转变换函数详解 先看一下函数定义:void glRotatef(GLfloat angle,  GLfloat x,     GLfloat y,     GLflo ...

  9. [转]百度地图API详解之地图坐标系统

    博客原文地址:http://www.jiazhengblog.com/blog/2011/07/02/289/ 我们都知道地球是圆的,电脑显示器是平的,要想让位于球面的形状显示在平面的显示器上就必然需 ...

  10. OpenGL ES: (4) EGL API详解 (转)

    上一节我们初步学习了 OpenGL ES.EGL.GLSL 的相关概念,了解了它们的功能,以及它们之间的关联.我们知道了 EGL 是绘制 API(比如 OpenGL ES)与 底层平台窗口系统之间的接 ...

随机推荐

  1. Unity UGUI的PointerEventData的介绍及使用

    Unity UGUI的PointerEventData的介绍及使用 1. 什么是PointerEventData? PointerEventData是Unity中UGUI系统中的一个重要组件,用于处理 ...

  2. Protobuf vs JSON

    Protobuf(Protocol Buffers)和 JSON 都是数据序列化格式,但它们在许多方面有着显著的不同.以下是对两者的一些主要比较: 数据大小和速度: Protobuf:由于 Proto ...

  3. windows访问linux分区文件

    正常情况下,linux可以访问windows系统的文件,而要想在windows下访问linux文件,需要借助第三方软件. 常用的有以下几款: 1.Linux Reader 2.Ext2 IFS 3.E ...

  4. 【问题解决】容器部署MySQL的数据在docker commit导出的镜像中丢失

    问题起因 最近公司有个甲方项目参加竞赛,要求在(基于kubeflow/arena)平台上部置应用,可以将MySQL打包在应用一起,也可以分开部署,没有提供volume相关的支持.大意是可以把初始好的数 ...

  5. 如何使用io_uring构建快速响应的I/O密集型应用?

    本文分享自华为云社区<如何使用io_uring构建快速响应的I/O密集型应用>,作者: Lion Long . 当涉及构建快速响应的I/O密集型应用时,io_uring技术展现出了其卓越的 ...

  6. vue3封装筛选项

    背景 项目开发中遇到筛选项,并且几个页面都有使用,依次写,太过于繁琐 筛选项解构如下 封装全局组件fjj-content <template> <div class="fj ...

  7. OptiX8入门(一)optixHello

    本人初学者,如有错误和更好的表述,请指出 环境:CLion+VS2022+CUDA Toolkit 12.0.1+OptiX8 下载好后打开SDK就可以看到OptiX官方提供的许多例子,CMake配置 ...

  8. 一文解锁vue3中hooks的使用姿势

    vue3 中的 hooks 是什么? 简单来说如果你的函数中用到了诸如 ref,reactive,onMounted 等 vue 提供的 api 的话,那么它就是一个 hooks 函数,如果没用到它就 ...

  9. 文心一言 VS 讯飞星火 VS chatgpt (93)-- 算法导论9.2 1题

    一.用go语言,证明:在 RANDOMIZED-SELECT 中,对长度为0的数组,不会进行递归调用. 文心一言: 在 Go 语言中,我们可以通过实现一个函数来检查数组的长度,如果长度为0,则不进行递 ...

  10. Haproxy搭建 Web 群集实现负载均衡

    Haproxy搭建 Web 群集实现负载均衡 1 Haproxy HAProxy是可提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,是免费.快速并且可靠的一种解决方案.HAProxy非常适用 ...