OpenGL/GLFW で Oculus Rift DK1/DK2 (PC SDK 0.8) を使う

## 手順

![oculus.png](https://qiita-image-store.s3.amazonaws.com/0/100289/df5c2cc8-e068-133d-8d5e-cc9769bfa5e8.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 DK1/DK2 をサポートするための変数

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

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

  // Oculus Rift の情報
  ovrHmdDesc hmdDesc;

  // Oculus Rift のレンダリング情報
  ovrEyeRenderDesc eyeRenderDesc[eyeCount];

  // Oculus Rift の視点情報
  ovrPosef eyePose[eyeCount];

  // Oculus Rift に転送する描画データ
  ovrLayer_Union layerData;

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

  // ミラー表示用の FBO のカラーテクスチャ
  ovrGLTexture *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
  //
  // 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 のセッションを作成する (LUID は OpenGL では使っていないらしい)
    ovrGraphicsLuid luid;
    if (OVR_FAILURE(ovr_Create(&const_cast<ovrSession>(session), &luid)))
    {
      // Oculus Rift のセッションの作成に失敗した
      NOTIFY("Oculus Rift のセッションが作成できません。");
      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 DK1/DK2 の設定

```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_EyeFovDepth;
    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.EyeFov.Fov[eye] = eyeFov;

      // Oculus Rift のレンズ補正等の設定値を取得する
      eyeRenderDesc[eye] = ovr_GetRenderDesc(session, ovrEyeType(eye), eyeFov);

      // Oculus Rift の片目の頭の位置からの変位を求める
      const auto &o(eyeRenderDesc[eye].HmdToEyeViewOffset);
      offset[eye][0] = o.x;
      offset[eye][1] = o.y;
      offset[eye][2] = o.z;

      // 片目の視野を求める
      const auto &f(eyeRenderDesc[eye].Fov);
      fov[eye][0] = -f.LeftTan;
      fov[eye][1] =  f.RightTan;
      fov[eye][2] = -f.DownTan;
      fov[eye][3] =  f.UpTan;

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

      // Oculus Rift 表示用の FBO のカラーバッファとして使うテクスチャセットを作成する
      ovrSwapTextureSet *colorTexture;
      ovr_CreateSwapTextureSetGL(session, GL_SRGB8_ALPHA8, size.w, size.h, &colorTexture);
      layerData.EyeFov.ColorTexture[eye] = colorTexture;

      // Oculus Rift 表示用の FBO のデプスバッファとして使うテクスチャセットの作成
      ovrSwapTextureSet *depthTexture;
      ovr_CreateSwapTextureSetGL(session, GL_DEPTH_COMPONENT32F, size.w, size.h, &depthTexture);
      layerData.EyeFovDepth.DepthTexture[eye] = depthTexture;
    }

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

    // TimeWarp に使う変換行列の成分
    auto &posTimewarpProjectionDesc(layerData.EyeFovDepth.ProjectionDesc);
    posTimewarpProjectionDesc.Projection22 = (mp[eyeL].get()[4 * 2 + 2] + mp[eyeL].get()[4 * 3 + 2]) * 0.5f;
    posTimewarpProjectionDesc.Projection23 = mp[eyeL].get()[4 * 2 + 3] * 0.5f;
    posTimewarpProjectionDesc.Projection32 = mp[eyeL].get()[4 * 3 + 2];

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

    // ミラー表示用の FBO を作成する
    if (OVR_SUCCESS(ovr_CreateMirrorTextureGL(session, GL_SRGB8_ALPHA8, width, height,
      reinterpret_cast<ovrTexture **>(&mirrorTexture))))
    {
      glGenFramebuffers(1, &mirrorFbo);
      glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFbo);
      glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
        mirrorTexture->OGL.TexId, 0);
      glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
      glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    }
  }
```

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

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

  // Oculus Rift 使用時
  if (session)
  {
    // フレームのタイミング計測開始
    const auto ftiming(ovr_GetPredictedDisplayTime(session, 0));

    // sensorSampleTime の取得は可能な限り ovr_GetTrackingState() の近くで行う
    layerData.EyeFov.SensorSampleTime = ovr_GetTimeInSeconds();

    // ヘッドトラッキングの状態を取得する
    const auto hmdState(ovr_GetTrackingState(session, ftiming, ovrTrue));

    // 正しい瞳孔間隔をもとに視点の姿勢を取得する
    const ovrVector3f hmdToEyeViewOffset[2] =
    {
      eyeRenderDesc[0].HmdToEyeViewOffset,
      eyeRenderDesc[1].HmdToEyeViewOffset
    };
    ovr_CalcEyePoses(hmdState.HeadPose.ThePose, hmdToEyeViewOffset, eyePose);
  }
```

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

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

  // Oculus Rift 使用時
  if (session)
  {
    // FBO のカラーバッファに使う現在のテクスチャのインデックスを取得する
    auto *const colorTexture(layerData.EyeFov.ColorTexture[eye]);
    colorTexture->CurrentIndex = (colorTexture->CurrentIndex + 1) % colorTexture->TextureCount;

    // FBO のデプスバッファに使う現在のテクスチャのインデックスを取得する
    auto *const depthTexture(layerData.EyeFovDepth.DepthTexture[eye]);
    depthTexture->CurrentIndex = (depthTexture->CurrentIndex + 1) % depthTexture->TextureCount;

    // FBO を設定する
    glBindFramebuffer(GL_FRAMEBUFFER, oculusFbo[eye]);
    const auto &ctex(reinterpret_cast<ovrGLTexture *>(&colorTexture->Textures[colorTexture->CurrentIndex]));
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ctex->OGL.TexId, 0);
    const auto &dtex(reinterpret_cast<ovrGLTexture *>(&depthTexture->Textures[depthTexture->CurrentIndex]));
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dtex->OGL.TexId, 0);

    // ビューポートを設定する
    const auto &vp(layerData.EyeFov.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(eyePose[eye].Position);
    position[eye][0] = p.x;
    position[eye][1] = p.y;
    position[eye][2] = p.z;

    // Oculus Rift のヘッドトラッキングによる片目の回転を求める
    const auto &o(eyePose[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)
  {
    // 特になし
  }
```

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

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

  // Oculus Rift 使用時
  if (session)
  {
    // Oculus Rift 上の描画位置と拡大率を求める
    ovrViewScaleDesc viewScaleDesc;
    viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
    viewScaleDesc.HmdToEyeViewOffset[0] = eyeRenderDesc[0].HmdToEyeViewOffset;
    viewScaleDesc.HmdToEyeViewOffset[1] = eyeRenderDesc[1].HmdToEyeViewOffset;

    // 描画データを更新する
    layerData.EyeFov.RenderPose[0] = eyePose[0];
    layerData.EyeFov.RenderPose[1] = eyePose[1];

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

    if (/* もしミラー表示を行うなら */)
    {
      // レンダリング結果をミラー表示用のフレームバッファにも転送する
      glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFbo);
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
      const auto w(mirrorTexture->OGL.Header.TextureSize.w);
      const auto h(mirrorTexture->OGL.Header.TextureSize.h);
      glBlitFramebuffer(0, h, w, 0, 0, 0, w, h, 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)
    {
      glDeleteTextures(1, &mirrorTexture->OGL.TexId);
      ovr_DestroyMirrorTexture(session, reinterpret_cast<ovrTexture *>(mirrorTexture));
    }

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

    // Oculus Rift 表示用の FBO を削除する
    for (int eye = 0; eye < eyeCount; ++eye)
    {
      // レンダリングターゲットに使ったテクスチャを破棄する
      auto *const colorTexture(layerData.EyeFov.ColorTexture[eye]);
      for (int i = 0; i < colorTexture->TextureCount; ++i)
      {
        const auto *const ctex(reinterpret_cast<ovrGLTexture *>(&colorTexture->Textures[i]));
        glDeleteTextures(1, &ctex->OGL.TexId);
      }
      ovr_DestroySwapTextureSet(session, colorTexture);

      // デプスバッファとして使ったテクスチャを破棄する
      auto *const depthTexture(layerData.EyeFovDepth.DepthTexture[eye]);
      for (int i = 0; i < depthTexture->TextureCount; ++i)
      {
        const auto *const dtex(reinterpret_cast<ovrGLTexture *>(&depthTexture->Textures[i]));
        glDeleteTextures(1, &dtex->OGL.TexId);
      }
      ovr_DestroySwapTextureSet(session, depthTexture);
    }

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