《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序

大家可以去B站看课程的视频支持一下作者哈:

OpenGL,Qt实现:1入门篇(已更完)_哔哩哔哩_bilibili课程相关源码、PPT、安装包,完整课程合集(1:入门篇;2:基础光照;3:模型加载;4:高级OpenGL;5:高级光照 ;程序员的数学3:线性代数):https://ke.qq.com/course/package/40726?flowToken=1041265https://www.bilibili.com/video/BV1UL411W71w

准备工作

1、关于OpenGL

OpenGL是一种规范,具体的API由各个厂家自行实现的。

2、GPU渲染管线(流水线)

《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序_第1张图片

3、状态

个人理解为参与对输入数据的处理的属性和条件。

《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序_第2张图片

上面的图片中,输入三个点,不同的状态参与对输入数据的处理会输出不同的内容。

4、状态机

储存了状态的集合。

OpenGL本身是一个巨大的状态机(包含了很多状态的属性)。

OpenGL的状态一般称为上下文(Context)。

OpenGL的函数可以分为状态设置函数和状态应用函数,学习的时候可以注意这一点。

5、OpenGL对象

一个对象指的是一些选项(状态)的集合,是OpenGL状态的一个子集。

OpenGL是一个大的状态机,包含了大量状态属性,从中取出一部分构成一个对象。

例如从OpenGL的状态中取出一些窗口大小、窗口名称、窗口颜色等选项集合成一个结构体,就构成一个窗口对象。

6、对象的作用

OpenGL的状态处于变化之中,如果每次变化都需要重新配置状态则不太行,可以使用对象记录OpenGL的状态。

总之:对象就是用来记录状态的。

理解下面的代码:

// 创建对象 
//uint objectId = 0; 
GLuint objectId = 0; 
glGenObject(1, &objectId); //给对象一个编号。

glBindObject(GL_WINDOW_TARGET, objectId);// 绑定对象至上下文,objectId 记录 GL_WINDOW_TARGET 的状态
 
// 设置 GL_WINDOW_TARGET 对象的一些状态,GL_WINDOW_TARGET 状态的改变被 objectId 记录下来了
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800); 
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600); 

glBindObject(GL_WINDOW_TARGET, 0); //取消 GL_WINDOW_TARGET 和 objectId 的绑定

......//GL_WINDOW_TARGET的其他操作

//一旦 GL_WINDOW_TARGET 重新绑定 objectId,objectId 记录的 GL_WINDOW_TARGET 选项就会重新生效

创建OpenGL窗口

1、QOpenGLWidget::makeCurrent()

如上所述,OpenGL是一个大的状态机,它的当前状态只有一个,在 paintGL()、resizeGL()、initializeGL() 之外的函数中调用标准OpenGL API函数,则必须首先调用此函数以获取OpenGL状态。

2、标准化设备坐标

OpenGL为了让坐标运算不受显示器分辨率的影响,将三维坐标标准化到了[-1,1]之间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在屏幕上。

标准化设备坐标看起来就像是下面这样:

《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序_第3张图片

一个标准化坐标数组:

《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序_第4张图片

3、顶点着色器

  • 顶点着色器会在GPU的内存(显存)中储存顶点数据
  • 顶点数据通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理(顶点数据实际是储存到VBO中的)。注意:顶点缓冲对象是对象,如上面所述,对象的作用是记录状态。
  • 顶点缓冲对象的缓冲类型是 GL_ARRAY_BUFFER。使用缓冲区是为了减少将内存中的数据传到显存需要的开销。将数据先放到内存中的缓冲区,数据满了再一次性传给显存。
  • 将顶点缓冲对象绑定为 GL_ARRAY_BUFFER,即可将数据传到显存。

4、顶点数组对象(Vertex Array Objects, VAO)

前面说了将数据传到显存。VAO的作用是配置OpenGL如何解释数据(记录数据是怎么定义的),有了VAO以后OpenGL才知道如何使用数据

注意:这也是对象,对象的作用是记录状态。牢记。

理解以下代码:

//1、创建VBO和VAO对象,并赋予ID
unsigned int VBO, VAO; 
glGenVertexArrays(1, &VAO); 
glGenBuffers(1, &VBO); 

//2、绑定VBO和VAO对象
glBindVertexArray(VAO);//使OpenGL知道这个uint类型的对象表示一个VAO
glBindBuffer(GL_ARRAY_BUFFER, VBO); 

//为当前绑定到GL_ARRAY_BUFFER的缓冲区对象创建一个新的数据存储,使用参数三的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

//告知显卡如何解析缓冲里的属性值:
//第0个数据:有3个值;float类型;不需要标准化;步长;偏移量是0(因为是第一个数据,所以没有偏移量)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//开启VAO管理的第一个属性值
glEnableVertexAttribArray(0); 

//VAO、VBO已经记录了足够的信息了,解除绑定
glBindBuffer(GL_ARRAY_BUFFER, 0); 
glBindVertexArray(0); 

关于OpenGL函数可查询:Khronos OpenGL® and OpenGL® ES Reference Pages - The Khronos Group Inchttps://www.khronos.org/registry/OpenGL-Refpages/

使用上面的代码画出一个三角形:

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include 
#include 

class Widget : public QOpenGLWidget,QOpenGLFunctions_4_5_Core
{
    Q_OBJECT

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

protected:
    virtual void initializeGL();
    virtual void paintGL();
};
#endif // WIDGET_H
#include "widget.h"

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
}

Widget::~Widget()
{
}

unsigned int VBO, VAO;
float vertices[] =
{
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
};

void Widget::initializeGL()
{
    initializeOpenGLFunctions();

    //1、创建VBO和VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    //2、绑定VBO和VAO对象
    glBindVertexArray(VAO);//使OpenGL知道这个uint类型的对象表示一个VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    //为当前绑定到GL_ARRAY_BUFFER的缓冲区对象创建一个新的数据存储,使用参数三的数据初始化数据存储
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //告知显卡如何解析缓冲里的属性值:
    //第0个数据:有3个值;float类型;不需要标准化;步长;偏移量是0(因为是第一个数据,所以没有偏移量)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //开启VAO管理的第一个属性值
    glEnableVertexAttribArray(0);

    //VAO、VBO已经记录了足够的信息了,解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void Widget::paintGL()
{
    glClearColor(0.2f,0.3f,0.3f,1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glBindVertexArray(VAO);//绑定VAO以便知道该如何使用数据。
    glDrawArrays(GL_TRIANGLES,0,3);
}

《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序_第5张图片

这里没有定义着色器而是使用默认的着色器绘制一个三角形。

使用OpenGL原生的方式编译链接着色器

Qt 对着色器编译是有封装的,先不管它,使用OpenGL原生的方式编译链接着色器看看。

#include "widget.h"

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
}

Widget::~Widget()
{
}

unsigned int VBO, VAO;
unsigned int shaderProgram;
float vertices[] =
{
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
};

const char *vertexShaderSource = "#version 330 core\n"
                                 "layout (location = 0) in vec3 aPos;\n"
                                 "void main()\n"
                                 "{\n"
                                 "  gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                 "}\0";

const char *fragmentShaderSource = "#version 330 core\n"
                                   "out vec4 FragColor;\n"
                                   "void main()\n"
                                   "{\n"
                                   "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                   "}\n\0";

void Widget::initializeGL()
{
    initializeOpenGLFunctions();
    
    //1、创建VBO和VAO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    
    //2、绑定VBO和VAO对象
    glBindVertexArray(VAO);//使OpenGL知道这个uint类型的对象表示一个VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    
    //为当前绑定到GL_ARRAY_BUFFER的缓冲区对象创建一个新的数据存储,使用参数三的数据初始化数据存储
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    //告知显卡如何解析缓冲里的属性值:
    //第0个数据:有3个值;float类型;不需要标准化;步长;偏移量是0(因为是第一个数据,所以没有偏移量)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //开启VAO管理的第一个属性值
    glEnableVertexAttribArray(0);
    
    //VAO、VBO已经记录了足够的信息了,解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    
//---------以下是使用自定义着色器----------------------------  

    //顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建顶点着色器对象 GL_VERTEX_SHADER表示顶点着色器
    glShaderSource(vertexShader, 1, &vertexShaderSource,nullptr);//顶点着色器绑定源码
    glCompileShader(vertexShader);//编译顶点着色器
    int success;
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);//检查编译错误
    if (!success)
    {
        char infoLog[512];
        glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
        qDebug()<< "编译顶点着色器出错,错误信息:" << infoLog ;
    }
    
    //片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//创建片段着色器 GL_FRAGMENT_SHADER表示片段着色器
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        char infoLog[512];
        glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
        qDebug()<< "编译片段着色器出错,错误信息:" << infoLog ;
    }
    
    //链接两个编译好的着色器
    shaderProgram = glCreateProgram();//创建着色器程序对象
    glAttachShader(shaderProgram, vertexShader);//添加要编译的程序
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);//链接着色器
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); //检测链接错误
    if (!success)
    {
        char infoLog[512];
        glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
        qDebug()<< "链接着色器出错,错误信息:" << infoLog;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

void Widget::paintGL()
{
    glClearColor(0.2f,0.3f,0.3f,1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(shaderProgram);//使用此着色器,没有这行则使用默认的着色器
    
    glBindVertexArray(VAO);//绑定VAO以便知道该如何使用数据。
    glDrawArrays(GL_TRIANGLES,0,3);
}

《Qt-OpenGL系列编程》课程学习记录(1):相关概念、VAO、VBO、绘制三角形、使用OpenGL原生方式编译链接着色器程序_第6张图片

你可能感兴趣的:(#,qt)