QT—3D绘图

OpenGL是一个跨平台的、用来渲染3D图形的标准API,Qt对OpenGL提供了强大的支持。Qt4时代的QtOpenGL模块在Qt5中已经不再建议使用,OpenGL相关的类被移到了Qt GUI模块。Qt Widgets模块中的QOpenGLWidget类提供了一个可以渲染OpenGL图形的部件,通过该部件可以轻松地将OpenGL图形整合到Qt应用程序中。

本章不会对OpenGL的专业知识进行过多讲解,只会涉及在Qt应用程序中进行
3D绘图的一.些最基本应用。如果想深入学习,则可以参考Qt GUI模块帮助文档中
OpenGL and OpenGL ES Integration 部分内容。


使用OpenGL绘制图形介绍

QOpenGLWidget类是一个用来渲染OpenGL图形的部件,它提供了在Qt应用程序中显示OpenGL图形的功能。

这个类使用起来很简单,只需要继承该类,然后像使用其他QWidget部件一样来使用它即可。

QGLWidget 提供了3个方便的虚函数,可以在子类中重新实现它们来执行典型的OpenGL任务:

  • initializeGL():设置OpenGL资源和状态。
    该函数只在第一次调用resizeGL()或paintGL()前被调用一次;

  • resizeGL():设置OpenGL的视口、投影等。
    每次部件改变大小时都会调用该函数;

  • paintGL():渲染OpenGL场景。
    每当部件需要更新时都会调用该函数。

着色器

从OpenGL2.0开始引人着色器的概念,除了固定功能的管线以外,增加了一种可编程着色管线,可以通过着色器控制顶点和片段的处理

从OpenGL 3.1开始,固定功能的管线被废弃并删除了,于是必须使用着色器来完成工作。

着色器是使用OpenGL着色语言(OpenGL Shading Language,GLSL)编写的一个小型函数
绘图时需要至少指定两个着色器:顶点着色器(vertexshader)片段着色器( fragmentshader,也称为片元着色器)。

Qt中QOpenGLShader类用来创建和编译着色器,支持使用OpenGL着色语言GLSLOpenGL/ES着色语言GLSL/ES编写的着色器。

QOpenGL ShaderProgram类用来创建并设置着色器程序,可以链接多个着色器,并在OpenGL当前环境(current context,也称为当前上下文)中绑定着色器程序。

QOpenGLFunctions 类提供了对OpenGL ES2.0 API的访问接口,QOpenGLExtraFunctions提供了对OpenGL ES3.0和3.1API的访问接口。

QAbstractOpenGLFunctions 是一个类族的基类,类族中的类涉及了所有OpenGL版本. 并为相应版本OpenGL的所有函数提供了访问接口。
例如,QOpenGLFunctions_ 2_1,QOpenGLFunctions_4_3_Core和QOpenGLFunetions_4_3_Compatibilit等

相关内容可以查看QAbstractOpenGLFunctions类的帮助文档。


例子 QOpenGLWidget中使用OpenGL绘制图形

下面通过一个简单的例程来看一下怎样在QOpenGLWidget中使用OpenGL绘制图形

读者也可以参考Qt中的Hello GL2 Example示例程序。

(项目源码:myopengl)新建空的Qt项目Empty qmake Project,项目名称为myopengl.完成后往项目中添加新的C++类,类名为MyOpenGLWidget,基类先不进行设置。

下面打开项目文件myopengl. pro,添加一行代码:

QT += widgets

保存该文件。再打开myglwidget. h文件,更改如下:

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLFunctions>
class QOpenGLShaderProgram;class MyOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{Q_OBJECTpublic:explicit MyOpenGLWidget(QWidget *parent = 0);protected:void initializeGL();void paintGL();void resizeGL(int width, int height);private:QOpenGLShaderProgram *program;
};
#endif // MYOPENGLWIDGET_H
class MyOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions

这里使用了多继承,自定义的MyOpenGLWidget类同时继承自QOpenGLWidget
类和QOpenGLFunctions类,这样就可以在类中直接使用QOpenGLFunctions中的
OpenGL函数,而不需要创建QOpenGLFunctions 对象

  QOpenGLShaderProgram *program;

这里声明了一个QOpenGLShaderProgram对象指针,作为着色器程序

下面到myglwidget. cpp文件中进行更改:
先添加头文件#include < QOpenGLShaderProgram>并更改构造函数:

#include "myopenglwidget.h"
#include <QOpenGLShaderProgram>MyOpenGLWidget::MyOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{
}

然后添加initializeGL()函数定义:

void MyOpenGLWidget::initializeGL()
{// 为当前环境初始化OpenGL函数initializeOpenGLFunctions();// 创建顶点着色器QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);const char *vsrc ="void main() {                             \n""   gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n""}                                         \n";vshader->compileSourceCode(vsrc);// 创建片段着色器QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);const char *fsrc ="void main() {                              \n""   gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n""}                                          \n";fshader->compileSourceCode(fsrc);// 创建着色器程序program = new QOpenGLShaderProgram;program->addShader(vshader);program->addShader(fshader);program->link();program->bind();
}

这里解释一下:

// 为当前环境初始化OpenGL函数initializeOpenGLFunctions();

这里首先调用QOpenGLFunctions::initializeOpenGLFunctions( )对OpenGL函数进行了初始化,这样QOpenGLFunctions中的函数只能在当前环境中使用。

   // 创建顶点着色器QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);//设置源码并编译const char *vsrc ="void main() {                             \n""   gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n""}                                         \n";vshader->compileSourceCode(vsrc);// 创建片段着色器QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);//设置源码并编译const char *fsrc ="void main() {                              \n""   gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n""}                                          \n";fshader->compileSourceCode(fsrc);

然后进行了着色器的相关设置。使用QOpenGLShader创建了一个顶点着色器和一个片段着色器,并使用compileSourceCode()函数为着色器设置了源码并进行了编译。

   // 创建着色器程序program = new QOpenGLShaderProgram;program->addShader(vshader);program->addShader(fshader);program->link();program->bind();

下面创建了着色器程序QOpenGLShaderProgram对象,使用addShader( )将前面已经编译好的着色器添加进来,然后调用link()函数将所有加人到程序中的着色器链接到一起,最后
调用bind()函数将该着色器程序绑定到当前OpenGL环境中。

为了使程序尽量简单,这里直接在程序中编写了着色器源码;

对于较复杂的着色器源码,一般是写在文件中的,可以使用compileSourceFile( )进行加载编译。


这个程序只是绘制一个白色的点,所以只需要指定一个顶点(gl_Position)vec4 (0.0, 0.0,0.0, 1. 0)和渲染颜色(gl_FragColor)vec4(1.0, 1.0,1.0,1.0),这里的vec4类型GLSL的4位浮点数向量.


这里对基本内容进行简单介绍:

可以把整个窗口的中心当作坐标原点,X轴从左到右,Y轴从下到上,Z轴从里到外,顶点(0.0, 0.0,0.0,1.0)的前3个分量分别是X.Y和Z轴的坐标,第4个分量默认为1.0,一般不用设置,所以该顶点就是坐标原点,后面会显示到窗口的中心。
在这里插入图片描述

颜色(gl_FragColor)(1.0,1.0, 1.0, 1.0)的前3个分量分别对应R红色、G绿色和B蓝色,第4个分量A对应alpha值,用于设置透明度,因为RGB均设置为1.0, 所以为白色。透明度1为不透明。


下面添加resize设置大小的函数:

void MyOpenGLWidget::resizeGL(int , int )
{
}

这里现在先保留为空,该函数不是必须设置的。


继续添加paintGL()绘制函数的定义:

void MyOpenGLWidget::paintGL()
{// 绘制glDrawArrays(GL_POINTS, 0, 1);
}

作为简单示例,这里直接调用了glDrawArrays()函数来进行OpenGL图形绘制。

glDrawArrays()函数原型为:

void QOpenGLFunctions::glDrawArrays(GLenum mode, GLint first, GLsizei count)

该函数使用当前绑定的顶点数组元素来建立几何图形.

第1个参数mode设置了构建图形的类型,如

  • GL_POINTS(点)
  • GL_LINES(线)
  • GL_LINE_STRIP(条带线)
  • GL_LINE_LOOP(循环线)
  • GL_TRIANGLES(独立三角形)
  • GL_TRIANGLE_STRIP(三角形条带)
  • GL_TRIANGLE_FAN(三角形扇面)等;

第2个参数first 指定元素起始位置

第3个参数count元素个数

就是用顶点数组中索引为first~first+count-1的元素为顶点来绘制mode指定的图形。

这里是glDrawArrays(GL_POINTS, 0, 1);绘制点,开始为0原点,个数为1

最后再向项目中添加 main.cpp文件,更改内容如下:

#include <QApplication>
#include "myopenglwidget.h"int main(int argc, char *argv[])
{QApplication app(argc,argv);MyOpenGLWidget w;w.resize(400, 300);w.show();return app.exec();
}

创建了一个 MyOpenGLWidget w;

现在运行程序可以看到,窗口背景默认为黑色,窗口中间绘制了一个白色点。
在这里插入图片描述

虽然很小,但是有的

这个程序虽然简单,但是展示了在Qt中绘制OpenGL图形的一般过程。下面将在这个程序的基础上进一步讲解OpenGL的其他内容。


绘制多边形

前面是一个顶点,绘制多边形需要更多的顶点

设置顶点一般使用数组来实现,然后将数组中的顶点数据输入到顶点着色器中。为了获得更好的性能,一般还会使用缓存。

使用顶点数组

(项目源码: src\12\12- 2\myopengl)继续在前面的程序中进行更改。首先将顶点着色器源码更改如下:

    // 创建顶点着色器QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);const char *vsrc ="in vec4 vPosition;                        \n""void main() {                             \n""   gl_Position = vPosition;               \n""}                                         \n";vshader->compileSourceCode(vsrc);

这里为顶点着色器声明了一个名为vPosition输入变量, in存储限制符表明了数据进入着色器的流向,与其对应的是out存储限制符,vPosition用于获取外部输入的顶点数据


//表明将输入的顶点位置复制到顶点着色器的指定输出位置gl_Position中。
“gl_Position = vPosition”

表明将输入的顶点位置复制到顶点着色器的指定输出位置gl_Position中。


下面来更改paintGL()绘画函数:

void MyOpenGLWidget::paintGL()
{// 顶点位置GLfloat vertices[] = {-0.8f, 0.8f,-0.8f, -0.8f,0.8f, -0.8f,0.8f, 0.8f};GLuint vPosition = program->attributeLocation("vPosition");glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, vertices);glEnableVertexAttribArray(vPosition);// 绘制glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

这里定义了一个顶点数组vertices[],一共4行,每行定义一个顶点位置。

在前面的例子中已经看到,顶点位置是vec4类型的,应该有4个值,但是这里每行只有2个值,其实vec4的默认值为(0,0,0,1);仅当指定了X和Y坐标时,其他两个坐标值将被自动指定为0和1。

这里以原点为中心设置了一个正方形的4个顶点,首先是左上角的顶点,然后沿逆时针方向设置了其他3个顶点,顶点顺序可以是顺时针也可以是逆时针,逆时针绘制出来的是正面,而顺时针绘制出来的是反面

 GLuint vPosition = program->attributeLocation("vPosition");

attributeLocation()可以返回变量在着色器程序参数列表中的位置,这里获取了vPosition的位置。

    glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, vertices);glEnableVertexAttribArray(vPosition);

然后使用glVertexAttribPointer()vPosition顶点数组vertices进行关联。

glVertexAttribPointer()原型如下:

inline void QOpenGLFunctions::glVertexAttribPointer(GLuint index, 
GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer)

该函数设置着色器中变量索引为index的变量对应的数据值。

其中:

  • index参数就是要输入变量的位置索引;
  • size表示每个顶点需要更新的分量数目,例如,这里vertices每行只有2个值,所以size为 2 ;
  • type指定了数组中元素的类型,例如,这里vertices是GLfloat类型的,所以这里type为GLfloat ;
  • normalized设置顶点数据在存储前是否需要进行归一化,这里设置为否;
  • stride是数组中每两个元素之间的大小偏移值,一般设置为0即可;
  • pointer 设置顶点数组指针或者缓存内的偏移量,这里使用了顶点数组,所以直接设置为vertices即可。
  • 最后需要使用glEnableVertexAttribArray()来启用顶点数组,这样就完成了所有设置。

调用glDrawArrays()进行绘制时需要设置图形类型为GL_TRIANGLE_FAN,因为有4个顶点,所以第3个参数为4。

这时运行程序可能发现,绘制的图形是较宽的长方形,而不是一个正方形,这是因为整个窗口的宽为400,高为300,


下面来解决这个问题。

在 paintGL()函数的开始添加如下代码:

    int w = width();int h = height();int side = qMin(w, h);glViewport((w - side) / 2, (h - side) / 2, side, side);

这里使用glViewport()设置视口为整个窗口中尽可能大的正方形,视口定义了所有OpenGL渲染操作最终显示的2D矩形。

(注意,这个操作本应该放到resizeGL()函数中进行,只是由于Qt版本原因,现在并不能实现应有的效果,所以放到了paintGl()中。)

paintGL()函数中一般还会调用glClear()来清除屏幕,在调用glViewport()之后添加如下一行代码:

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

这里清除了颜色缓存和深度缓存。

现在运行程序,并改变窗口大小,可以看到,正方形会随着窗口改变大小,但是不会被压缩变形,效果如图所示。
在这里插入图片描述

图—绘制正方形效果

使用缓存

前面程序使用的顶点数组中指定的数据会保存在客户端内存中,在进行glDrawArrays()等绘图调用时,这些数据必须从客户内存复制到图形内存

为了避免每次绘图时都复制这些数据,可以将其缓存到图形内存中

缓存对象在OpenGL服务器中创建,这样当需要顶点,索引,纹理图像等数据时,客户端程序就不需要每次都进行上传。

Qt中的QOpenGLBuffer类用来创建并管理OpenGL缓存对象

下面通过例子讲解该类的使用。
(项目源码:myopengl)继续在前面的程序中进行更改。先在myopenglwidget.h文件中添加头文件包含:

#include <QOpenGLBuffer>

然后添加private变量:

private:QOpenGLBuffer vbo;

下面到myopenglwidget.cpp中,在 paintGL()函数创建vertices数组后面添加如下代码:

 // 顶点位置GLfloat vertices[] = {-0.8f, 0.8f,-0.8f, -0.8f,0.8f, -0.8f,0.8f, 0.8f};vbo.create();vbo.bind();vbo.allocate(vertices, 20*sizeof(GLfloat));
  1. 首先调用create()函数在OpenGL服务器中创建缓存对象
  2. 然后使用bind()函数将与该对象相关联的缓存绑定到当前OpenGL环境
  3. allocate()函数在缓存中为数组分配空间并将缓存初始化为数组的内容

创建好缓存以后﹐就可以通过缓存为顶点着色器输入数据了。

下面将paintGL()函数中调用的glVertexAttribPointer()函数替换为:

     program->setAttributeBuffer(vPosition, GL_FLOAT, 0, 2, 0);

这里:

 // 顶点位置GLfloat vertices[] = {-0.8f, 0.8f,-0.8f, -0.8f,0.8f, -0.8f,0.8f, 0.8f};vbo.create();vbo.bind();vbo.allocate(vertices, 8*sizeof(GLfloat));GLuint vPosition = program->attributeLocation("vPosition");program->setAttributeBuffer(vPosition, GL_FLOAT, 0, 2, 0);glEnableVertexAttribArray(vPosition);
//     glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, vertices);
//     glEnableVertexAttribArray(vPosition);

setAttributeBuffer()函数glVertexAttribPointer()函数类似,其函数原型如下:

void QOpenGLShaderProgram::setAttributeBuffer(int location, GLenum type, int offset, 
int tupleSize, int stride = 0)

该函数用来为着色器中 location位置的变量设置顶点缓存,offset指定了缓存中要使用数据的偏移值
通过调用该函数就可以将vPosition变量与缓存中的顶点数据进行关联


现在可以运行程序查看效果:

在这里插入图片描述

绘制彩色3D图形

1 为图形设置顶点颜色

项目源码myopengl ,继续在前面的代码上更改,首先要更改着色器源码:

 // 创建顶点着色器QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);const char *vsrc ="in vec4 vPosition;                        \n""in vec4 vColor;                           \n""out vec4 color;                           \n""void main() {                             \n""   color = vColor;                        \n""   gl_Position = vPosition;               \n""}                                         \n";vshader->compileSourceCode(vsrc);

这里声明了输入变量vColor输出变量color,并将vColor获取的颜色数据传递给color

输出变量可以将数据传递给后续阶段使用,这里主要是传递给片段着色器

下面更改片段着色器源码如下:

 // 创建片段着色器QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);const char *fsrc ="in vec4 color;                             \n""out vec4 fColor;                           \n""void main() {                              \n""   fColor = color;                         \n""}                                          \n";fshader->compileSourceCode(fsrc);

这里声明了一个输入变量color,用来和顶点着色器的输出变量color对应。
输出变量fColor可以将color输入的颜色数据输出到着色管线中用来为图形着色。

下面到 paintGL()函数中,在 glDrawArrays()函数调用之前添加如下代码:

  // 顶点颜色GLfloat colors[] = {1.0f, 0.0f, 0.0f,0.0f, 1.0f, 0.0f,0.0f, 0.0f, 1.0f,1.0f, 1.0f, 1.0f};vbo.write(8*sizeof(GLfloat), colors, 12*sizeof(GLfloat));GLuint vColor = program->attributeLocation("vColor");program->setAttributeBuffer(vColor, GL_FLOAT, 8*sizeof(GLfloat), 3, 0);glEnableVertexAttribArray(vColor);

这里创建了一个颜色数组,共4行,分别为4个顶点进行着色

为了简便,这里直接在前面创建的缓存中写入了颜色数组数据,并为vColor变量指定了缓存。

write()函数原型如下:

void QOpenGLBuffer::write(int offset, const void *data, int count)

该函数会替换掉缓存中已有的内容,

  • 参数offset是要替换数据开始位置的偏移值,因为前面已经添加的顶点数组的大小为8·sizeof(GLfloat) ,所以这里需要将这个值作为偏移值。

为了不覆盖已有的数据,这里需要对缓存进行扩容,将前面程序中allocate()函数调用更改如下:

vbo.create();vbo.bind();vbo.allocate(vertices, 20*sizeof(GLfloat));

因为顶点数组有8个元素,颜色数组有12个元素,所以这里的大小设置为20·sizeof(GLfloat)。

现在运行程序可以看到,正方形的4个角分别是红,绿、蓝和白色。


在这里插入图片描述

这里我出现错误:not link,然后全黑
这里是OpenGL未调用。shader program is not linked

尝试安装directX 或者
禁用你的独立显卡,使用集成显卡

模拟器提示显卡/驱动版本较低,不支持dx11.0或OpenGL4.3以上怎么办?

QT5.12 Ui界面开发项目:QOpenGLShaderProgram::uniformLocation(model): shader program is not linked

2.实现3D效果

本小节采用的项目源码myopengl。继续在前面的程序中进行更改。首先更改顶点数组如下:

  // 顶点位置GLfloat vertices[2][4][3] = {{ {-0.8f, 0.8f, 0.8f}, {-0.8f, -0.8f, 0.8f}, {0.8f, -0.8f, 0.8f}, {0.8f, 0.8f, 0.8f} },{ {0.8f, 0.8f, 0.8f}, {0.8f, -0.8f, 0.8f}, {0.8f, -0.8f, -0.8f}, {0.8f, 0.8f, -0.8f} }};vbo.create();vbo.bind();vbo.allocate(vertices, 48*sizeof(GLfloat));

该数组每行指定了4个顶点(即一个正方形面),每个顶点由3个元素组成,因为要设置3D效果,所以每个顶点都指定了Z轴坐标

然后更改allocate()调用如下:

  vbo.allocate(vertices, 48*sizeof(GLfloat));

这里顶点数组有24个元素,后面颜色数组对应的也有24个元素,所以缓存大小为48sizeof(GLfloat)


下面更改设置vPosition的 setAttributeBuffer()函数:

 GLuint vPosition = program->attributeLocation("vPosition");program->setAttributeBuffer(vPosition, GL_FLOAT, 0, 3, 0);glEnableVertexAttribArray(vPosition);

因为现在数组中每个顶点由3个元素指定,所以这里第4个参数设置为3

下面更改颜色数组如下:

// 顶点颜色GLfloat colors[2][4][3] = {{ {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 1.0f} },{ {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 1.0f} }};

最后更改write函数调用:

 vbo.write(24*sizeof(GLfloat), colors, 24*sizeof(GLfloat));

这里24是24个元素(颜色数组)

下面更改VColor的setAttributeBuffer函数:

    program->setAttributeBuffer(vColor, GL_FLOAT, 24*sizeof(GLfloat), 3, 0);

最后绘制函数更改:

 // 绘制for(int i=0; i<2; i++)glDrawArrays(GL_TRIANGLE_FAN, i*4, 4);

这里要绘制两个面,所以用for()函数调用了2次 gIDrawArrays()函数进行绘制.

i*4: 第一次绘制用去了4个顶点,所以第2次调用时设置了起始位置(即第2个参数的值)为4

现在已经绘制出了立方体两个相邻的面,但是运行程序发现,因为角度问题只能看到前面的面。

在这里插入图片描述

下面通过使用透视投影矩阵对顶点进行变换来改变显示图形的角度

在调用绘制函数的这两行代码前添加如下代码:

 QMatrix4x4 matrix;matrix.perspective(45.0f, (GLfloat)w/(GLfloat)h, 0.1f, 100.0f);matrix.translate(0, 0, -3);matrix.rotate(-60, 0, 1, 0);  //绕Y轴逆时针旋转program->setUniformValue("matrix", matrix);
  • QMatrix4x4类可以表示一个3D空间中的4×4变换矩阵,
  • perspective()函数用来设置透视投影矩阵,这里设置了视角为45°,纵横比为窗口的纵横比,最近的位置为0.1,最远的位置为100。
  • translate()函数平移X、Y和Z轴,这里将Z轴平移-3,即向屏幕里移动
  • rotate()可以设置旋转角度,4个参数分别用来设置角度和X,Y,Z轴,比如这里将Y轴设置为1,就是绕Y轴旋转﹐角度为-60,也就是逆时针旋转60°;
    如果角度为正值,则就是顺时针旋转
  • 最后使用setUniformValue()函数将矩阵关联到顶点着色器的matrix变量

下面将顶点着色器源码更改如下:

    // 创建顶点着色器QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);const char *vsrc ="#version 130\n""in vec4 vPosition;                        \n""in vec4 vColor;                           \n""out vec4 color;                           \n""uniform mat4 matrix;                      \n""void main() {                             \n""   color = vColor;                        \n""   gl_Position = matrix * vPosition;      \n""}                                         \n";vshader->compileSourceCode(vsrc);

这里声明了一个matrix变量,使用了uniform 存储限制符,表明该变量不会在处理过程中发生变化,着色器无法写入到uniform变量,也无法改变它的值。

可以通过setUniformValue()函数来为uniform变量设置值。

gl_Position = matrix * vPosition;

最后在着色器main()函数中进行了矩阵与顶点的乘法运算,注意,矩阵应该在左侧而顶点在右侧。

//表明将输入的顶点位置与顶点的乘法运算到顶点着色器的指定输出位置gl_Position中。
“gl_Position = matrix * vPosition”
//确定位置


现在运行程序就已经可以看到3D立体效果了:

在这里插入图片描述


纹理贴图

前面的程序中生成了正方体的2个面,为了实现更加真实的3D效果,还可以使用图片作为2个面的纹理贴图

Qt的 QOpenGLTexture类封装了一个 OpenGL纹理对象,可以使用该类来设置纹理

该部分内容可以参考Textures Example示例程序。

项目源码:myopengl)在前面的程序中打开myopenglwidget.h文件,添加类前置声明:

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>class QOpenGLShaderProgram;
class QOpenGLTexture;

这里是使用了QOpenGLTexture类:

然后添加一个私有变量:

private:QOpenGLTexture *textures[2];

下面到myopenglwidget.cpp文件中添加头文件:

#include <QOpenGLTexture>

然后在initializeGL()函数的开始部分对变量进行初始化:

void MyOpenGLWidget::initializeGL()
{for (int i = 0; i < 2; ++i)textures[i] = new QOpenGLTexture(QImage(QString("../myopengl/side%1.png").arg(i + 1)).mirrored());

这里需要将两张图片复制到源码目录下。下面更改顶点着色器的源码如下:

const char *vsrc =      "in vec4 vPosition;                        \n""in vec4 vTexCoord;                        \n""out vec4 texCoord;                        \n""uniform mat4 matrix;                      \n""void main() {                             \n""   texCoord = vTexCoord;                  \n""   gl_Position = matrix * vPosition;      \n""}                                         \n";

texCoord = vTexCoord;
这里就是将前面的颜色相关变量换成了纹理相关变量,VTexCoord 用来输入纹理坐标

对应的,将片段着色器源码更改如下:

  const char *fsrc ="uniform sampler2D tex;                     \n""in vec4 texCoord;                          \n""out vec4 fColor;                           \n""void main() {                              \n""   fColor = texture(tex, texCoord);        \n""}                                          \n";

这里声明了一个sampler2D类型的采样器变量tex,然后在main()函数中使用texture()纹理函数,采样器tex 会以texCoord表示的纹理坐标进行采样,该函数返回包括采样的纹理数据的向量

paintGL()函数将前面设置顶点颜色数组的相关代码更改如下:

   // 纹理坐标GLfloat coords[2][4][2] = {{ {0.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f} },{ {0.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f} }};vbo.write(24*sizeof(GLfloat), coords, 16*sizeof(GLfloat));GLuint vTexCoord = program->attributeLocation("vTexCoord");program->setAttributeBuffer(vTexCoord, GL_FLOAT, 24*sizeof(GLfloat), 2, 0);glEnableVertexAttribArray(vTexCoord);program->setUniformValue("tex", 0);

这里就是将顶点颜色的相关设置更换为纹理坐标的设置

纹理顶点设置了X和Y坐标,可以简单地这样理解:

  • 对于X坐标,0.0表示纹理的左侧,0.5表示纹理的中点,1.0表示纹理的右侧;
  • 对于Y坐标,0.0表示纹理的底部,0.5表示纹理的中点,1.0表示纹理的顶部。

需要将纹理的4个顶点正确对应到正方形的4个顶点上。

下面更改绘制函数如下:

// 绘制for(int i=0; i<2; i++) {textures[i]->bind();glDrawArrays(GL_TRIANGLE_FAN, i*4, 4);}

QOpenGLTexture类的 bind()函数可以将纹理绑定到当前活动纹理单元来准备渲染。

现在运行程序可以看到,已经在两面正方形上使用了指定的图片。

在这里插入图片描述


为了更方便地查看3D效果,下面来实现使用按键控制图形旋转。

(项目源码myopengl)首先在 myopenglwidget.h文件中添加键盘按下事件处理函数的声明:

  void keyPressEvent(QKeyEvent *event);

添加private变量:

private:QOpenGLShaderProgram *program;QOpenGLBuffer vbo;QOpenGLTexture *textures[2];GLfloat translate, xRot, yRot, zRot;

下面到myopenglwidget.cpp文件中先添加头文件#include <QKeyEvent>,然后在构造函数中初始化变量:

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{translate = -6.0;xRot = zRot = 0.0;yRot = -30.0;
}

到paintGL)函数中更改设置矩阵的相关代码如下:

 QMatrix4x4 matrix;matrix.perspective(45.0f, (GLfloat)w/(GLfloat)h, 0.1f, 100.0f);matrix.translate(0, 0, translate);matrix.rotate(xRot, 1.0, 0.0, 0.0);matrix.rotate(yRot, 0.0, 1.0, 0.0);matrix.rotate(zRot, 0.0, 0.0, 1.0);program->setUniformValue("matrix", matrix);

然后添加键盘按下事件处理函数的定义:

void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{switch (event->key()) {case Qt::Key_Up:xRot += 10;break;case Qt::Key_Left:yRot += 10;break;case Qt::Key_Right:zRot += 10;break;case Qt::Key_Down:translate -= 1;break;case Qt::Key_Space:translate += 1;break;default:break;}update();QOpenGLWidget::keyPressEvent(event);
}

现在运行程序,则可以通过键盘方向键和空格键来控制图形的显示。

为了拥有更好的显示效果,可以开启深度测试,下面在initializeGL()函数中initializeOpenGL-Functions()函数调用之后添加如下代码:

   // 启用深度测试glEnable(GL_DEPTH_TEST);

全部代码如下:

void MyOpenGLWidget::initializeGL()
{for (int i = 0; i < 2; ++i)textures[i] = new QOpenGLTexture(QImage(QString("../myopengl/side%1.png").arg(i + 1)).mirrored());// 为当前环境初始化OpenGL函数initializeOpenGLFunctions();// 启用深度测试glEnable(GL_DEPTH_TEST);// 创建顶点着色器QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);const char *vsrc ="in vec4 vPosition;                        \n""in vec4 vTexCoord;                        \n""out vec4 texCoord;                        \n""uniform mat4 matrix;                      \n""void main() {                             \n""   texCoord = vTexCoord;                  \n""   gl_Position = matrix * vPosition;      \n""}                                         \n";vshader->compileSourceCode(vsrc);// 创建片段着色器QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);const char *fsrc ="uniform sampler2D tex;                     \n""in vec4 texCoord;                          \n""out vec4 fColor;                           \n""void main() {                              \n""   fColor = texture(tex, texCoord);        \n""}                                          \n";fshader->compileSourceCode(fsrc);// 创建着色器程序program = new QOpenGLShaderProgram;program->addShader(vshader);program->addShader(fshader);program->link();program->bind();}

再次运行可以看到3D图形效果:
在这里插入图片描述
可以通过键盘方向键和空格键来控制图形的显示。就是选择方向。

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

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

相关文章

【Qt】Qt环境配置与入门案例

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Qt环境配置与入门。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&…

(三)NI采集卡应用学习:在Qt中实现多路数据的同时采集、显示、绘图及存储

1、使用NI MAX创建多路输入的测量任务 本处设置Dev1/ai0:3四路电压输入&#xff0c;采样率均为100&#xff0c;差分连接。 2、代码 // 设置地址 void Widget::on_add_pushButton_clicked() {address_txt QFileDialog::getExistingDirectory(this, tr("Open Directory&q…

linux程序框架设计,《Linux与Qt程序设计》知识框架

本文主要是通过一本书来大致了解Qt开发的框架,不对具体内容做详细分析。 1.首先弄清楚概念:定义->以自己的话理解是什么-> 实现的是什么功能->用在哪些地方 2.前面认识到的知识点的特点-> 代码实现-> 工程代码分析 第一部分 Linux基础知识 第二部分 Qt程序基础…

QT开发应用程序(11)--图形绘制和文本输出

代码演示&#xff1a; #include <QPainter> MyDraw::MyDraw(QWidget *parent) :QWidget(parent) {QLinearGradient linearGradient(0,0,400,400);linearGradient.setColorAt(0.0,Qt::white);linearGradient.setColorAt(0.2,QColor(0,0,0));linearGradient.setColorAt(1.…

chatgpt赋能python:使用Python捕获错误:为您的代码添加可靠性

使用Python捕获错误&#xff1a;为您的代码添加可靠性 在编写Python代码时&#xff0c;错误很常见。您可能会因输入无效参数而收到TypeError&#xff0c;或者因无法连接到数据库而收到ConnectionError。当这些错误发生时&#xff0c;您的代码可能会崩溃或产生不正确的结果。尽…

chatgpt赋能python:关闭Python:如何优雅地退出一个Python程序

关闭Python&#xff1a;如何优雅地退出一个Python程序 Python作为一门开发语言&#xff0c;可以广泛应用于众多领域&#xff0c;如数据科学、人工智能、Web应用开发等等。但有时候&#xff0c;我们需要关闭一个正在运行的Python程序。本文将介绍如何优雅地退出一个Python程序&…

chatgpt赋能python:Python异常捕获存在的问题

Python 异常捕获存在的问题 作为一门广受欢迎、应用广泛的编程语言&#xff0c;Python 在处理异常方面有着比较完善的设计。Python 提供了 try…except…finally 这样的异常处理机制&#xff0c;通过这些机制&#xff0c;开发者可以捕获、处理程序中产生的异常&#xff0c;从而…

yagmail——快速发送邮件

yagmail——快速发送邮件 目标&#xff1a;如何利用python中的yagmail包&#xff0c;快速发送邮件。 python写的一个简单demo脚本代码如下&#xff1a; import yagmail import pandas as pd from loguru import logger# 保存excel文件 contents pd.DataFrame() contents[&quo…

电脑和微信怎么发邮件到别人邮箱,企业邮箱如何发email给别人

邮箱已成为主流的办公软件&#xff0c;除了日常的电脑办公使用以外&#xff0c;你知道还有哪些便捷的方式使用邮箱吗&#xff1f;现在是互联网时代&#xff0c;许多邮箱都研发了手机邮箱配合使用&#xff0c;那TOM企业邮箱有哪些便捷的服务呢&#xff1f;一起看看吧&#xff01…

怎么发送电子邮件到别人邮箱?手把手教你!

在日常生活中&#xff0c;我们在工作学习的时候都离不开电子邮件&#xff0c;无论两人的距离有多远&#xff0c;都是可以通过电子邮件的方式完成信息的即时传递。这样不仅可以传输文字信息&#xff0c;还能传输图片、视频等多种格式的信息。那么&#xff0c;怎么发送电子邮件到…

chatgpt赋能Python-pythonapp自动化

Python App自动化&#xff1a;优化SEO的终极解决方案 随着互联网的发展&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;变得日益重要。对于任何网站或应用程序开发人员来说&#xff0c;SEO应该是一个非常重要的考虑因素。为了帮助开发人员和企业提高其在线可见性&#x…

chatgpt赋能python:Python自动化断言介绍

Python 自动化断言介绍 软件业的各种应用程序不可避免地需要与用户进行交互。测试团队应该确保这些应用程序快速&#xff0c;可靠地响应用户交互&#xff0c;并注重高质量测试的策略和工具。Python自动化断言是一种测试策略&#xff0c;可帮助团队实现高品质和高效率的测试。P…

chatgpt赋能python:Python自动化操作桌面的方法和应用

Python自动化操作桌面的方法和应用 Python是一种高级编程语言&#xff0c;越来越多的研究人员和程序员将其应用在自动化操作任务上。本文将向您介绍如何利用Python自动化操作桌面并且展示一些实际应用。 桌面自动化操作是什么&#xff1f; 桌面自动化操作是使用编程语言编写…

chatgpt赋能python:Python表格自动化:优化你的数据处理工作效率

Python表格自动化&#xff1a;优化你的数据处理工作效率 作为一名有10年Python编程经验的工程师&#xff0c;我深切体会到编程能够为我们带来的高效和便利。其中&#xff0c;Python在数据分析和处理方面的优势尤为突出。本文将从Python表格自动化的角度&#xff0c;为大家介绍…

chatgpt赋能python:Python如何自动化办公

Python如何自动化办公 随着信息技术的不断发展和进步&#xff0c;自动化技术已经在各行各业得到了广泛的应用。作为一种高级编程语言&#xff0c;Python在自动化办公领域发挥了重要的作用。下面&#xff0c;我们来详细了解一下Python如何自动化办公。 什么是Python自动化办公…

chatgpt赋能Python-python_gui自动化点击

简介 Python是一种功能强大的编程语言&#xff0c;它的广泛应用让许多企业都使用了这种语言来编写脚本&#xff0c;编写小型应用程序和构建自动化工作流程。其中&#xff0c;Python GUI自动化成为了越来越普遍的需求。Python可以使用多种GUI库&#xff0c;例如PyQt、Tkinter和…

chatgpt赋能python:如何用Python自动化办公提升工作效率

如何用Python自动化办公提升工作效率 随着科技的不断发展和普及&#xff0c;人们越来越重视如何利用技术手段提高生产效率。对于很多办公人员来说&#xff0c;每天大量的重复性工作可能会让人感到枯燥乏味。而Python作为一种易学易用的编程语言&#xff0c;可以帮助办公人员自…

chatgpt赋能Python-python_gui_自动化

Python GUI自动化——让你的工作更高效 Python是一种多用途、高级语言&#xff0c;使用广泛。它是一种开发强大且易于维护的程序的语言。自从出现以来&#xff0c;Python已经在许多领域得到了广泛的应用&#xff0c;包括 Web 开发、数据分析、人工智能和自动化测试等。而其中&…

chatgpt赋能python:自动化办公:Python的应用

自动化办公&#xff1a;Python的应用 随着信息量的爆炸式增长&#xff0c;人们面临越来越多的数据处理任务。办公桌上堆积如山的资料&#xff0c;以及工作日程的繁忙&#xff0c;使得人们越来越需要自动化的解决方案。Python自从诞生以来&#xff0c;就在自动化开发领域占据了…

chatgpt赋能Python-python_gui自动化

Python GUI自动化&#xff1a;让测试变得更加高效和可靠 在当今的软件开发市场中&#xff0c;自动化测试已经成为了一项必不可少的技术。与手动测试相比&#xff0c;自动化测试具有更高的效率和更可靠的结果。在自动化测试的过程中&#xff0c;GUI&#xff08;图形用户界面&am…