序言
以glew、glfw库
OpenGL学习网站
glfw官网
OpenGL-API文档
glew官网
图形渲染管线:指3D坐标转为2D坐标的处理过程(实际上指的是一堆原始图形数据途径一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)
图形渲染管线各阶段:顶点数据->顶点着色器->图元->几何着色器->光栅化->片段着色器
- 顶点着色器:把单独的顶点作为输入。
- 图元装配:将顶点着色器输出的所有顶点作为输入,并将所有的点装配成指定图元的形状
- 几何着色器:把图元形式的一系列顶点的集合作为输入,可以通过产生新顶点构造出新的图元来生成其他形状
- 光栅化:把图元映射为最终屏幕上相应的像素,生成拱片段着色器使用的片段
- 片段着色器:目的计算一个像素的最终颜色
顶点数据
以三角形为例
顶点坐标
OpenGL所有坐标都是3D坐标(x, y, z),范围是-1.0~1.0之间。
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f -0.5f, 0.0f,
0.0f, 0.5, 0.0f
};
顶点缓冲对象
使用顶点缓冲对象(Vertex Buffer Objects, VBO)
管理这个内存,在GPU内存中储存大量顶点。
使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
顶点缓冲对象有一个独一无二的ID,可以使用glGenBuffers
函数和一个缓冲ID生成一个VBO
对象。
GLuint VBO;
glGenBuffers(1, &VBO); //生成缓冲对象
glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定到指定的缓冲类型上,使用后续使用GL_ARRAY_BUFFER都是指向的是VBO缓冲
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到缓冲内存中(VBO)
glBufferData
:一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
- 第一个参数:目标缓冲的类型(顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上)
- 第二个参数:指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
- 第三个参数:是我们希望发送的实际数据。即要将什么复制到缓冲内存中
- 第四个参数:指定了我们希望显卡如何管理给定的数据。
GL_STATIC_DRAW
:数据不会或几乎不会改变。GL_DYNAMIC_DRAW
:数据会被改变很多。GL_STREAM_DRAW
:数据每次绘制时都会改变。
上面的填写的是GL_STATIC_DRAW
,即数据不会变化。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW
或GL_STREAM_DRAW
,这样就能确保显卡把数据放在能够高速写入的内存部分。
顶点着色器
使用GLSL编写顶点着色器,看起来很像C语言,GLSL版本与OpenGL版本匹配。
#version 330 core //使用的GLSL版本330->OpenGL3.3 GLSL420->OpenGL4.2
layout (location = 0) in vec3 postion; //输入的顶点属性,0为设置的位置值 OpenGL代码中对应
void main()
{
//GLSL中向量是4个分量,需要转换。
//也可写成gl_Position = vec4(position, 1.0);只要position有三个对应分量即可
gl_Position = vec4(position.x, position.y, position.z, 1.0); //gl_Position的值将成为该顶点着色器的输出
}
编译着色器
着色器写完了,接下来就是编译着色器了,顶点着色器和片段着色器都一样。
//顶点着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); //创建顶点着色器
//将着色器源码附加到着色器对象上, vertexShaderSource就是上面写的顶点着色器,OpenGL只负责读取字符串
//第二个参数指将着色器源码附加到着色器对象数量,这里就只有顶点着色器,所以数量为1
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader); //编译着色器
//片段着色器
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
glShaderSource
:将着色器源码附加到着色器对象上
- 第一个参数:着色器对象
- 第二个参数:需要附加的着色器数量,上例只有一个顶点着色器
,所以填1
- 第三个参数:顶点着色器
源码,即上例写的顶点着色器
,着色器最终传递是以字符串
的形式传递的
- 第四个参数:指定着色器
源码数组的长度。可填NULL
检测着色器
检测着色器是否编译成功
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;
}
//片段
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); //检测
if(!success) {
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
片段着色器
和顶点着色器写法差不多,片段着色器只需要一个输出变量
#version 330 core //GLSL版本号与OpenGL版本号对应
out vec4 color; //out代表该变量是输出变量,即在片段着色器中输出
void main()
{
color = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
将片段着色器源码附加到着色器对象上
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
后续即将两个着色器对象链接到一个用于渲染的着色器程序中
着色器程序
着色器程序对象是多个着色器合并之后并最终链接完成的版本。
如果要使用刚才编译的着色器我们必须把它们链接为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。
已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,你会得到一个连接错误。
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader); //将顶点着色器附加到程序对象上
glAttachShader(shaderProgram, fragmentShader); //将片段着色器附加到程序对象上
glLinkProgram(shaderProgram); //链接程序对象
//检测链接着色器程序是否成功
GLint success;
GLchar infoLog[512];
glGetProgramiv(shaderProgram, GL_COMPILE_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX:COMPILATION_FAILED\n" << infoLog << std::endl;
}
glUseProgram(shaderProgram); //激活程序对象
//删除着色器对象,已经将着色器对象链接到程序对象上了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
已经把输入顶点数据发送给了GPU,并指示了GPU如何在顶点和片段着色器中处理它。
后续将顶点数据
链接到顶点着色器属性
上,即着色器中设置的变量layout (location = 0) in vec3 postion;
,将设置的顶点数据传到position
里
链接顶点属性
顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。即location
相对应的顶点属性。
使用glVertexAttribPointer
函数告诉OpenGL该如何解析顶点数据
glVertexAttribPointer(0, 3, GL_FOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0); //启用设置好的属性glVertexAttribPointer设置的0
glVertexAttribPointer
:设置顶点的数据属性。
- 第一个参数:指定要配置的顶点属性。上面的顶点着色器中使用
layout(location = 0)
定义的position
顶点属性值的位置值Location
,Location
已经设置为0
了,希望把数据传递到这个顶点属性上,则传0
。 - 第二个参数:指定的顶点属性大小。即上面设置的三角形顶点坐标
vertices
,每个顶点是有三个向量组成(x, y, z),所以大小为3。 - 第三个参数:指定数据类型。
vertices
里是GLfloat
组成,即glVertexAttribPointer
里的属性对应的是GL_GLOAT
(GLSL中vec*
都是浮点数值组成)。 - 第四个参数:是否希望数据被标准化。如果设置为
GL_TRUE
,所有数据都会被映射到0(对于有符号signed
数据是-1)到1之间。这里设置为GL_FALSE
。 - 第五个参数:叫作
步长
,它告诉我们在连续的顶点属性组之间的间隔。即顶点与顶点之间的多少字节,上例vertices
中-0.5f, -0.5f, 0.0f
点到0.5f -0.5f, 0.0f
点中间隔多少字节,一个顶点是3个向量,采用GLfloat
属性,即3 * sizeof(GLfloat)
。这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节
。 - 第六个参数:表示位置数据在缓冲中起始位置的偏移量(Offset)。类型是
GLvoid*
需要强制转换。vertices
的顶点数据都是0开始的,前面没有数据,所以为0。例-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f
中前面三个是顶点,后面三个是颜色值,如果是颜色的话(后面三个数组),那就是(GLvoid*)(3 * sizeof(GLfloat)
,因为前面还有三个顶点值。
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)
获取则是通过在调用glVetexAttribPointer
时绑定到GL_ARRAY_BUFFER
的VBO
决定的。
由于在调用glVetexAttribPointer
之前绑定的是先前定义的VBO
对象,顶点属性0现在会链接到它的顶点数据。
和顶点缓冲对象
组合使用
GLuint VBO;
glGenBuffers(1, &VBO); //生成VBO对象,即缓冲对象
glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定缓冲对象 将vbo绑定到GL_ARRAY_BUFFER
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到缓冲内存中(即GL_ARRAY_BUFFER)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(0); //启用顶点属性为0的数据,默认是禁用
上面是绑定一个顶点属性(layout(location = 0)
),那如果绑定多个呢?layout(location = 0)、layout(location = 1)、layout(location = 2)、.....layout(location = 9999)
。都要重复以下方法。
// 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(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
有什么办法使我们把所有这些状态配置储存在一个对象中,并且可以通过绑定这个对象来恢复状态呢?可采用下面方法来
顶点数组对象
顶点数组对象(Vertex Array Object, VAO)
可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
OpenGL的核心模式要求我们使用VAO
,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO
失败,OpenGL会拒绝绘制任何东西。
一个顶点数组对象会储存以下这些内容:
glEnableVertexAttribArray
和glDisableVertexAttribArray
的调用。- 通过
glVertexAttribPointer
设置的顶点属性配置。 - 通过
glVertexAttribPointer
调用进行的顶点缓冲对象与顶点属性链接。
和顶点缓冲对象(VBO)
很像,VBO
存储顶点数据,VAO
存储顶点属性。
用法和VBO
很像
GLuint VAO; //VAO可以是一个数组 GLuint VAOs[2];
glGenVertexArrays(1, &VAO); //顶点数组对象 数组 glBindVertexArray(2, &VAOs);
glBindVertexArray(VAO); //绑定顶点缓冲数组 数组 glBindVertexArray(VAO[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//顶点属性映射
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(0); //启用顶点属性为0的数据,默认是禁用
glBindVertexArray(0); //解除绑定
这样的话,VBO
就不需要glVertexAttribPointer
了,在VAO
里写即可。
VBO
和VAO
组合使用
GLuint VBO; //VBO可以是一个数组 GLuint VBOs[2];
glGenBuffers(1, &VBO); //生成VBO对象,即缓冲对象 数组 glGenBuffers(2, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定缓冲对象 将vbo绑定到GL_ARRAY_BUFFER 数组 glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //将顶点数据复制到缓冲内存中(即GL_ARRAY_BUFFER)
//glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
//glEnableVertexAttribArray(0); //启用顶点数据
//顶点数组对象
GLuint VAO;
glGenVertexArrays(1, &VAO); //顶点数组对象
glBindVertexArray(VAO); //绑定顶点缓冲数组
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), (GLvoid*)0); //位置
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GL_FLOAT), (GLvoid*)(3 * sizeof(GLfloat))); //颜色
glEnableVertexAttribArray(1);
glBindVertexArray(0); //解除绑定,避免在其他地方错误配置
// 5. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glBindVertexArray(0);
三角形完整代码
#include <glew.h>
#include <glfw3.h>
#include <iostream>
//顶点着色器
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\n";
//片段着色器
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
" color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n";
int main()
{
glfwInit(); //必须要将glfw初始化
//告诉GLFW使用OpenGL版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //使用的是OpenGL核心模式
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); //不允许调整窗口大小
//创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr) {
std::cout << "Failed to create GLFW Window" << std::endl;
glfwTerminate(); //销毁窗口与数据
return -1;
}
glfwMakeContextCurrent(window); //将OpenGL指向为当前窗口
glewExperimental = GL_TRUE; //用于告知GLEW使用现化OpenGL技术
//glew初始化
if (glewInit() != GLEW_OK) {
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
//视口
int width = 800, height = 600;
glfwGetFramebufferSize(window, &width, &height); //设置OpenGL渲染窗口的尺寸
glViewport(0, 0, width, height); //设置窗口的维度 前两个参数控制窗口左下角的位置, 第三、四个参数控制渲染窗口的宽度和高度
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
//顶点数据
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//编译顶点着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShader);
//检测顶点着色器是否编译成功
GLint vertexSuccess;
GLchar vertexInfoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexSuccess);
if (!vertexSuccess) {
glGetShaderInfoLog(vertexShader, 512, nullptr, vertexInfoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << std::endl;
}
//编译片段着色器
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
//检测片段着色器是否编译成功
GLint fragmentSuccess;
GLchar fragmentInfoLog[512];
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentSuccess);
if (!fragmentSuccess) {
glGetShaderInfoLog(fragmentShader, 512, nullptr, fragmentInfoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << std::endl;
}
//链接程序
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//顶点数组对象
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0); //最后解除绑定
while (!glfwWindowShouldClose(window)) {
//检查GLFW是否退出,即窗口是否关闭了,true代表结束了
glfwPollEvents(); //检查有没有事件发生(键盘输入、鼠标移动),如发生调用对应的回调函数 键盘事件:glfwSetKeyCallback(window, key_callback); key_callback即设定的回调函数
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //清空屏幕所用的颜色,即清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
glClear(GL_COLOR_BUFFER_BIT); //清空屏幕缓冲,这里是颜色缓冲
glUseProgram(shaderProgram);
//渲染指令
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glfwSwapBuffers(window); //交换颜色缓冲,用来绘制,输出显示在屏幕上
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
索引缓冲对象
假设我们不再绘制一个三角形而是绘制一个矩形。我们可以绘制两个三角形来组成一个矩形(OpenGL主要处理三角形)。这会生成下面的顶点的集合:
GLfloat vertices[] = {
// 第一个三角形
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 // 左上角
};
可以看到,有几个顶点叠加了。我们指定了右下角和左上角两次!一个矩形只有4个而不是6个顶点,这样就产生50%的额外开销。当我们有包括上千个三角形的模型之后这个问题会更糟糕,这会产生一大堆浪费。更好的解决方案是只储存不同的顶点,并设定绘制这些顶点的顺序。这样子我们只要储存4个顶点就能绘制矩形了,之后只要指定绘制的顺序就行了。
很幸运,索引缓冲对象的工作方式正是这样的。和顶点缓冲对象一样,EBO也是一个缓冲,它专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(独一无二的)顶点,和绘制出矩形所需的索引:
GLfloat vertices[] = {
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 indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
可以看到只需要定义4个顶点,无需定义6个顶点。
创建索引缓冲对象(EBO)
和VAO
、VBO
相似。
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(0); //启用设置好的属性glVertexAttribPointer设置的0
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //绘制 对应上面的glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements
:指明从索引缓冲渲染。
- 第一个参数:指定了绘制的模式,这个和
glDrawArrays
的一样。 - 第二个参数:绘制顶点的数量。这里需要绘制6个顶点,
indices
数量。 - 第三个参数:索引的类型。这里是
GL_UNSIGNED_INT
。 - 第四个参数:指定
EBO
中的偏移量(或者传递一个索引数组,但是这是当你不在使用索引缓冲对象的时候),但是我们会在这里填写0。
glDrawElements
函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER
目标的EBO
中获取索引。这意味着我们必须在每次要用索引渲染一个物体时绑定相应的EBO
,这还是有点麻烦。不过顶点数组对象同样可以保存索引缓冲对象的绑定状态。VAO
绑定时正在绑定的索引缓冲对象会被保存为VAO
的元素缓冲对象。绑定VAO
的同时也会自动绑定EBO
。
当目标是GL_ELEMENT_ARRAY_BUFFER
的时候,VAO
会储存glBindBuffer
的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO
之前解绑索引数组缓冲,否则它就没有这个EBO
配置了。
索引缓冲完整代码
#include <glew.h>
#include <glfw3.h>
#include <iostream>
//顶点着色器
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(position.x, position.y, position.z, 1.0);\n"
"}\n";
//片段着色器
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
" color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n";
int main()
{
glfwInit(); //必须要将glfw初始化
//告诉GLFW使用OpenGL版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //使用的是OpenGL核心模式
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); //不允许调整窗口大小
//创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);
if (window == nullptr) {
std::cout << "Failed to create GLFW Window" << std::endl;
glfwTerminate(); //销毁窗口与数据
return -1;
}
glfwMakeContextCurrent(window); //将OpenGL指向为当前窗口
glewExperimental = GL_TRUE; //用于告知GLEW使用现化OpenGL技术
//glew初始化
if (glewInit() != GLEW_OK) {
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
//视口
int width = 800, height = 600;
glfwGetFramebufferSize(window, &width, &height); //设置OpenGL渲染窗口的尺寸
glViewport(0, 0, width, height); //设置窗口的维度 前两个参数控制窗口左下角的位置, 第三、四个参数控制渲染窗口的宽度和高度
//三角形
/* GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};*/
GLfloat vertices[] = {
0.5f, 0.5f, 0.0f, //右上角
0.5, -0.5f, 0.0f, //右下角
-0.5f, -0.5f, 0.0f, //左下角
-0.5f, 0.5f, 0.0f //左上角
};
GLuint indices[] = {
0, 1, 3, //第一个三角形
1, 2, 3 //第二个三角形
};
//顶点数据
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//编译顶点着色器
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShader);
//检测顶点着色器是否编译成功
GLint vertexSuccess;
GLchar vertexInfoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexSuccess);
if (!vertexSuccess) {
glGetShaderInfoLog(vertexShader, 512, nullptr, vertexInfoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << vertexInfoLog << std::endl;
}
//编译片段着色器
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);
//检测片段着色器是否编译成功
GLint fragmentSuccess;
GLchar fragmentInfoLog[512];
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentSuccess);
if (!fragmentSuccess) {
glGetShaderInfoLog(fragmentShader, 512, nullptr, fragmentInfoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << fragmentInfoLog << std::endl;
}
//链接程序
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
GLint programSuccess;
GLchar programInfoLog[512];
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programSuccess);
if (!programSuccess) {
glGetProgramInfoLog(shaderProgram, 512, nullptr, programInfoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << programInfoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//顶点数组对象
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
/* glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);*/
//索引缓冲数组
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//最后解除绑定,一定要最后解除
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
while (!glfwWindowShouldClose(window)) {
//检查GLFW是否退出,即窗口是否关闭了,true代表结束了
glfwPollEvents(); //检查有没有事件发生(键盘输入、鼠标移动),如发生调用对应的回调函数 键盘事件:glfwSetKeyCallback(window, key_callback); key_callback即设定的回调函数
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //清空屏幕所用的颜色,即清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
glClear(GL_COLOR_BUFFER_BIT); //清空屏幕缓冲,这里是颜色缓冲
glUseProgram(shaderProgram);
//渲染指令
glBindVertexArray(VAO);
//glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //索引
glBindVertexArray(0);
glfwSwapBuffers(window); //交换颜色缓冲,用来绘制,输出显示在屏幕上
}
//删除
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}