【Modern OpenGL】转换 Transformations

说明:跟着learnopengl的内容学习,不是纯翻译,只是自己整理记录。
强烈推荐原文,无论是内容还是排版。 原文链接
本文地址: http://blog.csdn.net/aganlengzi/article/details/50421159

转换 Transformations

我们已经学会了怎样创建对象,并且学会利用着色或者纹理使他们呈现出表面细节,但是它们还并不是十分有趣,因为它们只是静止的对象。我们虽然可以通过在每帧中改变它们的顶点坐标值挥着通过重新配置他们的缓存区使它们动起来,但是这样做是十分繁杂并且消耗更多能量。实际上,有更好的办法可以转换一个对象:使用一个矩阵对象。

矩阵是一种十分强大的数学概念,一开始看上去是令人生畏的。但是当你习惯它们的时候,你会觉得它们非常有用。

但是,为了完全理解转换,在讨论矩阵之前,我们首先不得不深入探究一下向量。本次教程的目的是让你能有相关数学背景知识,以方便我们后面的讨论。如果你觉得这次的内容太难了,那就能理解多少就理解多少吧。当后面再次用到这里面的内容的时候,可以再回过头来看。

向量 Vectors

向量,指具有大小(magnitude)和方向的几何对象。向量可以有不同的维度,多少维都可以。如果我们使用二维向量,那它的物理意义就是二维平面上的一个方向。如果我们使用三维向量,那它的可以表示三维世界中的任意方向。

下面你将看到三个二维向量,它们在二维坐标系中用(x,y)的形式表示。因为表示二维向量更加直观,所以我们就不用三维向量作为例子了。实际上,也可以把这几个向量想象成三维向量,只是它们的z轴坐标被设置成了0而已。因为向量本质上是代表方向的,所以其起始点并不会改变它的值。在下图中,我们可以看到v和w是相等的,虽然它们的起点不同。

后面讨论的关于向量的内容在我们的初中或者高中肯定都已经学习过,主要有:
向量的表示(在坐标系中的表示和坐标表示);
向量和标量的加减乘除(向量的每个分量和标量做计算);
向量和向量的加减和意义(方向的改变);
向量的取反(反向);
向量的长度计算方法(勾股定理);
向量的单位向量(长度值为单位1时候的坐标表示);

向量之间乘法
点乘(向量表示):


点乘(坐标表示):两个向量a = [a1, a2,…, an]和b = [b1, b2,…, bn],
其点积定义为:a·b=a1b1+a2b2+……+anbn。
点乘得到的是一个具体数值。

 

叉乘:叉乘的结果还是一个向量,垂直原来两个所在的平面,方向也有原来两个向量决定。

 

现在先记这么多关于向量的知识吧,后面如果再用到的话就再回过头来看一看。

矩阵 Matrices

前面我们看了向量相关的知识,下面来看一下矩阵相关的知识:
矩阵(Matrix)是一个按照长方阵列排列的实数(还包括符号,表达式等等)集合。其中的每一个数(符号或表达式)都叫做矩阵的元素。一个m x n 的矩阵如下图所示:


其中的每个元素都可以通过(i,j)的形式索引到,其中i表示行,j表示列,其实就是一个二维数组,只不过注意矩阵中下标是从1开始的而不是从0开始的。
这大概就是关于矩阵定义的所有内容了。我们来看看作用在矩阵上的操作:

 

矩阵和数值的加减:每个元素都和数值相加减。
矩阵和矩阵的加减:只有相同行列数的矩阵才能够进行加减操作,对应元素相加减。
矩阵和数值的乘除:每个元素都和数值相乘除。
矩阵和矩阵相乘:两个矩阵一前一后,只有前面矩阵的列数和后面矩阵的行数相同时才能够进行相乘的操作。具体相乘操作是前面矩阵每行的每个元素和后面矩阵每列的每个元素相乘后相加得到结果矩阵中的(行,列)位置的数值。举个例子基本就清楚了:

 

矩阵和向量相乘

上面讲了向量,讲了矩阵,最终是要用它们。用它们做什么呢?相乘!至少形式上是可以满足矩阵和向量相乘的。一个m x n的矩阵和一个n维向量是正好可以相乘的,而且相乘的结果还是一个n维向量。换句话说,我们将一个m x n的矩阵作用在了一个n维向量上得到了作用的结果也就是二者相乘的结果。这次教程讲的是转换,这就是其所在了!矩阵就是用来对向量进行转换的工具。而对向量进行转换就只需要左乘相应的转换矩阵就好了。

先看一下最简单的转换矩阵单位矩阵。

单位矩阵

单位矩阵是个方阵(行列数相等),从左上角到右下角的对角线(称为主对角线)上的元素均为1。它的作用就像是数值乘法运算中的1。一个矩阵左乘一个单位矩阵,得到的还是本身。如下图所示:

 

缩放

缩放矩阵可以利用单位矩阵来理解。单位矩阵的主对角线上都是1,向量的每一个分量和其相乘后得到的值还是向量的值本身。如果将这些值改成不是1的数值,那么得到的效果就是不同的分量和这些非1值相乘的结果。因为向量表示的是一个点的坐标(目前以点的坐标举例)。如果图形上的所有点的坐标都做了相同的缩放操作(表示每个点的向量都左乘这个缩放矩阵),那么得到的整体图形就进行了缩放操作,这应该不难理解。缩放矩阵一般形式:


需要注意的有两点:首先,缩放矩阵有两种,一种是按比例缩放,一种是不按比例缩放。按比例缩放的缩放矩阵应该保证主对角线上除w分量(前面教程中讲过OpenGL中的向量分为x,y,z,w最多四个分量,实际上这是三维向量表示的标准统一化表示)相等;而不安比例的缩放则无需保证。其次,就是这个w分量,相当于我们在用四维向量来表示三维坐标,用四维方阵来转换4维向量。实际上,就缩放来说,没有必要用到四维,但是为什么要这样用呢?后面会讲到。

 

平移

和缩放矩阵类似,平移转换矩阵也能够从单位矩阵中推导出来。只不过缩放是对坐标值成比例(乘除)的改变。而平移是对向量分量的整体加减操作,举例来说就是:


可以看到,x,y,z上的平移量T_x,T_y,T_z在每次计算的时候都是和w分量相乘之后加到原来的向量分量数值上的。这个时候就体现出了向量和矩阵中的w分量的作用了。

 

为什么缩放的时候用不到w分量还要加上?实际上是为了统一表示,这就是齐次坐标w的作用所在。
关于齐次坐标,目前实际上记住:它的使用使得转换矩阵在形式上能够保证一致(行列数),这样在计算的时候不用担心不满足左边矩阵的列数不等于右边向量的行数的尴尬局面。另外,w分量的作用并不仅限于此,它的值也不仅限于1,在下一个教程中会讲到,利用w值来改变三维对象。

旋转

相较于以上介绍的缩放和平移转换矩阵,旋转转换矩阵在理解上可能会有些难度,虽然它在形式上和上面的两个转换矩阵比较相似(肯定比较相似,都是一个矩阵,只不过矩阵中的元素根据我们要实现的功能设置不同的数值)。

在学习旋转矩阵之前,我们应该首先看一下什么是向量的旋转。我们只说三维空间中的旋转。在三维空间中,所有的点都在三维坐标系中,都可以通过三维坐标来指定。其中某个点可以看成是从原点到这个点的一个实际的向量(带箭头的线段)。在三维空间中的旋转是和特定的坐标轴相关的,即旋转是绕某一个坐标轴进行一定角度的旋转。所以对于三维空间中的点的旋转转换矩阵,有三个,分别是:
绕x轴旋转变化矩阵:


绕y轴旋转变化矩阵:


绕z轴旋转变化矩阵:


很显然,我们在实际使用的时候不会只对绘制的对象进行按照x,y或z轴的单独的旋转,我们也可以按照先x后y最后z的方式组合达到我们的效果,但是这种方法是不推荐的,它会引入问题。推荐的方法是绕一个方向单位向量进行旋转,其形式如下,假设是绕(R_x,R_y,R_z)进行旋转:

 

组合矩阵

组合矩阵在本教程中的含义就是矩阵相乘。
实际上,我们在对生成的三维对象进行操作的时候,往往是对它们做一系列的转换,其中当然包括最基本的缩放、平移、旋转操作。但是,如果我们每次都要进行一系列操作(比如说缩放、平移和旋转三种操作),一种方法是:要变换的向量首先左乘缩放转换矩阵,得到的向量再左乘平移变换矩阵,得到的向量再左乘旋转变换矩阵,最终得到了我们想要的结果;另一种方法是:首先按照顺序将缩放矩阵左乘平移矩阵,得到的结果左乘旋转矩阵,然后要变换的向量左乘其结果。这两种方法的到的效果是一致的。但是从运算量上来看,第二种方法显然优于第一种,因为在第一种中,对象的每个点都要做相同的3次左乘矩阵的操作,而第二种只需要每个点完成1次左乘矩阵的操作就好了。所以,这才是使用变换矩阵和齐次坐标的意义所在。

实践一下 In practice

我们已经解释了转换背后的原理,是时候看一下我们应该怎样使用这些理论了。OpenGL本身是没有任何关于矩阵或者向量相关的内置信息的。所以我们需要自己来定义数学类和函数。在这个教程中我们使用之前就已经有的数学库来方便我们转换操作。幸运的是,GLM就是一个易用并且专为OpenGL定制过的数学库。

GLM

GLM是OpenGL Mathematics的缩写。它是一个只有头文件的库,也就一位置我们只需要包含它的合适的头文件就能够进行愉快的使用了。不需要像之前我们使用的GLEW、GLFW、SOIL等还需要编译和配置(配置还是需要的)。你可以从这儿下载到所需的文件。在配置的时候我们只需要让我们的工程能够找到需要的GLM的文件就可以了。

我的做法是将下载到的glm-0.9.7.1.zip解压到某个目录下比如说GLM_ROOT,然后:
在我的工程中—->属性—->VC++目录—->包含目录中添加GLM_ROOT就可以了。

实际上大多数情况下,我们只需要包含下面的三个头文件就已经够用了:

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

首先让我们先生成一个没有什么具体含义的转换矩阵,仅仅是为了测试一下矩阵和向量相乘的结果:

glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;

如上面的代码所示,我们想要做的是将(1,0,0)通过(1,1,0)生成的矩阵(如下图所示)转换成三个维度上的坐标分别是(2,1,0)的结果。


在代码中,我们通过GLM中的glm::vec4数据类型声明了一个四维向量vec;通过glm::mat4数据类型生成了一个4 x 4的矩阵trans,默认为单位矩阵;通过glm::translate函数借助于向量(1.0f, 1.0f, 0.0f)将这个矩阵变换成上图所示的转换矩阵,然后将向量vec左乘变换矩阵trans,并输出结果向量的x,y和z轴分量坐标。我运行的结果如下图所示:

 

接下来,让我们尝试一下更有趣的东西。
让我们对上次教程中那个由笑脸和盒子混合贴图而成的矩形进行操作:首先对其进行逆时针90度旋转,然后我们将其等比例缩放0.5。好的,开始干吧,首先我们定义转换矩阵:

glm::mat4 trans;
trans = glm::rotate(trans, 90.0f, glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));  

注意上面的转换顺序,因为转换矩阵对向量的操作都是左乘进行的,上面的glm::rotate和glm::scale函数也是默认左乘的规则生成转换矩阵,那么最终得到的trans相当于是“旋转 * 缩放”矩阵的结果,当这个组合后的变换矩阵作用到图形的每个坐标点的时候,实际上是先和缩放矩阵相乘,然后和旋转矩阵相乘的。另外,通过以上方法能够简单方便地生成组合后的最终转换矩阵,大大减少计算量(还记得上面讲到的第二种方法吧)。

有一些版本的GLM是不支持以度数来表示角度的,而是支持弧度表示,在这种情况下,可能需要手动进行一下转换。

剩下的问题就是将这个转换矩阵作用到我们图形上的每一个点了。当然应该是在vertex shader中进行坐标的转换(fragment中是进行颜色值生成的),当然你肯定也像我一样想到了用uniform,但是你可能像我一样没有记起来GLSL中也有一个mat4数据类型,表示一个4 x 4的矩阵。有了这个数据结构,我们才可以定义变量,才可以将其定义成uniform类型的变量,才可以将我们的矩阵传递进shader中,像下面这样:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;out vec3 ourColor;
out vec2 TexCoord;uniform mat4 transform;void main()
{gl_Position = transform * vec4(position, 1.0f);ourColor = color;TexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
} 

实际上,GLSL还有mat2和mat3数据类型,并且也支持大尺寸的矩阵指定部分给小尺寸的向量赋值的操作,和前面讲的向量中的类似的灵活操作相类似。

好的,上面的代码中,我们首先定义了用于传递转换矩阵的uniform变量,然后在主函数中将原来的点的位置向量左乘上了我们的transform矩阵。在OpenGL程序中,我们需要对这个transform矩阵进行赋值,这个应该是比较熟悉的:

GLuint transformLoc = glGetUniformLocation(ourShader.Program, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));

首先在我们的程序中查找uniform类型变量transform的位置,然后调用glUniformMatrix4fv函数来将我们定义的trans矩阵传递给这个位置。其中需要注意的是:
用于给uniform类型变量传递值的glUniform函数的后缀是Matrix4fv,它的各个参数的含义如下:
第一个参数比较简单,是上面找到的这个uniform类型变量的地址
第二个参数指定我们需要传递的矩阵的个数
第三个参数指定了我们是否需要对这个矩阵进行转置(使用GLM一般不用)
最后一个参数是实际的数据的地址,因为GLM存储的方式和OpenGL接收的方式有所不同,所以需要使用GLM内置的转换函数value_ptr进行数据格式的转换以保证数据的正确输入。

以上,我们利用GLM生成了一个转换矩阵,并且利用uniform mat4类型的变量将这个矩阵值传递到了vertex shader中,并在shader中将对象上的每个坐标向量都进行了左乘操作,结果应该就是这个样子了:

效果达到!

下面我们想让它动起来!让它进行旋转~
基本的步骤是相同的:
首先定义或者说利用GLM生成一个转换矩阵,其次将这个矩阵传递到vertex shader中并进行左乘操作。

glm::mat4 trans;
trans = glm::rotate(trans,(GLfloat)glfwGetTime() * 50.0f, glm::vec3(0.0f, 0.0f, 1.0f));

上面的这个矩阵是随着时间动态改变的,那个时间函数就是我们前面用到的动态改变三角形颜色的方法。定义的旋转轴是z轴。
glm::rotate函数的第一个参数是矩阵,第二个参数是角度,我们设置了随时间变化的角度;第三个参数是参照的方向向量,我们设置的是z轴。得到的效果应该是:

为了得到上述结果,需要注意的是吗,这个矩阵的生成需要在game loop中进行定义,否则变换矩阵并不会进行更新。也就得不到想要的旋转的效果。

所有的代码(main.cpp和shader)在这儿可以得到。

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

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

相关文章

android studio adb 命令行,Android Studio如何配置adb以及经常使用命令

用Android Studio一年多了&#xff0c;都没有使用其调试adb,今天就分享adb配置的方法&#xff0c;分享给你们.android直接打开电脑-属性-高级配置-环境变量。web这里我用图示范给你们&#xff1a;sql这样经常使用adb就配置成功。shell紧接着还有平时经常使用的adb命令&#xff…

【Modern OpenGL】坐标系统 Coordinate Systems

说明&#xff1a;跟着learnopengl的内容学习&#xff0c;不是纯翻译&#xff0c;只是自己整理记录。 强烈推荐原文&#xff0c;无论是内容还是排版。 原文链接 本文地址&#xff1a; http://blog.csdn.net/aganlengzi/article/details/50448453 坐标系统 Coordinate Systems 在…

【Modern OpenGL】摄像机系统 Camera

说明&#xff1a;跟着learnopengl的内容学习&#xff0c;不是纯翻译&#xff0c;只是自己整理记录。 强烈推荐原文&#xff0c;无论是内容还是排版。 原文链接 本文地址&#xff1a;http://blog.csdn.net/aganlengzi/article/details/50448469 摄像机 Camera 在前面的教程中…

UE MATH

1. 求两点的单位向量

材质

1. 随摄像机变化的镜面放射效果 2. 给物体表面增加抛光度 3. 菲涅耳透镜效果

html keyup事件,jquery keyup事件为什么不执行?

先指出你的一个错误点$(#skillKey).on(click, tr, function () {$(this).css(color,red);$(this).keyup(function(){alert(123)});});你这样绑定事件&#xff0c;结果是点击一次tr绑定一次&#xff0c;点了多少次就绑定了多少次&#xff0c;这个例子还是不明显&#xff0c;你在…

cable

1. 建立一个actor&#xff0c;添加一个cable, 然后添加两个mesh作为cable的两个端点 2. 在编辑器中只能设置cable终点attach的mesh和mesh的socket, 因此需要在actor的构成函数里手动的设置 cable起点attach的mesh和socket

Android-将切换tabs的指示器合并到ActionBar上

最近比较忙&#xff0c;好久没更新过博客。国庆第一天没回家&#xff0c;闲下来可以把之前就想贴上来的东西写一下。 使用过Smooth和Fuubo这两个优秀的第三方微博客户端的同学应该见过他们的主页UI&#xff0c;如下图&#xff1a; 他们把切换tabs的指示器放在了ActionBar上&…

html5教学文档笔记,4.HTML 教程- (HTML5 基础)

HTML 教程- (HTML5 基础)1.HTML 标题HTML 标题(Heading)是通过- 标签来定义的.2.HTML 段落HTML 段落是通过标签 来定义的.3.HTML 链接HTML 链接是通过标签 来定义的.提示:在 href 属性中指定链接的地址。菜鸟教程(runoob.com)这是一个链接使用了 href 属性这是一个链接使用了 …

虚幻4渲染系统结构解析

本文根据小米互娱 VR 技术专家 房燕良在 MDCC 2016 移动开发者大会上的演讲整理而成&#xff0c;PPT 下载地址&#xff1a;http://download.csdn.net/detail/sinat_14921509/9639244。 小米互娱 VR 技术专家 房燕良 房燕良&#xff0c;从 2001 年开始&#xff0c;自主研发 3 代…

Windows FFMPEG开发环境配置

1.去FFMPEG网站上下载Dev版本的库&#xff0c;里面有我们需要的头文件和lib文件&#xff0c;然后下载Shared版本的库&#xff0c;里面有我们需要的dll文件 http://ffmpeg.zeranoe.com/builds/ 记得区分32位和64位的库&#xff0c;这里碰到一个大坑&#xff0c;就是我下载的是6…

Ant命令行操作

Ant命令行操作 Ant构建文件可以将项目编译&#xff0c;打包&#xff0c;測试&#xff0c;它是Apache软件基金会jakarta文件夹中的一个子项目&#xff0c;具有跨平台性&#xff0c;操作简单&#xff0c;并且非常easy上手。 关于Ant执行&#xff0c;能够在项目中找到build.xml直接…

在vlan2用计算机名访问,计算机是如何访问一个网页的?vlan间如何实现通信?

昨天我们发布了关于一文讲弄懂什么是vlan、三层交换机、网关、子网掩码&#xff0c;有很多朋友问到关于网络通信的原理&#xff0c;今天我们这一篇文章&#xff0c;算是对昨天文章进行一个补充。首先我们要访问互联网&#xff0c;必须自己电脑上面有ip地址、子网掩码、网关、dn…

使用ffmpeg将BMP图片编码为x264视频文件,将H264视频保存为BMP图片,yuv视频文件保存为图片的代码

ffmpeg开源库&#xff0c;实现将bmp格式的图片编码成x264文件&#xff0c;并将编码好的H264文件解码保存为BMP文件。 实现将视频文件yuv格式保存的图片格式的测试&#xff0c;图像格式png,jpg, gif等等测试均OK 自己根据博客的代码&#xff0c;vs2010搭建的测试环境。资源下载…

川职院单招计算机考什么专业,四川单招考什么科目

2021年高职单招升学一对一咨询小艺老师:18290437291(微信)四川单招考什么科目2019年四川单招考试科目是什么&#xff0c;四川单招考试大概在几月份&#xff1f;四川单招考试都考什么内容&#xff0c;考试会不会很难&#xff1f;不同高校四川单招时间是不同的&#xff0c;一般都…

PHP编写命令行脚本和后台运行程序的注意事项

在一些场合(如开发,测试), 可能需要使用PHP编写一些命令行的处理脚本,或者是长时间后台运行的任务, 需要注意以下准则: 准则1. 尽量避免使用PHP编写后台运行程序, 尤其是类似while(true){….} 这种循环的处理脚本. 比如,有时候我们需要定期检查数据库,然后有数据进行处理,没有数…

通过live555实现H264 RTSP直播

前面的文章中介绍了《H264视频通过RTMP流直播》&#xff0c;下面将介绍一下如何将H264实时视频通过RTSP直播。 实现思路是将视频流发送给live555, 由live555来实现H264数据流直播。 视频采集模块通过FIFO队列将H264数据帧发送给live555. live555 在收到客户端的RTSP播放请求后&…

计算机网络设置端口转发,网件NETGEAR几款路由器端口转发功能设置方法

WPN824, RP614v2&#xff0c;MR814v2&#xff0c;WGR614&#xff0c;WGT624 端口转发设置实例。(以 RP614v2 为例)1. WPN824, RP614v2&#xff0c;MR814v2&#xff0c;WGR614&#xff0c;WGT624 如何设置端口转发&#xff1f; 先登陆到设备的配置截面 在‘高级选项(Advanced)’…