QT—3D绘图

2023-10-30

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_OBJECT

public:
    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图形效果:
在这里插入图片描述
可以通过键盘方向键和空格键来控制图形的显示。就是选择方向。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT—3D绘图 的相关文章

  • QGraphicsScene没有删除QWidget的功能

    QGraphicsScene 有一个addWidget QWidget 有函数 但是没有对应的removeWidget QWidget 它只有removeItem QGraphicsItem 如何删除 QWidget 这是一个基本示例 看看
  • Qt 5.5 QOpenGLWidget 链接错误未链接任何 openGL 调用

    我尝试使用 Qt 5 5 1 构建一个简单的 OpenGL 应用程序 一切都很好 直到我尝试使用 glClearColor 等 openGL 本机函数调用 该小部件实际上编译并产生黑屏 但在我尝试使用任何 openGL 本机函数后 它甚至不
  • 如何在qt中进行异步文件io?

    我想知道如何在qt中实现异步文件io 这在普通的 qt 中是否可以实现 或者有人需要使用另一个库 例如 libuv 来实现这样的事情 我正在查看 QDataStream 但即使它是一个 流 它也不是非阻塞的 我想一种解决方案是制作一个在内部
  • 在 R 中绘制 3D 数据

    我有一个 3D 数据集 data data frame x rep c 0 1 0 2 0 3 0 4 0 5 each 5 y rep c 1 2 3 4 5 5 data z runif 25 min data x data y 0 1
  • 我应该使用 QCoreApplication::processEvents() 还是 QApplication::processEvents()?

    我有一个从两者调用的方法QThreads和主线程 这个方法有时可能需要很长时间才能在循环中进行计算 所以我把QCoreApplication processEvents 这可以防止 GUI 冻结 在某个时刻我已经改变了QCoreApplic
  • 如何在 QT 中绘制点?

    我正在用 QT 用 C 编写一个应用程序 其中有 n 个点并计算它的凸包 然而 一旦计算出来 我不知道如何绘制点并绘制船体的边界 制作菜单按钮等很简单 但我不确定我是否知道执行此操作的工具 你怎么做到这一点 图形视图 addEllipse
  • 仅在发布模式下使用 glGenBuffer 时出现未处理的异常 - QT

    我在 Windows 7 上使用 Qt 4 8 发布模式编译项目时遇到了一些问题 调试时一切正常 但在发布时我收到未处理的异常 0xC0000005 访问冲突 我将范围缩小到发生这种情况的行 即生成像素缓冲区的时间 我的第一个猜测是 DLL
  • Qt qDebug() 在 Windows shell 中不起作用

    我正在使用一个qDebug Qt 框架的printf屏幕上有东西 当我从 Qt Creator 运行应用程序时它工作得很好 但是当我尝试从 Windows 执行它时cmd它什么也没显示 为什么会发生这种情况 你必须添加 CONFIG con
  • QMutex 是否需要是静态的,以便此类实例的其他线程调用知道暂停其操作?

    从多个线程调用以下附加函数 我不希望数据重写附加 因为计数器尚未增加 除了当前使用 Append 的线程之外 这是否会挂起所有进入的线程 或者其他线程会继续运行而不追加数据吗 互斥锁是否需要是 静态 的 或者每个实例都知道要暂停操作吗 如果
  • 在没有加载器的情况下实例化内联组件

    有没有办法实例化内联Component 即在同一文件中定义 而不使用Loader 我不太关心使用的性能影响Loader因为我要用很多东西污染我的文件Loader包装纸 我发现从 JavaScript 创建动态 QML 对象 http doc
  • 清除pyqt中布局中的所有小部件

    有没有办法清除 删除 布局中的所有小部件 self plot layout QtGui QGridLayout self plot layout setGeometry QtCore QRect 200 200 200 200 self r
  • Retina 显示屏中具有 QOpenGLWIdget 的 Qt MainWindow 显示错误大小

    我有一个 Qt 应用程序MainWindow 我嵌入一个QOpenGLWidget在里面 一切正常 直到我开始使用 Apple Retina 显示屏并在高 DPI 模式下运行我的应用程序 我的QOpenGLWidget只是它应该具有的大小的
  • 加速球之间的碰撞检测

    我正在编写一个物理引擎 模拟器 其中包含 3D 太空飞行 行星 恒星引力 船舶推力和相对论效应 到目前为止 一切进展顺利 但是 我需要帮助的一件事是碰撞检测算法的数学 我使用的运动迭代模拟基本上如下 注意 3D 矢量全部大写 For eac
  • 有人知道如何在android中实现像Unfold(这是iphone中的应用程序)这样的效果吗?

    我怎样才能实现这个效果呢 任何建议都会对我有帮助 None
  • 如何在针对 Windows XP 的情况下使用 VS2012 构建 Qt 4/5?

    我正在尝试使用 Visual Studio 2012 构建 Qt 4 8 5 Qt 5 2 1 针对 Windows XP SDK v7 1a 使用 VS2102 编译时 源代码与 SDK v7 1a 存在各种不兼容性 因此无法开箱即用 这
  • Mac OS X 上的 Qt 字体系列和样式

    我有一个基于 Qt PyQt 的 GUI 应用程序 在 Mac OS X 和其他平台 上运行 它允许用户选择字体并具有粗体和斜体选项的复选框 在更新到新的基于 Cocoa 的 Qt 时 用户发现QFontComboBox不再显示不同的字体样
  • 如何doxygen注释Qt属性?

    我想将 Doxygen 注释附加到我的 Q PROPERTY 例如 song h class Song public QObject Q OBJECT private Q PROPERTY QString title READ title
  • React-Three-Fiber:JSON 中位置 3 出现意外标记 c 错误

    我正在尝试使用 React Three Fiber 加载 glb 文件 但出现以下错误 Error Unexpected token c in JSON at position 3 我不确定我做错了什么 看来此问题最常见的解决方案是将 gl
  • Qt中用于线程间通信的类设计

    问题陈述 用相机跟踪物体并相应地移动相机的方位角和仰角 Process 相机获取物体的图像 处理相机的每一帧以查找物体 应该被跟踪 并将每帧中生成的信息传递给机械设备 万向节 以平移和倾斜方式移动摄像机 Design 主 Gui 在一个线程
  • 如何在Android中使用QML - QWebView

    我想在 Android 中部署一个 YouTube 应用程序 但它只能在我的电脑上运行 在安卓上不起作用 它不加载任何视频 问题仅出在 QWebView 上 我使用了与此类似的代码 http doc qt io archives qt 5

随机推荐

  • 目标检测:OneNet: Towards End-to-End One-Stage Object Detection

    目录 算法介绍 算法训练 算法介绍 OneNet算法的优点 1 全卷积 端到端 没有ROI 操作 也没有 attention 机制 2 标签分配是通过最小代价策略 不需要复杂的人工设计或者启发 3 没有任何后处理 比如 NMS max po
  • 本月与上月对比公式_Tableau 环比 for 本月某一段时间 与 上月同一段时间

    来源 https interworks com blog estam 2017 04 05 compare incomplete month same days previous month tableau April 5 2017 by
  • 前端html2canvas和dom-to-image实现截图功能

    目录 需求 历劫过程 截图知识点 html2canvas 文档地址 封装 使用教程 dom to image more 文档地址 封装 使用教程 解决跨域问题 以下是我花了大把时间 薅秃头得出来的最终结果 dom to image more
  • 自己做的负离子源供气系统的stm32 项目架构讲解

    前景 为了准备校招 熟悉下自己的项目 项目架构 1 电源部分 STM32 正负3 3供电 程序下载5V 光接收 0 5V AD电压采集 正负15V 2 程序下载 串口 CH340 UART协议 3 光接收 光纤接收 75452与非门 提高驱
  • std::result_of实现分组group by的功能

    工作中 有时候需要对相同的条件进行分类 比如同名的 同年龄的 添加头文件 include
  • nuitka打包的exe比用pyinstaller打包的exe运行速度快?

    自从使用pyinstaller 打包后 总感觉 pyinstaller 打的exe文件体积太大 运行速度差强人意 经朋友推荐 正自尝试使用 nuitka 关于nuitka的使用知呼上有兄弟贴了使用教程 如下地址 先给个nuitka官方帮助文
  • 人工智能系列-Python系列(一)初始与入门

    转载请注明预见才能遇见的博客 http my csdn net 原文地址 https blog csdn net pcaxb article details 90633523 人工智能系列 Python系列 一 初始与入门 目录 人工智能系
  • 2023华为OD机试真题Java实现【动态规划/基站维护最短距离】

    参考代码 小王是一名基站维护工程师 负责某区域的基站维护 某地方有n个基站 1
  • 小米3c路由器拆机_Redmi 9通过FCC认证;小米手环5曝光;小米路由器AX1800发布

    早在5月9日 就有人在小米海外官网上发现了Redmi 9 并且该机已经获得无线射频认证 型号为M2004J19G 而在今天 Redmi 9通过了FCC认证 从FCC文档上我们可以发现 Redmi 9配备了5000mAh大容量电池 结合此前卢
  • spring boot(四):thymeleaf使用详解

    在上篇文章springboot 二 web综合开发中简单介绍了一下thymeleaf 这篇文章将更加全面详细的介绍thymeleaf的使用 thymeleaf 是新一代的模板引擎 在spring4 0中推荐使用thymeleaf来做前端模版
  • IPV6学习笔记之IPV6地址结构

    IPV6 地址介绍 IPV6地址结构为 前缀 接口标识 前缀相当于IPV4中的网络ID 接口标识相当于主机ID IPv6地址共128 bit 分为8个16bit的块 中间用冒号隔开 例如 2001 0DB8 0000 0000 02AA F
  • 字符串处理专题

    codeup习题 文章目录 codeup习题 Problem A 字符串连接 Problem B 首字母大写 Problem C 字符串的查找删除 Problem D 单词替换 Problem E 字符串去特定字符 Problem F 数组
  • 巴比特

    摘要 据钛媒体报道 8月4日 华为在开发者大会上发布了HarmonyOS 4系统 通过盘古大模型的加持 智慧助手小艺将具备AI大模型能力 就在华为发布HarmonyOS 4系统一周后 小米大模型也浮出水面 其开发的大规模预训练语言模型MiL
  • JSP相关学习——JavaScript事件处理

    JavaScript事件处理 一 什么是事件处理程序 JavaScript可以以事件驱动的方式直接对客户端的输入作出响应 无须经过服务器端程序 也就是说 JavaScript是事件驱动的 二 事件类型 三 事件处理程序的调用 方式一 将事件
  • 一文带你实现刷新页面数据不丢失(操作状态保留)效果

    页面刷新后对数据的操作状态进行保留 也就是页面刷新数据不丢失效果 是我们对前端项目开发中一个算是比较基本的效果了 其实实现的话也是很简单的 今天就来带你一文彻底弄懂此效果的实现 其实总结起来有以下三个比较重要的点 1 监听要保留状态的数据
  • 【文献管理】Zotero基础操作

    文献管理 Zotero基础操作 文章目录 文献管理 Zotero基础操作 一 安装 注册 二 文献导入 1 新建文件夹 2 导入方式 三 文献管理 1 添加标签 2 添加笔记 3 添加插件 四 数据管理与同步 1 数据存放位置 2 数据备份
  • 操作系统模拟页面调度算法(OPT、FIFO、LRU)演示(vc6.0调试通过)

    PageSwitch cpp Defines the entry point for the console application include stdafx h 请分别用FIFO OPT算法实现 页面置换 的模拟 模拟程序的要求如下
  • python数据解析——xpath爬取文字和图片

    xpath解析 最常用且最便捷高效的一种解析方式 通用性 xpath解析原理 1 实例化一个etree的对象 且需要将被解析的页面源码数据加载到该对象中 2 调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕
  • 基于redis实现延时队列(二)

    背景 上篇文章中使用了redis的zset 定时器实现延时任务 虽然定时器设置为30秒执行一次 但是还是有时间上的差异化 现更换一种方式实现 可以避免时间上的差异 redis的key过期回调事件 也能达到延迟队列效果 配置修改 redis的
  • QT—3D绘图

    OpenGL是一个跨平台的 用来渲染3D图形的标准API Qt对OpenGL提供了强大的支持 Qt4时代的QtOpenGL模块在Qt5中已经不再建议使用 OpenGL相关的类被移到了Qt GUI模块 Qt Widgets模块中的QOpenG