学习文档地址: OpenGL中文教程
说明:本文档只是针对OpenGL教程的一个总结,并不详细,具体内容参考OpenGL中文教程。
本文使用的开发环境为Qt5.12,系统为Ubuntu
按照教程编译好GLFW,配置好GLAD,然后在pro文件中加入
LIBS+=-lglfw3 -ldl -lX11
环境搭建步骤完成。
Hello World
第一步,初始化环境:
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
我们用glfwInit()来初始化GLFW,用glfwWindowHint来设置主版本号和次版本号,并指定使用核心模式(CORE_PROFILE)
第二步,创建GLFW窗口:
这一步我理解为Qt的主窗体,后续的绘制都是依赖于改窗体, glfwMakeContextCurrent(window)是将该window设置为当前程序的context。(传统的Qt一般会有专门的设置函数,比如我想在布局器上添加一个窗口,使用 addWidget, GLFW的模式个人感觉和qwt类似,直接通过一个全局的函数设置,后面其它的对象就可以直接使用,个人理解)。
// 创建GLFW窗口
GLFWwindow* window = glfwCreateWindow(800,600, "hello_world", nullptr, nullptr);
if (window == nullptr)
{
qDebug() << "Failed to create glfw window";
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callbackTest2);
第三步,使用GLAD管理opengl指针:
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
qDebug() << "Faild to initialize GLAD";
return -1;
}
以上三个步骤,任何调用opengl函数的程序都必须实现。
比如你想通过glGetIntegerv函数获取GL_MAX_VERTEX_ATTRIBS的数量,直接使用glGetIntegerv是不行的,必须完成上面三个步骤之后再调用该函数。
第四步,创建顶点着色器:
在创建着色器之前,应该写好GLSL语言,创建着色器后,先绑定着色器对象和GLSL,然后编译,如下:
// 先创建一个着色器对象,用它来运行时动态编译着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr); // 绑定
glCompileShader(vertexShader); // 编译
第五步:创建片段着色器:
这是第二个也是最后一个我们可以控制的着色器了。和顶点着色器类似
// 片段着色器,可以控制的着色器就这两个了
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
第六步:创建着色器程序,连接顶点着色器和片段着色器
创建完着色器程序后,先将顶点和片段着色器依附到着色器程序上,然后使用glLinkProgram绑定着色器,绑定之后,我们就可以删除着色器对象了。
// 创建着色器程序
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
// 现在我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 在glUseProgram函数调用之后, 删除着色器对象,我们不再需要它们了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
第七步:创建三角形对象
因为程序的最后是用三角形绘制一个四边形,就相当远绘制两个相连的三角形,所以创建顶点的时候写了四个顶点,本来两个三角形应该是六个顶点,我们使用了EBO(索引缓冲对象),只写不重复的四个点就好了
// 三角形的顶点
float vertices[] = {
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
// 索引缓冲对象
// 告诉绘制程序,第一次绘制的三个索引是0,1,3,第二次绘制的是1,2,3,这样就成了一个四边形
unsigned int indices[] = {
0,1,3,
1,2,3
};
材料都准备齐了,下面准备真正的绘制了。
第八步:绑定绘制的对象和属性
首先创建VAO(顶点数组对象),将VBO(顶点缓冲对象)的配置保存下来,这样我们不用每个点都要重新配置一下属性。
然后创建VBO和EBO(索引缓冲对象)对象。
首先,生成VBO,VAO,EBO对象并绑定着色器
// 通过顶点缓冲对象VBO管理存放顶点的内存
unsigned int VBO, VAO, EBO;
// VAO, 顶点数组对象,批量管理以上属性
glGenVertexArrays(1,&VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// 绑定VAO对象,这个函数要在VBO和EBO属性绑定前绑定
glBindVertexArray(VAO);
// 把顶点数组复制到缓冲中供OpenGL使用,此处是GL_ARRAY_BUFFER(顶点缓冲类型)
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 把顶点复制到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 设置顶点属性指针,其实是个解析规则,具体看OpenGL教程解释
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE, 3* sizeof (float), (void*)0);
glEnableVertexAttribArray(0);
第九步:绘制
while (!glfwWindowShouldClose(window)) {
processInput(window); // 自定义函数,按ESC退出程序
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 清空时填充的颜色
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram); // 调用之前的着色器程序
glBindVertexArray(VAO); // 绑定着色器对象
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window);
glfwPollEvents();
}
最终生成图形如下:
下面是整个程序的完全代码:
#include "hellotriangle.h"
#include
#include
#include
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0,0,width, height);
}
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";
HelloTriangle::HelloTriangle()
{
// 先初始化
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 创建GLFW窗口
GLFWwindow* window = glfwCreateWindow(800,600, "hello_world", nullptr, nullptr);
if (window == nullptr)
{
qDebug() << "Failed to create glfw window";
glfwTerminate();
// return -1;
}
// 设置为当前上下文
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
qDebug() << "Faild to initialize GLAD";
}
// 先创建一个着色器对象,用她来运行时动态编译着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShader);
/************* 以下是log, 可以不写 ********************/
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
qDebug() << "COMPILATION FAILED: " << infoLog;
}
/*******************************************************/
// 片段着色器,可以控制的着色器就这两个了
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
/************************************************************/
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
qDebug() << "fragment error: " << infoLog;
}
/************************************************************/
// 两个着色器都编译了,余下的事情就是把两个着色器对象链接到一个用来渲染的着色器程序
// 创建着色器程序
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
// 现在我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
qDebug() << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog;
}
// 在glUseProgram函数调用之后, 删除着色器对象,我们不再需要它们了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 三角形的顶点
float vertices[] = {
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
unsigned int indices[] = {
0,1,3,
1,2,3
};
// 通过顶点缓冲对象VBO管理存放顶点的内存
unsigned int VBO, VAO, EBO;
// VAO, 顶点数组对象,批量管理以上属性
glGenVertexArrays(1,&VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
// 2.把顶点数组复制到缓冲中供OpenGL使用,此处是GL_ARRAY_BUFFER(顶点缓冲类型)
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // // 把顶点复制到缓冲内存中
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE, 3* sizeof (float), (void*)0);
glEnableVertexAttribArray(0);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteProgram(shaderProgram);
glfwTerminate();
}