OpenGL之坐标系统

坐标变换为标准化设备坐标,接着再转化为屏幕坐标的过程通常是分步进行的,也就是类似于流水线那样子。在流水线中,物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统(Coordinate System)。将物体的坐标变换到几个过渡坐标系(Intermediate Coordinate System)的优点在于,在这些特定的坐标系统中,一些操作或运算更加方便和容易,这一点很快就会变得很明显。对我们来说比较重要的总共有5个不同的坐标系统:

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

概述

为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。下面的这张图展示了整个流程以及各个变换过程做了什么:

  1. 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。
  2. 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。
  3. 接下来我们将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。
  4. 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。
  5. 最后,我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。

局部空间

局部空间是指物体所在的坐标空间,即对象最开始所在的地方。想象你在一个建模软件(比如说Blender)中创建了一个立方体。你创建的立方体的原点有可能位于(0, 0, 0),即便它有可能最后在程序中处于完全不同的位置。甚至有可能你创建的所有模型都以(0, 0, 0)为初始位置(译注:然而它们会最终出现在世界的不同位置)。所以,你的模型的所有顶点都是在局部空间中:它们相对于你的物体来说都是局部的。

世界空间

如果我们将我们所有的物体导入到程序当中,它们有可能会全挤在世界的原点(0, 0, 0)上,这并不是我们想要的结果。我们想为每一个物体定义一个位置,从而能在更大的世界当中放置它们。世界空间中的坐标正如其名:是指顶点相对于(游戏)世界的坐标。如果你希望将物体分散在世界上摆放(特别是非常真实的那样),这就是你希望物体变换到的空间。物体的坐标将会从局部变换到世界空间;该变换是由模型矩阵(Model Matrix)实现的

模型矩阵是一种变换矩阵,它能通过对物体进行位移、缩放、旋转来将它置于它本应该在的位置或朝向。你可以将它想像为变换一个房子,你需要先将它缩小(它在局部空间中太大了),并将其位移至郊区的一个小镇,然后在y轴上往左旋转一点以搭配附近的房子。你也可以把上一节将箱子到处摆放在场景中用的那个矩阵大致看作一个模型矩阵;我们将箱子的局部坐标变换到场景/世界中的不同位置。

观察空间

观察空间经常被人们称之OpenGL的摄像机(Camera)(所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space))。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。而这通常是由一系列的位移和旋转的组合来完成,平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里,它被用来将世界坐标变换到观察空间。在下一节中我们将深入讨论如何创建一个这样的观察矩阵来模拟一个摄像机。

裁剪空间

在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内且任何在这个范围之外的点都应该被裁剪掉(Clipped)。被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来。

因为将所有可见的坐标都指定在-1.0到1.0的范围内不是很直观,所以我们会指定自己的坐标集(Coordinate Set)并将它变换回标准化设备坐标系,就像OpenGL期望的那样。

为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix),它指定了一个范围的坐标,比如在每个维度上的-1000到1000。投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围(-1.0, 1.0)。所有在范围外的坐标不会被映射到在-1.0到1.0的范围之间,所以会被裁剪掉。在上面这个投影矩阵所指定的范围内,坐标(1250, 500, 750)将是不可见的,这是由于它的x坐标超出了范围,它被转化为一个大于1.0的标准化设备坐标,所以被裁剪掉了。

如果只是图元(Primitive),例如三角形,的一部分超出了裁剪体积(Clipping Volume),则OpenGL会重新构建这个三角形为一个或多个三角形让其能够适合这个裁剪范围。

由投影矩阵创建的观察箱(Viewing Box)被称为平截头体(Frustum),每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。将特定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection),因为使用投影矩阵能将3D坐标投影(Project)到很容易映射到2D的标准化设备坐标系中。

一旦所有顶点被变换到裁剪空间,最终的操作——透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被自动执行。

在这一阶段之后,最终的坐标将会被映射到屏幕空间中(使用glViewport中的设定),并被变换成片段。

观察坐标变换为裁剪坐标的投影矩阵可以为两种不同的形式,每种形式都定义了不同的平截头体。我们可以选择创建一个正射投影矩阵(Orthographic Projection Matrix)或一个透视投影矩阵(Perspective Projection Matrix)。

正射投影

正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器:

上面的平截头体定义了可见的坐标,它由由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;如果w分量等于1.0,透视除法则不会改变这个坐标。

要创建一个正射投影矩阵,我们可以使用GLM的内置函数glm::ortho

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

两个参数指定了平截头体的左右坐标第三和第四参数指定了平截头体的底部和顶部。通过这四个参数我们定义了近平面和远平面的大小,然后第五和第六个参数则定义了近平面和远平面的距离。这个投影矩阵会将处于这些x,y,z值范围内的坐标变换为标准化设备坐标。

正射投影矩阵直接将坐标映射到2D平面中,即你的屏幕,但实际上一个直接的投影矩阵会产生不真实的结果,因为这个投影没有将透视(Perspective)考虑进去。所以我们需要透视投影矩阵来解决这个问题。

透视投影

如果你曾经体验过实际生活给你带来的景象,你就会注意到离你越远的东西看起来更小。这个奇怪的效果称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样:

perspective

正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉)。OpenGL要求所有可见的坐标都落在-1.0到1.0范围内,作为顶点着色器最后的输出,因此,一旦坐标在裁剪空间内之后,透视除法就会被应用到裁剪空间坐标上:

out = \begin{pmatrix} x/w \\ y/w \\ z/w \end{pmatrix}

顶点坐标的每个分量都会除以它的w分量,距离观察者越远顶点坐标就会越小。这是也是w分量非常重要的另一个原因,它能够帮助我们进行透视投影。最后的结果坐标就是处于标准化设备空间中的。

在GLM中可以这样创建一个透视投影矩阵:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);

同样,glm::perspective所做的其实就是创建了一个定义了可视空间的大平截头体,任何在这个平截头体以外的东西最后都不会出现在裁剪空间体积内,并且将会受到裁剪。一个透视平截头体可以被看作一个不均匀形状的箱子,在这个箱子内部的每个坐标都会被映射到裁剪空间上的一个点。下面是一张透视平截头体的图片:

perspective_frustum

它的第一个参数定义了fov的值,它表示的是视野(Field of View),并且设置了观察空间的大小。如果想要一个真实的观察效果,它的值通常设置为45.0f,但想要一个末日风格的结果你可以将其设置一个更大的值。第二个参数设置了宽高比,由视口的宽除以高所得第三和第四个参数设置了平截头体的近和远平面。我们通常设置近距离为0.1f,而远距离设为100.0f。所有在近平面和远平面内且处于平截头体内的顶点都会被渲染。

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

把它们都组合到一起

我们为上述的每一个步骤都创建了一个变换矩阵:模型矩阵观察矩阵投影矩阵。一个顶点坐标将会根据以下过程被变换到裁剪坐标:

V_{clip}=M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local}

注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。

进入3D

既然我们知道了如何将3D坐标变换为2D坐标,我们可以开始使用真正的3D物体,而不是枯燥的2D平面了。

在开始进行3D绘图时,我们首先创建一个模型矩阵。这个模型矩阵包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变换它们到全局的世界空间。让我们变换一下我们的平面,将其绕着x轴旋转,使它看起来像放在地上一样。这个模型矩阵看起来是这样的:

进入3D

既然我们知道了如何将3D坐标变换为2D坐标,我们可以开始使用真正的3D物体,而不是枯燥的2D平面了。

在开始进行3D绘图时,我们首先创建一个模型矩阵。这个模型矩阵包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变换它们到全局的世界空间。让我们变换一下我们的平面,将其绕着x轴旋转,使它看起来像放在地上一样。这个模型矩阵看起来是这样的:

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

通过将顶点坐标乘以这个模型矩阵,我们将该顶点坐标变换到世界坐标。我们的平面看起来就是在地板上,代表全局世界里的平面。

接下来我们需要创建一个观察矩阵。我们想要在场景里面稍微往后移动,以使得物体变成可见的(当在世界空间时,我们位于原点(0,0,0))。要想在场景里面移动,先仔细想一想下面这个句子:

  • 将摄像机向后移动,和将整个场景向前移动是一样的。

这正是观察矩阵所做的,我们以相反于摄像机移动的方向移动整个场景。因为我们想要往后移动,并且OpenGL是一个右手坐标系(Right-handed System),所以我们需要沿着z轴的正方向移动。我们会通过将场景沿着z轴负方向平移来实现。它会给我们一种我们在往后移动的感觉。

在下一个教程中我们将会详细讨论如何在场景中移动。就目前来说,观察矩阵是这样的:

glm::mat4 view;
// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));

最后我们需要做的是定义一个投影矩阵。我们希望在场景中使用透视投影,所以像这样声明一个投影矩阵:

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

既然我们已经创建了变换矩阵,我们应该将它们传入着色器。首先,让我们在顶点着色器中声明一个uniform变换矩阵然后将它乘以顶点坐标:

#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);...
}

我们还应该将矩阵传入着色器(这通常在每次的渲染迭代中进行,因为变换矩阵会经常变动):

int modelLoc = glGetUniformLocation(ourShader.ID, "model"));
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
... // 观察矩阵和投影矩阵与之类似

我们的顶点坐标已经使用模型、观察和投影矩阵进行变换了,最终的物体应该会:

  • 稍微向后倾斜至地板方向。
  • 离我们有一些距离。
  • 有透视效果(顶点越远,变得越小)。

让我们检查一下结果是否满足这些要求:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/85957.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从Python代码到诗

🐳序言 在Python社区,没有强制的编码标准,这虽然赋予了开发者更多的自由,但也导致代码风格不一致性。使得部分代码变得晦涩难懂,本文将探讨一系列的开发技巧和最佳实践,开发出优雅的Python脚本。 1、参数接…

ElasticSearch(三)

1.数据聚合 聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如: 什么品牌的手机最受欢迎? 这些手机的平均价格、最高价格、最低价格? 这些手机每月的销售情况如何? 实现这些…

laravel框架 - 消息队列如何使用

业务场景:项目里边有很多视频资源需要上传到抖音资源库,通过队列一条一条上传。 参考实例:发送邮件,仅供参考 (1)创建任务【生成任务类】 在你的应用程序中,队列的任务类都默认放在 app/Jobs 目录下。如果这个目录不存…

解决ubuntu系统python2.7安装uwsgi报错

背景 因为项目老旧,仍需使用python2.7,仍需要使用pip2 安装依赖。在安装uwsgi的时候,报错。 错误一 Building wheel for uwsgi (setup.py) ... error ERROR: Command errored out with exit status 1: command: /usr/bin/python2 -u -…

vulhub打靶第三周

第三周 靶机下载地址:https://www.vulnhub.com/entry/chronos-1,735/ 环境折磨导致做晚了,再加上期末的考试多耽搁下来了,然后就是辗转反侧打比赛,拖了这么久,时隔三个月重新开打 因为陆陆续续打了两次,所…

[winerror 5] 拒绝访问。: ‘..\\data‘解决方案

使用Jupyter Notebook学习深度学习时出现错误如下:[winerror 5] 拒绝访问。: ‘…\data’ 解决方法: 打开anaconda3找到对应环境的python.exe 点开属性,点安全,选择如下: 点编辑,选择User,勾…

9、DVWA——XSS(Stored)

文章目录 一、存储型XSS概述二、low2.1 源码分析2.2 通关分析 三、medium3.1 源码分析3.2 通关思路 四、high4.1 源码分析4.2 通关思路 一、存储型XSS概述 XSS,全称Cross Site Scripting,即跨站脚本攻击,某种意义上也是一种注入攻击&#xff…

李航老师《统计学习方法》第1章阅读笔记

1.1 统计学习 统计学习的特点 统计学习:计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析 现在人们提及机器学习时,往往指统计机器学习,所以可以认为本书介绍的是机器学习方法 统计学习的对象 统计学习研究的对象是数据(data)…

《Web安全基础》09. WAF 绕过

web 1:基本概念1.1:DoS & DDos1.2:CC 攻击1.3:扫描绕过方式 2:WAF 绕过2.1:信息收集阶段2.2:漏洞发现阶段2.3:权限控制阶段2.3.1:密码混淆2.3.2:变量覆盖…

解决方案:TSINGSEE青犀+智能分析网关助力智慧仓储智能化监管

为全面保障物流仓储的安全性与完整性,解决仓库管理难题,优化物流仓储方式,提升仓储效率,降低人工成本,旭帆科技推出智慧仓储AI视频智能分析方案,利用物联网、大数据、云计算等技术,对仓储管理进…

IDEA2023新UI回退老UI

idea2023年发布了新UI,如下所示 但是用起来真心不好用,各种位置也是错乱,用下面方法可以回退老UI

【轨道机器人】成功驱动伺服电机(学生电源、DCH调试软件、DH系列伺服驱动器)

1、硬件平台 工控机 学生电源 DH系列伺服驱动器 电机 调试平台:DCH 2、如何利用dch驱动电机 点击可驱动电机 下面的步骤是比较关键的几步: 3、遇到的问题 不能成功驱动电机,还和厂家那边打电话,询问 发现是这…

JavaScript简介引入方式(JavaScript基础语法、JavaScript对象、BOM、DOM、事件监听)

🐌个人主页: 🐌 叶落闲庭 💨我的专栏:💨 c语言 数据结构 javaEE 操作系统 Redis 石可破也,而不可夺坚;丹可磨也,而不可夺赤。 JavaScript简介&引入方式 简介&#xf…

智能生活从这里开始:数字孪生驱动的社区

数字孪生技术,这个近年来备受瞩目的名词,正迅速渗透到社区发展领域,改变着我们居住的方式、管理的方式以及与周围环境互动的方式。它不仅仅是一种概念,更是一种变革,下面我们将探讨数字孪生技术如何推动社区智能化发展…

基于微服务的第二课堂管理系统(素质拓展学分管理平台)SpringCloud、SpringBoot 分布式,微服务

基于微服务的第二课堂管理系统 一款真正的企业级开发项目,采用标准的企业规范开发,有项目介绍视频和源码,需要学习的同学可以拿去学习,这是一款真正可以写在简历上的校招项目,能够真正学到东西的一个项目,话…

基于springboot高校场馆预订系统

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

重新认识架构—不只是软件设计

前言 什么是架构? 通常情况下,人们对架构的认知仅限于在软件工程中的定义:架构主要指软件系统的结构设计,比如常见的SOLID准则、DDD架构。一个良好的软件架构可以帮助团队更有效地进行软件开发,降低维护成本&#xff0…

Leetcode171. Excel 表列序号

给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如: A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 题解:力扣(LeetCode)官网 - 全球极客挚爱…

mysqld_exporter监控MySQL服务

一、MySQL授权 1、登录MySQL服务器对监控使用的账号授权 CREATE USER exporterlocalhost IDENTIFIED BY 123456 WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO exporterlocalhost; flush privileges;2、上传mysqld_exporter安装包&#…

Spring boot原理

起步依赖 Maven的传递依赖 自动配置 Springboot的自动配置就是当spring容器启动后,一些配置类、bean对象就自动存入到IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。 自动配置原理: 方案一…