OpenGL学习6——坐标系统

坐标系统(Coordinate Systems)

  • 将坐标转换为标准设备坐标(NDC) 一般是一步一步完成的,在最终将一个图形的顶点转换到NDC之前,我们会经过多个坐标系统。对我们来说比较重要的5种坐标系统是:
    • 本地空间或对象空间(Local space or Object space)
    • 世界空间(World space)
    • 视空间(View space)
    • 裁剪空间(Clip space)
    • 屏幕空间(Screen space)

1. 坐标空间总览

  • 在坐标转换过程中我们使用到多个变换矩阵,其中最重要的就是模型(model),视(view)和投影(projection)矩阵。顶点坐标到屏幕坐标大致的转换流程如下图所示(图片取自书中):

    坐标转换过程

      1. 本地坐标是与本地原点相关的对象坐标。
      1. 世界空间坐标是与场景中的全局原点相关的坐标,涉及多个对象。
      1. 视空间坐标是从相机或观察者角度看到的视界中的坐标。
      1. 裁剪坐标被转换到-1.0到1.0范围内,并决定了那些顶点会显示在屏幕上。
      1. 最后,我们将裁剪坐标转换为屏幕坐标。(见第2小节中的视口转换)
  • 视空间也是我们常讲的OpenGL相机(有时也称为相机空间)。视空间是将世界空间坐标转换到用户视角坐标的结果。因此,视空间就是从相机的角度看到的空间。一般将世界空间到视空间的变换组合到一个视矩阵(view matrix)

  • 在每个顶点着色器运行的结尾,OpenGL期待指定范围内的坐标,而任何处在范围外的坐标将被裁剪(clipped)。被裁剪的坐标将被丢弃,保留下的坐标则成为片元最终显示屏幕上。

  • 将顶点从视空间转换到裁剪空间,会定义一个投影矩阵(projection matrix)。投影矩阵在每个维度指定了一个坐标范围(例如-1000到1000),并只将该范围内的坐标转到NDC,所有该范围外的坐标将被裁剪。

  • 注意:如果一个元图形(primitive),如一个三角形的一部分处在裁剪体积(clipping volume) 之外,OpenGL会将该三角形重建为一个或多个三角形来适应裁剪范围。

  • 投影矩阵所创建的视框(viewing box) 称为截锥(frustum)

  • 将指定范围内的坐标转换到NDC——可以映射到2D视平面坐标的整个过程称为投影(projection)

  • 投影矩阵将视坐标转换为裁剪坐标一般采用两种不同的形式,每种都定义一个唯一的截锥:正射投影矩阵(orthographic projection matrix)透视投影矩阵(perspective projection matrix)

  • 正射投影矩阵定义一个立方截锥体。我们创建正射投影矩阵需要指定可见截锥体的宽度、高度和长度(图片取自书中)。

    正射投影

  • 正射投影截锥体直接将截锥体内坐标映射为标准设备坐标而不产生任何特殊的副作用,因为它不影响变换矢量的w分部。使用GLM内置函数创建正射投影矩阵如下:

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
1. 前两个参数指定截锥体左右的坐标。
2. 第3和4个参数指定截锥体的上下坐标。
3. 第5和6个参数指定远近两个平面之间的距离。
  • 现实生活中我们发觉远处的物体会显得较小,这种奇怪的效果我们成为透视(perspective)
  • 透视投影矩阵将指定截锥体范围内的坐标映射到裁剪空间,同时对每个顶点坐标的w分部进行处理,对于离视点越远的顶点坐标,坐标的w分部越大。
  • 因为OpenGL要求顶点着色器最终输出的坐标必须在范围-1.0到1.0内,因此一旦坐标映射到裁剪空间,就会将透视除法(perspective division) 应用于坐标:
    out = \left( \begin{matrix} x/w \\ y/w \\ z/w\end{matrix} \right)
  • 使用GLM创建透视投影矩阵(图片取自书中):
    透视投影
glm::mat4 proj = glm::perspecrive(glm::radians(45.0f), (float)width / (float)height, 0.1f, 100.0f);
1. 第1个参数定义了fov值(视野:field of view),设定视空间的大小,一般设置为45度。
2. 第2个参数设置纵横比(aspect ratio),通过将视口的宽除以高计算得到。
3. 第3和4个参数设置截锥体的远近截面,一般将近截面设置为0.1,将远截面设置为100.0。

2. 组合到一起

  • 我们创建一个变换矩阵将上述步骤组合起来:模型,视和投影矩阵。顶点坐标转换到裁剪坐标的过程如下公式所示(注意:矩阵的顺序是相反的):
    V_{clip}=M_{projection}\cdot M_{view}\cdot M_{model}\cdot V_{local}
  • OpenGL使用透视除法将裁剪空间坐标转换为标准设备坐标,然后使用glViewPort函数的参数将标准设备坐标映射成为屏幕上的点,这个过程称为视口转换(viewport transform)

3. 开始3D

  • 右手坐标系(Right-handed system):指的是正向x轴指向你的右侧,正向y轴指向上且正向z轴指向你后面。如下图所示:


    右手坐标系
  • 3.1 创建一个模型矩阵:绕x轴旋转

glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
  • 3.2 创建视矩阵:
glm::mat4 view = glm::mat4(1.0f);
// 注意我们将场景按反方向移动
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
  • 3.3 创建投影矩阵
glm::mat4 projection = glm::mat4(1.0f);
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
  • 3.4 修改顶点着色器,定义接受变换矩阵的uniform变量
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
    TexCoord = aTexCoord;
}
  • 3.5 将变换矩阵传递到顶点着色器
unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
unsigned int projectionLoc = glGetUniformLocation(ourShader.ID, "projection");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
  • 3.6 渲染效果


    3D平面

4. 更多3D

4.1 渲染一个立方体

  • 设置顶点数据(含36个顶点)
float vertices[] = {
    // 1        
    -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
     0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
    // 2
    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
     0.5f, -0.5f,  0.5f, 1.0f, 0.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 1.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
    // 3
    -0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
    // 4
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
     0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
     0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
     0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
     // 5
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
     0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
     0.5f, -0.5f,  0.5f, 1.0f, 0.0f,
     0.5f, -0.5f,  0.5f, 1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
    // 6
    -0.5f,  0.5f, -0.5f, 0.0f, 1.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f, 0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f, 0.0f, 1.0f
};
  • 修改模型矩阵,让立方体随时间旋转
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
  • 使用glDarwArrays函数绘制(注意这里没有使用元素缓冲区)
glDrawArrays(GL_TRIANGLES, 0, 36);
  • 渲染效果


    立方体1

4.2 改进立方体渲染——深度测试

  • OpenGL将深度信息存储在一个缓冲区,叫做z-buffer,也称为深度缓冲区(depth buffer)
  • 每个片元都存储了深度信息(片元的z值),且无论任何片元输出颜色前,OpenGL都会将片元的深度值与z-buffer进行对比,如果当前片元位于其他片元之后则被丢弃,否则覆盖。这个过程称为深度测试(depth test),由OpenGL自动完成。
  • 深度测试默认是禁用的,启用代码如下:
glEnable(GL_DEPTH_TEST);
  • 因为启动深度测试,因此渲染循环迭代需要与颜色缓冲区一样进行清除:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  • 启用深度测试后立方体的渲染效果


    立方体——深度测试

4.3 绘制多个立方体

  • 如果我们要在屏幕显示10个立方体,每个立方体看起来一样只是处于场景中的不同位置,那么我们无需修改缓冲区的顶点数据,唯一需要修改的是将立方体对象转换到世界空间的模型矩阵。
  • 为每个立方体定义一个平移矢量
glm::vec3 cubePositions[] = {
    glm::vec3(0.0f,  0.0f,  0.0f),
    glm::vec3(2.0f,  5.0f, -15.0f),
    glm::vec3(-1.5f, -2.2f, -2.5f),
    glm::vec3(-3.8f, -2.0f, -12.3f),
    glm::vec3(2.4f, -0.4f, -3.5f),
    glm::vec3(-1.7f,  3.0f, -7.5f),
    glm::vec3( 1.3f, -2.0f, -2.5f),
    glm::vec3( 1.5f,  2.0f, -2.5f),
    glm::vec3( 1.5f,  0.2f, -1.5f),
    glm::vec3(-1.3f,  1.0f, -1.5f)
};
  • 在渲染循环中使用不同的模型矩阵绘制立方体
glBindVertexArray(VAO);
for (unsigned int i = 0; i < 10; i++)
{
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, cubePositions[i]);
    float angle = 20.0f * i;
    model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
    unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model");
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
    glDrawArrays(GL_TRIANGLES, 0, 36);
}
  • 渲染效果


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

推荐阅读更多精彩内容