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

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

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

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

## OpenGL中的索引VBO
索引的用法很簡單。首先,需要創建一個額外的緩沖區存放索引。代碼與之前一樣,不過參數是`ELEMENT_ARRAY_BUFFER`,而非`ARRAY_BUFFER`。
```
<pre class="calibre16">```
std<span class="token1">:</span><span class="token1">:</span>vector<span class="token"><</span>unsigned int<span class="token">></span> indices<span class="token1">;</span>
<span class="token2">// fill "indices" as needed</span>
<span class="token2">// Generate a buffer for the indices</span>
GLuint elementbuffer<span class="token1">;</span>
<span class="token3">glGenBuffers</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>elementbuffer<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ELEMENT_ARRAY_BUFFER<span class="token1">,</span> elementbuffer<span class="token1">)</span><span class="token1">;</span>
<span class="token3">glBufferData</span><span class="token1">(</span>GL_ELEMENT_ARRAY_BUFFER<span class="token1">,</span> indices<span class="token1">.</span><span class="token3">size</span><span class="token1">(</span><span class="token1">)</span> <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>unsigned int<span class="token1">)</span><span class="token1">,</span> <span class="token">&</span>indices<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token1">,</span> GL_STATIC_DRAW<span class="token1">)</span><span class="token1">;</span>
```
```
只需把`glDrawArrays`替換為如下語句,即可繪制模型:
```
<pre class="calibre16">```
<span class="token2">// Index buffer</span>
<span class="token3">glBindBuffer</span><span class="token1">(</span>GL_ELEMENT_ARRAY_BUFFER<span class="token1">,</span> elementbuffer<span class="token1">)</span><span class="token1">;</span>
<span class="token2">// Draw the triangles !</span>
<span class="token3">glDrawElements</span><span class="token1">(</span>
GL_TRIANGLES<span class="token1">,</span> <span class="token2">// mode</span>
indices<span class="token1">.</span><span class="token3">size</span><span class="token1">(</span><span class="token1">)</span><span class="token1">,</span> <span class="token2">// count</span>
GL_UNSIGNED_INT<span class="token1">,</span> <span class="token2">// type</span>
<span class="token1">(</span>void<span class="token">*</span><span class="token1">)</span><span class="token6">0</span> <span class="token2">// element array buffer offset</span>
<span class="token1">)</span><span class="token1">;</span>
```
```
(小提示:最好使用`unsigned short`,不要用`unsigned int`。這樣更節省空間,速度也更快。)
## 填充索引緩沖區
現在遇到真正的問題了。如前所述,OpenGL只能使用一個索引緩沖區,而OBJ(及一些其他常用的3D格式,如Collada)每個屬性都有一個索引緩沖區。這意味著,必須通過某種方式把若干個索引緩沖區合并成一個。
合并算法如下:
```
<pre class="calibre16">```
For each input vertex
Try to find a similar <span class="token1">(</span> <span class="token">=</span> same <span class="token4">for</span> all attributes <span class="token1">)</span> vertex between all those we already output
If found <span class="token1">:</span>
A similar vertex is already <span class="token4">in</span> the VBO<span class="token1">,</span> use it instead <span class="token">!</span>
If not found <span class="token1">:</span>
No similar vertex found<span class="token1">,</span> 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)中還有些其他和性能相關的工具。