初始Opengl之QT
1. OpenGL 概述
OpenGL
是一个规范,此规范有各个厂家最终实现。OpenGL
的对外体现就是一个库的概念。
OpenGL
现在分为核心模式和立即渲染模式(即固定的渲染管线),核心模式是OpenGL 3.3
开始支持的,核心模式指可编程模式,核心模式的渲染管线(其实就是从一系列数据到最终配置图像的流程)如下:
其中顶点着色器
和片段着色器
需要程序员自己实现。
若要写一个OpenGL
的可视化程序则需要另外两个库的辅助GLFW
和GLAD
。
GLFW
的作用是用来解决图形界面显示、消息处理及用户输入处理。GLAD
则使得代码可以用于不同的OpenGL
驱动,即对OpenGL
做的封装,使得不同系统的上层使用可以更简便的使用OpenGL
。
可以借助QT
对OpenGL
的封装,实现无需配置第三方库的简便开发。
CPU
数据如何与GPU
数据交互,我们在程序中书写的顶点数据是在CPU
中的,如果GPU
要使用这些数据,我们应该明确的告诉GPU
应该怎么使用。这其中涉及到VBO(Vertex Buffer Objects)
、VAO(Vertex Array Objects)
及VEO(Vertex Element Objects)
,其中VEO
也被称为VIO(Vertex Index Objects)
VBO
: 用来在GPU
中创建内存并存储顶点数据,顶点缓冲对象类型为GL_ARRAY_BUFFER
VAO
: 用来配置OpenGL
如果解析这些内存,VAO
中存储的是VBO
数据类型的定义,而不存储数据本身VEO
: 它用来存储顶点数据的索引,绘图时根据索引进行绘制
VBO
、VAO
及VEO
的关系:
VAO
存储EBO
的glBindBuffer
的调用,但不会存储VBO
的glBindBuffer
调用
2. QT 项目
UI界面:
黑色窗口为OpenGL Widget
,这里对其进行了提升即修改了其继承的父类为自定的MyOpenGLWidget
,在控件中右键选择提升
即可操作界面如下:
myopenglwidget.h内容如下:
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
class MyOpenGLWidget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
explicit MyOpenGLWidget(QWidget *parent = nullptr);
~MyOpenGLWidget();
protected:
virtual void initializeGL() override;
virtual void paintGL() override;
signals:
private:
GLuint m_VBO;
GLuint m_VAO;
// 这里使用非静态变量glCreateProgram会报错,目前还不清楚具体的是为什么(也可能是我环境本身的问题)
static GLuint m_shaderProgram;
// 这里使用非静态变量报错,目前还不清楚具体的是为什么(也可能是我环境本身的问题)
static GLuint m_EBO;
};
#endif // MYOPENGLWIDGET_H
myopenglwidget.cpp内容如下:
#include "myopenglwidget.h"
#include <QDebug>
GLuint MyOpenGLWidget::m_shaderProgram = 0;
GLuint MyOpenGLWidget::m_EBO = 0;
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
}
MyOpenGLWidget::~MyOpenGLWidget()
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
}
GLfloat vertex[]={
-0.5f,-0.5f,0.0f,
0.5f,-0.5f,0.0f,
0.0f,0.5f,0.0f
};
// 存在重复的坐标点
//GLfloat vertexRectangle[]={
// 0.5f,0.5f,0.0f,
// 0.5f,-0.5f,0.0f,
// -0.5f,0.5f,0.0f,
// 0.5f,-0.5f,0.0f,
// -0.5f,-0.5f,0.0f,
// -0.5f,0.5f,0.0f
//};
GLfloat vertexRectangle[]={
0.5f,0.5f,0.0f,
0.5f,-0.5f,0.0f,
-0.5f,-0.5f,0.0f,
-0.5f,0.5f,0.0f
};
GLuint index[]={
0,1,3,
1,2,3
};
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos,1.0f);\n"
"}\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 MyOpenGLWidget::initializeGL()
{
/*
* 初始化OpenGL的函数接口
* 这个接口不能写在构造函数中,否则会程序运行出错,因为initializeOpenGLFunctions()中会需要窗口句柄,而窗口句柄在构造函数完成之后才会被创建
*/
initializeOpenGLFunctions();
// 创建VAO
glGenVertexArrays(1,&m_VAO);
// 创建VBO
glGenBuffers(1,&m_VBO);
// 绑定VAO和VBO对象
glBindVertexArray(m_VAO);
glBindBuffer(GL_ARRAY_BUFFER,m_VBO);
// 为缓冲对象创建一个新的数据存储
glBufferData(GL_ARRAY_BUFFER,sizeof(vertexRectangle),vertexRectangle,GL_STATIC_DRAW);
// 告诉先看如何解析缓冲里的属性值
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3 * sizeof(GLfloat),(void*)0);
// 开启VAO管理的第一个属性值
glEnableVertexAttribArray(0);
// 创建EBO,必须在VAO解绑之前创建,因为EBO也会被VAO所记录,但是解绑VBO却不会对VAO中的记录产生影响
glGenBuffers(1,&m_EBO);
// 绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,m_EBO);
// 给EBO分配空间,并存储数据
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(index),index,GL_STATIC_DRAW);
// 解除EBO,若此处解绑EBO则VAO中就会不在记录EBO内容,需要在使用的时候重新绑定EBO,这里放在析构函数中解绑
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
// 解除VBO和VAO的绑定
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArray(0);
// 创建顶点着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
// 绑定着色器
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
// 编译顶点着色器
glCompileShader(vertexShader);
// 创建片段着色器
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
// 编译片段着色器
glCompileShader(fragmentShader);
// 链接 shaders
m_shaderProgram = glCreateProgram();
glAttachShader(m_shaderProgram,vertexShader);
glAttachShader(m_shaderProgram,fragmentShader);
glLinkProgram(m_shaderProgram);
//释放
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 指定绘图的模式,这里设定为GL_LINE,此模式只绘制线框而不进行填充
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}
void MyOpenGLWidget::paintGL()
{
// 设置Clear的属性
glClearColor(0.2f,0.3f,0.3f,1.0f);
// 使用
glClear(GL_COLOR_BUFFER_BIT);
// 使用shader
glUseProgram(m_shaderProgram);
// 绑定VAO
glBindVertexArray(m_VAO);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,m_EBO);
// 绘制图形
//glDrawArrays(GL_TRIANGLES,0,3);
/*
* 根据索引绘制
* 若没有创建EBO,最后一个参数可以直接传递索引的数组
*/
glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0/*&index*/);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
// 解除VAO
glBindVertexArray(0);
}
mainwindow.cpp内容如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setCentralWidget(ui->openGLWidget);
}
MainWindow::~MainWindow()
{
delete ui;
}
其他代码均为存在修改,运行效果如下: