QT代替原有的部分gl默认库
在qt框架中使用OpenGL,即用qt窗口框架代替glfw窗口设计,用qt中的QOpenGLFunctions_4_5_Core(也可以为其他版本)代替glad来做OpenGL函数的管理。
故在vs2019中不再需要对glfw,glad进行配置操作(相关配置操作详看这里)。
本篇文章的重点
在qt中有对OpenGL库的封装库(QOpenGLShaderProgram,QOpenGLShader等等),本片文章既使用qt自带的封装库,也使用OpenGL的接口,便于以后写出自己的封装库。
有关qt自带的封装库学习自 “懂deeee珍惜”的现代OpenGL+Qt学习笔记之三:显示一个彩色三角形
个人认为以qt为窗口按钮框架,OpenGL做图形处理是最优选择。至于最终哪种形式更好,希望读者和本博主在今后的学习中再做判断。
使用qt框架
openglwidget.h
#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
class QOpenGLShaderProgram;
class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
OpenGLWidget(QWidget *parent = 0);
protected:
void initializeGL();
void resizeGL(int w, int h);
void paintGL();
private:
QOpenGLShaderProgram *program;
QOpenGLVertexArrayObject VAO;
};
#endif // OPENGLWIDGET_H
在头文件中引入两个私有类:
一个是QOpenGLShaderProgram类指针program,用作渲染管线的绑定。
另一个是QOpenGLBuffer VAO,用于储存顶点属性。
关于OpenGL的用法本文不做介绍,有关内容参照本人OpenGL专栏
openglwidget.cpp
#include "openglwidget.h"
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
OpenGLWidget::OpenGLWidget(QWidget *parent)
:QOpenGLWidget(parent)
{
}
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
// vertex shader
QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
vshader->compileSourceFile("://shader/basic.vert");
// fragment shader
QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
fshader->compileSourceFile("://shader/basic.frag");
// shader program
program = new QOpenGLShaderProgram;
program->addShader(vshader);
program->addShader(fshader);
program->link();
program->bind();
// vertices array
GLfloat positionData[] = {
-0.8f, -0.8f, 0.0f,
0.8f, -0.8f, 0.0f,
0.0f, 0.8f, 0.0f };
GLfloat colorData[] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };
QOpenGLBuffer VBO(QOpenGLBuffer::VertexBuffer);
VBO.create();
if(!VBO.isCreated()){
qDebug()<<"vbo is not created.";
}
VBO.bind();
VAO.create();
if(!VAO.isCreated()){
qDebug()<<"vao is not created.";
}
VAO.bind();
// vertex positions
VBO.allocate(vertices1,sizeof (vertices1));
program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 8*sizeof(GL_FLOAT));
program->enableAttributeArray(0);
program->setAttributeBuffer(1, GL_FLOAT, 3*sizeof (GL_FLOAT), 3, 8*sizeof(GL_FLOAT));
program->enableAttributeArray(1);
//program->setAttributeBuffer(2, GL_FLOAT, 6*sizeof (GL_FLOAT), 2, 8*sizeof(GL_FLOAT));
//program->enableAttributeArray(2);
program->release();
VAO.release();
// set color used to clear background
glClearColor(0.5f, 0.0f, 1.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
}
void OpenGLWidget::resizeGL(int w, int h)
{
}
void OpenGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
program->bind();
VAO.bind();
// draw
glDrawArrays(GL_TRIANGLES, 0, 3 );
}
initializeOpenGLFunctions();
相当于对glad初始化。任何对OpenGL函数的调用都需要初始化,(在这儿吃了大亏),无论是OpenGL接口还是qt的封装库。
shader
QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
vshader->compileSourceFile("://shader/basic.vert");
相当于对
GLuint vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
另加文件读取 的封装
program
program = new QOpenGLShaderProgram;
program->addShader(vshader);
program->addShader(fshader);
program->link();
program->bind();
相当于对
Program = glCreateProgram();
glAttachShader(Program, vertexShader);
glAttachShader(Program, fragmentShader);
glLinkProgram(Program);
glUseProgram(Program);
的封装
顶点属性
QOpenGLBuffer VBO(QOpenGLBuffer::VertexBuffer);
// vertex buffer object
VAO.create();
VBO.create();
VBO.bind();
VAO.bind();
// vertex positions
VBO.allocate(vertices1,sizeof (vertices1));
program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 8*sizeof(GL_FLOAT));
program->enableAttributeArray(0);
program->setAttributeBuffer(1, GL_FLOAT, 3*sizeof (GL_FLOAT), 3, 8*sizeof(GL_FLOAT));
program->enableAttributeArray(1);
//program->setAttributeBuffer(2, GL_FLOAT, 6*sizeof (GL_FLOAT), 2, 8*sizeof(GL_FLOAT));
//program->enableAttributeArray(2);
program->release();
VAO.release();
VAO.bind()一定要在vbo.bind()之后才能绑定成功。
void QOpenGLBuffer::allocate(const void *data, int count)
Allocates count bytes of space to the buffer, initialized to the
contents of data. Any previous contents will be removed. It is assumed
that create() has been called on this buffer and that it has been
bound to the current context.
在vbo.bind之后在显卡开辟出count字节内存,并将data数据存入。
主要封装函数为
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
启用顶点属性(另一种方法)
GLuint vPosition = program->attributeLocation("VertexPosition");
program->setAttributeBuffer(vPosition, GL_FLOAT, 0, 3, 0);
glEnableVertexAttribArray(vPosition);
program->attributeLocation封装了
GLint glGetAttribLocation( GLuint program, const GLchar *name);//获取顶点属性地址
program->setAttributeBuffer(vPosition(顶点属性位置), GL_FLOAT, 0(偏移量), 3(顶点个数), 0(步长));封装的函数暂且不知。
类似于如下函数。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(0));
添加修改数据(补充)
vbo.write(9sizeof(GLfloat), colorData, 9sizeof(GLfloat));
vbo.write(修改起始位置,修改数据,修改字节数)
Replaces the count bytes of this buffer starting at offset with the contents of data. Any other bytes in the buffer will be left unmodified.
修改该vbo中从起始位置开始特定字节数的数据。
封装函数暂且不知。有可能是内部自设函数。
在绘制函数中绘制
program->bind();
VAO.bind();
glDrawArrays(GL_TRIANGLES, 0, 3 );
绑定绘制管线和绘制数据,然后绘制。
不使用qt封装库运行代码
代码来自我的OpenGL三角形的绘制博客
详细解释参阅博客内容。
#include "openglwidget.h"
#include <iostream>
const GLchar* vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 position;\n"//这儿有分号
"layout (location = 1) in vec3 inColor;\n"
"out vec3 Color;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position, 1.0);\n"
"Color = inColor;\n"
"}\0";
const GLchar* fragmentShaderSource =
"#version 330 core\n"
"in vec3 Color;"
"out vec4 FragColor;\n" //输出一个四分量向量,表示最终颜色。
"void main(){\n"
"FragColor = vec4(1.0f,0.0f,0.0f,0.5f);\n"
"}\n";
OpenGLWidget::OpenGLWidget(QWidget* parent)
:QOpenGLWidget(parent)
{
}
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
/*使用顶点着色器*/
GLuint vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
//判断编译是否成功
GLint success;
GLchar infoLog[512];//存储错误信息
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
//检查是否编译成功
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
//获取错误消息
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
/*使用片段着色器*/
GLuint fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success){
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
/*链接为着色器程序对象*/
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
//检查链接状态
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::PROGRAM::SHADER::LINK_FAILED\n" << infoLog << std::endl;
}
//把着色器对象链接到程序对象以后,我们就不再需要它们了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
/*使用索引数据存储点的顺序,用IBO索引缓冲对象管理*/
GLfloat vertices[] = {
1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
};
GLuint VBO1;
glGenBuffers(1, &VBO1);
glGenVertexArrays(1, &VAO1);
glBindVertexArray(VAO1);
glBindBuffer(GL_ARRAY_BUFFER, VBO1);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用顶点属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);//启用顶点属性
glBindVertexArray(0);
glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
}
void OpenGLWidget::resizeGL(int w, int h)
{
}
void OpenGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_BLEND);
glUseProgram(shaderProgram);//要用在循环里
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//设置为填充模式(默认)
glBindVertexArray(VAO1);
glDrawArrays(GL_TRIANGLES, 0, 3);
// glDrawArrays渲染顺序为先进后出的栈顺序。
}
结语
下一节将进行对自定义库的封装,自定义库的学习即可更清楚的了解qt封装库的内部操作,也可不受限于qt而定制个性化的库函数。
补充(2021.8.7):在qt框架下自定义库较为艰难,需要熟知qt框架。故今后将先使用qt封装库,待学习完OpenGL全部基本知识,再来学习qt框架。(为了自定义库,挠头苦干4天,头发掉了不少,最后学会了放下执念!!!)