OpenGL/GLFW で Oculus Rift CV1 (PC SDK 1.11) を使う

## 手順

![oculus.png](https://qiita-image-store.s3.amazonaws.com/0/100289/69b42ed2-5565-053a-9356-cd6cefb9e47a.png)

## 設定

### ヘッダファイルの読み込み

```cpp
//
// Oculus Rift 関連の設定
//

// Windows API 関連の設定
#if defined(_WIN32)
#  define NOMINMAX
#  define GLFW_EXPOSE_NATIVE_WIN32
#  define GLFW_EXPOSE_NATIVE_WGL
#  define OVR_OS_WIN32
#  include <GLFW/glfw3native.h>
#  if defined(APIENTRY)
#    undef APIENTRY
#  endif
#  define NOTIFY(msg) MessageBox(NULL, TEXT(msg), TEXT("すまんのう"), MB_ICONERROR | MB_OK)
#else
#  define NOTIFY(msg) std::cerr << msg << '\n'
#endif

// Oculus Rift SDK ライブラリ (LibOVR) の組み込み
#include <OVR_CAPI_GL.h>
#include <Extras/OVR_Math.h>

// Oculus Rift の目の数と識別子
const ovrEyeType eyeCount(ovrEye_Count), eyeL(ovrEye_Left), eyeR(ovrEye_Right);
```

### Oculus Rift CV1 をサポートするための変数

```cpp
  //
  // Oculus Rift CV1 を使うための変数
  //

  // Oculus Rift のセッション
  const ovrSession session(nullptr);

  // Oculus Rift の情報
  ovrHmdDesc hmdDesc;

  // Oculus Rift にレンダリングするフレームの番号
  long long frameIndex(0);

  // Oculus Rift への描画情報
  ovrLayerEyeFov layerData;

  // Oculus Rift 表示用の FBO のデプステクスチャ
  GLuint oculusDepth[eyeCount]{ 0 };

  // Oculus Rift 表示用の FBO
  GLuint oculusFbo[eyeCount]{ 0 };

  // ミラー表示用の FBO のカラーテクスチャ
  ovrMirrorTexture mirrorTexture(nullptr);

  // ミラー表示用の FBO
  GLuint mirrorFbo(0);
```

### Oculus Rift から取得する情報

```cpp
  //
  // OpenGL による描画時に参照する情報
  //

  // 視野
  GLfloat fov[eyeCount][4];

  // 左右の目の頭の位置からの変位
  GLfloat offset[eyeCount][3];

  // 頭の位置
  GLfloat position[eyeCount][3];

  // 頭の向き (四元数)
  GLfloat orientation[eyeCount][4];
```

## 初期化

### Oculus Rift PC SDK (LibOVR) の初期化

```cpp
//
// デフォルトの LUID を取得する
//
static ovrGraphicsLuid GetDefaultAdapterLuid()
{
  ovrGraphicsLuid luid = ovrGraphicsLuid();

#if defined(_WIN32)
  IDXGIFactory* factory = nullptr;

  if (SUCCEEDED(CreateDXGIFactory(IID_PPV_ARGS(&factory))))
  {
    IDXGIAdapter* adapter = nullptr;

    if (SUCCEEDED(factory->EnumAdapters(0, &adapter)))
    {
      DXGI_ADAPTER_DESC desc;

      adapter->GetDesc(&desc);
      memcpy(&luid, &desc.AdapterLuid, sizeof(luid));
      adapter->Release();
    }

    factory->Release();
  }
#endif

  return luid;
}
```

```cpp
//
// LUID を比較する
//
static int Compare(const ovrGraphicsLuid& lhs, const ovrGraphicsLuid& rhs)
{
  return memcmp(&lhs, &rhs, sizeof(ovrGraphicsLuid));
}
```

```cpp
  //
  // Oculus Rift C SDK (LibOVR) の初期化
  //

  // 最初のインスタンスのときだけ true
  static bool firstTime(true);

  // 一度だけ実行
  if (firstTime)
  {
    // 実行済みの印をつける
    firstTime = false;

    // GLFW を初期化する
    if (glfwInit() == GL_FALSE)
    {
      // GLFW の初期化に失敗した
      NOTIFY("GLFW の初期化に失敗しました。");
      return;
    }

    // プログラム終了時には GLFW を終了する
    atexit(glfwTerminate);

    // Oculus Rift (LibOVR) を初期化する
    ovrInitParams initParams = { ovrInit_RequestVersion, OVR_MINOR_VERSION, NULL, 0, 0 };
    if (OVR_FAILURE(ovr_Initialize(&initParams)))
    {
      // LibOVR の初期化に失敗した
      NOTIFY("LibOVR が初期化できません。");
      return;
    }

    // プログラム終了時には LibOVR を終了する
    atexit(ovr_Shutdown);

    // Oculus Rift のセッションを作成する
    ovrGraphicsLuid luid;
    if (OVR_FAILURE(ovr_Create(&const_cast<ovrSession>(session), &luid)))
    {
      // Oculus Rift のセッションの作成に失敗した
      NOTIFY("Oculus Rift のセッションが作成できません。");
      return;
    }

    // デフォルトのグラフィックスアダプタが使われているか確かめる
    if (Compare(luid, GetDefaultAdapterLuid()))
    {
      // デフォルトのグラフィックスアダプタが使用されていない
      NOTIFY("OpenGL ではデフォルトのグラフィックスアダプタ以外使用できません。");
      return;
    }

    // Oculus ではダブルバッファモードにしない
    glfwWindowHint(GLFW_DOUBLEBUFFER, GL_FALSE);

    // SRGB モードでレンダリングする
    glfwWindowHint(GLFW_SRGB_CAPABLE, GL_TRUE);
  }

  // GLFW のウィンドウを開く
  GLFWwindow *const window(glfwCreateWindow(width, height, title, monitor, share));
  if (!window)
  {
    // GLFW のウィンドウが開けなかった
    NOTIFY("GLFW のウィンドウが開けません。");
    return;
  }

  //
  // OpenGL の設定
  //

  // sRGB カラースペースを使う
  glEnable(GL_FRAMEBUFFER_SRGB);

  // 表示確認用に背景色を付けておく
  glClearColor(0.2f, 0.3f, 0.4f, 0.0f);
  //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
```

### Oculus Rift CV1 の設定

```cpp
  //
  // Oculus Rift の設定
  //

  // Oculus Rift に表示するとき
  if (session)
  {
    // Oculus Rift の情報を取り出す
    hmdDesc = ovr_GetHmdDesc(session);

#  if defined(_DEBUG)
    // Oculus Rift の情報を表示する
    std::cout
      << "\nProduct name: " << hmdDesc.ProductName
      << "\nResolution:   " << hmdDesc.Resolution.w << " x " << hmdDesc.Resolution.h
      << "\nDefault Fov:  (" << hmdDesc.DefaultEyeFov[ovrEye_Left].LeftTan
      << "," << hmdDesc.DefaultEyeFov[ovrEye_Left].DownTan
      << ") - (" << hmdDesc.DefaultEyeFov[ovrEye_Left].RightTan
      << "," << hmdDesc.DefaultEyeFov[ovrEye_Left].UpTan
      << ")\n              (" << hmdDesc.DefaultEyeFov[ovrEye_Right].LeftTan
      << "," << hmdDesc.DefaultEyeFov[ovrEye_Right].DownTan
      << ") - (" << hmdDesc.DefaultEyeFov[ovrEye_Right].RightTan
      << "," << hmdDesc.DefaultEyeFov[ovrEye_Right].UpTan
      << ")\nMaximum Fov:  (" << hmdDesc.MaxEyeFov[ovrEye_Left].LeftTan
      << "," << hmdDesc.MaxEyeFov[ovrEye_Left].DownTan
      << ") - (" << hmdDesc.MaxEyeFov[ovrEye_Left].RightTan
      << "," << hmdDesc.MaxEyeFov[ovrEye_Left].UpTan
      << ")\n              (" << hmdDesc.MaxEyeFov[ovrEye_Right].LeftTan
      << "," << hmdDesc.MaxEyeFov[ovrEye_Right].DownTan
      << ") - (" << hmdDesc.MaxEyeFov[ovrEye_Right].RightTan
      << "," << hmdDesc.MaxEyeFov[ovrEye_Right].UpTan
      << ")\n" << std::endl;
#  endif

    // Oculus Rift に転送する描画データを作成する
    layerData.Header.Type = ovrLayerType_EyeFov;
    layerData.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft;   // OpenGL なので左下が原点

    // Oculus Rift 表示用の FBO を作成する
    for (int eye = 0; eye < eyeCount; ++eye)
    {
      // Oculus Rift の視野を取得する
      const auto &eyeFov(hmdDesc.DefaultEyeFov[eye]);
      layerData.Fov[eye] = eyeFov;

      // 片目の視野を求める
      fov[eye][0] = -eyeFov.LeftTan;
      fov[eye][1] =  eyeFov.RightTan;
      fov[eye][2] = -eyeFov.DownTan;
      fov[eye][3] =  eyeFov.UpTan;

      // Oculus Rift 表示用の FBO のサイズ
      const auto size(ovr_GetFovTextureSize(session, ovrEyeType(eye), eyeFov, 1.0f));
      layerData.Viewport[eye].Pos = OVR::Vector2i(0, 0);
      layerData.Viewport[eye].Size = size;

      // Oculus Rift 表示用の FBO のカラーバッファとして使うテクスチャセットの特性
      const ovrTextureSwapChainDesc colorDesc =
      {
        ovrTexture_2D,                    // Type
        OVR_FORMAT_R8G8B8A8_UNORM_SRGB,   // Format
        1,                                // ArraySize
        size.w,                           // Width
        size.h,                           // Height
        1,                                // MipLevels
        1,                                // SampleCount
        ovrFalse,                         // StaticImage
        0, 0
      };

      // Oculus Rift 表示用の FBO のバッファとして使うテクスチャセットを作成する
      layerData.ColorTexture[eye] = nullptr;
      if (OVR_SUCCESS(ovr_CreateTextureSwapChainGL(session, &colorDesc, &layerData.ColorTexture[eye])))
      {
        // Oculus Rift 表示用の FBO のカラーバッファとして使うテクスチャセットを作成する
        int length(0);
        if (OVR_SUCCESS(ovr_GetTextureSwapChainLength(session, layerData.ColorTexture[eye], &length)))
        {
          for (int i = 0; i < length; ++i)
          {
            GLuint texId;
            ovr_GetTextureSwapChainBufferGL(session, layerData.ColorTexture[eye], i, &texId);
            glBindTexture(GL_TEXTURE_2D, texId);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
          }
        }

        // Oculus Rift 表示用の FBO のデプスバッファとして使うテクスチャセットを作成する
        glGenTextures(1, &oculusDepth[eye]);
        glBindTexture(GL_TEXTURE_2D, oculusDepth[eye]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, size.w, size.h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
      }
    }

    // Oculus Rift の画面のアスペクト比を求める
    aspect = static_cast<GLfloat>(layerData.Viewport[eyeL].Size.w)
      / static_cast<GLfloat>(layerData.Viewport[eyeL].Size.h);

    // Oculus Rift のレンダリング用の FBO を作成する
    glGenFramebuffers(eyeCount, oculusFbo);

    // ミラー表示用の FBO のカラーバッファとして使うテクスチャセットの特性
    const ovrMirrorTextureDesc mirrorDesc =
    {
      OVR_FORMAT_R8G8B8A8_UNORM_SRGB,   // Format
      width,                            // Width
      height,                           // Height
      0
    };

    // ミラー表示用の FBO を作成する
    if (OVR_SUCCESS(ovr_CreateMirrorTextureGL(session, &mirrorDesc, &mirrorTexture)))
    {
      // ミラー表示用の FBO のカラーバッファとして使うテクスチャを作成する
      GLuint texId;
      if (OVR_SUCCESS(ovr_GetMirrorTextureBufferGL(session, mirrorTexture, &texId)))
      {
        glGenFramebuffers(1, &mirrorFbo);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFbo);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texId, 0);
        glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
      }
    }

    // 姿勢のトラッキングにおける床の高さを 0 に設定する
    ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel);
  }
```

## 両方の目に共通の前処理

```cpp
  //
  // 両方の目に共通の前処理
  //

  // Oculus Rift 使用時
  if (session)
  {
    // セッションの状態を取得する
    ovrSessionStatus sessionStatus;
    ovr_GetSessionStatus(session, &sessionStatus);

    // アプリケーションが終了を要求しているときはウィンドウのクローズフラグを立てる
    if (sessionStatus.ShouldQuit) glfwSetWindowShouldClose(window, GL_TRUE);

    // 現在の状態をトラッキングの原点にする
    if (sessionStatus.ShouldRecenter) ovr_RecenterTrackingOrigin(session);

    // Oculus Rift に表示されていないときは戻る
    if (!sessionStatus.IsVisible) return false;

    // HmdToEyeOffset などは実行時に変化するので毎フレーム ovr_GetRenderDesc() で ovrEyeRenderDesc を取得する
    const ovrEyeRenderDesc eyeRenderDesc[] =
    {
      ovr_GetRenderDesc(session, eyeL, hmdDesc.DefaultEyeFov[0]),
      ovr_GetRenderDesc(session, eyeR, hmdDesc.DefaultEyeFov[1])
    };

    // 正しい瞳孔間隔をもとに視点の姿勢を取得する
    const ovrVector3f hmdToEyeOffset[2] =
    {
      eyeRenderDesc[0].HmdToEyeOffset,
      eyeRenderDesc[1].HmdToEyeOffset
    };

    // Oculus Rift の両目の頭の位置からの変位を求める
    mv[0] = ggTranslate(-hmdToEyeOffset[0].x, -hmdToEyeOffset[0].y, -hmdToEyeOffset[0].z);
    mv[1] = ggTranslate(-hmdToEyeOffset[1].x, -hmdToEyeOffset[1].y, -hmdToEyeOffset[1].z);

    // 視点の姿勢情報を取得する
    ovr_GetEyePoses(session, frameIndex, ovrTrue, hmdToEyeOffset, layerData.RenderPose, &layerData.SensorSampleTime);
  }

  // OpenGL によるレンダリングは true のときのみ行う
  return true;
```

## 目 (eye) ごとの前処理

```cpp
  //
  // 目 (eye) ごとの前処理
  //

  // Oculus Rift 使用時
  if (session)
  {
    // Oculus Rift にレンダリングする FBO に切り替える
    if (layerData.ColorTexture[eye])
    {
      // FBO のカラーバッファに使う現在のテクスチャのインデックスを取得する
      int curIndex;
      ovr_GetTextureSwapChainCurrentIndex(session, layerData.ColorTexture[eye], &curIndex);

      // FBO のカラーバッファに使うテクスチャを取得する
      GLuint curTexId;
      ovr_GetTextureSwapChainBufferGL(session, layerData.ColorTexture[eye], curIndex, &curTexId);

      // FBO を設定する
      glBindFramebuffer(GL_FRAMEBUFFER, oculusFbo[eye]);
      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, curTexId, 0);
      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, oculusDepth[eye], 0);

      // ビューポートを設定する
      const auto &vp(layerData.Viewport[eye]);
      glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h);

      // FBO をクリアする
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    // Oculus Rift のヘッドトラッキングによる片目の位置を求める
    const auto &p(layerData.RenderPose[eye].Position);
    position[eye][0] = p.x;
    position[eye][1] = p.y;
    position[eye][2] = p.z;

    // Oculus Rift のヘッドトラッキングによる片目の回転を求める
    const auto &o(layerData.RenderPose[eye].Orientation);
    orientation[eye][0] = o.x;
    orientation[eye][1] = o.y;
    orientation[eye][2] = o.z;
    orientation[eye][3] = o.w;
  }
```

## 目 (eye) ごとの後処理

```cpp
  //
  // 目 (eye) ごとの後処理
  //

  // Oculus Rift 使用時
  if (session)
  {
    // GL_COLOR_ATTACHMENT0 に割り当てられたテクスチャが wglDXUnlockObjectsNV() によって
    // アンロックされるために次のフレームの処理において無効な GL_COLOR_ATTACHMENT0 が
    // FBO に結合されるのを避けるために行う (もしかしたら今は不要なのかもしれない)
    glBindFramebuffer(GL_FRAMEBUFFER, oculusFbo[eye]);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);

    // 保留中の変更を layerData.ColorTexture[eye] に反映しインデックスを更新する
    ovr_CommitTextureSwapChain(session, layerData.ColorTexture[eye]);
  }
```

## 両方の目に共通の後処理

```cpp
  //
  // 両方の目に共通の後処理
  //

  // Oculus Rift 使用時
  if (session)
  {
    // 描画データを Oculus Rift に転送する
    const auto *const layers(&layerData.Header);
    if (OVR_FAILURE(ovr_SubmitFrame(session, frameIndex++, nullptr, &layers, 1)))
    {
      // 転送に失敗したら Oculus Rift の設定を最初からやり直す必要があるらしい
      // けどめんどくさいのでしない
    }

    if (/* もしミラー表示を行うなら */)
    {
      // レンダリング結果をミラー表示用のフレームバッファにも転送する
      glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFbo);
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
      glBlitFramebuffer(0, height, width, 0, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
      glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    }

    // 残っている OpenGL コマンドを実行する
    glFlush();
  }
```

## プログラム終了時の処理

```cpp
  //
  // プログラム終了時の処理
  //

  // Oculus Rift 使用時
  if (session)
  {
    // ミラー表示用の FBO を削除する
    if (mirrorFbo) glDeleteFramebuffers(1, &mirrorFbo);

    // ミラー表示に使ったテクスチャを破棄する
    if (mirrorTexture) ovr_DestroyMirrorTexture(session, mirrorTexture);

    // Oculus Rift のレンダリング用の FBO を削除する
    glDeleteFramebuffers(eyeCount, oculusFbo);

    // Oculus Rift 表示用の FBO を削除する
    for (int eye = 0; eye < eyeCount; ++eye)
    {
      // レンダリングターゲットに使ったテクスチャを破棄する
      if (layerData.ColorTexture[eye])
      {
        ovr_DestroyTextureSwapChain(session, layerData.ColorTexture[eye]);
        layerData.ColorTexture[eye] = nullptr;
      }

      // デプスバッファとして使ったテクスチャを破棄する
      glDeleteTextures(1, &oculusDepth[eye]);
      oculusDepth[eye] = 0;
    }

    // Oculus Rift のセッションを破棄する
    ovr_Destroy(session);
    const_cast<ovrSession>(session) = nullptr;
  }
```