??本節(jié)將對上節(jié)學(xué)習(xí)的內(nèi)容稍作補充戒洼,然后開始復(fù)習(xí)及拓展訓(xùn)練燕垃,以提高熟練度枢劝。對于習(xí)題的答案并不一定是最優(yōu)解,練習(xí)為主卜壕,并不做過多文字上的解釋您旁。
索引緩沖對象(Element Buffer Object,EBO)
??如果我們現(xiàn)在的需求不是一個三角形轴捎,而是一個矩形鹤盒。我們可以通過繪制兩個三角形的辦法來組成一個矩形(OpenGL主要處理三角形)。那么就要改變我們的輸入頂點數(shù)據(jù):
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 // 左上角
};
??可以看到這兩個三角形有兩個頂點重復(fù)了侦副,第一個是右下角0.5f, -0.5f, 0.0f
侦锯,第二個是左上角-0.5f, 0.5f, 0.0f
。目前看來重復(fù)的頂點數(shù)據(jù)并不會帶來什么嚴(yán)重的后秦驯,但是如果是面對一個非常精致的模型尺碰,里面包含了海量的三角形,這種情況下的重復(fù)所帶來的性能開銷是不可忽視的译隘。不過考慮周詳?shù)腛penGL顯然早已經(jīng)知道了這個問題并提供了很好的解決辦法亲桥,索引緩沖對象(Element Buffer Object,EBO)為此而生固耘。EBO與VBO一樣题篷,也是一個緩沖,它專門存儲索引厅目,存儲的是頂點數(shù)據(jù)的索引番枚,用索引的重復(fù)代替頂點的重復(fù)法严,那么如何設(shè)置頂點數(shù)據(jù)的索引呢?首先葫笼,我們要定義一個數(shù)組存儲不重復(fù)的頂點深啤,像列出一個矩形的所有頂點一樣:
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 // 左上角
};
??這是符合我們數(shù)學(xué)認(rèn)知的一個矩形的頂點表示。然后再定義一個數(shù)組存儲繪制出矩形所需的的索引渔欢,當(dāng)然是按繪制三角形的頂點順序存放索引:
unsigned int indices[] = { // 注意索引從0開始!
0, 1, 3, // 第一個三角形
1, 2, 3 // 第二個三角形
};
??接下來要做的就是創(chuàng)建一個EBO對象墓塌,然后再把索引數(shù)組送進EBO里。
unsigned int EBO;
glGenBuffers(1, &EBO);
??當(dāng)然要想對這個EBO進行操作還是要先綁定這個EBO奥额。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
??一切都與討論VBO時相似苫幢,唯一不同的就是緩沖的類型為GL_ELEMENT_ARRAY_BUFFER
。
??現(xiàn)在才是需要稍加注意的地方垫挨,如果我們使用了EBO韩肝,那么繪制的函數(shù)就不再是glDrawArrays
函數(shù),而是glDrawElements
函數(shù)九榔,指明是從索引緩沖渲染哀峻。
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
??第一個參數(shù)指定了我們繪制的模式,這個和glDrawArrays的一樣哲泊;第二個參數(shù)是我們打算繪制的頂點個數(shù)剩蟀,這里是6;第三個是參數(shù)是索引的類型切威;最后一個參數(shù)指定EBO中的偏移量育特,由于這里并沒有偏移量,所以是0先朦。
??glDrawElements
函數(shù)從當(dāng)前綁定的EBO中獲取索引缰冤,這意味著我們每次調(diào)用glDrawElements
繪制不同的物體都要綁定不同的EBO,這雖然不是特別麻煩喳魏,但如果有人樂意幫我做這件事那自然最好棉浸。我們的善心人士VAO果真樂于助人,在VAO綁定時去綁定EBO刺彩,這個步驟就會被VAO所保存迷郑,以后在渲染循環(huán)中綁定VAO時就會自動把EBO也綁定了。
??當(dāng)目標(biāo)是
GL_ELEMENT_ARRAY_BUFFER
的時候创倔,VAO會儲存glBindBuffer
的函數(shù)調(diào)用三热。這也意味著它也會儲存解綁調(diào)用,所以確保你沒有在解綁VAO之前解綁索引數(shù)組緩沖三幻,否則它就沒有這個EBO配置了。
??上述的提示我親身去體驗了一下呐能,在我解綁VAO前就把EBO給解綁了確實報錯:
??最后的代碼如下:
// ..:: 初始化代碼 :: ..
// 1. 綁定頂點數(shù)組對象
glBindVertexArray(VAO);
// 2. 把我們的頂點數(shù)組復(fù)制到一個頂點緩沖中念搬,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 復(fù)制我們的索引數(shù)組到一個索引緩沖中抑堡,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 設(shè)定頂點屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 繪制代碼(渲染循環(huán)中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);
??接下來就可以運行程序了:
??補充的內(nèi)容到此結(jié)束,接下來是習(xí)題時間朗徊。
1. 添加更多頂點到數(shù)據(jù)中首妖,使用glDrawArrays,嘗試?yán)L制兩個彼此相連的三角形
??我打算繪制一個正三角爷恳,一個倒三角有缆,兩者公用其中一條邊,形成一個棱形:
頂點數(shù)據(jù):
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
};
索引數(shù)組:
unsigned int indices[] = { // 注意索引從0開始!
0, 1, 3, // 第一個三角形
1, 2, 3 // 第二個三角形
};
??其余代碼不做改變温亲,得到結(jié)果:
??這道題稍顯簡單棚壁。
2. 創(chuàng)建相同的兩個三角形,但對它們的數(shù)據(jù)使用不同的VAO和VBO
??那么當(dāng)前圖案的頂點數(shù)據(jù)和其所屬的VBO及VAO不再另行改變栈虚,而是再新造一組頂點數(shù)據(jù)袖外、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 // 第二個三角形
};
??雖說不再改變魂务,但是要做一些修改曼验,譬如原本的三角形函數(shù)的某些參數(shù)要改為VAO[0]、VBO[0]和EBO[0]粘姜。這里是對原來作出的修改:
//創(chuàng)建VAO
unsigned int VAO[2];
glGenVertexArrays(2, VAO);
glBindVertexArray(VAO[0]);
//創(chuàng)建VBO且與頂點數(shù)據(jù)綁定
unsigned int VBO[2];
glGenBuffers(2, VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//創(chuàng)建EBO且與索引數(shù)據(jù)綁定
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);
??在渲染循環(huán)中,原本glBindVertexArray
函數(shù)的參數(shù)是VAO孤紧,現(xiàn)在為了看第二個圖案的效果豺裆,所以參數(shù)是VAO[1],用我們新創(chuàng)建好的VAO坛芽。
glBindVertexArray(VAO[1]);
3.創(chuàng)建兩個著色器程序留储,第二個程序使用一個不同的片段著色器,輸出黃色咙轩;再次繪制這兩個三角形获讳,讓其中一個輸出為黃色
??那我們就直接讓第二個圖案(漏斗)輸出黃色吧。
源碼:
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);
鏈接著色器程序?qū)ο螅?/p>
unsigned int shaderProgram2;
shaderProgram2 = glCreateProgram();
glAttachShader(shaderProgram2, vertexShader); //用的是原來的頂點著色器
glAttachShader(shaderProgram2, fragmentShader2);
glLinkProgram(shaderProgram2);
刪除著色器:
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glDeleteShader(fragmentShader2);
在渲染循環(huán)中活喊,使用第二個著色器程序?qū)ο螅?/p>
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[] = { //棱形頂點數(shù)據(jù)
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[] = { //漏斗頂點數(shù)據(jù)
-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);//渲染窗口大小
//創(chuàng)建VAO
unsigned int VAO[2];
glGenVertexArrays(2, VAO);
glBindVertexArray(VAO[0]);
//創(chuàng)建VBO且與頂點數(shù)據(jù)綁定
unsigned int VBO[2];
glGenBuffers(2, VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//創(chuàng)建EBO且與索引數(shù)據(jù)綁定
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;
}
//著色器程序?qū)ο? 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);
}
}