macOSでOpenGLプログラミング（3-7. ビュー行列でカメラを動かす）

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

# はじめに

[前回](https://qiita.com/sazameki/items/91401f33f72600b5bed5)は、GLKitの関数を使ってパースペクティブ行列を作り、それに合わせてOpenGLの右手座標系を導入して描画を行う方法を説明しました。

今回は、ビュー行列を使って、カメラを回転・移動させる方法について解説しましょう。

# 1. 描画用データを用意する

カメラを回転・移動させた時に、それが分かりやすくなるように、いろんなデータをカメラの周囲に用意しておきましょう。これまでは頂点データを格納するvectorをGameクラスのコンストラクタだけで使っていましたが、描画データの個数をレンダリング時に確認しやすいように、頂点データのvectorをGameクラスのインスタンス変数として宣言するように変更しましょう。

```cpp:Game.hpp（一部）
class Game
{
private:
    /* 省略 */
  
    std::vector<VertexData> data;
};
```

頂点データは、次のように、中央に3つの三角ポリゴン、地面を表す2つの三角ポリゴン、地面の四隅に4つの三角ポリゴンを配置します。

```cpp:Game.cpp（一部）
Game::Game()
{
    /* 省略 */

    // キャラクター3体
    data.push_back({ {  0.2f, -1.0f, -2.0f }, { 0.0f, 0.75f, 1.0f, 1.0f } });
    data.push_back({ {  1.2f, -1.0f, -2.0f }, { 0.0f, 0.75f, 1.0f, 1.0f } });
    data.push_back({ {  1.2f,  0.0f, -2.0f }, { 0.0f, 0.75f, 1.0f, 1.0f } });

    data.push_back({ {  -0.5f, -1.0f, -1.0f }, { 1.0f, 0.4f, 0.7f, 1.0f } });
    data.push_back({ {  0.7f, -1.0f, -1.0f }, { 1.0f, 0.4f, 0.7f, 1.0f } });
    data.push_back({ {  0.7f,  0.0f, -1.0f }, { 1.0f, 0.4f, 0.7f, 1.0f } });

    data.push_back({ { -0.5f, -1.0f, 3.0f }, { 0.6f, 0.2f, 1.0f, 1.0f } });
    data.push_back({ {  0.5f, -1.0f, 3.0f }, { 0.6f, 0.2f, 1.0f, 1.0f } });
    data.push_back({ {  0.0f,  0.0f, 3.0f }, { 0.6f, 0.2f, 1.0f, 1.0f } });

    // 四隅のコーン
    data.push_back({ { -2.8f, -1.0f, -5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { -3.2f, -1.0f, -5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { -3.0f,  1.0f, -5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });

    data.push_back({ { 2.8f, -1.0f, -5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { 3.2f, -1.0f, -5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { 3.0f,  1.0f, -5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });

    data.push_back({ { -2.8f, -1.0f, 5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { -3.2f, -1.0f, 5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { -3.0f,  1.0f, 5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });

    data.push_back({ { 2.8f, -1.0f, 5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { 3.2f, -1.0f, 5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });
    data.push_back({ { 3.0f,  1.0f, 5.0f }, { 0.0f, 1.0f, 0.2f, 1.0f } });

    // 地面
    data.push_back({ {  3.0f, -1.0f,  5.0f }, { 0.0f, 0.0f, 0.7f, 1.0f } });
    data.push_back({ {  3.0f, -1.0f, -5.0f }, { 0.0f, 0.0f, 0.7f, 1.0f } });
    data.push_back({ { -3.0f, -1.0f, -5.0f }, { 0.0f, 0.0f, 0.7f, 1.0f } });

    data.push_back({ {  3.0f, -1.0f,  5.0f }, { 0.0f, 0.0f, 0.7f, 1.0f } });
    data.push_back({ { -3.0f, -1.0f,  5.0f }, { 0.0f, 0.0f, 0.7f, 1.0f } });
    data.push_back({ { -3.0f, -1.0f, -5.0f }, { 0.0f, 0.0f, 0.7f, 1.0f } });

    // 以下続く
}
```

これらの頂点データをつなぐために、インデックス・リストをfor文を使って作るようにしましょう。

```cpp:Game.cpp（続き）
Game::Game()
{
    /* 省略 */

    std::vector<GLushort> indices;
    for (int i = 0; i < data.size(); i++) {
        indices.push_back(i);
    }

    // 以下続く
}
```

# 2. カメラの回転角度・位置を操作する

次に、カメラの回転角度を表す変数`cameraPos`と、カメラ位置を表す変数`cameraAngle`を、Gameクラスのインスタンス変数として用意します。

```cpp:Game.hpp（一部）
class Game
{
private:
    /* 省略 */

    std::vector<VertexData> data;
    GLKVector3              cameraPos;
    float                   cameraAngle;
};
```

こうして用意した変数を、Gameクラスのコンストラクタの最後で初期化しておきましょう。

```cpp:Game.cpp（一部）
Game::Game()
{
    /* 省略 */

    cameraPos = GLKVector3Make(0.0f, 0.0f, 0.0f);
    cameraAngle = 0.0f;
}
```

GameクラスのRender()関数の先頭に、キーボード入力に応じて、カメラ位置を変更するコードとカメラの回転角度を変更するコードを追加しましょう。ASDWキーでX-Z平面上をカメラが移動するようにし、左右の矢印キーでY軸を中心として回転できるようにします。

またそれと同時に、`data`配列に格納された個数分の頂点データの描画を行うように、`glDrawElements()`関数の呼び出しを修正しておきましょう。

```cpp:Game.cpp（一部）
void Game::Render()
{
    if (Input::GetKey(KeyCode::A)) {
        cameraPos.x -= 2.0f * Time::deltaTime;
    } else if (Input::GetKey(KeyCode::D)) {
        cameraPos.x += 2.0f * Time::deltaTime;
    } else if (Input::GetKey(KeyCode::W)) {
        cameraPos.z -= 2.0f * Time::deltaTime;
    } else if (Input::GetKey(KeyCode::S)) {
        cameraPos.z += 2.0f * Time::deltaTime;
    }

    if (Input::GetKey(KeyCode::LeftArrow)) {
        cameraAngle += M_PI / 4 * Time::deltaTime;
    } else if (Input::GetKey(KeyCode::RightArrow)) {
        cameraAngle -= M_PI / 4 * Time::deltaTime;
    }

    /* 省略 */

    glDrawElements(GL_TRIANGLES, (GLsizei)data.size(), GL_UNSIGNED_SHORT, (void *)0);
}
```

# 3. ビュー行列とプロジェクション行列を合成する

最後に、`cameraPos`変数で表されたカメラ位置にカメラを移動させるコードと、`cameraAngle`変数で表された回転角度だけカメラを回転させるコードを追加しましょう。

と言っても、**実はカメラを移動したり回転させたりすることはできません**。OpenGLでもDirectXでも、**カメラの位置と方向は固定されているのです**。そこで、**カメラの代わりに、その分だけ頂点データの方を回転させたり移動させたりすることで、擬似的にカメラを移動・回転したことにします**。カメラの移動と回転（実際に動かすのは頂点データの方ですが）を表す行列のことを、**ビュー行列**と呼びます。

まずはカメラの移動と回転を表す行列を作りましょう。`GLKMatrix4MakeTranslation()`関数と`GLKMatrix4MakeRotation()`関数を使います。

```cpp:Game.cpp（一部）
    GLKMatrix4 transMat = GLKMatrix4MakeTranslation(cameraPos.x, cameraPos.y, cameraPos.z);
    GLKMatrix4 rotMat = GLKMatrix4MakeRotation(cameraAngle, 0.0f, 1.0f, 0.0f);
```

次に、移動行列と回転行列を合成して、その逆行列を求めることで、カメラの代わりに頂点データを移動させるビュー行列を作成します。

```cpp:Game.cpp（一部）
    GLKMatrix4 viewMat = GLKMatrix4Multiply(transMat, rotMat);
    viewMat = GLKMatrix4Invert(viewMat, NULL);
```

最後に、プロジェクション行列とビュー行列を合成したものをuniform変数`mat`にセットすることで、カメラの移動と回転が実現できます。

```cpp:Game.cpp（一部）
    GLKMatrix4 projMat = GLKMatrix4MakePerspective(
        GLKMathDegreesToRadians(60.0f), 640.0f / 480.0f, 0.00001f, 50.0f);
    GLKMatrix4 mat = GLKMatrix4Multiply(projMat, viewMat);
    program->Use();
    program->SetUniform("mat", mat);
```

ここまで実装できたら実行して、キーボード操作でカメラの移動と回転が制御できることを確認しましょう。ASDWキーでX-Z平面上のカメラ移動、左右の矢印キーでY軸を中心としたカメラ回転です。

　　<img width="450" src="https://qiita-image-store.s3.amazonaws.com/0/166309/8d8b134c-0f3a-e324-7d6b-e68a8477ddae.png">

　　<img width="450" src="https://qiita-image-store.s3.amazonaws.com/0/166309/7054c9c4-3392-a7ab-f57c-44387d59c47e.png">

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


# 4. まとめ

今回は、カメラの移動と回転を表すビュー行列の作り方と、プロジェクション行列にそれを合成する方法について解説しました。

次回は、ビュー行列を作るために、LookAt行列と呼ばれる行列を利用することで、カメラを特定の位置に向ける方法を紹介したいと思います。

----

次の記事：[macOSでOpenGLプログラミング（3-8. LookAt()関数でビュー行列を作る）](https://qiita.com/sazameki/items/c099f434757e56d54502)