本篇是通过OpenGL库,版本为3.3,渲染出一个三角形,效果如下:
参考链接:
https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
渲染流程如下:(GLFW配置正常写,可参考上篇博文:OpenGL 渲染一个窗口)
- 创建顶点着色器程序并编译:glCreateShader(GL_VERTEX_SHADER)
- 创建片段着色器程序并编译:glCreateShader(GL_FRAGMENT_SHADER)
- 定义并编译链接程序(将多个着色器合并之后并最终链接完成的版本):glCreateProgram()
- 定义三角形的三维结构坐标(我们渲染是三角形,所以z轴坐标为0)
- 定义并绑定与解绑VBO和VAO: 注意VBO与VAO的绑定与解绑顺序(绑定: 先VAO 再VBO;解绑:先VBO 再VAO))
- 在渲染循环中绘制图形
至于为什么要绑定与解绑(一家之言):这里的绑定与解绑不是对VBO或VAO怎么样,而是把VBO绑定到OpenGL的特定传输通道(不一定是传输通道,可以理解成一种数据结构,如:GL_ARRAY_BUFFER)上。
while循环每循环一次就需要绑定一次(传输一次数据),绑定好理解,要绘制图形,肯定要传数据过去,就绑定,至于解绑,首先不一定要写,测试过将解绑语句注释掉依旧可以运行,此外当你再次绑定时,之前绑定的就被覆盖了,相当于解绑。
所以不要过分纠结绑定与解绑,但一定要注意绑定顺序,先VAO再VBO再EBO
如何绑定:
- 使用glBindVertexArray绑定VAO,从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用
- 绑定VAO之后,当目标是 GL_ARRAY_BUFFER 或者 GL_ELEMENT_ARRAY_BUFFER 的时候,VAO会储存glBindBuffer的函数调用,也就会自动存储VBO和EBO
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
关于绘图:
// 不使用EBO
/*
* glDrawArrays(GL_TRIANGLES, 0, 3):
* 第一个参数是我们打算绘制的OpenGL图元的类型
* 第二个参数指定了顶点数组的起始索引,我们这里填0
* 第三个参数指定我们打算绘制多少个顶点
*/
glDrawArrays(GL_TRIANGLES, 0, 3);
// 使用EBO
/*
* glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0):
* 第一个参数指定了我们绘制的模式
* 第二个参数是我们打算绘制顶点的个数
* 第三个参数是索引的类型
* 最后一个参数里我们可以指定EBO中的偏移量
*/
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
源码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
// 定义回调函数
// --------------
// 窗口改变回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
// 窗口按键输入
void processInput(GLFWwindow* window);
// 其他相关变量
// --------------
// 宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// 顶点着色器
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";
int main()
{
// 1.初始化窗口和设置属性
// ----------------------
// 1.1 初始化和配置 glfw
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 1.2 创建glfw窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Hello world", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// 1.3 使用glad加载OpenGL函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to inittalize GLAD" << std::endl;
return -1;
}
// 1.4 确定视口(Viewport)的大小
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 2.设置两个必须的着色器:顶点和片段着色器
// ----------------------------------------
// 2.1 设置顶点着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER); // 把需要创建的着色器类型以参数形式提供给glCreateShader
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // 把这个着色器源码附加到着色器对象上
glCompileShader(vertexShader); // 编译源码
// 2.2 检测顶点着色器编译结果
int success;
char 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;
}
// 2.3 片段着色器
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); // 使用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::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 3.定义并编译链接程序
// --------------------
// 3.1 着色器程序:多个着色器合并之后并最终链接完成的版本,称链接
unsigned int shaderProgram;
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::LINK_FAILED\n" << infoLog << std::endl;
}
// 3.2 着色器对象链接到程序对象以后,就不再需要它们,可以删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 4.定义物体三维结构坐标
// ----------------------
// 定义三角形3D坐标
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// 5.定义并绑定与解绑VBO和VAO: 注意VBO与VAO的绑定与解绑顺序
// --------------------------------------------------
// 5.1 设置顶点缓冲对象(Vertex Buffer Objects, VBO)管理内存(坐标点数据)与 VAO
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO); // 使用glGenVertexArrays函数和一个缓冲ID生成一个VAO对象
glGenBuffers(1, &VBO); // 使用glGenBuffers函数和一个缓冲ID生成一个VBO对象
// 5.2 绑定: 先VAO 再VBO
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
// 调用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)
//
// 5.3 传入数据:调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 5.4 设置顶点属性指针:在渲染前指定OpenGL该如何解释顶点数据
// 使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
// 使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性,顶点属性默认是禁用的
glEnableVertexAttribArray(0);
// 5.5 解绑:先VBO 再VAO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// 这是一段很重要的话,阐述VAO的作用:
// ----------------------------------
/*
从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。
当我们打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。
这段代码应该看起来像这样:
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲*OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
注:前面做的一切都是等待这一刻,一个储存了我们顶点属性配置和应使用的VBO的顶点数组对象。
一般当你打算绘制多个物体时,你首先要生成/配置所有的VAO(和必须的VBO及属性指针),然后储存它们供后面使用。
当我们打算绘制物体的时候就拿出相应的VAO,绑定它,绘制完物体后,再解绑VAO。
如果乜有VAO,就需要每绘制一个物体时,就需要重复下面这个过程:
// 0. 复制顶点数组到缓冲*OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
*/
// 6.开启渲染循环(Render Loop)
while (!glfwWindowShouldClose(window))
{
// 6.1 输入控制:
processInput(window);
// 6.2 渲染指令
// 修改背景颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 绘图
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 6.3 检查调用事件,并交换缓冲
glfwPollEvents();
glfwSwapBuffers(window);
}
// 7.回收资源
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
glfwTerminate();
return 0;
}
// 窗口改变回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
// 窗口按键输入
void processInput(GLFWwindow* window)
{
// 按下esc按键,退出程序
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}