手順
設定
ヘッダファイルの読み込み
//
// 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;
}