原博主博客地址:http://blog.csdn.net/qq21497936
本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78942027
《OpenGL学习笔记》系列博客目录地址:http://blog.csdn.net/qq21497936/article/category/7315532
EBO(Element Buffer Object)、EBO(Index Buffer Object):索引缓冲对象。
索引缓冲对象专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点,使用索引可重复使用同一个顶点。
首先我们先看六边形的六个顶点坐标:
在不使用索引的情况下,我们的顶点数据如下:
// 7.顶点数据
float vertices[] = {
-0.866, -0.5, 0.0,
-0.866, 0.5, 0.0,
0.0 , 1.0, 0.0, // 三角形1
-0.866, 0.5, 0.0,
0.0 , 1.0, 0.0,
0.866, 0.5, 0.0, // 三角形2
0.0 , 1.0, 0.0,
0.866, 0.5, 0.0,
0.866, -0.5, 0.0, // 三角形3
0.866, 0.5, 0.0,
0.866, -0.5, 0.0,
0.0 , -1.0, 0.0, // 三角形4
0.866, -0.5, 0.0,
0.0 , -1.0, 0.0,
-0.866, -0.5, 0.0, // 三角形5
0.0 , -1.0, 0.0,
-0.866, -0.5, 0.0,
-0.866, 0.5, 0.0, // 三角形6
};
绘制的顶点数量,是18个,所以修改程序的绘制顶点数量:
while (!glfwWindowShouldClose(window))
{
……
// 绘制三角形:三角形 数组起始索引 绘制多少个顶点
// glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawArrays(GL_TRIANGLES, 0, 18);
……
}
使用索引缓冲器,其实我们所有的三角形只使用到了六个顶点(需要重复使用的通过索引可重复使用),VAO中就保存该6个顶点可以了:
// 7.0索引缓冲期 增加的索引缓冲器顶点索引
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 2, // 第一个三角形
1, 2, 3, // 第二个三角形
2, 3, 4, // 第三个三角形
3, 4, 5, // 第四个三角形
4, 5, 0, // 第五个三角形
5, 0, 1, // 第六个三角形
};
使用EBO和VBO一样,首先是使用过程是一样的,其次是使用的注意点也是一样的,就是绑定EBO/VBO到解绑EBO/VBO(需要生效的)的过程一定要在绑定顶点数据和解绑顶点数据之间,否该绘制该顶点,缓存未被生效。
以下是在原来的VAO,VBO代码部分新增了EBO的代码:
// 8.使用VAO和VBO
unsigned int VBO, VAO;
// glGenVertexArrays() 创建一个顶点数组对象
// 第一个参数:需要创建的缓存数量
// 第二个参数:存储单一ID或多个ID的GLuint变量或数组的地址。
glGenVertexArrays(1, &VAO);
// glGenBuffers() 创建一个缓存对象并且返回缓存对象的标示符。
glGenBuffers(1, &VBO);
// 顶点对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
// glBindBuffer()有1个参数:buffer。(绑定和解绑的顺序很重要,勿更改)
glBindVertexArray(VAO);
// 缓存对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
// glBindBuffer()有2个参数:target与buffer。(绑定和解绑的顺序很重要,勿更改)
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 当缓存初始化之后,使用glBufferData()将顶点数据拷贝到缓存对象
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
#if 1 // 索引缓冲器-添加代码
// 8.0 与VBO类似,先创建EBO对象,再先绑定EBO
// 然后用glBufferData把索引复制到缓冲里
// 同样,和VBO类似,把这些函数调用放在绑定和解绑函数调用之间
// 区别在于,只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER。
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
#endif
// 设置顶点属性指针,glVertexAttribPointer()函数告诉OpenGL该如何解析顶点数据
// 顶点属性位置 顶点属性大小 数据的类型 是否被标准化 步长 偏移
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// glEnableVertexAttribArray()以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的
glEnableVertexAttribArray(0);
// 解绑缓存着色器(绑定和解绑的顺序很重要,勿更改)
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑顶点着色器(绑定和解绑的顺序很重要,勿更改)
glBindVertexArray(0);
修改后的绘制循环代码:
while (!glfwWindowShouldClose(window))
{
// 捕捉输入
processInput(window);
// 清空颜色缓冲
// 添加渲染指令,背景RGBA的A不生效, 更改为白色
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
/* 新增代码 第二段/共三段----------------------------------START */
// 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 激活VAO表示的顶点缓存
glBindVertexArray(VAO);
// 每次重新更新视口,如果不这样,拉升窗口后,会变形。
// 你把你要观察的视景体映射到你的视口就没有问题,但是如果你不设置视景体,
// opengl默认会以屏幕坐标,就是屏幕中心为原点,到四周的距离为1的这个范围
// 作为视景体的观察部分映射到你设置的视口上。
// 可以尝试,屏蔽此句,然后运行程序拉升窗口试试效果,更好理解
// glViewport(0, 0, 600, 600);
// 绘制三角形:三角形 数组起始索引 绘制多少个顶点
// 不使用索引缓冲器绘制顶点
#if 1
// 绘制六边的两两连接的三角形
// glDrawArrays(GL_TRIANGLES, 0, 18);
#endif
#if 1
// 索引缓冲器-添加代码 18个索引点(三角形,3个所以组成一个三角形)
// 这里18代表是18个索引,顶点数坐标也是18,只是巧合罢了
glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_INT, 0);
#endif
// 绘制三角形:线条 数组起始索引 绘制多少个顶点
// glDrawArrays(GL_LINES, 0 , 2);
/* 新增代码 第二段/共三段----------------------------------END */
// 检查并调用事件,交换缓冲(执行交换缓存才会更新新数据到界面)
glfwSwapBuffers(window);
glfwPollEvents();
}
#include
#include
#include
#include
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
// 在GLFW中实现一些输入控制,使用GLFW的glfwGetKey函数
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
int main()
{
glfwInit();
/*
请确认您的系统支持OpenGL3.3或更高版本,否则此应用有可能会
崩溃或者出现不可预知的错误。如果想要查看OpenGL版本的话,
在Linux上运行glxinfo,或者在Windows上使用其它的工具(例如
OpenGL Extension Viewer)。如果你的OpenGL版本低于3.3,检
查一下显卡是否支持OpenGL 3.3+(不支持的话你的显卡真的太老
了),并更新你的驱动程序,有必要的话请更新显卡。
*/
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 如果使用的是Mac OS X系统,你还需要加下面这行代码到你的
// 初始化代码中这些配置才能起作用
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
// 创建一个窗口对象,这个窗口对象存放了所有和窗口相关的数据,
// 而且会被GLFW的其他函数频繁地用到
// glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数。
// 第三个参数表示这个窗口的名称(标题),最后两个参数暂时忽略。
// 函数会返回一个GLFWwindow对象,我们会在其它的GLFW操作中使用到。
GLFWwindow* window = glfwCreateWindow(800, 600, "QQ:21497936", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数
// 之前我们需要初始化GLAD。
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 还需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数。
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 我们必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),
// 这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。
// 可以通过调用glViewport函数来设置窗口的维度(Dimension)。
// OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转
// 换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL
// 中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。
// 注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上
// 将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。
glViewport(0, 0, 600, 600);
// 在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,
// 需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),
// 它能在我们让GLFW退出前一直保持运行。
/* 新增代码 第一段/共三段----------------------------------START */
// 1.顶点着色器
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"
"}";
// 2.片段着色器
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"
"}";
// 3.创建顶点着色器并编译
int success;
char infoLog[512];
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 4.创建片段着色器并编译
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 5.连接着色器
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 6.删除已经使用的着色器(因为后面不再需要绑定了)
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
#if 0
// 7.顶点数据
float vertices[] = {
-0.866, -0.5, 0.0,
-0.866, 0.5, 0.0,
0.0 , 1.0, 0.0, // 三角形1
-0.866, 0.5, 0.0,
0.0 , 1.0, 0.0,
0.866, 0.5, 0.0, // 三角形2
0.0 , 1.0, 0.0,
0.866, 0.5, 0.0,
0.866, -0.5, 0.0, // 三角形3
0.866, 0.5, 0.0,
0.866, -0.5, 0.0,
0.0 , -1.0, 0.0, // 三角形4
0.866, -0.5, 0.0,
0.0 , -1.0, 0.0,
-0.866, -0.5, 0.0, // 三角形5
0.0 , -1.0, 0.0,
-0.866, -0.5, 0.0,
-0.866, 0.5, 0.0, // 三角形6
};
#endif
#if 1 // 索引缓冲器-添加代码
// 7.顶点数据
float vertices[] = {
-0.866, -0.5, 0.0, // 1 三角形5
-0.866, 0.5, 0.0, // 1 2 三角形6
0.0 , 1.0, 0.0, // 三角形1 2 3
0.866, 0.5, 0.0, // 三角形2 3 4
0.866, -0.5, 0.0, // 三角形3 4 5
0.0 , -1.0, 0.0, // 三角形4 5 6
};
// 7.0索引缓冲期 增加的索引缓冲器顶点索引
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 2, // 第一个三角形
1, 2, 3, // 第二个三角形
2, 3, 4, // 第三个三角形
3, 4, 5, // 第四个三角形
4, 5, 0, // 第五个三角形
5, 0, 1 // 第六个三角形
};
#endif
// 8.使用VAO和VBO
unsigned int VBO, VAO;
// glGenVertexArrays() 创建一个顶点数组对象
// 第一个参数:需要创建的缓存数量
// 第二个参数:存储单一ID或多个ID的GLuint变量或数组的地址。
glGenVertexArrays(1, &VAO);
// glGenBuffers() 创建一个缓存对象并且返回缓存对象的标示符。
glGenBuffers(1, &VBO);
// 顶点对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
// glBindBuffer()有1个参数:buffer。(绑定和解绑的顺序很重要,勿更改)
glBindVertexArray(VAO);
// 缓存对象创建之后,在使用缓存对象之前,需要将缓存对象连接到相应的缓存上。
// glBindBuffer()有2个参数:target与buffer。(绑定和解绑的顺序很重要,勿更改)
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 当缓存初始化之后,使用glBufferData()将顶点数据拷贝到缓存对象
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
#if 1 // 索引缓冲器-添加代码
// 8.0 与VBO类似,先创建EBO对象,再先绑定EBO
// 然后用glBufferData把索引复制到缓冲里
// 同样,和VBO类似,把这些函数调用放在绑定和解绑函数调用之间
// 区别在于,只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER。
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
#endif
// 设置顶点属性指针,glVertexAttribPointer()函数告诉OpenGL该如何解析顶点数据
// 顶点属性位置 顶点属性大小 数据的类型 是否被标准化 步长 偏移
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// glEnableVertexAttribArray()以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的
glEnableVertexAttribArray(0);
// 解绑缓存着色器(绑定和解绑的顺序很重要,勿更改)
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑顶点着色器(绑定和解绑的顺序很重要,勿更改)
glBindVertexArray(0);
/* 新增代码 第一段/共三段----------------------------------END */
while (!glfwWindowShouldClose(window))
{
// 捕捉输入
processInput(window);
// 清空颜色缓冲
// 添加渲染指令,背景RGBA的A不生效, 更改为白色
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
/* 新增代码 第二段/共三段----------------------------------START */
// 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 激活VAO表示的顶点缓存
glBindVertexArray(VAO);
// 每次重新更新视口,如果不这样,拉升窗口后,会变形。
// 你把你要观察的视景体映射到你的视口就没有问题,但是如果你不设置视景体,
// opengl默认会以屏幕坐标,就是屏幕中心为原点,到四周的距离为1的这个范围
// 作为视景体的观察部分映射到你设置的视口上。
// 可以尝试,屏蔽此句,然后运行程序拉升窗口试试效果,更好理解
// glViewport(0, 0, 600, 600);
// 绘制三角形:三角形 数组起始索引 绘制多少个顶点
// 不使用索引缓冲器绘制顶点
#if 1
// 绘制六边的两两连接的三角形
// glDrawArrays(GL_TRIANGLES, 0, 18);
#endif
#if 1
// 索引缓冲器-添加代码 18个索引点(三角形,3个所以组成一个三角形)
// 这里18代表是18个索引,顶点数坐标也是18,只是巧合罢了
glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_INT, 0);
#endif
// 绘制三角形:线条 数组起始索引 绘制多少个顶点
// glDrawArrays(GL_LINES, 0 , 2);
/* 新增代码 第二段/共三段----------------------------------END */
// 检查并调用事件,交换缓冲(执行交换缓存才会更新新数据到界面)
glfwSwapBuffers(window);
glfwPollEvents();
}
/* 新增代码 第三段/共三段----------------------------------END */
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
/* 新增代码 第三段/共三段----------------------------------END */
// 当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。
glfwTerminate();
return 0;
}
#if 1
// 使用xiankuan线框模式 随便放哪里,只要再glad之后(因为glad之后才能准确找到gl函数地址)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endi
想用线框模式绘制图形,可知填充模式,通过glPolygonMode(GL_FRONT_AND_BACK,GL_LINE)函数配置OpenGL如何绘制图元。第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉用线来绘制。之后的绘制调用会一直以线框模式绘制三角形,直到我们用glPolygonMode(GL_FRONT_AND_BACK,GL_FILL)将其设置回默认模式。
原博主博客地址:http://blog.csdn.net/qq21497936
本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78942027