跟着LearnOpenGL学习6--变换

文章目录

  • 一、前言
  • 二、向量
    • 2.1、向量与标量运算
    • 2.2、向量取反
    • 2.3、向量加减
    • 2.4、长度
    • 2.5、向量相乘
  • 三、矩阵
    • 3.1、矩阵的加减
    • 3.2、矩阵的数乘
    • 3.3、矩阵相乘
  • 四、矩阵与向量相乘
    • 4.1、单位矩阵
    • 4.2、缩放
    • 4.3、位移
    • 4.4、旋转
    • 4.5、矩阵的组合
  • 五、变换实战
    • 5.1、GLM配置
    • 5.2、GLM测试
    • 5.3、GLM实战

一、前言

尽管我们现在已经知道了如何创建一个物体、着色、加入纹理,给它们一些细节的表现,但因为它们都还是静态的物体,仍是不够有趣。

我们可以尝试着在每一帧改变物体的顶点并且重配置缓冲区从而使它们移动,但这太繁琐了,而且会消耗很多的处理时间。

我们现在有一个更好的解决方案,使用(多个)矩阵(Matrix)对象可以更好的变换(Transform)一个物体。矩阵是一种非常有用的数学工具,尽管听起来可能有些吓人,不过一旦你理解了它们后,它们会变得非常有用。

为了深入了解变换,我们首先要在讨论矩阵之前进一步了解一下向量。这一节的目标是让你拥有将来需要的最基础的数学背景知识。如果你发现这节十分困难,尽量尝试去理解它们,当你以后需要它们的时候回过头来复习这些概念。


二、向量

向量:一个有方向(Direction)和大小(Magnitude)的箭头;

你可以把向量想像成一个藏宝图上的指示:“向左走10步,向北走3步,然后向右走5步”;“左”就是方向,“10步”就是向量的长度。那么这个藏宝图的指示一共有3个向量。向量可以在任意维度(Dimension)上,但是我们通常只使用2至4维。如果一个向量有2个维度,它表示一个平面的方向(想象一下2D的图像),当它有3个维度的时候它可以表达一个3D世界的方向。

下面你会看到3个向量,每个向量在2D图像中都用一个箭头(x, y)表示。我们在2D图片中展示这些向量,因为这样子会更直观一点。你可以把这些2D向量当做z坐标为0的3D向量。由于向量表示的是方向,起始于何处并不会改变它的值。下图我们可以看到向量v¯和w¯是相等的,尽管他们的起始点不同:

在这里插入图片描述
数学家喜欢在字母上面加一横表示向量,比如说v¯。当用在公式中时它们通常是这样的:

在这里插入图片描述
由于向量是一个方向,所以有些时候会很难形象地将它们用位置(Position)表示出来。为了让其更为直观,我们通常设定这个方向的原点为(0, 0, 0),然后指向一个方向,对应一个点,使其变为位置向量。比如说位置向量(3, 5)在图像中的起点会是(0, 0),并会指向(3, 5)。我们可以使用向量在2D或3D空间中表示方向与位置,和普通数字一样,我们也可以用向量进行多种运算。


2.1、向量与标量运算

标量(Scalar)只是一个数字(或者说是仅有一个分量的向量)。当把一个向量加/减/乘/除一个标量,我们可以简单的把向量的每个分量分别进行该运算。对于加法来说会像这样:

在这里插入图片描述
注意

  • 其中的+可以是+,-,·或÷,其中·是乘号;
  • -和÷运算时不能颠倒(标量-/÷向量),因为颠倒的运算是没有定义的;

注意,数学上是没有向量与标量相加这个运算的,但是很多线性代数的库都对它有支持(比如说我们用的GLM)。


2.2、向量取反

对一个向量取反(Negate)会将其方向逆转。一个指向东北的向量取反后就指向西南方向了。我们在一个向量的每个分量前加负号就可以实现取反了(或者说用-1数乘该向量):

在这里插入图片描述


2.3、向量加减

向量的加法可以被定义为是分量的(Component-wise)相加,即将一个向量中的每一个分量加上另一个向量的对应分量:

在这里插入图片描述
在这里插入图片描述
就像普通数字的加减一样,向量的减法等于加上第二个向量的相反向量:

在这里插入图片描述
两个向量的相减会得到这两个向量指向位置的差。这在我们想要获取两点的差会非常有用;

在这里插入图片描述


2.4、长度

我们使用勾股定理(Pythagoras Theorem)来获取向量的长度(Length)/大小(Magnitude)。如果你把向量的x与y分量画出来,该向量会和x与y分量为边形成一个三角形:

在这里插入图片描述
因为两条边(x和y)是已知的,如果希望知道斜边 v ⃗ \vec{v} v 的长度,我们可以直接通过勾股定理来计算:

在这里插入图片描述
例子中向量(4, 2)的长度等于:

在这里插入图片描述
有一个特殊类型的向量叫做单位向量(Unit Vector)。单位向量有一个特别的性质——它的长度是1;

我们可以用任意向量的每个分量除以向量的长度得到它的单位向量:
在这里插入图片描述
我们把这种方法叫做一个向量的标准化(Normalizing)。单位向量头上有一个^样子的记号。通常单位向量会变得很有用,特别是在我们只关心方向不关心长度的时候(如果改变向量的长度,它的方向并不会改变)。


2.5、向量相乘

两个向量相乘是一种很奇怪的情况。普通的乘法在向量上是没有定义的,因为它在视觉上是没有意义的。

但是在相乘的时候我们有两种特定情况可以选择:一个是点乘(Dot Product),记作 v ⃗ \vec{v} v · k ⃗ \vec{k} k ,另一个是叉乘(Cross Product),记作 v ⃗ \vec{v} v x k ⃗ \vec{k} k

点乘
两个向量的点乘等于它们的数乘结果乘以两个向量之间夹角的余弦值:

在这里插入图片描述
它们之间的夹角记作θ。为什么这很有用?想象如果v¯和k¯都是单位向量,它们的长度会等于1。这样公式会有效简化成:

在这里插入图片描述
现在点积只定义了两个向量的夹角。你也许记得90度的余弦值是0,0度的余弦值是1。使用点乘可以很容易测试两个向量是否正交(Orthogonal)或平行(正交意味着两个向量互为直角)

点乘是通过将对应分量逐个相乘,然后再把所得积相加来计算的:

在这里插入图片描述
要计算两个单位向量间的夹角,我们可以使用反余弦函数cos−1 ,可得结果是143.1度。现在我们很快就计算出了这两个向量的夹角。点乘会在计算光照的时候非常有用。

叉乘
叉乘只在3D空间中有定义,它需要两个不平行向量作为输入,生成一个正交于两个输入向量的第三个向量。如果输入的两个向量也是正交的,那么叉乘之后将会产生3个互相正交的向量。接下来的教程中这会非常有用。下面的图片展示了3D空间中叉乘的样子:

在这里插入图片描述
不同于其他运算,如果你没有钻研过线性代数,可能会觉得叉乘很反直觉,所以只记住公式就没问题啦(记不住也没问题)。下面你会看到两个正交向量A和B叉积:
在这里插入图片描述


三、矩阵

简单来说矩阵就是一个矩形的数字、符号或表达式数组。矩阵中每一项叫做矩阵的元素(Element)。下面是一个2×3矩阵的例子:
在这里插入图片描述
矩阵可以通过(i, j)进行索引,i是行,j是列,这就是上面的矩阵叫做2×3矩阵的原因(2行3列,也叫做矩阵的维度(Dimension))。这与你在索引2D图像时的(x, y)相反,获取4的索引是(2, 1)(第二行,第一列)(译注:如果是图像索引应该是(1, 2),先算列,再算行)。

矩阵基本也就是这些了,它就是一个矩形的数学表达式阵列。和向量一样,矩阵也有非常漂亮的数学属性。矩阵有几个运算,分别是:矩阵加法、减法和乘法。

3.1、矩阵的加减

矩阵与标量之间的加减定义如下:
在这里插入图片描述
标量值要加到矩阵的每一个元素上。矩阵与标量的减法也相似:
在这里插入图片描述

注意,数学上是没有矩阵与标量相加减的运算的,但是很多线性代数的库都对它有支持(比如说我们用的GLM)。

矩阵与矩阵之间的加减就是两个矩阵对应元素的加减运算,所以总体的规则和与标量运算是差不多的,只不过在相同索引下的元素才能进行运算。这也就是说加法和减法只对同维度的矩阵才是有定义的。一个3×2矩阵和一个2×3矩阵(或一个3×3矩阵与4×4矩阵)是不能进行加减的。我们看看两个2×2矩阵是怎样相加的:
在这里插入图片描述
同样的法则也适用于减法:
在这里插入图片描述


3.2、矩阵的数乘

和矩阵与标量的加减一样,矩阵与标量之间的乘法也是矩阵的每一个元素分别乘以该标量;
在这里插入图片描述
现在我们也就能明白为什么这些单独的数字要叫做标量(Scalar)了。简单来说,标量就是用它的值缩放(Scale)矩阵的所有元素(译注:注意Scalar是由Scale + -ar演变过来的)。前面那个例子中,所有的元素都被放大了2倍。


3.3、矩阵相乘

矩阵相乘还有一些限制:

  • 只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘,例如1x3与3x4可以相乘;
  • 矩阵相乘不遵守交换律(Commutative),也就是说A⋅B≠B⋅A;

在这里插入图片描述
基本规则:

  • 第一行乘第一列求和,组成新矩阵的第一行第一个数;
  • 第一行乘第二列求和,组成新矩阵的第一行第二个数;
  • 第一行乘第三列求和,组成新矩阵的第一行第三个数;
  • 第二行乘第一列求和,组成新矩阵的第二行第一个数;
  • 以此类推;

四、矩阵与向量相乘

前面已将了解了矩阵相乘,向量可以看成是一个N行1列的矩阵,那么按矩阵乘法的规则,一个M行N列的矩阵和一个N行1列的向量是可以相乘的。

但是为什么我们会关心矩阵能否乘以一个向量?好吧,正巧,很多有趣的2D/3D变换都可以放在一个矩阵中,用这个矩阵乘以我们的向量将**变换(Transform)**这个向量。如果你仍然有些困惑,我们来看一些例子,你很快就能明白了。

4.1、单位矩阵

在OpenGL中,由于某些原因我们通常使用4×4的变换矩阵,而其中最重要的原因就是大部分的向量都是4分量的。我们能想到的最简单的变换矩阵就是单位矩阵(Identity Matrix)。单位矩阵是一个除了对角线以外都是0的N×N矩阵。这种变换矩阵使一个向量完全不变:

在这里插入图片描述

向量看起来完全没变。从乘法法则来看就很容易理解来:第一个结果元素是矩阵的第一行的每个元素乘以向量的每个对应元素。因为每行的元素除了第一个都是0;

你可能会奇怪一个没变换的变换矩阵有什么用?单位矩阵通常是生成其他变换矩阵的起点,如果我们深挖线性代数,这还是一个对证明定理、解线性方程非常有用的矩阵。


4.2、缩放

对一个向量进行缩放(Scaling)就是对向量的长度进行缩放,而保持它的方向不变。

由于我们进行的是2维或3维操作,我们可以分别定义一个有2或3个缩放变量的向量,每个变量缩放一个轴(x、y或z)。

我们先来尝试缩放向量:

  • 把向量沿着x轴缩放0.5,使它的宽度缩小为原来的二分之一;
  • 沿着y轴把向量的高度缩放为原来的两倍;

我们看看把向量缩放(0.5, 2)倍所获得的是什么样的:

(3, 2) · (0.5, 2) = (1.5, 4)
在这里插入图片描述
记住,OpenGL通常是在3D空间进行操作的,对于2D的情况我们可以把z轴缩放1倍,这样z轴的值就不变了。

我们刚刚的缩放操作是不均匀(Non-uniform)缩放,因为每个轴的缩放因子(Scaling Factor)都不一样。如果每个轴的缩放因子都一样那么就叫均匀缩放(Uniform Scale)

我们下面会构造一个变换矩阵来为我们提供缩放功能。我们从单位矩阵了解到,每个对角线元素会分别与向量的对应元素相乘。如果我们把1变为3会怎样?这样子的话,我们就把向量的每个元素乘以3了,这事实上就把向量缩放3倍。如果我们把缩放变量表示为我们可以为任意向量定义一个缩放矩阵:

在这里插入图片描述

注意:第四个缩放向量仍然是1,因为在3D空间中缩放w分量是无意义的。w分量另有其他用途,在后面我们会看到。


4.3、位移

**位移(Translation)**是在原始向量的基础上,加上另一个向量,从而获得一个在不同位置的新向量的过程,从而在位移向量基础上移动了原始向量。

和缩放矩阵一样,在4×4矩阵上有几个特别的位置用来执行特定的操作,对于位移来说它们是第四列最上面的3个值。如果我们把位移向量表示为**(Tx,Ty,Tz)**,我们就能把位移矩阵定义为:

在这里插入图片描述
这样是能工作的,因为所有的位移值都要乘以向量的w行,所以位移值会加到向量的原始值上(想想矩阵乘法法则)。而如果你用3x3矩阵我们的位移值就没地方放也没地方乘了,所以是不行的。

齐次坐标(Homogeneous Coordinates)
向量的w分量也叫齐次坐标。想要从齐次向量得到3D向量,我们可以把x、y和z坐标分别除以w坐标。我们通常不会注意这个问题,因为w分量通常是1.0。使用齐次坐标有几点好处:它允许我们在3D向量上进行位移(如果没有w分量我们是不能位移向量的),而且下一章我们会用w值创建3D视觉效果。
如果一个向量的齐次坐标是0,这个坐标就是方向向量(Direction Vector),因为w坐标是0,这个向量就不能位移(译注:这也就是我们说的不能位移一个方向)。


4.4、旋转

首先我们来定义一个向量的旋转到底是什么。2D或3D空间中的旋转用角(Angle)来表示。角可以是角度制或弧度制的,周角是360角度或2 PI弧度。我个人更喜欢用角度,因为它们看起来更直观。

大多数旋转函数需要用弧度制的角,但幸运的是角度制的角也可以很容易地转化为弧度制的:

  • 弧度转角度:角度 = 弧度 * (180.0f / PI)
  • 角度转弧度:弧度 = 角度 * (PI / 180.0f)
    PI约等于3.14159265359。

转半圈会旋转360/2 = 180度,向右旋转1/5圈表示向右旋转360/5 = 72度。下图中展示的2D向量 v ⃗ \vec{v} v 是由 k ⃗ \vec{k} k 向右旋转72度所得的:

在这里插入图片描述

  • 在3D空间中旋转需要定义一个角和一个旋转轴(Rotation Axis);
  • 物体会沿着给定的旋转轴旋转特定角度;

使用三角学,给定一个角度,可以把一个向量变换为一个经过旋转的新向量。这通常是使用一系列正弦和余弦函数(一般简称sin和cos)各种巧妙的组合得到的。当然,讨论如何生成变换矩阵超出了这个教程的范围。

旋转矩阵在3D空间中每个单位轴都有不同定义,旋转角度用θ表示:

沿x轴旋转
在这里插入图片描述

沿y轴旋转
在这里插入图片描述

沿z轴旋转
在这里插入图片描述

  • 利用旋转矩阵我们可以把任意位置向量沿一个单位旋转轴进行旋转;
  • 也可以将多个矩阵复合,比如先沿着x轴旋转再沿着y轴旋转;
  • 但是这会很快导致一个问题——万向节死锁(Gimbal Lock);
  • 在这里我们不会讨论它的细节,但是对于3D空间中的旋转,一个更好的模型是沿着任意的一个轴旋转;
  • 而不是对一系列旋转矩阵进行复合;
  • 这样的一个(超级麻烦的)矩阵是存在的,见下面这个公式,其中(Rx,Ry,Rz)代表任意旋转轴:

在这里插入图片描述
在数学上讨论如何生成这样的矩阵仍然超出了本节内容。但是记住,即使这样一个矩阵也不能完全解决万向节死锁问题(尽管会极大地避免)。避免万向节死锁的真正解决方案是使用四元数(Quaternion),它不仅更安全,而且计算会更有效率。四元数可能会在后面的教程中讨论。


4.5、矩阵的组合

使用矩阵进行变换的真正力量在于,根据矩阵之间的乘法,我们可以把多个变换组合到一个矩阵中。

让我们看看我们是否能生成一个变换矩阵,让它组合多个变换。假设我们有一个顶点(x, y, z),我们希望将其缩放2倍,然后位移(1, 2, 3)个单位。我们需要一个位移和缩放矩阵来完成这些变换。结果的变换矩阵看起来像这样:

在这里插入图片描述
注意

  • 当矩阵相乘时我们先写位移再写缩放变换的;
  • 矩阵乘法是不遵守交换律的,这意味着它们的顺序很重要;
  • 当矩阵相乘时,在最右边的矩阵是第一个与向量相乘的,所以你应该从右向左读这个乘法;
  • 建议您在组合矩阵时,先缩放,然后旋转,最后位移,否则它们会(消极地)互相影响;
  • 比如,如果你先位移再缩放,位移的向量也会同样被缩放(译注:比如向某方向移动2米,2米也许会被缩放成1米);

用最终的变换矩阵左乘我们的向量会得到以下结果:

在这里插入图片描述
不错!向量先缩放2倍,然后位移了(1, 2, 3)个单位。


五、变换实战

现在我们已经解释了变换背后的所有理论,是时候将这些知识利用起来了;OpenGL没有自带任何的矩阵和向量知识,幸运的是,有个易于使用,专门为OpenGL量身定做的数学库,那就是GLM。

5.1、GLM配置

GLM是OpenGL Mathematics的缩写,它是一个只有头文件的库,也就是说我们只需包含对应的头文件就行了,不用链接和编译。

GLM可以在它们的网站:https://glm.g-truc.net/0.9.8/index.html上下载。把头文件的根目录复制到你的includes文件夹,然后你就可以使用这个库了。

下载完可以看到glm文件夹中全是头文件,所以将glm文件夹复制到自己的工程路径下:

在这里插入图片描述
在这里插入图片描述

工程文件(*.pro)添加路径依赖:

INCLUDEPATH += $$PWD/glm
DEPENDPATH += $$PWD/glm

在这里插入图片描述

注意:GLM库从0.9.9版本起,默认会将矩阵类型初始化为一个零矩阵(所有元素均为0),而不是单位矩阵(对角元素为1,其它元素为0)。如果你使用的是0.9.9或0.9.9以上的版本,你需要将所有的矩阵初始化改为glm::mat4 mat = glm::mat4(1.0f);我选的是0.9.8.5版本;


5.2、GLM测试

我们需要的GLM的大多数功能都可以从下面这3个头文件中找到:

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

举例:把一个向量(1, 0, 0)位移(1, 1, 0)个单位

//测试代码
//===================================================
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;
qDebug() << vec.x << vec.y << vec.z;    //(1, 0, 0) --> (2, 1, 0)
//===================================================

在这里插入图片描述


5.3、GLM实战

操作1:把箱子逆时针旋转90度。然后缩放0.5倍,使它变成原来的一半大。我们先来创建变换矩阵:

如何把矩阵传递给着色器?我们在前面简单提到过GLSL里也有一个mat4类型。所以我们将修改顶点着色器让其接收一个mat4的uniform变量,然后再用矩阵uniform乘以位置向量:

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

在把位置向量传给gl_Position之前,我们先添加一个uniform,并且将其与变换矩阵相乘。我们的箱子现在应该是原来的二分之一大小并(向左)旋转了90度。当然,我们仍需要把变换矩阵传递给着色器:

unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
  • 首先查询uniform变量的地址,然后用有Matrix4fv后缀的glUniform函数把矩阵数据发送给着色器;
  • 第一个参数你现在应该很熟悉了,它是uniform的位置值;
  • 第二个参数告诉OpenGL我们将要发送多少个矩阵,这里是1;
  • 第三个参数询问我们是否希望对我们的矩阵进行转置(Transpose),也就是说交换我们矩阵的行和列;
  • OpenGL开发者通常使用一种内部矩阵布局,叫做列主序(Column-major Ordering)布局。GLM的默认布局就是列主序,所以并不需要转置矩阵,我们填GL_FALSE
  • 最后一个参数是真正的矩阵数据,但是GLM并不是把它们的矩阵储存为OpenGL所希望接受的那种,因此我们要先用GLM的自带的函数value_ptr来变换这些数据;

完整代码
在这里插入图片描述

#include "mainwindow.h"
#include <QApplication>//在包含GLFW的头文件之前包含了GLAD的头文件;
//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h);
//所以需要在其它依赖于OpenGL的头文件之前包含GLAD;
#include <glad/glad.h>
#include <GLFW/glfw3.h>//GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>#include <iostream>
#include <QDebug>#include "shader.h"
#include "stb_image.h"void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;using namespace std;int main(int argc, char *argv[])
{QApplication a(argc, argv);//MainWindow w;//w.show();//初始化GLFW//--------------------glfwInit();//配置GLFW//--------------------//告诉GLFW使用的OpenGL本是3.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//告诉GLFW使用的是核心模式(Core-profile)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//创建一个新的OpenGL环境和窗口//-----------------------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();    //glfw销毁窗口和OpenGL环境,并释放资源return -1;}//设置参数window中的窗口所关联的OpenGL环境为当前环境//-----------------------------------glfwMakeContextCurrent(window);//设置窗口尺寸改变大小时的回调函数(窗口尺寸发送改变时会自动调用)//-----------------------------------glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//glad加载系统相关的OpenGL函数指针//---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}Shader ourShader("C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs","C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs");//顶点数据//---------------------------------------------------------------------float vertices[] = {//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上};unsigned int indices[] = {0, 1, 3, // first triangle1, 2, 3  // second triangle};unsigned int VBO, VAO, EBO;glGenVertexArrays(1, &VAO);     //创建顶点数组对象glGenBuffers(1, &VBO);          //创建顶点缓冲对象glGenBuffers(1, &EBO);glBindVertexArray(VAO);         //绑定VAOglBindBuffer(GL_ARRAY_BUFFER, VBO);     //将VBO与GL_ARRAY_BUFFER缓冲区绑定glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  //将顶点数据复制到GL_ARRAY_BUFFER缓冲区,之后可通过VBO进行操作glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//设定顶点属性指针//位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//颜色属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);//纹理属性glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));glEnableVertexAttribArray(2);//创建纹理// -------------------------//纹理1unsigned int texture1;glGenTextures(1, &texture1);glBindTexture(GL_TEXTURE_2D, texture1);//设置纹理映射参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);//设置纹理过滤参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//加载图片、创建纹理、generate mipmapsint width, height, nrChannels;stbi_set_flip_vertically_on_load(true);unsigned char *data = stbi_load("C:/Qt_Pro/OpenGL_GLFW/Sources/container.jpg", &width, &height, &nrChannels, 0);if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);//纹理2unsigned int texture2;glGenTextures(1, &texture2);glBindTexture(GL_TEXTURE_2D, texture2);//设置纹理映射参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);//设置纹理过滤参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//加载图片、创建纹理、generate mipmapsdata = stbi_load("C:/Qt_Pro/OpenGL_GLFW/Sources/awesomeface.png", &width, &height, &nrChannels, 0);if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);ourShader.use();glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置ourShader.setInt("texture2", 1); // 或者使用着色器类设置//渲染循环//我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;//我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;//因此,我们需要在程序中添加一个while循环,它能在我们让GLFW退出前一直保持运行;//------------------------------------------------------------------------------while (!glfwWindowShouldClose(window))  //如果用户准备关闭参数window所指定的窗口,那么此接口将会返回GL_TRUE,否则将会返回GL_FALSE{//用户输入//------------------------------------------------------------------------------processInput(window);   //检测是否有输入//渲染指令//------------------------------------------------------------------------------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);//绑定纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);//创建变换矩阵glm::mat4 transform;transform = glm::rotate(transform, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));transform = glm::scale(transform, glm::vec3(0.5, 0.5, 0.5));// get matrix's uniform location and set matrixourShader.use();    //激活着色器程序对象unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));//绘制三角形glBindVertexArray(VAO);             //绑定VAOglDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// glBindVertexArray(0);            //解绑VAO//告诉GLFW检查所有等待处理的事件和消息,包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待,它会先处理这些消息再返回;否则该函数会立即返回//---------------------------------------------------------------------------------------------------------------------------------glfwPollEvents();//请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)//------------------------------------------------------------------------------glfwSwapBuffers(window);}//释放资源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);//glDeleteProgram(shaderProgram);//glfw销毁窗口和OpenGL环境,并释放资源(之后必须再次调用glfwInit()才能使用大多数GLFW函数)//------------------------------------------------------------------glfwTerminate();return a.exec();
}//检测是否有输入
//---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)   //ESC键,退出glfwSetWindowShouldClose(window, true);
}//给glfw窗口注册的尺寸改变回调函数
//---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{//确保视口匹配新的窗口尺寸,请注意:宽度和高度将比视网膜显示器上指定的大得多glViewport(0, 0, width, height);
}

完美!我们的箱子向左侧旋转,并是原来的一半大小,所以变换成功了!


操作2:把箱子放在窗口的右下角,让箱子随着时间旋转
要让箱子随着时间推移旋转,我们必须在游戏循环中更新变换矩阵,因为它在每一次渲染迭代中都要更新。我们使用GLFW的时间函数来获取不同时间的角度:

glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));

要记住的是前面的例子中我们可以在任何地方声明变换矩阵,但是现在我们必须在每一次迭代中创建它,从而保证我们能够不断更新旋转角度。这也就意味着我们不得不在每次游戏循环的迭代中重新创建变换矩阵。通常在渲染场景的时候,我们也会有多个需要在每次渲染迭代中都用新值重新创建的变换矩阵。

在这里我们先把箱子围绕原点(0, 0, 0)旋转,之后,我们把旋转过后的箱子位移到屏幕的右下角。记住,实际的变换顺序应该与阅读顺序相反:尽管在代码中我们先位移再旋转,实际的变换却是先应用旋转再是位移的。明白所有这些变换的组合,并且知道它们是如何应用到物体上是一件非常困难的事情。只有不断地尝试和实验这些变换你才能快速地掌握它们。

着色器不需要修改,和上面操作一致!!!

完整代码
在这里插入图片描述

#include "mainwindow.h"
#include <QApplication>//在包含GLFW的头文件之前包含了GLAD的头文件;
//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h);
//所以需要在其它依赖于OpenGL的头文件之前包含GLAD;
#include <glad/glad.h>
#include <GLFW/glfw3.h>//GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>#include <iostream>
#include <QDebug>#include "shader.h"
#include "stb_image.h"void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;using namespace std;int main(int argc, char *argv[])
{QApplication a(argc, argv);//MainWindow w;//w.show();//初始化GLFW//--------------------glfwInit();//配置GLFW//--------------------//告诉GLFW使用的OpenGL本是3.3glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//告诉GLFW使用的是核心模式(Core-profile)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//创建一个新的OpenGL环境和窗口//-----------------------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();    //glfw销毁窗口和OpenGL环境,并释放资源return -1;}//设置参数window中的窗口所关联的OpenGL环境为当前环境//-----------------------------------glfwMakeContextCurrent(window);//设置窗口尺寸改变大小时的回调函数(窗口尺寸发送改变时会自动调用)//-----------------------------------glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//glad加载系统相关的OpenGL函数指针//---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}Shader ourShader("C:/Qt_Pro/OpenGL_GLFW/shader/shader.vs","C:/Qt_Pro/OpenGL_GLFW/shader/shader.fs");//顶点数据//---------------------------------------------------------------------float vertices[] = {//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上};unsigned int indices[] = {0, 1, 3, // first triangle1, 2, 3  // second triangle};unsigned int VBO, VAO, EBO;glGenVertexArrays(1, &VAO);     //创建顶点数组对象glGenBuffers(1, &VBO);          //创建顶点缓冲对象glGenBuffers(1, &EBO);glBindVertexArray(VAO);         //绑定VAOglBindBuffer(GL_ARRAY_BUFFER, VBO);     //将VBO与GL_ARRAY_BUFFER缓冲区绑定glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  //将顶点数据复制到GL_ARRAY_BUFFER缓冲区,之后可通过VBO进行操作glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//设定顶点属性指针//位置属性glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//颜色属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);//纹理属性glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));glEnableVertexAttribArray(2);//创建纹理// -------------------------//纹理1unsigned int texture1;glGenTextures(1, &texture1);glBindTexture(GL_TEXTURE_2D, texture1);//设置纹理映射参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);//设置纹理过滤参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//加载图片、创建纹理、generate mipmapsint width, height, nrChannels;stbi_set_flip_vertically_on_load(true);unsigned char *data = stbi_load("C:/Qt_Pro/OpenGL_GLFW/Sources/container.jpg", &width, &height, &nrChannels, 0);if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);//纹理2unsigned int texture2;glGenTextures(1, &texture2);glBindTexture(GL_TEXTURE_2D, texture2);//设置纹理映射参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);//设置纹理过滤参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//加载图片、创建纹理、generate mipmapsdata = stbi_load("C:/Qt_Pro/OpenGL_GLFW/Sources/awesomeface.png", &width, &height, &nrChannels, 0);if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);ourShader.use();glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置ourShader.setInt("texture2", 1); // 或者使用着色器类设置//渲染循环//我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口;//我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入;//因此,我们需要在程序中添加一个while循环,它能在我们让GLFW退出前一直保持运行;//------------------------------------------------------------------------------while (!glfwWindowShouldClose(window))  //如果用户准备关闭参数window所指定的窗口,那么此接口将会返回GL_TRUE,否则将会返回GL_FALSE{//用户输入//------------------------------------------------------------------------------processInput(window);   //检测是否有输入//渲染指令//------------------------------------------------------------------------------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);//绑定纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);//创建变换矩阵glm::mat4 transform = glm::mat4(1.0f); //初始化单位矩阵transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f));    //创建位移矩阵transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));  //创建旋转矩阵// get matrix's uniform location and set matrixourShader.use();    //激活着色器程序对象unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));//绘制三角形glBindVertexArray(VAO);             //绑定VAOglDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// glBindVertexArray(0);            //解绑VAO//告诉GLFW检查所有等待处理的事件和消息,包括操作系统和窗口系统中应当处理的消息。如果有消息正在等待,它会先处理这些消息再返回;否则该函数会立即返回//---------------------------------------------------------------------------------------------------------------------------------glfwPollEvents();//请求窗口系统将参数window关联的后缓存画面呈现给用户(双缓冲绘图)//------------------------------------------------------------------------------glfwSwapBuffers(window);}//释放资源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);//glDeleteProgram(shaderProgram);//glfw销毁窗口和OpenGL环境,并释放资源(之后必须再次调用glfwInit()才能使用大多数GLFW函数)//------------------------------------------------------------------glfwTerminate();return a.exec();
}//检测是否有输入
//---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)   //ESC键,退出glfwSetWindowShouldClose(window, true);
}//给glfw窗口注册的尺寸改变回调函数
//---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{//确保视口匹配新的窗口尺寸,请注意:宽度和高度将比视网膜显示器上指定的大得多glViewport(0, 0, width, height);
}

这就是我们刚刚做到的!一个位移过的箱子,它会一直转,一个变换矩阵就做到了!现在你可以明白为什么矩阵在图形领域是一个如此重要的工具了。我们可以定义无限数量的变换,而把它们组合为仅仅一个矩阵,如果愿意的话我们还可以重复使用它。在着色器中使用矩阵可以省去重新定义顶点数据的功夫,它也能够节省处理时间,因为我们没有一直重新发送我们的数据(这是个非常慢的过程)。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

流氓软件篡改微软EDGE浏览器主页面的那些伎俩

微软的EDGE浏览器很好用&#xff0c;但也很容易被绑架&#xff0c;在网上下载各类免费软件&#xff0c;只要你安装完&#xff0c;十有八九就给你把主页改成某某导航了。尽管打开EDGE直接进入360、毒霸、好123等链接对上网影响也不大&#xff0c;打开这些导航页面后&#xff0c;…

npm install报错 -> npm ERR! Unexpected token ‘.‘ 报错解决办法

原因&#xff1a; 我遇到这个问题的场景是用nvm1.1.7的版本安装了16.x以上的node, 然后再下载依赖的时候就报错了 总结一下就是nvm版本太低了&#xff0c;他的里面没有集成高版本node导致的 解决&#xff1a; 我们把nvm版本换到最新的就可以了 1. 卸载掉当前所有的node nvm …

如何不出国一年内拿到加拿大女王大学金融硕士学位证书?

作为加拿大最好的公立大学之一&#xff0c;QueensUniversity位于安大略省的金斯顿市。最近&#xff0c;它在QS全球大学排名中跻身第209位&#xff0c;同时在加拿大的综合排名中名列第7位。这表明女王大学在学术研究和教育方面都有着出色的表现。Queens University坐落于安大略省…

跨境电商如何进行仓储物流管理?

跨境电商如何进行仓储物流管理&#xff1f; 01跨境电商仓储物流管理痛点在哪&#xff1f; 供应链不稳定&#xff1a;因为要涉及多个国家的生产和供应环节&#xff0c;跨境物流的过程中还需要遵守目的地国家和货物品类的规定&#xff0c;这会增加仓储和物流成本&#xff0c;并…

图像压缩编码基础——笔记整理

图像压缩基础 1)压缩的原因:数字视频码率高达216Mb/s。数据量之大&#xff0c;无论是网络传输&#xff0c;还是存储都构成巨大压力。在保持信号质量的前提&#xff0c;要降低码率及数据量。 2)压缩的原理: 图像信息存在着大量的规律性或相关性&#xff0c;在传输的前一个样值中…

批量图片压缩解决方案之Imagine

背景&#xff1a; 一个网站或者App&#xff0c;在运维过程中&#xff0c;为了节省流量&#xff0c;提升页面响应时间&#xff0c;往往会对图片进行批量压缩&#xff0c;从而减少浏览器加载一个页面的整体体积。而图片压缩之后又要保证图片尺寸不变&#xff0c;质量损失较小&…

ImageIO的图片压缩算法

调用CompressPictureUtils.compress(要压缩的图片路径&#xff0c;保存的图片路径)可以对图片进行压缩。 左边的原图&#xff0c;右边是压缩后的图片。 调用函数的方法&#xff1a; public static void main(String[] args) {CompressPictureUtils.compress("D:\\1.jpg&q…

图像压缩原理

原文网址&#xff1a;http://blog.csdn.net/newchenxf/article/details/51693753 转载请注明出处。 1 图像可压缩的原因 一张原始图像(1920x1080)&#xff0c;如果每个像素32bit表示&#xff08;RGBA&#xff09;&#xff0c;那么&#xff0c;图像需要的内存大小 1920x1080x4 …

C# 图片压缩处理

今天老大让做一个图片压缩功能&#xff0c;自己在网上找了一些解决方法&#xff0c;也同时对方法进行了提炼&#xff01; 在下面的代码中会有一个字段Encoder.Quality 字段&#xff0c;这个对应的有个一个values是神奇的值 MSDN解释是&#xff1a;Quality 类别将指定的图像压…

线性代数——基变换和图像压缩

信息量过大的情况下使用压缩&#xff0c;如果不压缩系统超载&#xff0c;导致无法发送图片或视频&#xff0c;所以进行压缩处理 &#xff08;这里的图像压缩指的是有损压缩&#xff09; 下图是一个像素为 512 512 512\times512 512512的图像&#xff0c;黑点表示为一个像素 这…

图像压缩小波变换原理

图像编码算法尽可能节省图像的存储空间和减少传输带宽需求&#xff0c;图像编码的目的是在满足一定解码重构质量的条件下利用尽可能少的比特数对图像进行表示。数字图像中的像素都不是独立存在的&#xff0c;小到相邻像素之间&#xff0c;大到图像块与图像块之间&#xff0c;不…

JPEG图像压缩算法的python实现

摘要 文章在研究JPEG压缩编码对图像数据压缩的基本原理的基础上&#xff0c;设计了JPEG图像压缩算法程序实现流程&#xff0c;利用 Python语言对程序进行了编写&#xff0c;并实现了对压缩质量进行控制&#xff0c;验证了JPEG压缩编码对图像数据压缩的可行性。 用 JPEG压缩软件…

图像及图像压缩的研究

一、图片格式、应用场景 1、BMP格式 BMP是英文Bitmap&#xff08;位图&#xff09;的简写&#xff0c;它是Windows操作系统中的标准图像文件格式&#xff0c;能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的开发&#xff0c;BMP位图格…

unity图像压缩算法原理

概述 在计算机图形学中&#xff0c;存在许多纹理压缩方案。压缩既减少了纹理内存占用&#xff0c;又降低了使用纹理的带宽要求。本文中&#xff0c;“纹理压缩”与“图像压缩”不同&#xff0c;因为纹理压缩方案的设计允许作为纹理采样的一部分进行有效的随机访问。“图像压缩…

【图像压缩】有损压缩实现无损预测

updating... 1 绪论 一篇很好的结合对比学习与特征压缩的工作。 本文贡献&#xff1a; 1.公式化面向下游预测任务压缩的概念 2.描述了在增强不变性任务上高表现所需要的比特数。 3.提出无监督目标函数训练压缩器近似最优码率。 4.结合zero-shot方法CLIP&#xff0c;在Ima…

基于PCA的图像压缩实现

基于PCA的图像压缩实现 注&#xff1a;该内容为校内课程实验&#xff0c;仅供参考&#xff0c;请勿抄袭&#xff01; 源码&#xff1a;PPCA-for-Image-Compession 摘要   随着计算机互联网的发展和数据的日益增长&#xff0c;如何高效的处理和传输海量数据成为大数据处理的…

基于深度学习的图像压缩

近年来&#xff0c;深度学习在计算机视觉领域已经占据主导地位&#xff0c;不论是在图像识别还是超分辨重现上&#xff0c;深度学习已成为图片研究的重要技术&#xff0c;但它们的能力并不仅限于这些任务&#xff1b;现在深度学习技术已进入图片压缩领域。下面就说说神经网络在…

图像压缩算法

这里说​十种常用的图像压缩算法 数据压缩是保留相同或绝大部分数据前提下减小文件大小的过程。它的原理是消除不必要的数据或以更高效的格式重新组织数据。在进行数据压缩时&#xff0c;你可以选择使用有损方法或无损方法。有损方法会永久性地擦除掉一些数据&#xff0c;而无…

C#图像压缩相关方法总结

前往我的主页以获得更好的阅读体验C#图像压缩相关方法总结 - DearXuan的主页https://blog.dearxuan.com/2022/02/07/C-%E5%9B%BE%E5%83%8F%E5%8E%8B%E7%BC%A9%E7%9B%B8%E5%85%B3%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93/ 前言 本文所描述的所有内容和算法&#xff0c;均未使用任…

Matlab实现图像压缩

文章和代码以及样例图片等相关资源&#xff0c;已经归档至【Github仓库&#xff1a;digital-image-processing-matlab】或者公众号【AIShareLab】回复 数字图像处理 也可获取。 文章目录 目的原理图像压缩原理离散余弦变换(DCT)图像压缩原理行程编码&#xff08;RLE&#xff09…