OpenGL学习随笔(二)——2022.1.22

        上回通过用顶点着色器和片段着色器绘制了一个30像素大小的点,这次主要简单介绍一下各个着色器的功能,介绍检测OpenGL和GLSL错误的模板,以及从文件当中读取GLSL源代码的模板,最后绘制一个简单的二维动画。

一、各着色器功能

        顶点着色器:所有的顶点数据都会被传入顶点着色器,顶点们会被一个一个的处理,即顶点着色器会对每个顶点执行一次。对拥有许多顶点的大型复杂规模而言,顶点着色器会执行成百上千甚至上万次,这些执行是并行的。顶点着色器一次只处理一个顶点。

        曲面细分着色器:用以生成大量三角形,通常是网格形式,同时也提供了一些可以以各种方法操作这些三角形的工具。当简单形状上需要很多顶点时,曲面细分着色器能很好的发挥作用。

        几何着色器:顶点着色器赋予程序员一次操作一个顶点的能力,而几何着色器赋予程序员一次处理一个图元的能力(最常用的图元是三角形)。当到达几何着色阶段时,管线肯定已经完成了将顶点组合为三角形的过程(图元组装),接下来几何着色器会让程序员可以同时访问每个三角形的所有顶点。按图元处理有很多用途,如可以让图元变形,还可以删除图元,同时几何着色器也提供了生成额外图元的方法,几何着色器也能在物体上增加表面纹理。

        光栅化:3D模型中的点、三角形、颜色等全部都要展现在一个2D显示器上。这个2D屏幕由光栅(矩阵像素阵列)组成。当3D物体光栅化后,OpenGL将物体中的图元(通常是三角形)转化为片段。片段拥有关于像素的信息。光栅化过程确定了用以显示3个顶点所确定的三角形的所有像素需要绘制的位置。

        片段着色器:片段着色器用于为光栅化的像素指定颜色。片段着色器还提供了其他计算颜色的方式,比如可以基于像素位置决定输出颜色等。片段着色器一次操作一个像素。

二、检测OpenGL和GLSL错误

        编译和运行GLSL代码与普通代码的过程不同,GLSL编译总发生在C++运行时,且GLSL运行在GPU上,因此操作系统不总是能捕获OpenGL运行时的错误(GLSL错误不会导致C++程序崩溃)。这时,我们通常需要将有关GLSL的日志打印出来,其中glGetShaderiv()和glGetProgramiv()用于提供有关编译过的GLSL着色器和程序信息。以下提供了三个用于捕获和显示GLSL错误的模块。

        checkOpenGLError:检查OpenGL错误标志,即是否发生OpenGL错误,既可以用于检测GLSL编译错误,又可以检测OpenGL运行时的错误。

        paintShaderLog:当GLSL编译失败时,显示OpenGL日志内容。

        paintProgramLog:当GLSL链接失败时,显示OpenGL日志内容。

void printShaderLog(GLuint shader)
{
    int len = 0;
    int chWrittn = 0;
    char *log;
    glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&len);
    if(len > 0){
        log = (char*)malloc(len);
        glGetShaderInfoLog(shader,len,&chWrittn,log);
        cout<<"shader Info Log:"<<log<<endl;
        free(log);
    }
}

void printProgramLog(int prog)
{
    int len = 0;
    int chWrittn = 0;
    char *log;
    glGetProgramiv(prog,GL_INFO_LOG_LENGTH,&len);
    if(len > 0){
        log = (char*)malloc(len);
        glGetProgramInfoLog(prog,len,&chWrittn,log);
        cout<<"Program Info Log:"<<log<<endl;
        free(log);
    }

}

bool checkOpenGLError()
{
    bool foundError = false;
    int glErr = glGetError();
    while(glErr != GL_NO_ERROR){
        cout<<"glError:"<<glErr<<endl;
        foundError = true;
        glErr = glGetError();
    }
    return foundError;

}

        应用示例如下:

//捕获编译着色器时的错误
glCompileShader(vShader);//编译着色器
checkOpenGLError();
glGetShaderiv(vShader,GL_COMPILE_STATUS,&vertCompiled);//GLint vertCompiled
if(vertComplied != 1){
    cout<<"vertex compilation failed:"<<endl;
    printShaderLog(vShader);
}

//捕获链接时的着色器错误
glAttachShader(vfProgram,vShader);
glAttachShader(vfProgram,fShader);
glLinkProgram(vfProgram);
checkOpenGLError();
glGetProgramiv(vfProgram,GL_LINK_STATUS,&linked);//GLint linked
if(linked != 1){
    cout<<"linking failed"<<endl;
    printProgramLog(vfProgram);
}
 

三、从文件读取GLSL源代码

        当程序变得复杂时,将GLSL着色器代码内联存储到字符串中就显得不太实际,应该将GLSL代码放到文件(.glsl) 当中,并用读取文件的方式读取GLSL代码。下面提供了一个读取着色器代码的模块。readShaderSource()读取着色器文本并返回一个字符串数组,其中的每个字符串是文件中的一行文本。根据读入的行数确定数组的大小。

QString MyWidget::readShaderSource(const char*filePath)
{
    QString content;
    ifstream fileStream(filePath, ios::in);
    QString line = "";
    while(!fileStream.eof()){
        getLine(fileStream,line);
        content.append(line+"\n");
    }
    fileStream.close();
    return content;
}

   四、简单的三角形动画

        与上一回中绘制一个点的程序类似,只需要稍微修改顶点着色器代码便可以绘制三角形,再添加变量控制动画即可。需要注意的是,使用glUniform1f函数需要引入glew库,但是Qt Creator本身不具备这个库,需要添加库之后在.pro文件中将库连接起来,否则编译不成功。

mywidget.h

#ifndef MYWIDGET_H
#define MYWIDGET_H
#define numVAOs 1
#include<GL/glew.h>  //该头文件一定要在最前面
#include<QOpenGLWidget>
#include<QOpenGLFunctions>
#include<QOpenGLBuffer>
#include<QOpenGLShader>
#include<QOpenGLShaderProgram>
#include<GL/glfw3.h>
#include<GL/gl.h>
#include<GL/glu.h>

//继承OpenGLWidget,重写initializeGL(),paintGL(),resizeGL()三个函数即可绘制OpenGL图元
class MyWidget : public QOpenGLWidget,protected QOpenGLFunctions
{
public:
    MyWidget(QWidget *parent);
    GLuint createShaderProgram();//GLuint相当于C++里的unsinged int类型
    
public slots://定义槽函数
    void animate();

protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int width, int height) override;
private:
    GLuint renderingProgram;
    GLuint vao[numVAOs];
    float x = 0.0f;
    float inc = 0.01f;
};

#endif // MYWIDGET_H

  mywidget.cpp 

#include "mywidget.h"
//#include"checkerror.h"
#include<QOpenGLVertexArrayObject>
#include<QString>
#include<iostream>
#include<fstream>
MyWidget::MyWidget(QWidget *parent)
{
    Q_UNUSED(parent);
    QSurfaceFormat format = QSurfaceFormat::defaultFormat();
    format.setProfile(QSurfaceFormat::CoreProfile);
    format.setVersion(4, 3);
    QSurfaceFormat::setDefaultFormat(format);
}

void MyWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(1.0,0.0,0.0,1.0);//设置背景色为红色
    glClear(GL_COLOR_BUFFER_BIT);
    renderingProgram = createShaderProgram();
    //glGenVertexArrays(numVAOs,vao);
    //glBindVertexArray(vao[0]);
}
void MyWidget::paintGL()
{
    glClear(GL_DEPTH_BOUNDS_EXT);
    glClearColor(1.0,0.0,0.0,1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(renderingProgram);
    
    //每次更新偏移量
    x += inc;
    if(x > 1.0f) inc = -0.02f;
    if(x < -1.0f) inc = 0.02f;
    //获取顶点着色器中uniform类全局变量offset的位置
    GLuint offsetLoc = glGetUniformLocation(renderingProgram,"offset");
    //将x的值赋给uniform类变量offset
    glUniform1f(offsetLoc,x);
    
    //将3个顶点绘制为三角形
    glDrawArrays(GL_TRIANGLES,0,3);
}
void MyWidget::resizeGL(int width, int height)
{

}

GLuint MyWidget::createShaderProgram()
{
    //修改顶点着色器使之变为三角形
    //使用unifom类的全局变量offset控制动画
    const char *vshaderSource =
        "#version 430 \n"
         "uniform float offset; \n"
        "void main(void) \n"
        "{ if(gl_VertexID ==0) gl_Position = vec4(0.25+offset , -0.25, 0.0, 1.0);\n"
        "else if(gl_VertexID == 1) gl_Position = vec4(-0.25+offset ,-0.25,0.0,1.0);\n"
        "else gl_Position = vec4(0.0 +offset,0.25,0.0,1.0);}";

    //顶点沿着管线移动到光栅着色器,它们会在这里被转化为像素(片段)位置,最终这些像素(片段)到达片段着色器
    //片段着色器的目的就是将要展示的像素赋予RGB颜色
    //"out"标签表明color变量是输出变量
    //此处vec4前三个元表示RGB颜色,第四个元表示不透明度
    const char *fshaderSource =
        "#version 430 \n"
        "out vec4 color; \n"
        "void main(void) \n"
        "{ color = vec4(0.0,0.0,1.0,1.0);}";

    //调用glCreateShader(parameter)创建类型为parameter的空着色器
    //创建每个着色器对象后会返回一个整数ID作为后面引用它们的序号
    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);//GLuint相当于“unsigned int"
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

    //glShaderSource将GLSL代码从字符串载入空着色器对象中
    //四个参数:1、存放着色器的着色器对象,2、着色器源代码中的字符串数量,3、包含源代码的字符串指针,4、
    glShaderSource(vShader,1,&vshaderSource,NULL);
    glShaderSource(fShader,1,&fshaderSource,NULL);

    //glCompileShader编译着色器
    glCompileShader(vShader);
    glCompileShader(fShader);

    //创建程序对象,并存储指向它的整数ID
    //OpenGL的程序对象包含一系列编译过的着色器,使用glAttachShader将着色器加入程序对象
    //之后使用glLinkProgram来请求GLSL编译器来确保它们的兼容性。
    GLuint vfProgram  = glCreateProgram();
    glAttachShader(vfProgram,vShader);
    glAttachShader(vfProgram,fShader);
    glLinkProgram(vfProgram);

    return vfProgram;


}

void MyWidget::animate()
{
    this->update();//调用update时会自动调用PaintGL函数重绘
}

 mainwindow.h 

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include"mywidget.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    MyWidget *my_widget;
};
#endif // MAINWINDOW_H

  mainwindow.cpp 

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QTimer>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //创建OpenGLWidget对象
    my_widget = new MyWidget(this);
    //将mainWindow界面设置为OpenGLWidget
    setCentralWidget( my_widget );
    //设置界面的大小
    resize( 1000, 800 );
    
    //设置计时器并连接槽函数
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, my_widget, &MyWidget::animate);
    timer->start(10);//设置重绘的时间间隔
    
}

MainWindow::~MainWindow()
{
    delete ui;
}

  main.cpp 

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

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

运行结果:

OpenGL学习随笔(二)——2022.1.22

上一篇:openGL加载obj三维模型


下一篇:OPENGL学习项目--立体正方形的实时三维模型