Instanced Drawを行う

## はじめに
　ここでは数あるOpenGLの描画方法の内の一つ、Instanced Drawについて解説を行う。
まずは、glDrawArraysを利用する場合の問題点(というほどでもないんだけど)を指摘した上で、それを解消する手段として、Instanced Drawの解説を進める。

**解説用ソース**
[ModernOpenGL-AdventCal2017/03-instancedrender](https://github.com/yumataesu/ModernOpenGL-AdventCal2017/tree/master/03-instancedrender)

## Instanced Drawの前に
　glDrawArrays()で大量のオブジェクトを描画したとする。例えば、パーティクルをレンダリングしたい時、以下のようなコードになるだろう。

```C++
glBindVertexArray(vao);
for(int i = 0; i < PARTICLE_NUM; i++) {
    glDrawArrays(GL_TRIANGLE_STRIPE, 0, 4);
}
glBindVertexArray(0);
```


## ドローコール数そのものがボトルネックになる
　上記は、glDrawArrays()がパーティクルの数だけ発行されることになる。特に問題があるわけではないが、<b>CPUからGPUへのドローコール数自体がボトルネックになってくるという問題が潜在している。</b>

![2月-28-2018 23-22-49.gif](https://qiita-image-store.s3.amazonaws.com/0/104234/cd00c729-b146-595f-2c69-8807c3c83443.gif)
*1つのプリミティヴを8回描画するイメージ*




## Instanced Drawは、似た形状をドローコール1回で描画する機能
　これから紹介するInstanced Drawを行うと、ドローコールを1回に抑えることができ、パフォーマンスの改善が見込める。<b>パーティクルなど似た形状のオブジェクトを大量に描画する際によく採用されている。</b>
![2月-28-2018 23-19-01.gif](https://qiita-image-store.s3.amazonaws.com/0/104234/f172c794-7cac-82fc-070e-86057520b65c.gif)
*8個のプリミティヴを一気に描画するイメージ*




## Instanced Drawを行うには
　<b>Instanced Drawを行うには、「ここからここまでが一つのプリミティヴ(インスタンス)だよ」とGPUに伝え、専用の描画コマンドを利用することで実現できる。</b>

下記を利用する。

* glVertexAttribDivisor()
* glDrawArraysInstanced()
* glDrawElementsInstanced()



---

## 実装&解説

　ランダムに散りばめられたポリゴンをInstanced Drawするプログラムを用意した。このプログラムを元に、実装と解説を進めていく。
[ModernOpenGL-AdventCal2017 / 03-instancedrender](https://github.com/yumataesu/ModernOpenGL-AdventCal2017/tree/master/03-instancedrender)
![3月-01-2018 12-13-38.gif](https://qiita-image-store.s3.amazonaws.com/0/104234/50b397b4-f05d-6f28-3594-6a8c4c07d317.gif)
*10000個の正方形ポリゴンをInstanced Drawする*


### 方針
　まず、以下の方針で実装する。

* 頂点/位置/色を属性としてVAOにバインドする
* 頂点情報は同じなので使いまわす
* 位置属性を利用して、Vertex Shader側で並行移動させる
* EBOを利用する

（本来ならば位置(vec3)だけでなく、モデル行列をバインドするのだがここでは割愛する）


### 1. VBOを作成し、VAOにバインドする
　以下のバインドポイントでVAOにバインドした。

0→頂点属性
1→位置属性
2→色属性

[source code](https://github.com/yumataesu/ModernOpenGL-AdventCal2017/blob/master/03-instancedrender/src/ofApp.cpp#L30-L64)


### 2. glVertexAttribDivisor()でインスタンス毎に分割

　ここがキモになる部分、glVertexAttribDivisor()はその名の通り、VAO内のそれぞれの属性をインスタンス毎に分割する関数。

**glVertexAttribDivisor( GLuint index, GLuint divisor);**
> **index**
>　VAOにバインドしている属性のバインドポイントを指定
>
> **divisor**
>　glVertexAttribPointerの第2引数で指定している頂点数で分割する。



以下の様になる

```C++
glVertexAttribDivisor(0, 0); //頂点属性（0）は、1つしかないので分割しない
glVertexAttribDivisor(1, 1);   //位置属性（1）は、3頂点分で分割する
glVertexAttribDivisor(2, 1);  //色属性（2）は、4頂点分で分割する
```

![スクリーンショット 2018-03-01 11.26.23.png](https://qiita-image-store.s3.amazonaws.com/0/104234/6b22d441-6ff6-6171-0e49-ed63af73f384.png)



glVertexAttribDivisor()を行うことで、1つのVAO内のデータを複数のインスタンスとして分割することができる。

![スクリーンショット 2018-03-01 17.07.35.png](https://qiita-image-store.s3.amazonaws.com/0/104234/082c6f18-72d1-41c0-9f88-d55cc198f991.png)

[source code](https://github.com/yumataesu/ModernOpenGL-AdventCal2017/blob/master/03-instancedrender/src/ofApp.cpp#L66-L68)

　

<b>もし第2引数を2に指定するとどうなるか</b>
　例えば、10000インスタンスを生成する想定でglVertexAttribDivisor(2, 2);すると、5001インスタンス目から、予期せぬ色でプリミティヴがレンダリングされる。（ほとんどの場合真っ黒になると思う）

```C++
glVertexAttribDivisor(0, 0);
glVertexAttribDivisor(1, 1);
glVertexAttribDivisor(2, 2);
```

![スクリーンショット 2018-03-01 11.45.59.png](https://qiita-image-store.s3.amazonaws.com/0/104234/e8155b88-6319-b06b-3eb1-c4972370f755.png)






### 3. 描画する

描画も非常にシンプル。EBOを使っているか否かで、以下を使い分けるだけ。`glDrawElementsInstanced()`の第5引数に描画するインスタンスの数を指定する。

* glDrawArraysInstanced()
* glDrawElementsInstanced()

```C++
glBindVertexArray(vao);
glDrawElementsInstanced(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_INT, NULL, 10000);
glBindVertexArray(0);
```


## oFで同じことをする
　ofVboのみで完結できる。
oFは、0,1,2,3のバインドポイントを予約しているので、位置属性は「4」でバインドしている。
[source code](https://github.com/yumataesu/ModernOpenGL-AdventCal2017/blob/master/03-instancedrender/src/ofApp.cpp#L84-L100)



## まとめ
　ここでは、Instanced Drawについての解説を行った。
大量のオブジェクトを描画する場合、Instanced Drawを用いるとドローコールが1回に軽減でき、パフォーマンスの向上が見込める。また、Instanced Drawは、`glVertexAttribDivisor()`、`glDrawArraysInstanced()`、`glDrawElementsInstanced()`を駆使することで実現できる。