LoginSignup
3
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-06

手順

oculus.png

設定

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

//
// 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 をサポートするための変数

  //
  // 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 から取得する情報

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

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

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

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

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

初期化

Oculus Rift PC SDK (LibOVR) の初期化

//
// デフォルトの 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;
}
//
// LUID を比較する
//
static int Compare(const ovrGraphicsLuid& lhs, const ovrGraphicsLuid& rhs)
{
  return memcmp(&lhs, &rhs, sizeof(ovrGraphicsLuid));
}
  //
  // 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 の設定

  //
  // 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);
  }

両方の目に共通の前処理

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

  // 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) ごとの前処理

  //
  // 目 (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) ごとの後処理

  //
  // 目 (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]);
  }

両方の目に共通の後処理

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

  // 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();
  }

プログラム終了時の処理

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

  // 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;
  }
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3