Learn OpenGL with Qt——必备知识(易出错导致绘图失败的地方)

OpenGL Context(上下文)的概念:

如果你使用过Qt的QPainter类进行过绘图的话,可能理解起来会简单一点,QPainter需要一个PaintDevice(绘图设备)参数,

而我们的QWidget,QImage,QPixmap就继承自它,因此可以这样使用:

Qimage img(400,400);
QPainter painter(&img);

也可以在QWidget的绘图函数中这样: 

void paintEvent(QPaintEvent*){
    QPainter painter(this);
}

上面的代码主要是创建了一个QPainter对象,并且设置了该对象操作的渲染设备,之后我们可以调用QPainter的各种成员方法在该设备进行绘图。

而OpenGL中进行绘图也需要一个这样的东西——Render Context (渲染上下文),简单来说,它也是一个绘图设备。回顾一下我们是如何在Qt中创建OpenGL窗口,首先创建一个QWidget,然后修改继承自QOpenGLWidget和QOpenGLFunctions,继承QOpenGLWidget是为了创建窗口,而继承QOpenGLFunctions只是为了让我们能够少写一点代码(你应该知道Qt把所有OpenGL函数封装为QOpenGLFunctions的成员函数),我们完全可以在QOpenGLWidget中创建一个 QOpenGLFunctions变量,只不过可能要这样来使用它:

class Test:public QOpenGLWidget{

private:
    QOpenGLFunctions func;

public:
    void initializeGL(){
        func.initializeOpenGLFunctions();
    }
    void paintGL(){
        //...
        func.glDrawArray(...);
    }
}

我们调用了 func.initializeOpenGLFunctions();这个函数,它的作用是将func的渲染上下文对象设置为当前的上下文对象。

QOpenGLWidget中的 void initializeGL()  void paintGL()这两个函数,在调用之前会使用成员函数makeCurrent(),将自己的上下文设置整个程序的当前上下文,并且在函数调用结束会使用doneCurrent()将程序当前上下文设为空。

是不是感觉跟QPainter painter(this)一样,我们之后绘图只需要调用func的成员函数就行了,这样能理解起来是不是很简单。

多窗体绘图

其实总结一下我们使用OpenGL绘图需要的就是两个东西:RC(渲染上下文)和QOpenGLFunctions(其实可能还需要传递着色器程序*)。而QOpenGLContext中就已经包含了QOpenGLFunctions,而QOpenGLWidget又包含了QOpenGLContext;

因此我们绘图可以直接使用下面的代码直接获取到该RC的Functions

this->context()->functions(); //this指QopenGLWidget

这里主要是理解OpenGL的绘图机制,免的不熟的人可能公用一个Functions,导致绘图只有一部分。

在QOpengLWidget类中调用外部函数进行OpenGL操作(绘图、创建OpenGL对象...)

可能我们有时候会需要这样的操作:

void paintGL(){
    myPaint();
}
void myPaint(){
    glDrawArray();
}

这两个函数在同一个类(该类继承了QOpenGLFunctions)的时候可能不会出错,但在不同类中就会出现出现问题了,因为外部类并没有QOOpenGLFunctions或者并不是使用的同一个Func,所以为了能够进行这些操作,我们往往需要传递QOpenGLWidget的指针,上图的绘图操作就可以改为:

void paintGL(){
    myPaint(this);
}
void myPaint(QOpenGLWidget *w){
    QOpenGLFunctions* func=w->context()->functions();
    func->glDrawArray();
}

还需要主要的是,如果你想在外部函数中创建VAO,VBO,FBO,Texture之类的opengl对象,如果你这个函数并不是放在paintGL和initializeGL中调用的(本质上就是程序的当前上下文为空),则务必需要在此函数创建OpenGL对象之前调用OpenGLwidget的makeCurrent(),在这些对象操作完毕之后调用doneCurrent(),否则你虽然能成功创建OpenGL对象,但是却是对一个空的OpenGL上下文操作,因此无法正确绘图,正确的操作看起来会是这样:

void myFunc(QOpenGLWidget *w){
    w.makeCurrent();
    QOpenGLVertexArrayObject VAO;
    VAO.create();
    VAO.bind();
    VAO.release()
    w.doneCurrent();
}

VAO与普通缓冲区(VBO,EBO)的操作理解

C/C++通过指针进行间接寻址,而OpenGL中是通过各种类型object id进行寻址,在创建一个opengl对象时系统会给它分配一个id,我们要间接寻址都是这个id,比如RC(渲染上下文)中的一个状态就保存着当前VAO的Id,而VAO中又保存了各种缓存的id(VBO、EBO..)

需要注意的是,不要在VAO.release()之前调用VBO或EBO的release()函数

VAO也是一个状态机,Qt中的代码并不能很好的体现这一特点,因此很容易出错。

比如这样的代码,看上去似乎没错

VAO.create();
VAO.bind();
VBO.create();
VBO.bind();
//do something...
VBO.release();
VAO.release();

其实是大错特错,假设VAO是一个这样的结构体(其实本质上差不多的)

struct VAO{
    int ID;
    int VBO_ID;
    int EBO_ID;
    //...
}

而RC中则存储着当前VAO的Id;

再来看上面的代码

VAO.create();       创建一个VAO对象,OpenGL会给它(顶点数组缓存对象)分配一个id
VAO.bind();         将RC中的当前顶点数组缓存对象Id设置为VAO的id
VBO.create();       创建一个VBO对象,OpenGL会给它(缓存对象)分配一个id
VBO.bind();         将当前顶点数组中的VBO_ID设置为此对象的Id
//do something...
VBO.release();      将当前顶点数组中的VBO_ID设置为0
VAO.release();      将RC中的当前顶点数组缓存对象ID设置为0

这样一看,我们是把VBO”绑定"到VAO上,又把0绑定到VAO上替换了VBO,这就导致了VBO并没有正确的绑定到VAO上

因此,我们需要把VBO解绑放到VAO解绑之后,或者直接不解绑

正确理解QOpenGLShaderProgram.setAttributeBuffer()

这个函数跟原生OpenGL中glVertexAttribPointer()方法相似

这个函数是用来干什么的呢?请记好了!

作用:这个函数为当前缓存对象(VBO)设置顶点解析的格式。

重点:使用这个对象的时候需要绑定了VBO;

一些功能扩展和细节操作最好翻译官方文档,当然你也可以在评论中留言,看到一定会第一时间回复。

最近转去学UE4了,OpenGL的教程暂时没时间继续写了,等以后有空了,补上。

猜你喜欢

转载自blog.csdn.net/qq_40946921/article/details/108041536