Qt知识回顾(十一)——3D绘图

  OpenGL是一个跨平台的、用来渲染3D图形的标准API,Qt对OpenGL提供了强大的支持。Qt Widgets模块中的QOpenGLWidget类提供了一个可以渲染OpenGL图形的部件,通过该部件可以轻松地将OpenGL图形整合到Qt应用程序中。

使用OpenGL绘制图像介绍

  QOpenGLWidget类是一个用来渲染OpenGL图形的部件,它提供了在Qt应用程序中显示OpenGL图形的功能。这个类使用起来很简单,只需要继承该类,然后像使用其他QWidget部件一样来使用它即可。QOpenGLWidget提供了3个方便的虚函数,可以在子类中重新实现它们来执行典型的OpenGL任务:
  >initializeGL():设置OpenGL资源和状态、该函数只在第一次调用resizeGL()或paintGL()前被调用一次;
  >resizeGL():设置OpenGL的视口、投影等。每次部件改变大小时都会调用该函数;
  >painter():渲染OpenGL场景。每当部件需要更新时都会调用该函数。
  从OpenGL 2.0开始引入着色器的概念,除了固定功能的管道以外,增加了一种可编程着色管道,可以通过着色器控制顶点和片段的处理。从OpenGL3.1开始,固定功能的管线被废弃并删除了,于是必须使用着色器来完成工作。着色器是使用OpenGL着色语言(OpenGL Shading Language,GLSL)编写的一个小型函数。绘图时需要至少指定两个着色器:顶点着色器和片段着色器。Qt中QOpenGLShader类用来创建和编译着色器,支持使用OpenGL着色语言GLSL和OpenGL/ES着色语言GLSL/ES编写的着色器。QOpenGLShaderProgram类用来创建并设置着色器程序,可以链接多个着色器,并在OpenGL当前环境中绑定着色器程序。QOpenGLFunction类提供了对OpenGL ES 2.0 API的访问接口,QOpenGLExtraFunctions类提供了对OpenGL ES 3.0和3.1API的访问接口。QAbstractOpenGLFunctions是一个类族的基类,类族中的类涉及了所有的OpenGL版本,并为相应版本OpenGL的所有函数提供了访问接口。
绘制点的简单例子如下:
myopenglwiegt.h

//myopenglwiegt.h
#ifndef MYOPENGLWIEGT_H
#define MYOPENGLWIEGT_H

#include<QOpenGLWidget>
#include<QOpenGLFunctions>

class QOpenGLShaderProgram;

//多继承,自定义的MyopenGLWiegt类同时继承自QOpenGLWidget和QOpenGLFunctions
//这样就可以在类中直接使用QOpenGLFunctions中的OpenGL函数,而不需要创建QOpenGLFuctions对象
//这里声明了一个QOpenGLShaderProgram对象指针,作为着色器程序
class MyopenGLWiegt:public QOpenGLWidget,protected QOpenGLFunctions
{
    Q_OBJECT
public:
    MyopenGLWiegt(QWidget *parent = 0);
protected:
    void initializeGL();
    void paintGL();
    void resizeGL(int width,int height);

private:
    QOpenGLShaderProgram *program;
};

#endif // MYOPENGLWIEGT_H

myopenglwiegt.cpp

#include "myopenglwiegt.h"
#include<QOpenGLShaderProgram>

MyopenGLWiegt::MyopenGLWiegt(QWidget *parent):QOpenGLWidget (parent)
{

}

/*
 * 这里首先调用QOpenGLFunctions::initializeOpenGLFunctions()对OpenGL函数进行初始化,这样QOpenGLFunctions中的函数只能在当前环境中使用
 * 然后进行了着色器的i相关设置,使用QOpenGLShader创建了一个顶点着色器和一个片段着色器,并使用compileSourceCode()函数为着色器设置了源码并进行了编译
 * 下面创建了着色器程序QOpenGLShaderProgram对象,使用addShapder()将前面已经编译好的着色器添加进来,然后调用link()函数将所有加入到程序中的着色器链接到一起
 * 最后调用bind()函数将该着色器程序绑定到当前OpenGLhuanjingzhong1
 * (为了使程序尽量简单,这里直接在程序中编写了着色器源码,对于较复杂的着色器源码,一般是写在文件中的,可以使用compileSourceFile()进行加载编译。
 *  这个程序只是绘制一个白色的点,所以只需要指定一个顶点vec4和渲染颜色vec4,这里的vec4类型是GLSL的4位浮点数向量)
*/
void MyopenGLWiegt::initializeGL()
{
    //为当前环境初始化OpenGL函数
    initializeOpenGLFunctions();
    //创建顶点着色器
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex,this);
    const char *vsrc =
            "void main(){                        \n"
            "gl_Position = vec4(0.0,0.0,1.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();

}

/*
 * 作为简单示例,这里直接调用glDrawArrays()函数来进行OpenGL图形绘制
 * void glDrawArrays(GLenum mode,GLint first,GLsizei count)该函数使用当前绑定的顶点数组来建立几何图形
 * 第一个参数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指定的图形
*/
void MyopenGLWiegt::paintGL()
{
    glDrawArrays(GL_POINTS,0,1);
}

void MyopenGLWiegt::resizeGL(int width, int height)
{

}

main.cpp

#include<QApplication>
#include"myopenglwiegt.h"

int main(int argc,char* argv[])
{
    QApplication app(argc,argv);
    MyopenGLWiegt w;
   // w.resize(400,300);
    w.show();
    return app.exec();
}

绘制多边形

  想要绘制复杂的图形,就需要设置更多的顶点,设置顶点一般使用数组来实现,然后将数组中的顶点数据输入人到顶点着色器中。为了获取更好的性能,一般还会使用缓冲。

使用顶点数组
  继续在前面的程序中进行更改,首先将顶点着色器源码更改如下:

const char *vsrc =
            "in vec4 vPosition;                 \n"
            "void main(){                        \n"
            "    gl_Position = vPosition;        \n"
            "}                                    \n";

  然后更改paintGL()函数:

/*
 * 这里定义了一个顶点数组vertices,一共4行,每行定义一个顶点位置。在前面的例子中已经看到,顶点位置是vec4类型的,应该有4个值,但是这里每行只有两个值,其实vec4的默认值为(0,0,0,1)
 * 当仅制定了了X和Y坐标时,其他两个坐标将被自动指定为0和1。这里以原点为中心设置了一个正方形的4个顶点,首先是左上角的顶点,然后沿逆时针方向设置了其他3个顶点,定点顺序可以是顺时针也可以是逆时针
 * 逆时针绘制出来的是正面,而顺时针绘制出来的是反面。
 * attributeLocation()可以返回变量在着色器程序参数列表中的位置,这里获取了vPosition的位置
 * 然后使用glVertexAttribPointer()将vPosition与顶点数组vertices进行关联
 * 最后需要使用GlEnableVertexAttribArray()来启动顶点数组,这样就完成了所有设置
 * [void 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设置顶点数据在存储前是否需要进行归一化
 * strider是数组中每两个元素之间的大小偏移值,一般设置为0即可
 * pointer设置顶点数组指针或者缓存内的偏移量,这里使用了顶点数组,所以直接设置为vertices即可]
 *
 *
*/
void MyopenGLWiegt::paintGL()
{
    int w = width();
    int h = height();
    int side = qMin(w,h);
    glViewport((w - side)/2,(h - side)/2,side,side);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    //顶点位置
    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);
}

使用缓冲
  前面程序使用的是顶点数组中指定的数据会保存在客户端内存中,在进行glDrawArrays()等绘图调用时,这些数据必须从客户内存复制到图形内存。为了避免每次绘图时都复制这些数据,可以将其缓存到图形内存中。缓存对象在OpenGL服务器中创建,这样当需要顶点、索引、纹理图像等数据时,客户端程序就不需要每次都进行上传。Qt中的QOpenGLBuffer类用来创建并管理OpenGL缓存对象。
  继续在前面程序中进行更改,先在myopenglwidget.h文件中添加头文件包含:#include<QOpenGLBuffer>,然后添加private变量:QOpenGLBuffer vbo;然后到myopenglwidget.cpp中,在paintGL()函数创建vertices数组后面添加如下代码:

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

  首先调用create()函数在OpenGL服务器中创建缓存对象,然后使用bind()函数将与该对象相关联的缓存绑定到当前OpenGL环境,allocate()函数在缓存中为数组分配空间并将缓存初始化为数组的内容。当创建好换,就可以通过为顶点着色器输入数据了。下面将paintGL()函数中调用glVertexAttribPointer()函数替换为:program->setAttributeBuffer(vPosition,GL_FLOAT,0,2,0);该函数与glVertexAttribPointer()函数类似,其函数原型为void QOpenGLShaderProgram::setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)该函数用来为着色器中location位置的变量设置顶点缓存,offset制定了缓存中要使用数据的偏移量。通过调用该函数就可以将vPosition变量与缓存中的顶点数据进行关联。

绘制彩色3D图形

  主要是为图形的每个顶点进行着色,然后添加其他的面来形成明显的3D效果。

为图形设置顶点颜色
  继续在前面的程序中进行更改,首先更改顶点着色器源码如下:

//这里声明了输入变量vColor和输出变量color,并将vColor获取的颜色数据传递给color
  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";

  下面改变片段着色器如下:

//这里声明了一个输入变量color,用来和顶点着色器的输出变量color对应,
//而输出变量fColor可以将color输入的颜色数据输出到着色管线中用来为图形着色。
 const char *fsrc =
            "in vec4 color;                          \n"
            "out vec4 fColor;                         \n"
            "void main(){                            \n"
            "   fColor = color;                      \n"
            "}                                        \n";

  下面改变片段着色器如下:下面到paintGL()函数中国,在glDrawArrays()函数调用之前添加如下代码:

//这里创建了一个颜色数组,共4行,分别为4个顶点进行着色。
//为了简便,这里直接在前面创建的缓冲中写入了颜色数组数据,并为vColor变量指定了缓存
  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);

  write()函数的原型为:void QOpenGLBuffer::write(int offset, const void *data, int count)该函数会替换掉缓存中已有的内容,参数offset是要替换数据开始位置的偏移值,因为前面已经添加的顶点数组大小为8*sizeof(GLfloat),所以这里需要将这个值作为偏移值。为了不覆盖已有的数据,这里需要对缓存进行扩容,将前面程序中allocate()函数调用更改如下:

//因为顶点数据有8个元素,颜色数组有12个元素,所以这里的大小设置为20*sizeof(GLfloat)
vbo.allocate(vertices,20*sizeof(GLfloat));

实现3D效果
  继续在前面程序中进行更改,首先更改顶点数组为:

//该数组每行指定了4个顶点,每个顶点由3个元素组成,因为要设置3D效果,所以每个顶点都指定了Z坐标
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}}
    };

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

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

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

//因为现在数组中每个顶点由3个元素指定,所以这里第4个参数设置为3
program->setAttributeBuffer(vPosition,GL_FLOAT,0,3,0);

  下面更改颜色数组如下:

 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));

  下面更改vColor的setAttributeBuffer()函数:

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

  最后将绘制函数更改如下:

//这里要绘制两个面,所以用for()函数调用了2次glDrawArrays()函数进行绘制,
//第一次绘制用去了4个顶点,所以第2次调用时设置了起始位置为4
for(int i=0;i<2;i++)
    {
       glDrawArrays(GL_TRIANGLE_FAN,i*4,4);
    }

  因为角度问题只能看到前面的面。下面通过使用透视投影矩阵对顶点进行变换来改变显示图形的角度,在调用绘制函数的这两行代码之前添加如下代码:

//QMatrix4x4类可以表示一个3D空间中的4X4变换矩阵,
//perspective()函数用来设置透视投影矩阵,这里设置了视角为45°,纵横比为窗口的纵横比,最近的位置为0.1,最远的位置为100.
//然后使用translate()函数平移X、Y和Z轴,这里将Z轴平移-3,即向屏幕里移动。
//rotate()可以设置旋转角度,4个参数分别用来设置角度和X、Y、Z轴,
//最后使用setUniformValue()函数将矩阵关联到顶点着色器的matrix变量。
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); //绕t轴逆时针旋转
    program->setUniformValue("matrix",matrix);

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

 const char *vsrc =
            "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";

使用纹理贴图

  前面的程序生成了正方体的两个面,为了实现更加真实的3D效果,还可以使用图片作为2个面的纹理贴图。Qt的QOpenGLTexture类封装了一个QpenGL纹理对象,可以使用该类来设置纹理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值