
本节将对上节学习的内容稍作补充,然后开始复习及拓展训练,以提高熟练度。对于习题的答案并不一定是最优解,练习为主,并不做过多文字上的解释。
索引缓冲对象(Element Buffer Object,EBO)
如果我们现在的需求不是一个三角形,而是一个矩形。我们可以通过绘制两个三角形的办法来组成一个矩形(OpenGL主要处理三角形)。那么就要改变我们的输入顶点数据:
float 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 // 左上角
};
可以看到这两个三角形有两个顶点重复了,第一个是右下角0.5f, -0.5f, 0.0f
,第二个是左上角-0.5f, 0.5f, 0.0f
。目前看来重复的顶点数据并不会带来什么严重的后,但是如果是面对一个非常精致的模型,里面包含了海量的三角形,这种情况下的重复所带来的性能开销是不可忽视的。不过考虑周详的OpenGL显然早已经知道了这个问题并提供了很好的解决办法,索引缓冲对象(Element Buffer Object,EBO)为此而生。EBO与VBO一样,也是一个缓冲,它专门存储索引,存储的是顶点数据的索引,用索引的重复代替顶点的重复,那么如何设置顶点数据的索引呢?首先,我们要定义一个数组存储不重复的顶点,像列出一个矩形的所有顶点一样:
float 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 // 左上角
};
这是符合我们数学认知的一个矩形的顶点表示。然后再定义一个数组存储绘制出矩形所需的的索引,当然是按绘制三角形的顶点顺序存放索引:
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
接下来要做的就是创建一个EBO对象,然后再把索引数组送进EBO里。
unsigned int EBO;
glGenBuffers(1, &EBO);
当然要想对这个EBO进行操作还是要先绑定这个EBO。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
一切都与讨论VBO时相似,唯一不同的就是缓冲的类型为GL_ELEMENT_ARRAY_BUFFER
。
现在才是需要稍加注意的地方,如果我们使用了EBO,那么绘制的函数就不再是glDrawArrays
函数,而是glDrawElements
函数,指明是从索引缓冲渲染。
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
第一个参数指定了我们绘制的模式,这个和glDrawArrays的一样;第二个参数是我们打算绘制的顶点个数,这里是6;第三个是参数是索引的类型;最后一个参数指定EBO中的偏移量,由于这里并没有偏移量,所以是0。
glDrawElements
函数从当前绑定的EBO中获取索引,这意味着我们每次调用glDrawElements
绘制不同的物体都要绑定不同的EBO,这虽然不是特别麻烦,但如果有人乐意帮我做这件事那自然最好。我们的善心人士VAO果真乐于助人,在VAO绑定时去绑定EBO,这个步骤就会被VAO所保存,以后在渲染循环中绑定VAO时就会自动把EBO也绑定了。

当目标是
GL_ELEMENT_ARRAY_BUFFER
的时候,VAO会储存glBindBuffer
的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。
上述的提示我亲身去体验了一下,在我解绑VAO前就把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);
[...]
// ..:: 绘制代码(渲染循环中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);
接下来就可以运行程序了:

补充的内容到此结束,接下来是习题时间。
1. 添加更多顶点到数据中,使用glDrawArrays,尝试绘制两个彼此相连的三角形
我打算绘制一个正三角,一个倒三角,两者公用其中一条边,形成一个棱形:
顶点数据:
float vertices[] = {
0.0f, 0.5f, 0.0f,
0.5f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f,
-0.5f, 0.0f, 0.0f
};
索引数组:
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
其余代码不做改变,得到结果:

这道题稍显简单。
2. 创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO
那么当前图案的顶点数据和其所属的VBO及VAO不再另行改变,而是再新造一组顶点数据、VAO及VBO。我造一个漏斗吧:
float vertices2[] = {
-0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 2, // 第一个三角形
2, 3, 4 // 第二个三角形
};
虽说不再改变,但是要做一些修改,譬如原本的三角形函数的某些参数要改为VAO[0]、VBO[0]和EBO[0]。这里是对原来作出的修改:
//创建VAO
unsigned int VAO[2];
glGenVertexArrays(2, VAO);
glBindVertexArray(VAO[0]);
//创建VBO且与顶点数据绑定
unsigned int VBO[2];
glGenBuffers(2, VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//创建EBO且与索引数据绑定
unsigned int EBO[2];
glGenBuffers(2, EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
这里是新增的VBO、VAO和EBO的绑定:
glBindVertexArray(VAO[1]);
glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
在渲染循环中,原本glBindVertexArray
函数的参数是VAO,现在为了看第二个图案的效果,所以参数是VAO[1],用我们新创建好的VAO。
glBindVertexArray(VAO[1]);

3.创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色
那我们就直接让第二个图案(漏斗)输出黄色吧。
源码:
const char * fragmentShaderSource2 =
" #version 330 core \n "
" out vec4 FragColor; \n "
" void main() \n "
" {FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);} \n ";
片段着色器:
unsigned int fragmentShader2;
fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader2, 1, &fragmentShaderSource2, NULL);
glCompileShader(fragmentShader2);
链接着色器程序对象:
unsigned int shaderProgram2;
shaderProgram2 = glCreateProgram();
glAttachShader(shaderProgram2, vertexShader); //用的是原来的顶点着色器
glAttachShader(shaderProgram2, fragmentShader2);
glLinkProgram(shaderProgram2);
删除着色器:
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glDeleteShader(fragmentShader2);
在渲染循环中,使用第二个着色器程序对象:
while (!glfwWindowShouldClose(window))
{
//处理输入
processInput(window);
//渲染指令
glClearColor(1.0f, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram2);
glBindVertexArray(VAO[1]);
//glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
//接收输入,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}

包含3道题的所有代码在此:
#define GLEW_STATIC
#include<iostream>
#include<GL/glew.h>
#include<GLFW/glfw3.h>
void processInput(GLFWwindow*);
float vertices[] = { //棱形顶点数据
0.0f, 0.5f, 0.0f,
0.5f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f,
-0.5f, 0.0f, 0.0f
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
float vertices2[] = { //漏斗顶点数据
-0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
};
unsigned int indices2[] = { // 注意索引从0开始!
0, 1, 2, // 第一个三角形
2, 3, 4 // 第二个三角形
};
const char * vertexShaderSource =
"#version 330 core \n"
"layout (location = 0) in vec3 aPos; \n"
"void main() \n"
"{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);} \n";
const char * fragmentShaderSource =
" #version 330 core \n " //输出橘色
" out vec4 FragColor; \n "
" void main() \n "
" {FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);} \n ";
const char * fragmentShaderSource2 =
" #version 330 core \n " //输出黄色
" out vec4 FragColor; \n "
" void main() \n "
" {FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);} \n ";
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //使用的OpenGL版本号3.3
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //使用流水线配置模式
GLFWwindow* window = glfwCreateWindow(800, 600, "Learn OpenGL 2019-07-24 17:25:23", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
//初始化GLEW
glewExperimental = true;
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initial GLEW." << std::endl;
glfwTerminate();
return -1;
}
glViewport(0, 0, 800, 600);//渲染窗口大小
//创建VAO
unsigned int VAO[2];
glGenVertexArrays(2, VAO);
glBindVertexArray(VAO[0]);
//创建VBO且与顶点数据绑定
unsigned int VBO[2];
glGenBuffers(2, VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//创建EBO且与索引数据绑定
unsigned int EBO[2];
glGenBuffers(2, EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//顶点着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
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;
}
//片段着色器
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
unsigned int fragmentShader2;
fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glShaderSource(fragmentShader2, 1, &fragmentShaderSource2, NULL);
glCompileShader(fragmentShader2);
int success1;
char infoLog1[512];
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success1);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog1);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog1 << std::endl;
}
//着色器程序对象
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
unsigned int shaderProgram2;
shaderProgram2 = glCreateProgram();
glAttachShader(shaderProgram2, vertexShader);
glAttachShader(shaderProgram2, fragmentShader2);
glLinkProgram(shaderProgram2);
//删除着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glDeleteShader(fragmentShader2);
//链接顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
glEnableVertexAttribArray(0);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(VAO[1]);
glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
while (!glfwWindowShouldClose(window))
{
//处理输入
processInput(window);
//渲染指令
glClearColor(1.0f, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram2);
glBindVertexArray(VAO[1]);
//glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
//接收输入,交换缓冲
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window,GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, true);
}
}
网友评论