macOSでOpenGLプログラミング（2-2. 構造体とvectorを使って頂点データを指定する）

[macOSでOpenGLプログラミングの目次に戻る](http://qiita.com/sazameki/items/d060f20f621c658046d9)


# はじめに

[前回](http://qiita.com/sazameki/items/e60848466f7016e48b14)は、シェーダを書いて読み込み、VBOとVAOを使って画面描画を行う方法を解説しました。

今回は、頂点データを構造体とvectorを使って用意し、それによってVBOとVAOそれぞれの指定が分かりやすくなるということを解説したいと思います。

# 1. VertexData構造体を定義する

Game.cppを編集して、vectorヘッダをインクルードして、頂点データを表すVertexData構造体を定義します。

```cpp:Game.cpp（先頭部分）
#include "Game.hpp"

#include <cmath>
#include <vector>

struct VertexData
{
    GLfloat     pos[3];
    GLfloat     color[4];
};
```

GLfloat型の数値を3つ組み合わせた配列で頂点座標を表し、GLfloat型の数値を4つ組み合わせた配列で色情報を表します。

# 2. Gameクラスのコンストラクタを書き換える

次に、Gameクラスのコンストラクタを編集して、前回はGLfloat型の数値を並べて頂点データを定義していたのを、C++標準のstd::vectorとVertexDataを組み合わせた形で頂点データを定義するように変更します。

```cpp:Game.cpp（コンストラクタ）
Game::Game()
{
    program = new ShaderProgram("myshader.vsh", "myshader.fsh");

    std::vector<VertexData> data;
    data.push_back({ { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } });
    data.push_back({ {  0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } });
    data.push_back({ {  0.0f,  0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } });

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData) * data.size(), &data[0], GL_STATIC_DRAW);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), ((VertexData *)0)->pos);
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(VertexData), ((VertexData *)0)->color);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}
```

いかがでしょうか。頂点座標と色情報が別々に指定できるようになって分かりやすくなりましたし、何よりVBOとVAOでのデータ・フォーマットの指定も分かりやすくなっていると思います。

## 2.1. VBOのデータ転送方法の比較

実際に見比べてみましょう。まずVBOの変更前と変更後を比較してみます。

```cpp:VBOのデータ転送の比較
// 変更前
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 7 * 3, data, GL_STATIC_DRAW);

// 変更後
glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData) * data.size(), &data[0], GL_STATIC_DRAW);
```

変更前はGLfloat型の数値が頂点座標3個＋色情報4個の7個で、さらにそれが3セット分あることを頭で計算して指定しなければいけませんでした。

しかし変更後のものでは、C++のstd::vectorを使って、データを構造体としてまとめたVertexDataを利用することで、頂点データ1つ分のサイズを「`sizeof(VertexData)`」と書くだけで計算できるようになり、それに`size()`関数を使って求めたデータの個数を掛けるだけでVBOの指定ができるようになっています。

## 2.2. VAOのフォーマット指定方法の比較

次に、VAOの変更前と変更後を比較してみます。

```cpp:VAOのフォーマット指定の比較
// 変更前
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 7, (GLfloat *)0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 7, ((GLfloat *)0)+3);

// 変更後
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), ((VertexData *)0)->pos);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(VertexData), ((VertexData *)0)->color);
```

`glVertexAtrribPointer()`関数の第5引数で、データのstride値を求めるために、変更後は「`sizeof(VertexData)`」と書くだけで良くなっているのはVBOと同じですね。

`glVertexAtrribPointer()`関数の第6引数でのオフセット値の指定方法について、オフセット値ゼロをVertexDataのポインタ型にキャストしたものを使って、矢印演算子でposとcolorそれぞれの配列にアクセスすることで、pos配列の先頭のオフセット値とcolor配列の先頭のオフセット値を適切に指定できるようになっています。これは今後、法線ベクトルやテクスチャのUV座標などを指定するなど、頂点属性の数が増えた場合に、間違いなく簡単にオフセット値を指定できるようになることを示しています。

実行結果は前回と同じです。

　<img width="420" src="https://qiita-image-store.s3.amazonaws.com/0/166309/e85d220c-965e-a259-fd00-dfe6fb08f690.png">

ここまでのプロジェクト：[MyGLGame_step2-2.zip](http://sazameki.jp/temp/MyGLGame_step2-2.zip)


# 3. まとめ

今回は、構造体とvectorを使って元データを用意することによって、より簡単にVBOとVAOの指定ができるようになることを見ました。

次回はインデックス・リストを使って、頂点データの順番を制御する方法について解説しましょう。

---
次の記事：[macOSでOpenGLプログラミング（2-3. 頂点のインデックス・リストを使って描画する）](http://qiita.com/sazameki/items/d208d8762c5cbb3b4fc9)