当前位置: 首页 > news >正文

【OpenGL】openGL常见矩阵

参考博客:https://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-3-matrices/

一、齐次坐标(Homogeneous coordinates)

目前为止,我们仍然把三维顶点视为三元组(x,y,z)。现在引入一个新的分量w,得到向量(x,y,z,w)。请先记住以下两点(稍后我们会给出解释):

  • 若w == 1,则向量(x, y, z, 1)为空间中的点。
  • 若w == 0,则向量(x, y, z, 0)为方向。

(请务必将此牢记在心。)

  • 二者有什么区别呢?对于旋转,这点区别倒无所谓。当您旋转点和方向时,结果是一样的。但对于平移(将点沿着某个方向移动)情况就不同了。”平移一个方向”是毫无意义的。

  • 齐次坐标使得我们可以用同一个公式对点和方向作运算。

二、变换矩阵(Transformation matrices)

2.1 矩阵简介

简而言之,矩阵就是一个行列数固定的、纵横排列的数表。比如,一个2x3矩阵看起来像这样:

[257981]\begin{bmatrix} 2 & 5 & 7 \\ 9 & 8 & 1 \end{bmatrix} [295871]

三维图形学中我们只用到4x4矩阵,它能对顶点(x,y,z,w)作变换。这一变换是用矩阵左乘顶点来实现的:

[abcdefghijklmnop]×[xyzw]=[ax+by+cz+dwex+fy+gz+hwix+jy+kz+lwmx+ny+oz+pw]\begin{bmatrix} a & b & c & d \\ e & f & g & h \\ i & j & k & l \\ m & n & o & p \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} = \begin{bmatrix} ax + by + cz + dw \\ ex + fy + gz + hw \\ ix + jy + kz + lw \\ mx + ny + oz + pw \end{bmatrix} aeimbfjncgkodhlp×xyzw=ax+by+cz+dwex+fy+gz+hwix+jy+kz+lwmx+ny+oz+pw

矩阵x顶点(记住顺序!!矩阵左乘顶点,顶点用列向量表示)= 变换后的顶点

这看上去复杂,实则不然。左手指着a,右手指着x,得到_ax_。 左手移向右边一个数b,右手移向下一个数y,得到_by_。依次类推,得到_cz_、dw。最后求和ax + by + cz + dw,就得到了新的x!每一行都这么算下去,就得到了新的(x, y, z, w)向量。

这种重复无聊的计算就让计算机代劳吧。

用C++,GLM表示:

glm::mat4 myMatrix;
glm::vec4 myVector;
// fill myMatrix and myVector somehow
glm::vec4 transformedVector = myMatrix * myVector; // Again, in this order ! this is important.

使用QT可以这样做:

QMatrix4x4 myMatrix;  
QVector4D myVector;  
QVector4D transformVector = myMatrix * myVector;

用GLSL表示:

mat4 myMatrix;
vec4 myVector;
// fill myMatrix and myVector somehow
vec4 transformedVector = myMatrix * myVector; // Yeah, it's pretty much the same than GLM

(还没把这些代码粘贴到程序里调试吗?赶紧试试!)

2.2 平移矩阵(Translation matrices)

平移矩阵是最简单的变换矩阵。平移矩阵是这样的:

[100X010Y001Z0001]\begin{bmatrix} 1 & 0 & 0 & X \\ 0 & 1 & 0 & Y \\ 0 & 0 & 1 & Z \\ 0 & 0 & 0 & 1 \end{bmatrix} 100001000010XYZ1

其中,X、Y、Z是点的位移增量。

例如,若想把向量(\begin{bmatrix}10 \ 10 \ 10 \ 1\end{bmatrix})沿X轴方向平移10个单位,可得:

[10010010000100001]∗[1010101]=[1∗10+0∗10+0∗10+10∗10∗10+1∗10+0∗10+0∗10∗10+0∗10+1∗10+0∗10∗10+0∗10+0∗10+1∗1]=[10+0+0+100+10+0+00+0+10+00+0+0+1]=[2010101]\begin{bmatrix} 1 & 0 & 0 & 10 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 10 \\ 10 \\ 10 \\ 1 \end{bmatrix}= \begin{bmatrix} 1*10 + 0*10 + 0*10 + 10*1 \\ 0*10 + 1*10 + 0*10 + 0*1 \\ 0*10 + 0*10 + 1*10 + 0*1 \\ 0*10 + 0*10 + 0*10 + 1*1 \end{bmatrix}= \begin{bmatrix} 10 + 0 + 0 + 10 \\ 0 + 10 + 0 + 0 \\ 0 + 0 + 10 + 0 \\ 0 + 0 + 0 + 1 \end{bmatrix}= \begin{bmatrix} 20 \\ 10 \\ 10 \\ 1 \end{bmatrix} 100001000010100011010101=110+010+010+101010+110+010+01010+010+110+01010+010+010+11=10+0+0+100+10+0+00+0+10+00+0+0+1=2010101

(算算看!一定得亲手算!!)

这样就得到了齐次向量[2010101]\begin{bmatrix}20 \\ 10 \\ 10 \\ 1\end{bmatrix}2010101!记住,末尾的1表示这是一个点,而不是方向。经过变换计算后,点仍然是点。

下面来看看,对一个代表Z轴负方向的向量作上述平移变换会得到什么结果:

[10010010000100001]∗[00−10]=[1∗0+0∗0+0∗0+10∗00∗0+1∗0+0∗0+0∗00∗(−1)+0∗(−1)+1∗(−1)+0∗(−1)0∗0+0∗0+0∗0+1∗0]=[0+0+0+00+0+0+00+0+(−1)+00+0+0+0]=[00−10]\begin{bmatrix} 1 & 0 & 0 & 10 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 0 \\ 0 \\ -1 \\ 0 \end{bmatrix}= \begin{bmatrix} 1*0 + 0*0 + 0*0 + 10*0 \\ 0*0 + 1*0 + 0*0 + 0*0 \\ 0*(-1) + 0*(-1) + 1*(-1) + 0*(-1) \\ 0*0 + 0*0 + 0*0 + 1*0 \end{bmatrix}= \begin{bmatrix} 0 + 0 + 0 + 0 \\ 0 + 0 + 0 + 0 \\ 0 + 0 + (-1) + 0 \\ 0 + 0 + 0 + 0 \end{bmatrix}= \begin{bmatrix} 0 \\ 0 \\ -1 \\ 0 \end{bmatrix} 100001000010100010010=10+00+00+10000+10+00+000(1)+0(1)+1(1)+0(1)00+00+00+10=0+0+0+00+0+0+00+0+(1)+00+0+0+0=0010

那怎么用代码表示平移变换呢?

用C++,GLM表示:

#include <glm/gtx/transform.hpp> // after <glm/glm.hpp>glm::mat4 myMatrix = glm::translate(glm::mat4(), glm::vec3(10.0f, 0.0f, 0.0f));
glm::vec4 myVector(10.0f, 10.0f, 10.0f, 0.0f);
glm::vec4 transformedVector = myMatrix * myVector; // guess the result

用QT,QMatrix4x4表示:

//平移x,y,z方向的大小  
QMatrix4x4 MyGLWidget::translateMatrix(float x, float y, float z)  
{  QMatrix4x4 translate_matrix;  translate_matrix.translate(x,y,z);  return translate_matrix;  
}

**用GLSL表示:**呃,实际中我们几乎不用GLSL计算变换矩阵。大多数情况下在C++代码中用glm::translate()算出矩阵,然后把它传给GLSL。在GLSL中只做一次乘法:

vec4 transformedVector = myMatrix * myVector;

2.3、单位矩阵(Identity matrix)

单位矩阵很特殊,它什么也不做。单位矩阵的身份和自然数“1”一样基础而重要,因此在这里要特别提及一下。

[1000010000100001]×[xyzw]=[1∗x+0∗y+0∗z+0∗w0∗x+1∗y+0∗z+0∗w0∗x+0∗y+1∗z+0∗w0∗x+0∗y+0∗z+1∗w]=[x+0+0+00+y+0+00+0+z+00+0+0+w]=[xyzw]\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix}= \begin{bmatrix} 1*x + 0*y + 0*z + 0*w \\ 0*x + 1*y + 0*z + 0*w \\ 0*x + 0*y + 1*z + 0*w \\ 0*x + 0*y + 0*z + 1*w \end{bmatrix}= \begin{bmatrix} x + 0 + 0 + 0 \\ 0 + y + 0 + 0 \\ 0 + 0 + z + 0 \\ 0 + 0 + 0 + w \end{bmatrix}= \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix} 1000010000100001×xyzw=1x+0y+0z+0w0x+1y+0z+0w0x+0y+1z+0w0x+0y+0z+1w=x+0+0+00+y+0+00+0+z+00+0+0+w=xyzw

用C++表示:

glm::mat4 myIdentityMatrix = glm::mat4(1.0);

用QT表示:

QMatrix4x4 myIdentityMatrix;
myIdentityMatrix.setToIdentity();

2.4 缩放矩阵(Scaling matrices)

缩放矩阵也很简单:

[x0000y0000z00001]\begin{bmatrix} x & 0 & 0 & 0 \\ 0 & y & 0 & 0 \\ 0 & 0 & z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} x0000y0000z00001

例如把一个向量(点或方向皆可)沿各方向放大2倍:

[2000020000200001]×[xyzw]=[2∗x+0∗y+0∗z+0∗w0∗x+2∗y+0∗z+0∗w0∗x+0∗y+2∗z+0∗w0∗x+0∗y+0∗z+1∗w]=[2∗x+0+0+00+2∗y+0+00+0+2∗z+00+0+0+1∗w]=[2∗x2∗y2∗zw]\begin{bmatrix} 2 & 0 & 0 & 0 \\ 0 & 2 & 0 & 0 \\ 0 & 0 & 2 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} x \\ y \\ z \\ w \end{bmatrix}= \begin{bmatrix} 2*x + 0*y + 0*z + 0*w \\ 0*x + 2*y + 0*z + 0*w \\ 0*x + 0*y + 2*z + 0*w \\ 0*x + 0*y + 0*z + 1*w \end{bmatrix}= \begin{bmatrix} 2*x + 0 + 0 + 0 \\ 0 + 2*y + 0 + 0 \\ 0 + 0 + 2*z + 0 \\ 0 + 0 + 0 + 1*w \end{bmatrix}= \begin{bmatrix} 2*x \\ 2*y \\ 2*z \\ w \end{bmatrix} 2000020000200001×xyzw=2x+0y+0z+0w0x+2y+0z+0w0x+0y+2z+0w0x+0y+0z+1w=2x+0+0+00+2y+0+00+0+2z+00+0+0+1w=2x2y2zw

w还是没变。您也许会问:”缩放一个向量”有什么用?嗯,大多数情况下是没什么用,所以一般不会去缩放向量;但在某些特殊情况下它就派上用场了。(顺便说一下,单位矩阵只是缩放矩阵的一个特例,其(X, Y, Z) = (1, 1, 1)。单位矩阵同时也是旋转矩阵的一个特例,其(X, Y, Z)=(0, 0, 0))。

用C++表示:

// Use #include <glm/gtc/matrix_transform.hpp> and #include <glm/gtx/transform.hpp>
glm::mat4 myScalingMatrix = glm::scale(2.0f, 2.0f ,2.0f);

用QT表示:

//缩放x,y,z的大小比例  
QMatrix4x4 MyGLWidget::scaleMatrix(float x, float y, float z) {  QMatrix4x4 scale_matrix;  scale_matrix.scale(x,y,z);  return scale_matrix;  
}

2.5、旋转矩阵(Rotation matrices)

旋转矩阵比较复杂。这里略过细节,因为日常应用中,您并不需要知道矩阵的内部构造。 想了解更多,请看“矩阵和四元组常见问题”(这个资源很热门,应该有中文版吧)。

用C++表示:

// Use #include <glm/gtc/matrix_transform.hpp> and #include <glm/gtx/transform.hpp>
glm::vec3 myRotationAxis( ??, ??, ??);
glm::rotate( angle_in_degrees, myRotationAxis );

用QT表示:

//默认逆时针angle,负数表示顺时针,绕(0,0,0)->(x,y,z)指向的方向旋转
QMatrix4x4 MyGLWidget::rotateMatrix(float angle,float x,float y,float z) {QMatrix4x4 rotate_matrix;rotate_matrix.rotate(angle,x,y,z);return rotate_matrix;
}

2.6 累积变换

前面已经学习了如何旋转、平移和缩放向量。把这些矩阵相乘就能将它们组合起来,例如:

TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;

!!!注意!!!这行代码首先执行缩放,接着旋转,最后才是平移。这就是矩阵乘法的工作方式。

变换的顺序不同,得出的结果也不同。您不妨亲自尝试一下:

  • 向前一步(小心别磕着爱机)然后左转;
  • 左转,然后向前一步

实际上,上述顺序正是你在变换游戏角色或者其他物体时所需的:先缩放;再调整方向;最后平移。例如,假设有个船的模型(为简化,略去旋转):

  • 错误做法:

    • 按(10, 0, 0)平移船体。船体中心目前距离原点10个单位。
    • 将船体放大2倍。以原点为参照,每个坐标都变成原来的2倍,就出问题了。最后您得到的是一艘放大的船,但其中心位于2*10=20。这并非您预期的结果。
  • 正确做法:

    • 将船体放大2倍,得到一艘中心位于原点的大船。
    • 平移船体。船大小不变,移动距离也正确。

矩阵-矩阵乘法和矩阵-向量乘法类似,所以这里也会省略一些细节,不清楚的读者请移步矩阵和四元组常见问题 。现在,就让计算机来算:

用C++,GLM表示:

glm::mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix;
glm::vec4 myTransformedVector = myModelMatrix * myOriginalVector;

用QT表示:

QMatrix4x4 scale_matrix = scaleMatrix(1.0,1.0,0.0);  
QMatrix4x4 translate_matrix = translateMatrix(0.0,0.0,0.0);  QMatrix4x4 rotate_matrix = rotateMatrix(0.0f,0.0f,0.0f,0.0f);  //叠加操作  
QMatrix4x4 transform_matrix = translate_matrix * rotate_matrix * scale_matrix;

用GLSL表示:

mat4 transform = mat2 * mat1;
vec4 out_vec = transform * in_vec;

三、模型(Model)、观察(View)和投影(Projection)矩阵

在接下来的课程中,我们假定您已知如何绘制Blender经典模型小猴Suzanne。

利用模型、观察和投影矩阵,可以将变换过程清晰地分解为三个阶段。虽然此法并非必需(前两课我们就没用这个方法嘛),但采用此法较为稳妥。我们将看到,这种公认的方法对变换流程作了清晰的划分。

3.1 模型矩阵

这个三维模型和可爱的红色三角形一样,由一组顶点定义。顶点的XYZ坐标是相对于物体中心定义的:也就是说,若某顶点位于(0,0,0),则其位于物体的中心。

在这里插入图片描述

我们希望能够移动它,玩家也需要用键鼠控制这个模型。这很简单,只需记住:缩放_旋转_平移就够了。在每一帧中,用算出的这个矩阵去乘(在GLSL中乘,不是在C++中!)所有的顶点,物体就会移动。唯一不动的是世界空间(World Space)的中心。
在这里插入图片描述

现在,物体所有顶点都位于_世界空间_。下图中黑色箭头的意思是:从模型空间(Model Space)(顶点都相对于模型的中心定义)变换到世界空间(顶点都相对于世界空间中心定义)。

在这里插入图片描述

下图概括了这一过程:
在这里插入图片描述

3.2 观察矩阵

这里再引用一下《飞出个未来》:

引擎推动的不是飞船而是宇宙。飞船压根就没动过。

在这里插入图片描述

仔细想想,摄像机的原理也是相通的。如果想换个角度观察一座山,您可以移动摄像机也可以……移动山。后者在实际中不可行,在计算机图形学中却十分方便。

起初,摄像机位于世界坐标系的原点。移动世界只需乘一个矩阵。假如你想把摄像机向(X轴正方向)移动3个单位,这和把整个世界(包括网格)向(X轴负方向)移3个单位是等效的!脑子有点乱?来写代码吧:

// Use #include <glm/gtc/matrix_transform.hpp> and #include <glm/gtx/transform.hpp>
glm::mat4 ViewMatrix = glm::translate(glm::mat4(), glm::vec3(-3.0f, 0.0f, 0.0f));

下图展示了:从世界空间(顶点都相对于世界空间中心定义)到摄像机空间(Camera Space,顶点都相对于摄像机定义)的变换。
在这里插入图片描述

趁脑袋还没爆炸,来欣赏一下GLM强大的glm::LookAt函数吧:

glm::mat4 CameraMatrix = glm::LookAt(cameraPosition, // the position of your camera, in world spacecameraTarget,   // where you want to look at, in world spaceupVector        // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
);

在QT中是这样的:

//观察矩阵  
QVector3D camera_pos = {4,4,3};  
QVector3D look_dir = {1,0,0};  
QVector3D up_dir = {0,1,0};  //lookAt  
QMatrix4x4 view_matrix;  
view_matrix.lookAt(camera_pos,look_dir,up_dir);

下图解释了上述变换过程:

在这里插入图片描述

3.3 投影矩阵

现在,我们处于摄像机空间中。这意味着,经历了这么多变换后,现在一个坐标X == 0且Y == 0的顶点,应该被画在屏幕的中心。但仅有x、y坐标还不足以确定物体是否应该画在屏幕上:它到摄像机的距离(z)也很重要!两个x、y坐标相同的顶点,z值较大的一个将会最终显示在屏幕上。

这就是所谓的透视投影(perspective projection):

在这里插入图片描述

好在用一个4x4矩阵就能表示这个投影¹ :

// Generates a really hard-to-read matrix, but a normal, standard 4x4 matrix nonetheless
glm::mat4 projectionMatrix = glm::perspective(glm::radians(FoV), // The vertical Field of View, in radians: the amount of "zoom". Think "camera lens". Usually between 90&deg; (extra wide) and 30&deg; (quite zoomed in)4.0f / 3.0f,       // Aspect Ratio. Depends on the size of your window. Notice that 4/3 == 800/600 == 1280/960, sounds familiar ?0.1f,              // Near clipping plane. Keep as big as possible, or you'll get precision issues.100.0f             // Far clipping plane. Keep as little as possible.
);

在QT中可以这样使用:

//投影矩阵
float angle = 45;
float aspect = this->width() * 1.0f / this->height();//距离相机的位置,渲染范围:0.1-100
float near_plane = 0.1f;
float far_plane = 100.f;QMatrix4x4 projection_matrix;
projection_matrix.perspective(angle,aspect,near_plane,far_plane);

最后一个变换:

从摄像机空间(顶点都相对于摄像机定义)到齐次坐空间(Homogeneous Space)(顶点都在一个小立方体中定义。立方体内的物体都会在屏幕上显示)的变换。

最后一幅图示:

在这里插入图片描述

再添几张图,以便大家更好地理解投影变换。投影前,蓝色物体都位于摄像机空间中,红色的东西是摄像机的平截头体(frustum):这是摄像机实际能看见的区域。

在这里插入图片描述

用投影矩阵去乘前面的结果,得到如下效果:

在这里插入图片描述

此图中,平截头体变成了一个正方体(每条棱的范围都是-1到1,图不太明显),所有的蓝色物体都经过了相同的变形。因此,离摄像机近的物体就显得大一些,远的显得小一些。这和现实生活一样!

让我们从平截头体的”后面”看看它们的模样:

在这里插入图片描述

这就是您得到的图像!看上去太方方正正了,因此,还需要做一次数学变换使之适合实际的窗口大小。

在这里插入图片描述

这就是实际渲染的图像啦!

复合变换:模型观察投影矩阵(MVP)

再来一连串深爱已久的标准矩阵乘法:

// C++ : compute the matrix
glm::mat4 MVPmatrix = projection * view * model; // Remember : inverted !
// C++ : compute the matrix
QMatrix4x4 MVPmatrix = projection * view * model; // Remember : inverted !
// GLSL : apply it
transformed_vertex = MVP * in_vertex;

四、使用MVP矩阵

保持上一章节的内容不变,我们添加一个doMVP函数,用于生成一个MVP矩阵,当然也可以独立生成M、V、P三个矩阵,然后传入到vertex shader中再计算出MVP矩阵,这里采用后者

  • 这里的参数可以自行修改,以理解不同的参数对渲染结果的影响,也可以去官网查询api参数具体的含义

  • 上传到shader需要在shader定义uniform变量,同时使用glGetUniformLocation获取对应的uniform变量,注意名称要一一对应,然后使用glUniform*函数上传对应类型的参数


void MyGLWidget::doMVP()
{//模型矩阵QMatrix4x4 model_matrix;model_matrix.setToIdentity();//观察矩阵QVector3D camera_pos = {4,4,3};QVector3D look_dir = {1,0,0};QVector3D up_dir = {0,1,0};//lookAtQMatrix4x4 view_matrix;view_matrix.lookAt(camera_pos,look_dir,up_dir);//投影矩阵float angle = 45;float aspect = this->width() * 1.0f / this->height();//距离相机的位置,渲染范围:0.1-100float near_plane = 0.1f;float far_plane = 100.f;QMatrix4x4 projection_matrix;projection_matrix.perspective(angle,aspect,near_plane,far_plane);//获取uniform变量uniform_model_matrix_location = glGetUniformLocation(shader_program_id,"model_matrix");uniform_view_matrix_location = glGetUniformLocation(shader_program_id,"view_matrix");uniform_projection_matrix_location = glGetUniformLocation(shader_program_id,"projection_matrix");//矩阵传入shaderglUniformMatrix4fv(uniform_model_matrix_location,1,GL_FALSE,model_matrix.data());glUniformMatrix4fv(uniform_view_matrix_location,1,GL_FALSE,view_matrix.data());glUniformMatrix4fv(uniform_projection_matrix_location,1,GL_FALSE,projection_matrix.data());}

这里采用的变化函数doTransform生成对应的Model矩阵,也就是对模型空间内的坐标进行变化,比如平移,缩放,旋转

void MyGLWidget::doTransform()  
{  QMatrix4x4 scale_matrix = scaleMatrix(1.0,1.0,0.0);  QMatrix4x4 translate_matrix = translateMatrix(0.0,0.0,0.0);  QMatrix4x4 rotate_matrix = rotateMatrix(0.0f,0.0f,0.0f,0.0f);  //叠加操作  QMatrix4x4 transform_matrix = translate_matrix * rotate_matrix * scale_matrix;  //获取uniform变量:transform_matrix,从着色器程序获取  uniform_transform_matrix_location = glGetUniformLocation(shader_program_id,"transform_matrix");  //上传uniform变量的4x4矩阵:transform_matrix,不需要转置,4fv结尾含义是4x4矩阵float类型,向量(vector)形式  glUniformMatrix4fv(uniform_transform_matrix_location,1,GL_FALSE,transform_matrix.data());  
}

最后就是,注意在vertex shader中定义对应的uniform变量,这个变量是全局唯一的,类似单例,所以即使重名得到的也是同一个结果:

uniform mat4 transform_matrix;  uniform mat4 model_matrix;  
uniform mat4 view_matrix;  
uniform mat4 projection_matrix;

上述代码加入后,最后运行的结果如下:

在这里插入图片描述

五、完整代码

my_glwidget.h

//  
// Created by liuhang on 2025/9/16.  
//  #ifndef OPENGL_LEARNING_MY_GLWIDGET_H  
#define OPENGL_LEARNING_MY_GLWIDGET_H  #include<QOpenGLWidget>  
#include<QOpenGLFunctions>  
#include<QMatrix4x4>  
#include<QTimer>  class MyGLWidget : public QOpenGLWidget,protected QOpenGLFunctions  
{  Q_OBJECT  
public:  explicit MyGLWidget(QWidget* parent = nullptr);  ~MyGLWidget() override;  protected:  void initializeGL() override;  void paintGL() override;  void resizeGL(int w,int h) override;  void doTransform();  void doMVP();  
private:  void loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path);  QMatrix4x4 translateMatrix(float x, float y, float z);  QMatrix4x4 scaleMatrix(float x,float y,float z);  QMatrix4x4 rotateMatrix(float angle,float x,float y,float z);  private:  GLuint vertex_array_id;  GLuint vertex_buffer_id;  GLuint vertex_shader_id;  GLuint fragment_shader_id;  GLuint shader_program_id;  GLint uniform_transform_matrix_location;  GLint uniform_model_matrix_location;  GLint uniform_view_matrix_location;  GLint uniform_projection_matrix_location;  float global_i = 0;  QTimer timer;  
};  #endif //OPENGL_LEARNING_MY_GLWIDGET_H

my_glwidget.cpp

//  
// Created by liuhang on 2025/9/16.  
//  #include "my_glwidget.h"  
#include<string>  
#include<fstream>  
#include<sstream>  
#include<iostream>  
#include<filesystem>  
#include<QThread>  
#include<math.h>  static const float vertex_buffer_data[] ={  -1.0f, -1.0f, 0.0f,  1.0f, -1.0f, 0.0f,  0.0f,  1.0f, 0.0f  
};  MyGLWidget::MyGLWidget(QWidget *parent): QOpenGLWidget(parent)  
{  
#if 0  timer.setInterval(1);  connect(&timer,&QTimer::timeout,this,[this](){        static float count = 0;        count+= 0.01f;        global_i = std::fabs(sin(count));  this->update();  timer.setInterval(1);        timer.start();    });  
#endif  #if 0  timer.setInterval(1);  connect(&timer,&QTimer::timeout,this,[this](){        global_i+= 1;        if(fabs(global_i - 100) < 0.1f){            global_i = 0;        }        this->update();  timer.setInterval(1);        timer.start();    });  timer.start();#endif  }  MyGLWidget::~MyGLWidget()  {  makeCurrent();  glDeleteVertexArrays(1,&vertex_array_id);  glDeleteBuffers(1,&vertex_buffer_id);  doneCurrent();  
}  void MyGLWidget::initializeGL() {  initializeOpenGLFunctions();  glClearColor(0.2f,0.3f,0.3f,1.0f);  glGenVertexArrays(1,&vertex_array_id);  glBindVertexArray(vertex_array_id);  glGenBuffers(1,&vertex_buffer_id);  glBindBuffer(GL_ARRAY_BUFFER,vertex_buffer_id);  glBufferData(GL_ARRAY_BUFFER,sizeof(vertex_buffer_data),vertex_buffer_data,GL_STATIC_DRAW);  //启用layout = 0为当前绑定的VAO  glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,nullptr);  glEnableVertexAttribArray(0);  //加载着色器  loadShader("/Users/liuhang/CLionProjects/opengl-learning/opengl1-3-matrix/shader/shader.vert",  "/Users/liuhang/CLionProjects/opengl-learning/opengl1-3-matrix/shader/shader.frag");  //    doTransform();  //解绑VAO  glBindVertexArray(0);  }  void MyGLWidget::paintGL() {  glClear(GL_COLOR_BUFFER_BIT);  glBindVertexArray(vertex_array_id);  //使用着色器程序  glUseProgram(shader_program_id);  //缩放  doTransform();  doMVP();  
#if 0  QMatrix4x4 scale_matrix = scaleMatrix(global_i,global_i,global_i);  QMatrix4x4 translate_matrix = translateMatrix(0.0,0.0,0.0);  QMatrix4x4 rotate_matrix = rotateMatrix(0.0f,0.0f,0.0f,0.0f);  QMatrix4x4 transform_matrix = translate_matrix * rotate_matrix * scale_matrix;    //获取uniform变量:transform_matrix,从着色器程序获取  uniform_transform_matrix_location = glGetUniformLocation(shader_program_id,"transform_matrix");  //上传uniform变量的4x4矩阵:transform_matrix,不需要转置,4fv结尾含义是4x4矩阵float类型,向量(vector)形式  glUniformMatrix4fv(uniform_transform_matrix_location,1,GL_FALSE,transform_matrix.data());  
#endif  glDrawArrays(GL_TRIANGLES,0,3);  
}  void MyGLWidget::resizeGL(int w, int h) {  glViewport(0, 0, w, h);  
}  void MyGLWidget::loadShader(std::string const& vertex_shader_path,std::string const& fragment_shader_path) {  //创建顶点着色器  vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);  //读取shader.vert  std::string vertex_shader_code;  std::ifstream vertex_shader_stream(vertex_shader_path,std::ios::in);  if(vertex_shader_stream.is_open()){  std::stringstream ss;  ss << vertex_shader_stream.rdbuf();  vertex_shader_code = ss.str();  vertex_shader_stream.close();  }  else{  std::cout << "current path = " << std::filesystem::current_path() << std::endl;  if (!std::filesystem::exists(vertex_shader_path)) {  std::cerr << "File not found: " << vertex_shader_path << std::endl;  }  std::cerr << "read vertex_shader fail!" << std::endl;  return;  }  //加载vertex着色器程序代码  const char* vertex_shader_code_pointer = vertex_shader_code.c_str();  glShaderSource(vertex_shader_id,1,&vertex_shader_code_pointer,nullptr);  //编译vertex shader  glCompileShader(vertex_shader_id);  //查看vertex编译结果  GLint Result = GL_FALSE;  int infoLogLength;  glGetShaderiv(vertex_shader_id,GL_COMPILE_STATUS,&Result);  glGetShaderiv(vertex_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  if(infoLogLength > 0){  std::vector<char> vertex_shader_error_message(infoLogLength+1);  glGetShaderInfoLog(vertex_shader_id, infoLogLength, nullptr, &vertex_shader_error_message[0]);  std::cout << "vertex shader:" << &vertex_shader_error_message[0] << std::endl;  }  //创建片段着色器  fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);  //读取shader.frag  std::string fragment_shader_code;  std::ifstream fragment_shader_stream(fragment_shader_path,std::ios::in);  if(fragment_shader_stream.is_open()){  std::stringstream ss;  ss << fragment_shader_stream.rdbuf();  fragment_shader_code = ss.str();  fragment_shader_stream.close();  }  else{  std::cout << "current path = " << std::filesystem::current_path() << std::endl;  if (!std::filesystem::exists(vertex_shader_path)) {  std::cerr << "File not found: " << vertex_shader_path << std::endl;  }  std::cerr << "read fragment_shader fail!" << std::endl;  }  //加载fragment着色器程序代码  const char* fragment_shader_code_pointer = fragment_shader_code.c_str();  glShaderSource(fragment_shader_id,1,&fragment_shader_code_pointer,nullptr);  //编译fragment shader  glCompileShader(fragment_shader_id);  //查看fragment编译结果  glGetShaderiv(fragment_shader_id,GL_COMPILE_STATUS,&Result);  glGetShaderiv(fragment_shader_id,GL_INFO_LOG_LENGTH,&infoLogLength);  if(infoLogLength > 0){  std::vector<char> fragment_shader_error_message(infoLogLength+1);  glGetShaderInfoLog(fragment_shader_id, infoLogLength, nullptr, &fragment_shader_error_message[0]);  std::cout << "fragment shader compile error:" << &fragment_shader_error_message[0] << std::endl;  }  //创建着色器程序  shader_program_id = glCreateProgram();  //附加着色器到程序  glAttachShader(shader_program_id,vertex_shader_id);  glAttachShader(shader_program_id,fragment_shader_id);  //链接shader  glLinkProgram(shader_program_id);  //检查程序链接结果  glGetProgramiv(shader_program_id,GL_LINK_STATUS,&Result);  glGetProgramiv(shader_program_id,GL_INFO_LOG_LENGTH,&infoLogLength);  if(infoLogLength > 0){  std::vector<char>program_error_message(infoLogLength + 1);  glGetProgramInfoLog(shader_program_id,infoLogLength,nullptr,&program_error_message[0]);  std::cout << "program link:" << &program_error_message[0] << std::endl;  }  //删除着色器编译结果  glDeleteShader(vertex_shader_id);  glDeleteShader(fragment_shader_id);  
}  //平移x,y,z方向的大小  
QMatrix4x4 MyGLWidget::translateMatrix(float x, float y, float z)  
{  QMatrix4x4 translate_matrix;  translate_matrix.translate(x,y,z);  return translate_matrix;  
}  //缩放x,y,z的大小比例  
QMatrix4x4 MyGLWidget::scaleMatrix(float x, float y, float z) {  QMatrix4x4 scale_matrix;  scale_matrix.scale(x,y,z);  return scale_matrix;  
}  //默认逆时针angle,负数表示顺时针,绕(0,0,0)->(x,y,z)指向的方向旋转  
QMatrix4x4 MyGLWidget::rotateMatrix(float angle,float x,float y,float z) {  QMatrix4x4 rotate_matrix;  rotate_matrix.rotate(angle,x,y,z);  return rotate_matrix;  
}  void MyGLWidget::doTransform()  
{  QMatrix4x4 scale_matrix = scaleMatrix(1.0,1.0,0.0);  QMatrix4x4 translate_matrix = translateMatrix(0.0,0.0,0.0);  QMatrix4x4 rotate_matrix = rotateMatrix(0.0f,0.0f,0.0f,0.0f);  //叠加操作  QMatrix4x4 transform_matrix = translate_matrix * rotate_matrix * scale_matrix;  //获取uniform变量:transform_matrix,从着色器程序获取  uniform_transform_matrix_location = glGetUniformLocation(shader_program_id,"transform_matrix");  //上传uniform变量的4x4矩阵:transform_matrix,不需要转置,4fv结尾含义是4x4矩阵float类型,向量(vector)形式  glUniformMatrix4fv(uniform_transform_matrix_location,1,GL_FALSE,transform_matrix.data());  
}  void MyGLWidget::doMVP()  
{  //模型矩阵  QMatrix4x4 model_matrix;  model_matrix.setToIdentity();  //观察矩阵  QVector3D camera_pos = {4,4,3};  QVector3D look_dir = {1,0,0};  QVector3D up_dir = {0,1,0};  //lookAt  QMatrix4x4 view_matrix;  view_matrix.lookAt(camera_pos,look_dir,up_dir);  //投影矩阵  float angle = 45;  float aspect = this->width() * 1.0f / this->height();  //距离相机的位置,渲染范围:0.1-100  float near_plane = 0.1f;  float far_plane = 100.f;  QMatrix4x4 projection_matrix;  projection_matrix.perspective(angle,aspect,near_plane,far_plane);  //获取uniform变量  uniform_model_matrix_location = glGetUniformLocation(shader_program_id,"model_matrix");  uniform_view_matrix_location = glGetUniformLocation(shader_program_id,"view_matrix");  uniform_projection_matrix_location = glGetUniformLocation(shader_program_id,"projection_matrix");  //矩阵传入shader  glUniformMatrix4fv(uniform_model_matrix_location,1,GL_FALSE,model_matrix.data());  glUniformMatrix4fv(uniform_view_matrix_location,1,GL_FALSE,view_matrix.data());  glUniformMatrix4fv(uniform_projection_matrix_location,1,GL_FALSE,projection_matrix.data());  }

shader.vert

#version 330 core  layout(location = 0) in vec3 vertexPosion_modelspace;  
uniform mat4 transform_matrix;  uniform mat4 model_matrix;  
uniform mat4 view_matrix;  
uniform mat4 projection_matrix;  void main(){  mat4 MVP_matrix = projection_matrix * view_matrix * model_matrix;  gl_Position = MVP_matrix * transform_matrix * vec4(vertexPosion_modelspace,1.0);  
}

shader.frag

#version 330 core  out vec3 fragment_color;  void main(){  fragment_color = vec3(1.0,0.0,0.0);  
}
http://www.dtcms.com/a/392108.html

相关文章:

  • DeepSeek大模型混合专家模型,DeepSeekMoE 重构 MoE 训练逻辑
  • 450. 删除二叉搜索树中的节点
  • 实用工具:基于Python的图片定位导出小程序
  • 滚珠螺杆在工业机器人关节与线性模组的智能控制
  • 【AI】coze的简单入门构建智能体
  • Python数据分析:函数定义时的装饰器,好甜的语法糖。
  • Java数据结构——包装类和泛型
  • 【C++进阶】C++11的新特性 | 列表初始化 | 可变模板参数 | 新的类功能
  • 广东省省考备考(第一百零三天9.20)——言语(强化训练)
  • 面试编程题(四)
  • OpenHarmony之充电振动定制
  • 前端单元测试入门:使用 Vitest + Vue 测试组件逻辑与交互
  • 泛英国生物样本库全基因组关联分析
  • 【LeetCode 每日一题】2785. 将字符串中的元音字母排序
  • 游戏开发中的友好提示,错误信息,异常描述等数据管理的必要性
  • 总线协议(Bus Protocol)如何支持总线错误条件?
  • simuilink 中的引用模型(reference model)的作用? 它和子系统的区别? 如何生成引用模型?
  • HTML+JS实现table表格和鼠标移入移出效果
  • windows11用Qt6自带的mingw编译OSGEarth(自用记录)
  • 仓颉编程语言青少年基础教程:泛型(Generic)和区间(Range)类型
  • 原码反码补码------相关理解
  • 【Python】字典
  • 玩转deepseek之海报生成器
  • C++强制类型转换和I/O流深度解析
  • Transformer 和 MoE
  • Python基础 7》数据类型_元组(Tuple)
  • AI大模型入门第四篇:借助RAG实现精准用例自动生成!
  • leetcode 198 打家劫舍问题,两个dp数组->一个dp数组
  • 嵌入式ARM架构学习8——串口
  • Motion-sensor基础应用