手順
設定
ヘッダファイルの読み込み
//
// 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 をサポートするための変数
//
// 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 から取得する情報
//
// OpenGL による描画時に参照する情報
//
// 視野
GLfloat fov[eyeCount][4];
// 左右の目の頭の位置からの変位
GLfloat offset[eyeCount][3];
// 頭の位置
GLfloat position[eyeCount][3];
// 頭の向き (四元数)
GLfloat orientation[eyeCount][4];
初期化
Oculus Rift PC SDK (LibOVR) の初期化
//
// 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 の設定
//
// 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);
}
}
両方の目に共通の前処理
//
// 両方の目に共通の前処理
//
// 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) ごとの前処理
//
// 目 (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) ごとの後処理
//
// 目 (eye) ごとの後処理
//
// Oculus Rift 使用時
if (session)
{
// 特になし
}
両方の目に共通の後処理
//
// 両方の目に共通の後処理
//
// 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();
}
プログラム終了時の処理
//
// プログラム終了時の処理
//
// 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;
}