Linux环境QT5.9+OpenGL绘制图形与渲染(6)详解系列
这是一个系列的博客,OpenGL是基于计算机图形学为基础发展出来的一个分支,必须理解清楚的是“向量”,由数学向量与C语言结合发展出了shader language,shader封装再结合C++就是UE4和UE5图形引擎了。向量vector(不是C++里面的vector容器啊,不要搞混了),最重要的就是引入数学矩阵Matrix,一个矩阵就是一个向量,即将向量计算变成矩阵变换,同时一个向量vector可以等于两个矩阵计算的结果。


这是向量A,换算成矩阵的样子

矩阵的乘法满足于以下运算律:
结合律:(AB)C = A(BC)
左分配律:(A + B)C = AC + BC
右分配律:C(A + B) = CA + CB
矩阵乘法不满足交换律:
由此我们明白了,矩阵Matrix与向量Vector的关系,但是还没搞明白OpenGL与shader的关系。
OpenGL有vertex shader 和 fragment shader等过程,这些就是封装过的shader在OpenGL里面使用。
关于纹理滤波的问题:
线性插值滤波(GL_LINEAR)==的纹理贴图,这需要机器有相当高的处理能力,但是看起来效果会很好;
最临*值滤波(GL_NEAREST),它只占用很小的处理能力,看起来效果会比较差,但是使用它因为不占用资源,工程在很快和很慢的机器上都可以正常运行;也可以混合使用线性插值滤波和最临*值滤波,纹理看起来效果会好一些;
Mipmap,这是一种创建纹理的新方法;您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失,刚才还很不错的图案变得很难看;当您告诉OPenGL创建一个mipmaped纹理时,OPenGL将选择它已经创建的外观最佳的纹理(带有很多细节)来绘制,而不仅仅是缩放原先的图像(这将导致细节丢失)。
关于光照
(1)当不开启光照时,使用顶点颜色来产生整个表面的颜色。
用glShadeModel可以设置表面内部像素颜色产生的方式。GL_FLAT/GL_SMOOTH.
(2)一般而言,开启光照后,在场景中至少需要有一个光源(GL_LIGHT0.。.GL_LIGHT7)
通过glEnable(GL_LIGHT0) glDisable(GL_LIGHT0) 来开启和关闭指定的光源。
— 全局环境光 —
GLfloat gAmbient[] = {0.6, 0,6, 0,6, 1.0};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, gAmbient);
(3)设置光源的光分量 – 环境光/漫色光/镜面光
默认情况下,GL_LIGHT0.。.GL_LIGHT7 的GL_AMBIENT值为(0.0, 0.0, 0.0, 1.0);
GL_LIGHT0的GL_DIFFUSE和GL_SPECULAR值为(1.0, 1.0, 1.0, 1.0),
GL_LIGHT1.。.GL_LIGHT7 的GL_DIFFUSE和GL_SPECULAR值为(0.0, 0.0, 0.0, 0.0)。
GLfloat lightAmbient[] = {1.0, 1.0, 1.0, 1.0};
GLfloat lightDiffuse[] = {1.0, 1.0, 1.0, 1.0};
GLfloat lightSpecular[] = {0.5, 0.5, 0.5, 1.0};
glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpecular);
(4)设置光源的位置和方向
– *行光 – 没有位置只有方向
GLfloat lightPosiTIon[] = {8.5, 5.0, -2.0, 0.0}; // w=0.0
glLightfv(GL_LIGHT0, GL_POSITION, lightPosiTIon);
– 点光源 – 有位置没有方向
GLfloat lightPosiTIon[] = {8.5, 5.0, -2.0, 1.0}; // w不为0
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
– 聚光灯 – 有位置有方向
GLfloat lightPosition[] = {-6.0, 1.0, 3.0, 1.0}; // w不为0
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
GLfloat lightDirection[] = {1.0, 1.0, 0.0};
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, lightDirection); // 聚光灯主轴方向 spot direction
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0); // cutoff角度 spot cutoff
** *行光不会随着距离d增加而衰减,但点光源和聚光灯会发生衰减。
attenuation为衰变系数,系数值越大,衰变越快。
默认情况下,c=1.0, l=0.0, q=0.0
glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2.0); // c 系数
glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0); // l 系数
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.5); // q 系数


.h文件
ifndef MYGLWIDGET_H
define MYGLWIDGET_H
include
include
include
class MyGLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
MyGLWidget(QWidget *parent = nullptr);
~MyGLWidget();
protected:
virtual void resizeGL(int w, int h);
virtual void initializeGL();
virtual void paintGL();
virtual void keyPressEvent(QKeyEvent *event);
virtual void timerEvent(QTimerEvent *event);
private:
void loadGLTexture();
private:
bool mFullScreen;
GLfloat mXRotate;
GLfloat mYRotate;
GLfloat mZRotate;
GLuint mTexture[1];
};
endif // MYGLWIDGET_H
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
.cpp文件
include
include
include <GL/glu.h>
include <myglwidget.h>
//我们增加了三个新的浮点变量… xrot , yrot 和 zrot 。这些变量用来使立方体绕X、Y、Z轴旋转。
//GLuint texture[1] 为一个纹理分配存储空间。如果您需要不止一个的纹理,应该将参数1改成您所需要的参数。
MyGLWidget::MyGLWidget(QWidget *parent) :
QOpenGLWidget(parent), mFullScreen(false), mXRotate(0.0f), mYRotate(0.0f),
mZRotate(0.0f)
{
showNormal();
startTimer(50);
}
MyGLWidget::~MyGLWidget()
{
glDeleteTextures(1, &mTexture[0]);
}
//这段代码用来加载位图文件。如果文件不存在,返回 NULL 告知程序无法加载位图。
//开始解释这段代码之前,关于用作纹理的图像我想有几点十分重要,并且您必须明白。
//此图像的宽和高必须是2的n次方;宽度和高度最小必须是64象素;并且出于兼容性的原因,图像的宽度和高度不应超过256象素。
//如果您的原始素材的宽度和高度不是64,128,256象素的话,使用图像处理软件重新改变图像的大小。
//可以肯定有办法能绕过这些限制,但现在我们只需要用标准的纹理尺寸。
void MyGLWidget::loadGLTexture()
{
//现在载入图像,并将其转换为纹理。
QImage image(":/images/dog.jpg");
image = image.convertToFormat(QImage::Format_RGB888);
image = image.mirrored();
glGenTextures(1, &mTexture[0]);// 创建纹理
// 使用来自位图数据生成 的典型纹理
glBindTexture(GL_TEXTURE_2D, mTexture[0]);
//下来我们创建真正的纹理。下面一行告诉OpenGL此纹理是一个2D纹理 ( GL_TEXTURE_2D )。
//参数“0”代表图像的详细程度,通常就由它为零去了。参数三是数据的成分数。
//因为图像是由红色数据,绿色数据,蓝色数据三种组分组成。 TextureImage[0]->sizeX 是纹理的宽度。
//如果您知道宽度,您可以在这里填入,但计算机可以很容易的为您指出此值。
//TextureImage[0]->sizey 是纹理的高度。参数零是边框的值,一般就是“0”。
//GL_RGB 告诉OpenGL图像数据由红、绿、蓝三色数据组成。
//GL_UNSIGNED_BYTE 意味着组成图像的数据是无符号字节类型的。
//最后… TextureImage[0]->data 告诉OpenGL纹理数据的来源。此例中指向存放在 TextureImage[0] 记录中的数据。
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
image.width(), image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE,
image.bits());
//下面的两行告诉OpenGL在显示图像时,
//当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时
//OpenGL采用的滤波方式。通常这两种情况下我都采用 GL_LINEAR 。
//这使得纹理从很远处到离屏幕很*时都*滑显示。使用 GL_LINEAR 需要CPU和显卡做更多的运算。
//如果您的机器很慢,您也许应该采用 GL_NEAREST 。
//您也可以结合这两种滤波方式。在*处时使用 GL_LINEAR ,远处时 GL_NEAREST 。
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 线形滤波
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 线形滤波
}
void MyGLWidget::resizeGL(int width, int height)
{
if (height == 0) { // 防止被零除
height = 1; // 将高设为1
}
glViewport(0, 0, width, height); //重置当前的视口
//此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。
//glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projection matrix(投影矩阵)。
//投影矩阵负责为我们的场景增加透视。 glLoadIdentity()*似于重置。它将所选的矩阵状态恢复成其原始状态。
//glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。
glMatrixMode(GL_PROJECTION);// 选择投影矩阵
glLoadIdentity();// 重置投影矩阵
//设置视口的大小
gluPerspective(45.0f, (GLfloat)width/(GLfloat)height, 0.1f, 100.0f);
glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵
glLoadIdentity(); // 重置模型观察矩阵
}
//loadGLTexture()这行代码调用载入位图并生成纹理。纹理创建好了,我们启用2D纹理映射。
void MyGLWidget::initializeGL()
{
loadGLTexture();
glEnable(GL_TEXTURE_2D);// 启用纹理映射
//下一行启用smooth shading(阴影*滑)。阴影*滑通过多边形精细的混合色彩,并对外部光进行*滑。
glShadeModel(GL_SMOOTH); // 启用阴影*滑
//色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。
//glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。
//如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,您将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。
//要得到白色背景,您应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,您该将所有的颜色设为最暗(0.0f)。
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景
//接下来的三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。
//深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
//我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。
glClearDepth(1.0f); // 设置深度缓存
glEnable(GL_DEPTH_TEST); // 启用深度测试
glDepthFunc(GL_LEQUAL); // 所作深度测试的类型
//接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 告诉系统对透视进行修正
}
//开始两行代码 glClear() 和 glLoadIdentity() 是第一课中就有的代码。
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 清除屏幕并设为我们在 InitGL() 中选定的颜色,本例中是黑色。
//深度缓存也被清除。模型观察矩阵也使用glLoadIdentity()重置。
void MyGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存
glLoadIdentity(); // 重置当前的模型观察矩阵
glTranslatef(0.0f,0.0f,-5.0f); // 移入屏幕 5 个单位
//下面三行使立方体绕X、Y、Z轴旋转。旋转多少依赖于变量 xrot , yrot 和 zrot 的值。
glRotatef(mXRotate,1.0f,0.0f,0.0f); // X轴旋转
glRotatef(mYRotate,0.0f,1.0f,0.0f); // Y轴旋转
glRotatef(mZRotate,0.0f,0.0f,1.0f); // Z轴旋转
//如果您在您的场景中使用多个纹理,您应该使用来 glBindTexture(GL_TEXTURE_2D, texture[ 所使用纹理对应的数字 ]) 选择要绑定的
//纹理。当您想改变纹理时,应该绑定新的纹理。有一点值得指出的是,您不能在 glBegin() 和 glEnd() 之间绑定纹理,必须在 glBegin()
//之前或 glEnd() 之后绑定。注意我们在后面是如何使用 glBindTexture 来指定和绑定纹理的。
glBindTexture(GL_TEXTURE_2D, mTexture[0]); // 选择纹理
//为了将纹理正确的映射到四边形上,您必须将纹理的右上角映射到四边形的右上角,纹理的左上角映射到四边形的左上角,
//纹理的右下角映射到四边形的右下角,纹理的左下角映射到四边形的左下角。
//glTexCoord2f 的第一个参数是X坐标。 0.0f 是纹理的左侧。 0.5f 是纹理的中点, 1.0f 是纹理的右侧。
//glTexCoord2f 的第二个参数是Y坐标。 0.0f 是纹理的底部。 0.5f 是纹理的中点, 1.0f 是纹理的顶部。
//所以纹理的左上坐标是 X:0.0f,Y:1.0f ,四边形的左上顶点是 X: -1.0f,Y:1.0f 。其余三点依此类推。
glBegin(GL_QUADS);
// 前面
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的左下
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 纹理和四边形的右下
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的右上
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的左上
// 后面
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右下
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的右上
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的左上
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的左下
// 顶面
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的左上
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的左下
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的右下
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的右上
// 底面
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右上
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的左上
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 纹理和四边形的左下
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的右下
// 右面
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的右下
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的右上
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的左上
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 纹理和四边形的左下
// 左面
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的左下
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的右下
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的右上
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的左上
glEnd();
}
void MyGLWidget::timerEvent(QTimerEvent *event)
{
//现在增加 xrot , yrot 和 zrot 的值。尝试变化每次各变量的改变值来调节立方体的旋转速度,或改变+/-号来调节立方体的旋转方向。
mXRotate += 0.3f; // X 轴旋转
mYRotate += 0.2f; // Y 轴旋转
mZRotate += 0.4f; // Z 轴旋转
update();
QOpenGLWidget::timerEvent(event);
}
void MyGLWidget::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_F2:
{
mFullScreen = !mFullScreen;
if(mFullScreen) {
showFullScreen();
}
else {
showNormal();
}
update();
break;
}
case Qt::Key_Escape:
{
qApp->exit();
break;
}
}
}
.qrc文件

浙公网安备 33010602011771号