# 第九課:VBO索引
## 索引的原理
目前為止,建立VBO時我們總是重復存儲一些共享的頂點和邊。
本課將介紹索引技術。借助索引,我們可以重復使用一個頂點。這是用*索引緩沖區(index buffer)*來實現的。

索引緩沖區存儲的是整數;每個三角形有三個整數索引,用索引就可以在各種*屬性緩沖區*(頂點坐標、顏色、UV坐標、其他UV坐標、法向緩沖區等)中找到頂點的信息。這有點像OBJ文件格式,但有一點相差甚遠:索引緩沖區只有一個。這意味著若兩個三角形共用一個頂點,那這個頂點的所有屬性對兩個三角形來說都是一樣的。
## 共享vs分開
來看看法向的例子。下圖中,藝術家創建了兩個三角形,試圖模擬一個平滑曲面。可以把兩個三角形的法向融合成一個頂點的法向。為方便觀看,我畫了一條紅線表示平滑曲面。

然而在第二幅圖中,美工想畫的是“縫隙”或“邊緣”。若融合了法向,就意味著色器會像前例一樣進行平滑插值,生成一個平滑的表面:

因此在這種情況下,把頂點的法向分開存儲反而更好;在OpenGL中,唯一實現方法是:把頂點連同其屬性完整復制一份。

## OpenGL中的索引VBO
索引的用法很簡單。首先,需要創建一個額外的緩沖區存放索引。代碼與之前一樣,不過參數是`ELEMENT_ARRAY_BUFFER`,而非`ARRAY_BUFFER`。
~~~
std::vector<unsigned int> indices;
// fill "indices" as needed
// Generate a buffer for the indices
GLuint elementbuffer;
glGenBuffers(1, &elementbuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
~~~
只需把`glDrawArrays`替換為如下語句,即可繪制模型:
~~~
// Index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
// Draw the triangles !
glDrawElements(
GL_TRIANGLES, // mode
indices.size(), // count
GL_UNSIGNED_INT, // type
(void*)0 // element array buffer offset
);
~~~
(小提示:最好使用`unsigned short`,不要用`unsigned int`。這樣更節省空間,速度也更快。)
## 填充索引緩沖區
現在遇到真正的問題了。如前所述,OpenGL只能使用一個索引緩沖區,而OBJ(及一些其他常用的3D格式,如Collada)每個屬性都有一個索引緩沖區。這意味著,必須通過某種方式把若干個索引緩沖區合并成一個。
合并算法如下:
~~~
For each input vertex
Try to find a similar ( = same for all attributes ) vertex between all those we already output
If found :
A similar vertex is already in the VBO, use it instead !
If not found :
No similar vertex found, add it to the VBO
~~~
完整的C++代碼位于`common/vboindexer.cpp`,注釋很詳盡。如果理解了以上算法,讀懂代碼應該沒問題。
若兩頂點的坐標、UV坐標和法線都相等,則認為兩頂點是同一頂點。若還有其他屬性,這一標準得酌情修改。
為了表述的簡單,我們采用了蹩腳的線性查找來尋找相似頂點。實際中用`std::map`會更好。
## 補充:FPS計數器
雖然和索引沒有直接關系,但現在去看看“FPS計數器”是很合適的——這樣我們就能看到,索引究竟能提升多少性能。[“工具——調試器”](http://www.opengl-tutorial.org/miscellaneous/useful-tools-links/#header-4)中還有些其他和性能相關的工具。