一篇搞懂数学在OpenGL中的应用及矩阵

目录


 话不多说,我把我看的视频链接贴出来,下面的笔记是由视频学习和自己的补充而来。这次是(19-20)的笔记

跟着这个小哥的教学视频学的(YouTube原视频,科学上网AI字幕) ►               http://bit.ly/2lt7ccM
这个是哔哩哔哩网站有人搬运的 ►最好的OpenGL教程之一_哔哩哔哩_bilibili

其实在OpenGL使用中大部分并不特别涉及你需要自己手动去算那么复杂的线性变换,我们先了解一下线性代数的矩阵内容,然后我们再回到代码看看,OpenGL中的投影用到的矩阵的原因和效果

一、图形学中的矩阵

1.矩阵的计算公式

矩阵是一个按照长方阵列排列的数学对象,它在线性代数和其他数学领域中有广泛的应用。以下是一些基本的矩阵计算公式:

1.  矩阵加法:

•   如果两个矩阵 A 和 B的维度相同(即行数和列数相同),它们可以相加。结果矩阵 C 的每个元素等于 A 和 B对应位置的元素之和。 C=A+B

2.  矩阵减法:

•   如果两个矩阵 A 和 B 的维度相同,它们可以相减。结果矩阵 C 的每个元素等于 A 对应位置的元素减去 B 对应位置的元素。 C=A−B

3.  矩阵乘法:

•   如果矩阵 A 的列数等于矩阵 B 的行数,它们可以相乘。结果矩阵 C 的每个元素是 A 的行与 B 的列对应元素的乘积之和。 Cij=∑k=1nAik⋅Bkj

•   这个计算可以用矩阵乘法的运算符表示为 C=AB。

4.  矩阵转置:

•   矩阵的转置是将矩阵的行和列交换得到的新矩阵。如果 A 是一个 m×n 的矩阵,其转置 AT 是一个 n×m 的矩阵,其中 AijT=Aji。

5.  标量与矩阵的乘法:

•   一个标量(常数)可以与一个矩阵的每个元素相乘。如果 c 是一个标量,A 是一个矩阵,那么 cA 的每个元素等于 c 乘以 A 对应位置的元素。

这些是矩阵的基本运算。矩阵在线性代数、图形学、物理学等领域中都有广泛的应用。

2.矩阵变换

让我们通过几个具体的例子来说明不同矩阵变换的计算和实际效果:

1. 平移矩阵:

矩阵计算:

其中 tx 和 ty 是平移的水平和垂直距离。

实际效果: 假设有一个二维点 P(x,y),应用平移矩阵后,新的点 P′(x′,y′) 的坐标为:

2. 缩放矩阵:

矩阵计算:

其中 sx 和 sy 是水平和垂直方向上的缩放因子。

实际效果: 应用缩放矩阵后,二维点 P(x,y) 的新坐标为:

3. 旋转矩阵:

矩阵计算(绕原点旋转):

其中 θ 是旋转角度。

实际效果: 应用旋转矩阵后,二维点 P(x,y) 的新坐标为:

这些例子说明了矩阵计算如何实现平移、缩放和旋转的图形变换。通过将这些变换组合使用,可以创建更复杂的图形效果。在实际应用中,这些变换通常用于构建三维图形的模型视图、投影视图等。

3.为什么旋转,平移都是左乘矩阵,不能右乘

在图形学和计算机图形学中,矩阵乘法的选择通常是基于向量乘法的约定和坐标系统的定义。对于二维和三维图形变换,我们使用齐次坐标系统和矩阵乘法来表示平移、旋转等变换。

齐次坐标系统和矩阵乘法

在齐次坐标系统中,我们使用四维向量来表示三维空间中的点。一个三维点 (x,y,z) 可以表示为四维向量 (x,y,z,1)。这样的表示允许我们使用四维矩阵来表示平移、旋转等变换。

在二维情况下,我们使用三维向量表示二维点,同样使用四维矩阵表示变换。

为什么是左乘矩阵?

矩阵与向量的顺序: 在图形学中,我们通常使用列向量来表示点,这就意味着我们将矩阵放在左边(左乘矩阵)。

复合变换的顺序: 当我们对一个点进行多次变换时,这些变换是按照从右到左的顺序应用的,因此我们使用左乘矩阵来表示这个复合变换。

变换链的可读性: 采用左乘矩阵的方式可以更自然地表示变换的链式结构。例如,如果有一个平移矩阵 T 和一个旋转矩阵 R,则复合变换 R⋅T 表示先平移后旋转。

具体例子

考虑一个平移矩阵 T 和一个旋转矩阵 R,以及一个二维点 (x,y)。对于平移,我们希望点在平移后变成 (x+tx,y+ty)。对于旋转,我们希望点在旋转后变成 (x′,y′)。

如果我们使用左乘矩阵的形式:

其中,tx 和 ty 是平移的水平和垂直距离,θ 是旋转角度。

这种表示方式使得复合变换的应用更为自然。因此,左乘矩阵的选择更符合图形学中的表示约定和变换的顺序。

4.齐次坐标系统

在齐次坐标系统中,四维向量通常表示三维空间中的点或向量,并且有一个额外的第四个分量。这个额外的分量通常是 w,有时也称为齐次坐标。在齐次坐标系统中,四维向量表示为 (x,y,z,w)。

1. 点的表示:

对于一个三维点 (x,y,z),在齐次坐标中表示为 (x,y,z,1)。

这里的 w 常常设置为 1,表示点的齐次坐标。

2. 向量的表示:

对于一个三维向量 (x,y,z),在齐次坐标中同样表示为 (x,y,z,0)。

这里的 w 常常设置为 0,表示向量的齐次坐标。

3. 齐次坐标和矩阵变换:

在矩阵变换中,我们使用四维矩阵来表示平移、旋转等变换。

齐次坐标的引入使得矩阵变换可以统一表示为一个 4×4 的矩阵。

齐次坐标的 w 分量在矩阵变换中用来确保在变换后的坐标系统中正确表示点的位置。

4. 投影:

在透视投影中,齐次坐标的 w 分量用来表示深度信息。

在投影变换中, w 分量的值可以影响到投影的效果,从而实现透视效果。

总结:

齐次坐标中的第四个分量 w 在不同的场景下有不同的含义,但总的来说,它可以用来扩展三维向量和点的表示,使得矩阵变换可以更加灵活地应用于图形学和计算机图形学中的各种操作。

5.变换先后顺序

在图形学中,矩阵的乘法顺序非常重要,它决定了变换的顺序。一般来说,在矩阵乘法中,右乘的矩阵先作用于向量。考虑平移矩阵 T 和旋转矩阵 R,我们可以详细解释它们的作用顺序。

1. 先平移后旋转的顺序(右乘矩阵):

假设有一个二维点 P(x,y)。先应用平移矩阵 T,再应用旋转矩阵 R,得到新的坐标 P′。表示为:

P′=R⋅T⋅P

其中,

具体的计算过程为:

2. 先旋转后平移的顺序(左乘矩阵):

如果我们希望先旋转后平移,即先应用旋转矩阵 R,再应用平移矩阵 T,那么表示为:

P′=T⋅R⋅P

这时,平移矩阵 T 放在左边,旋转矩阵 R 放在右边。具体的计算过程为:

选择哪种顺序取决于具体需求:如果先平移再旋转,对象会绕着自身中心旋转。如果先旋转再平移,对象会绕着全局坐标系的原点旋转后再平移。在实际应用中,选择哪种顺序取决于你希望的变换效果。

二、利用矩阵来变换图形

有些走远了,所以我们还是进入正题,在OpenGL代码中如何进行变换,接下来我们就以正交矩阵来进行变换吧。

1.引入glm数学库,主要是矩阵的库就行:

https://github.com/g-truc/glm/releases/tag/0.9.9.8

#include "glm/glm.hpp"#include "glm/gtc/matrix_transform.hpp"

2.声明一个矩阵 正交矩阵

glm::mat4 proj = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);

3.在着色器类中声明设置矩阵函数

void Shader::SetUniformMat4f(const std::string& name, const glm::mat4& matrix)
{
    //location 指定要修改的统一变量的位置。
    //count 对于vector(glUniform*v)命令,指定要修改的元素数。如果目标统一变量不是数组,则应为1;如果是数组,则为1或更大。
    // 对于matrix(glUniformMatrix*)命令,指定要修改的矩阵数。如果目标一致变量不是矩阵数组,则该值应为1;如果是矩阵数组,应为1或更大。
    //transpose 对于矩阵命令,指定在值加载到统一变量中时是否转置矩阵。
    // v0、v1、v2、v3
    //对于标量命令,指定要用于指定统一变量的新值。
    //value 对于矢量和矩阵命令,指定一个指向计数值数组的指针,该数组将用于更新指定的统一变量。
    //&matrix[0][0]就是把 glm::mat4&转化为数组储存,并引用指针
    GLCall(glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &matrix[0][0]));
}

4.着色器代码、其中u_MVP就是我们的投影矩阵

#shader vertex
#version 330 core

layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texCoord;

out vec2 v_TexCoord;

//模型视觉投影矩阵
uniform mat4 u_MVP;

void main()
{
    //利用正交矩阵把图形的顶点变形到想要的位置上去
    gl_Position = u_MVP * position;
    v_TexCoord = texCoord;
};

5.可以看到我们实际上是用了gl_Position = u_MVP * position;上面数学公式提到的一些矩阵变换数学运算,只不过不是我们去算,意思就是这样去变换

shader.SetUniformMat4f("u_MVP", proj);

就是这样我们完成了在OpenGL中利用矩阵这样的数学高大上的东西,变换了坐标,而且其实很多复杂的变化也是这样来实现的, 其实学过线性代数的应该都知道,这就是线性代数有魅力的地方,就好像魔方一样,通过许多的变换矩阵来完成到你想要的那个展现矩阵上去,帮助我们在OpenGL上完成了对2D矩阵的显示变换。

而我们知道,投影其实也是差不多的意思,当在2D游戏中,就像我们刚刚上面所做的,用一个正交矩阵来完成等比例的放大缩小,而让整个游戏的比例不变,当一个3D的物体要在2D屏幕上显示,还要拥有着3D的视觉,也就是近大远小,就必须有投影的概念,经典的就有正交投影和透视投影两种投影矩阵,无意外就是通过矩阵来实现变换的。

也就是我们可以变化glm::mat4 proj = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);中的这些值,,也可以变换顶点坐标中的值,因为最后渲染器都是会通过数学矩阵变化,把这些顶点值转换为一个个在-1到1间的值,比如最后值在0.25,就是在屏幕中心往右1/4的位置,这就是标准统一坐标系的引入了,所以无非就是用过这些计算,复杂的图像复杂的计算来的出这些图像的位置或者大小,呈现的xyz轴坐标,达到显示的目的,这就是OpenGL中的数学应用了。

(补充)

现在我们涉及另外两个矩阵,一个是model矩阵,一个是view矩阵,外加上我们知道的projection矩阵,
这三个矩阵就是MVP矩阵,而且通常就是以MVP的顺序来进行变换的。

我们可以想象一下这个渲染过程
首先,我们并没有图,然后我们有了一个立方体的顶点数组,这个时候,我们要建模,对吧,先要把立方体这些顶点先画出来,然后这个时候,如果这个立方体要变化了呢,在这个过程中要用到model矩阵进行变换,我概括为物体自己动,然后就是view矩阵,你可以把view矩阵当成一个相机,相机在左边看一个立方体和在右边看一个立方体,是不是会不一样,这个时候我们相当于要画一个立方体的左侧视图和右侧视图,这种变换就需要用到view矩阵,我概括为我们人的视角动,最后就是投影矩阵了,前面已经说过了,投影矩阵就是建立起一个标准的坐标系,让我们的立方体投影到对应的位置上去,而不是东一块西一块,这就是投影矩阵的作用,我概括为OpenGL要把画面打在2D的屏幕上面。

三、OpenGL中的三种变换矩阵

在OpenGL中,MVP(Model-View-Projection)矩阵是一种用于进行3D图形变换的常见矩阵组合。下面是它们的作用分别:

Model Matrix(模型矩阵):作用: 用于将模型的局部坐标系转换为世界坐标系。模型矩阵包含了平移、旋转和缩放等变换,使得模型可以在世界空间中正确定位和变换。
公式: FinalPosition = ModelMatrix * VertexPosition

View Matrix(视图矩阵):作用: 用于定义观察者的视角和位置,将世界坐标系中的物体变换到相机坐标系。视图矩阵决定了我们从哪个视角观察场景。
公式: CameraSpacePosition = ViewMatrix * WorldSpacePosition

Projection Matrix(投影矩阵):作用: 用于将相机坐标系中的物体变换为裁剪坐标系,进行透视或正交投影。投影矩阵定义了视锥体,将场景投影到裁剪坐标系,最终形成我们看到的屏幕图像。
公式: ClipSpacePosition = ProjectionMatrix * CameraSpacePosition

MVP Matrix(Model-View-Projection 矩阵):作用: 将模型、视图和投影矩阵组合在一起,用于将局部坐标系的模型变换到屏幕空间。MVP矩阵是通过将模型矩阵、视图矩阵和投影矩阵相乘得到的。
公式: ClipSpacePosition = MVPMatrix * VertexPosition

总体而言,MVP矩阵的应用是将3D场景中的对象正确变换和投影到屏幕上,使得它们在屏幕上正确可视化。这些矩阵的组合和使用是3D图形渲染中的基本步骤。

proj * view * model矩阵是这样然后* position的,所以真正运算是先model再view再projecttion的,而在数学类中,这个vec不是矩阵,是靠translate转换为矩阵的

glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(200, 200, 0));

这样就比我们纯写矩阵要好,因为这样我们看看

//声明一个矩阵,用于view矩阵变换
        glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(-100, 0, 0));

这个就是把视角向右移动100,然后观察就知道物体应该向左移动100,我们写成glm::vec3(-100, 0, 0)就很方便

//声明一个矩阵,用于model矩阵变换
glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(200, 200, 0));

 这个就是把物体向上向右移动200,是不是看到数据就很直观,然后我们再代码中这样子的矩阵变量传给着色器中的uniform变量矩阵,就可以达到那些平移,投影,变化等等的效果了。

		//声明一个矩阵 正交矩阵用于投影,proj和坐标position相乘,投影在屏幕的对应位置
		glm::mat4 proj = glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f);

		//声明一个矩阵,用于view矩阵变换
		glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 0));
		
		//声明一个矩阵,用于model矩阵变换
		glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(200, 200, 0));
		//把三种变换融合 proj * view * model矩阵是这样然后* position的,所以真正运算是先model再view再projecttion的
		glm::mat4 mvp = proj * view * model;

		shader.Bind();
		shader.SetUniformMat4f("u_MVP", mvp);

 

版权声明:本文为博主作者:$老无所依¥原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/CodeWorld1999/article/details/135170937

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2024年4月1日
下一篇 2024年4月1日

相关推荐