
ovr_WaitToBeginFrame、ovr_BeginFrame、およびovr_EndFrameを呼び出してコンポジターに送信できます。このセクションでは、このプロセスの詳細について詳しく説明します。
ovrEyeRenderDesc構造体のHmdToEyePoseメンバーはovrPosef 6-DOF変換です。つまり、傾角ディスプレイ(ディスプレイが互いに平行ではない)のHMDでは、レンダリング中のカメラは回転し、HMDのポーズから変換されます。このようにして、ユーザーの前方向はHMDのポーズのZ軸で規定され、アイポーズは別の向きを示すZ軸になります。
ovrEyeRenderDesc::HmdToEyePoseで指定されるアイポーズと同じ方法で向きが設定されます。この2つのカメラの距離は、目の間の距離、つまり瞳孔間距離(IPD)と同じになります。ovrSessionオブジェクトをヘッドセット用に作成します。ovrHmdDescデータに基づき、求められるFOVとテクスチャーサイズを計算します。ovrTextureSwapChainオブジェクトをAPI固有の方法で割り当てます。Direct3D 11、12ではovr_CreateTextureSwapChainDXを、OpenGLではovr_CreateTextureSwapChainGLを、Vulkanではovr_CreateTextureSwapChainVkを呼び出します。VK_PRESENT_MODE_IMMEDIATE_KHR、nVidia用にVK_PRESENT_MODE_MAILBOX_KHRを使用します。これにより、次のフレームのレンダリングの前にループがメインモニターのvsyncを待機するため、遅延やパフォーマンス低下を招くという問題を防ぐことができます。ovr_GetTrackingStateとovr_CalcEyePosesを使用して、フレームタイミング情報に基づいてビューをレンダリングするためのアイポーズを計算します。ovr_GetTextureSwapChainCurrentIndex、ovr_GetTextureSwapChainBufferDX、ovr_GetTextureSwapChainBufferGL、またはovr_GetTextureSwapChainBufferVkを使用して取得できます。テクスチャーへのレンダリングが完了すると、アプリでovr_CommitTextureSwapChainを呼び出す必要があります。ovr_WaitToBeginFrameを呼び出し、アプリでのフレームのレンダリングが可能になったら、ovr_BeginFrameを呼び出します。フレーム送信の準備が整ったら、ovr_EndFrameを呼び出し、ovrLayerEyeFov構造体で前の手順のスワップテクスチャーセットを渡します。フレームの送信に単一レイヤーが必要ですが、複数のレイヤーをとレイヤータイプを使用して高度なレンダリングを行えます。ovr_EndFrameはレイヤーテクスチャーをコンポジターに渡し、そこで歪み、タイムワープ、GPU同期化などを処理してから、ヘッドセットに表示します。ovr_WaitToBeginFrame、ovr_BeginFrame、ovr_EndFrameを組み合わせると、複数スレッド環境でパフォーマンス最適化手法を導入することができます。例えば、複数処理中のフレームを同時に分割し、重複させるなどです。前のリリースでは、これらの3つの関数がovr_SubmitFrameに組み込まれていましたが、その呼び出しは現在廃止されています。代わりにovr_WaitToBeginFrame、ovr_BeginFrame、ovr_EndFrameを使用してください。ovr_DestroyTextureSwapChainを呼び出してスワップテクスチャーバッファを破棄します。ovr_DestroyMirrorTextureを呼び出してミラーテクスチャーを破棄します。ovrSessionオブジェクトを破棄するには、ovr_Destroyを呼び出します。ovrTextureSwapChainを割り当てます。次のコードは、必要なテクスチャーサイズを計算する方法を示しています。// Configure Stereo settings. Sizei recommenedTex0Size = ovr_GetFovTextureSize(session, ovrEye_Left, session->DefaultEyeFov[0], 1.0f); Sizei recommenedTex1Size = ovr_GetFovTextureSize(session, ovrEye_Right, session->DefaultEyeFov[1], 1.0f); Sizei bufferSize; bufferSize.w = recommenedTex0Size.w + recommenedTex1Size.w; bufferSize.h = max ( recommenedTex0Size.h, recommenedTex1Size.h );
session->DefaultEyeFovから取得した)推奨のFOVを使用します。関数ovr_GetFovTextureSizeは、これらのパラメーターに基づき、左右の目で求められるテクスチャーサイズを計算します。ovr_GetSessionPhysicalDeviceVkを呼び出し、LUIDとマッチする現在の物理デバイスを取得します。次に、返された物理デバイスと関連するVkDeviceを作成します。Win32_VulkanAppUtil.hのものであり、Oculus SDKに同梱されるOculusRoomTiny_Advancedサンプルアプリに付属するものです。static const uint32_t AMDVendorId = 0x1002;
isAMD = (gpuProps.vendorID == AMDVendorId);
static const char* deviceExtensions[] =
{
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_KHX_EXTERNAL_MEMORY_EXTENSION_NAME,
#if defined(VK_USE_PLATFORM_WIN32_KHR)
VK_KHX_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME,
#endif
};
static const char* deviceExtensionsAMD[] =
{
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
ovr_SetSynchonizationQueueVkを呼び出し、再生リストを特定します。ovr_CreateTextureSwapChainGL、ovr_CreateTextureSwapChainDX、またはovr_CreateTextureSwapChainVkを呼び出し、テクスチャースワップチェーンをAPI固有の方法で割り当てることができます。ovrTextureSwapChain textureSwapChain = 0;
ovrTextureSwapChainDesc desc = {};
desc.Type = ovrTexture_2D;
desc.ArraySize = 1;
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
desc.Width = bufferSize.w;
desc.Height = bufferSize.h;
desc.MipLevels = 1;
desc.SampleCount = 1;
desc.StaticImage = ovrFalse;
if (ovr_CreateTextureSwapChainGL(session, &desc, &textureSwapChain) == ovrSuccess)
{
// Sample texture access:
int texId;
ovr_GetTextureSwapChainBufferGL(session, textureSwapChain, 0, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
...
}
ovrTextureSwapChain textureSwapChain = 0;
std::vector<ID3D11RenderTargetView*> texRtv;
ovrTextureSwapChainDesc desc = {};
desc.Type = ovrTexture_2D;
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
desc.ArraySize = 1;
desc.Width = bufferSize.w;
desc.Height = bufferSize.h;
desc.MipLevels = 1;
desc.SampleCount = 1;
desc.StaticImage = ovrFalse;
desc.MiscFlags = ovrTextureMisc_None;
desc.BindFlags = ovrTextureBind_DX_RenderTarget;
if (ovr_CreateTextureSwapChainDX(session, DIRECTX.Device, &desc, &textureSwapChain) == ovrSuccess)
{
int count = 0;
ovr_GetTextureSwapChainLength(session, textureSwapChain, &count);
texRtv.resize(textureCount);
for (int i = 0; i < count; ++i)
{
ID3D11Texture2D* texture = nullptr;
ovr_GetTextureSwapChainBufferDX(session, textureSwapChain, i, IID_PPV_ARGS(&texture));
DIRECTX.Device->CreateRenderTargetView(texture, nullptr, &texRtv[i]);
texture->Release();
}
}
ovrTextureSwapChain TexChain;
std::vector<D3D12_CPU_DESCRIPTOR_HANDLE> texRtv;
std::vector<ID3D12Resource*> TexResource;
ovrTextureSwapChainDesc desc = {};
desc.Type = ovrTexture_2D;
desc.ArraySize = 1;
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
desc.Width = sizeW;
desc.Height = sizeH;
desc.MipLevels = 1;
desc.SampleCount = 1;
desc.MiscFlags = ovrTextureMisc_DX_Typeless;
desc.StaticImage = ovrFalse;
desc.BindFlags = ovrTextureBind_DX_RenderTarget;
// DIRECTX.CommandQueue is the ID3D12CommandQueue used to render the eye textures by the app
ovrResult result = ovr_CreateTextureSwapChainDX(session, DIRECTX.CommandQueue, &desc, &TexChain);
if (!OVR_SUCCESS(result))
return false;
int textureCount = 0;
ovr_GetTextureSwapChainLength(Session, TexChain, &textureCount);
texRtv.resize(textureCount);
TexResource.resize(textureCount);
for (int i = 0; i < textureCount; ++i)
{
result = ovr_GetTextureSwapChainBufferDX(Session, TexChain, i, IID_PPV_ARGS(&TexResource[i]));
if (!OVR_SUCCESS(result))
return false;
D3D12_RENDER_TARGET_VIEW_DESC rtvd = {};
rtvd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
rtvd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
texRtv[i] = DIRECTX.RtvHandleProvider.AllocCpuHandle(); // Gives new D3D12_CPU_DESCRIPTOR_HANDLE
DIRECTX.Device->CreateRenderTargetView(TexResource[i], &rtvd, texRtv[i]);
}
注: Direct3D 12では、ovr_CreateTextureSwapChainDXを呼び出すと、呼び出し元からID3D12DeviceではなくID3D12CommandQueueがSDKに提供されます。呼び出し元は、すべてのVRアイテクスチャーのレンダリングが、必ずこのID3D12CommandQueueインスタンスで実行されるようにしなければなりません。あるいは、「結合ノード」フェンスとして使用し、VRアイテクスチャーのレンダリングを再生リストに含めるほかのコマンドの実行を待機することもできます。
bool Create(ovrSession aSession, VkExtent2D aSize, RenderPass& renderPass, DepthBuffer& depthBuffer)
{
session = aSession;
size = aSize;
ovrTextureSwapChainDesc desc = {};
desc.Type = ovrTexture_2D;
desc.ArraySize = 1;
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
desc.Width = (int)size.width;
desc.Height = (int)size.height;
desc.MipLevels = 1;
desc.SampleCount = 1;
desc.MiscFlags = ovrTextureMisc_DX_Typeless;
desc.BindFlags = ovrTextureBind_DX_RenderTarget;
desc.StaticImage = ovrFalse;
ovrResult result = ovr_CreateTextureSwapChainVk(session, Platform.device, &desc, &textureChain);
if (!OVR_SUCCESS(result))
return false;
int textureCount = 0;
ovr_GetTextureSwapChainLength(session, textureChain, &textureCount);
texElements.reserve(textureCount);
for (int i = 0; i < textureCount; ++i)
{
VkImage image;
result = ovr_GetTextureSwapChainBufferVk(session, textureChain, i, &image);
texElements.emplace_back(RenderTexture());
CHECK(texElements.back().Create(image, VK_FORMAT_R8G8B8A8_SRGB, size, renderPass, depthBuffer.view));
}
return true;
}
OVR_FORMAT_R8G8B8A8_UNORM_SRGBのように正しくマーキングします。これは、スタティックテクスチャーをクワッドレイヤーテクスチャーとしてコンポジターに提供するアプリでも推奨されます。そうしない場合、テクスチャーが想定よりもかなり明るく表示されます。ovr_CreateTextureSwapChainDXの記述に示すテクスチャー形式は、テクスチャーのコンテンツを読み取るときにShaderResourceViewのディストーションコンポジターに使用されます。そのため、アプリではsRGB空間(OVR_FORMAT_R8G8B8A8_UNORM_SRGBなど)にあるテクスチャースワップチェーンの形式をリクエストする必要があります。OVR_FORMAT_R8G8B8A8_UNORMなど)にレンダリングし、HLSLコードを使用してリニアからガンマへのコンバージョンを実行するようアプリが構成されている場合や、ガンマ補正を考慮しない場合は次のように処理します。OVR_FORMAT_R8G8B8A8_UNORM_SRGBなど)のテクスチャースワップチェーンをリクエストする。ovrTextureMisc_DX_Typelessフラグを指定する。DXGI_FORMAT_R8G8B8A8_UNORMなど)を作成する。注: 深度バッファ形式(OVR_FORMAT_D32など)のovrTextureMisc_DX_Typelessフラグは常に型がないものとして変換されるため無視されます。
ovrTextureMisc_DX_Typelessフラグの使用方法を示しています。ovrTextureSwapChainDesc desc = {};
desc.Type = ovrTexture_2D;
desc.ArraySize = 1;
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
desc.Width = sizeW;
desc.Height = sizeH;
desc.MipLevels = 1;
desc.SampleCount = 1;
desc.MiscFlags = ovrTextureMisc_DX_Typeless;
desc.BindFlags = ovrTextureBind_DX_RenderTarget;
desc.StaticImage = ovrFalse;
ovrResult result = ovr_CreateTextureSwapChainDX(session, DIRECTX.Device, &desc, &textureSwapChain);
if(!OVR_SUCCESS(result))
return;
int count = 0;
ovr_GetTextureSwapChainLength(session, textureSwapChain, &count);
for (int i = 0; i < count; ++i)
{
ID3D11Texture2D* texture = nullptr;
ovr_GetTextureSwapChainBufferDX(session, textureSwapChain, i, IID_PPV_ARGS(&texture));
D3D11_RENDER_TARGET_VIEW_DESC rtvd = {};
rtvd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
rtvd.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
DIRECTX.Device->CreateRenderTargetView(texture, &rtvd, &texRtv[i]);
texture->Release();
}
ofovr_CreateTextureSwapChainGLのフォーマットパラメーターは、テクスチャーのコンテンツを読み取るときにディストーションコンポジターに使用されます。そのため、アプリでは、できればsRGB空間(OVR_FORMAT_R8G8B8A8_UNORM_SRGBなど)にあるテクスチャースワップチェーンの形式をリクエストする必要があります。また、これらのテクスチャーをレンダリングする前にアプリでglEnable(GL_FRAMEBUFFER_SRGB)を呼び出す必要があります。OVR_FORMAT_R8G8B8A8_UNORM_SRGBなど)のテクスチャースワップチェーンをリクエストする。GL_FRAMEBUFFER_SRGB)を呼び出さない。ovr_CreateTextureSwapChainVkのフォーマットパラメーターが、テクスチャーのコンテンツを読み取るときにディストーションコンポジターに使用されます。コンポジターでsRGB補正レンダリングが行われるため、アプリではsRGB空間(OVR_FORMAT_R8G8B8A8_UNORM_SRGBなど)でテクスチャースワップチェーン形式をリクエストする必要があります。コンポジターはsRGBからリニアへのコンバージョンにGPUのハードウェアサンプラーを利用します。OVR_FORMAT_R8G8B8A8_UNORMなど)へのレンダリングが優先し、SPIRVコードを使ってリニアからガンマへのコンバージョンが処理される場合も、対応するsRGB形式を要求し、ovrTextureSwapChainDescのフラグフィールドでovrTextureMisc_DX_Typelessを使用する必要があります。これによってRenderTargetViewをリニア形式で作成でき、コンポジターでsRGBとして処理することができます。そうしない場合、予期しないガンマ曲線アーティファクトが生じます。深度バッファ形式(OVR_FORMAT_D32_FLOATなど)のovrTextureMisc_DX_Typelessフラグは常に型がないものとして変換されるため無視されます。ovr_CreateMirrorTextureDX、ovr_CreateMirrorTextureGL、ovr_CreateMirrorTextureWithOptionsVkの関数ドキュメントを参照してください。ovr_WaitToBeginFrame、ovr_BeginFrame、およびovr_EndFrameを介してアイテクスチャーをコンポジターに送信します。フレームが送信されると、コンポジターはそれをヘッドセットに表示する前にディストーション、タイムワープ、GPU同期を処理します。// Initialize VR structures, filling out description. ovrEyeRenderDesc eyeRenderDesc[2]; ovrPosef hmdToEyeViewPose[2]; ovrHmdDesc hmdDesc = ovr_GetHmdDesc(session); eyeRenderDesc[0] = ovr_GetRenderDesc(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0]); eyeRenderDesc[1] = ovr_GetRenderDesc(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1]); hmdToEyeViewPose[0] = eyeRenderDesc[0].HmdToEyePose; hmdToEyeViewPose[1] = eyeRenderDesc[1].HmdToEyePose; // Initialize our single full screen Fov layer. ovrLayerEyeFov layer; layer.Header.Type = ovrLayerType_EyeFov; layer.Header.Flags = 0; layer.ColorTexture[0] = textureSwapChain; layer.ColorTexture[1] = textureSwapChain; layer.Fov[0] = eyeRenderDesc[0].Fov; layer.Fov[1] = eyeRenderDesc[1].Fov; layer.Viewport[0] = Recti(0, 0, bufferSize.w / 2, bufferSize.h); layer.Viewport[1] = Recti(bufferSize.w / 2, 0, bufferSize.w / 2, bufferSize.h); // ld.RenderPose and ld.SensorSampleTime are updated later per frame.
ovrEyeRenderDesc構造には、左右の目のHmdToEyePoseをはじめ、レンダリングに役立つ値が含まれています。アイビューのオフセットは後で、目の間隔の調整に使用されます。ovrLayerEyeFov構造の初期化も行います。Oculus SDK 0.6以降、フレーム送信ではレイヤーを使用して、複数のビューイメージまたはテクスチャークワッドを重ね合わせて作成できます。この例では、1つのレイヤーを使用してVRシーンを表します。そのために、視野全体に及ぶデュアルアイレイヤーを記述するovrLayerEyeFovを使用します。両目に同じテクスチャーセットを使用しているため、両目のカラーテクスチャーをpTextureSetに初期化し、この共有テクスチャーの左側と右側に描画するための視点をそれぞれ構成します。// Get both eye poses simultaneously, with IPD offset already included. double displayMidpointSeconds = ovr_GetPredictedDisplayTime(session, 0); ovrTrackingState hmdState = ovr_GetTrackingState(session, displayMidpointSeconds, ovrTrue); ovr_CalcEyePoses(hmdState.HeadPose.ThePose, hmdToEyeViewPose, layer.RenderPose);
ovr_GetTrackingStateによって報告されます。ovr_GetTrackingStateが理解している必要があります。上記のコードはovr_GetPredictedDisplayTimeを呼び出し、現在のフレームのdisplayMidpointSecondsを取得し、これを使用してトラッキングステータスの最適な予測を計算します。トラッキングステータスから得た頭の姿勢はovr_CalcEyePosesに渡され、左右の目の正しい視界のポーズを計算します。これらのポーズは直接、layer.RenderPose[2]配列に保存されます。アイポーズの準備ができると、実際のフレームレンダリングを処理できます。if (isVisible)
{
// Get next available index of the texture swap chain
int currentIndex = 0;
ovr_GetTextureSwapChainCurrentIndex(session, textureSwapChain, ¤tIndex);
++frameIndex;
ovrResult result = ovr_WaitToBeginFrame(session, frameIndex);
// Clear and set up render-target.
DIRECTX.SetAndClearRenderTarget(pTexRtv[currentIndex], pEyeDepthBuffer);
// Render Scene to Eye Buffers
result = ovr_BeginFrame(session, frameIndex);
for (int eye = 0; eye < 2; eye++)
{
// Get view and projection matrices for the Rift camera
Vector3f pos = originPos + originRot.Transform(layer.RenderPose[eye].Position);
Matrix4f rot = originRot * Matrix4f(layer.RenderPose[eye].Orientation);
Vector3f finalUp = rot.Transform(Vector3f(0, 1, 0));
Vector3f finalForward = rot.Transform(Vector3f(0, 0, -1));
Matrix4f view = Matrix4f::LookAtRH(pos, pos + finalForward, finalUp);
Matrix4f proj = ovrMatrix4f_Projection(layer.Fov[eye], 0.2f, 1000.0f, 0);
// Render the scene for this eye.
DIRECTX.SetViewport(layer.Viewport[eye]);
roomScene.Render(proj * view, 1, 1, 1, 1, true);
}
// Commit the changes to the texture swap chain
ovr_CommitTextureSwapChain(session, textureSwapChain);
}
// Submit frame with one layer we have.
ovrLayerHeader* layers = &layer.Header;
result = ovr_EndFrame(session, frameIndex, nullptr, &layers, 1);
isVisible = (result == ovrSuccess);
originPosおよびoriginRotの値)を、トラッキング状態に基づいて計算され、レイヤーに保存されている新しいポーズと組み合わせます。これらの始点の値は、3Dでプレイヤーを動かすためのインプット値によって変更されることがあります。ovr_EndFrameを呼び出してフレームデータをコンポジターに渡します。ここからはコンポジターが処理を担当し、共有メモリからテクスチャーデータにアクセスし、変形を加え、Riftに表示します。ovr_EndFrameが返され、ランタイムで新しいフレームが受け入れられるようになります。完了すると、その戻り値はovrSuccessまたはovrSuccess_NotVisibleになります。ovrSuccess_NotVisibleが返されます。これはVRアプリがフォーカスを失った場合に発生することがあります。サンプルコードでは、isVisibleフラグを更新し、レンダリングロジックによるチェックを行ってこのケースを処理します。フレームが表示されない間、不要なGPU負荷をなくすためレンダリングを一時停止します。ovrError_DisplayLostが返される場合、デバイスが削除され、セッションが無効になります。共有リソースをリリース(ovr_DestroyTextureSwapChain)し、セッションを破棄(ovr_Destroy)してこれを再作成 (ovr_Create)してから、新しいリソースを作成(ovr_CreateTextureSwapChainXXX)します。アプリの既存の非公開グラフィックリソースは、新しいovr_Create呼び出しから別のGraphicsLuidが返されない限りは、再作成する必要はありません。ovr_GetPredictedDisplayTime関数から、アプリ提供のフレームインデックスを使用してフレームタイミング情報を報告し、複数のスレッドで正しいタイミングが報告されるようにします。ovr_GetTimeInSecondsから返されます。ただし、ovr_GetPredictedDisplayTimeから返されるタイミング値を使用したほうが、シミュレーションと動きの予測結果が向上するため、最新の時間が使用されることはほぼありません。この関数には次の署名があります。ovr_GetPredictedDisplayTime(ovrSession session, long long frameIndex);
ovr_WaitToBeginFrame、ovr_BeginFrame、およびovr_EndFrameに渡す必要があります。マルチスレッディングについて詳しくは、次の複数スレッドでのレンダリングのセクションで説明します。ovr_GetPredictedDisplayTimeの結果は、関数の呼び出し元であるスレッドによっては1フレームずれたり、あるいは、スレッドのスケジュールによってはランダムになり、不正確になる場合さえあります。この問題への対策として、前のセクションでは、アプリによってトラッキングされ、フレームデータとともにスレッドをまたいで渡されるframeIndexのコンセプトについて説明しました。ovr_EndFrameに渡されている必要があります(これはovr_WaitToBeginFrameおよびovr_BeginFrameを呼び出した後に行われる必要があります)。ovr_GetPredictedDisplayTimeに渡してポーズ予測のための正しいタイミングを取得します。ovr_GetTrackingStateを呼び出します。また、レンダリング設定に必要であればovr_CalcEyePosesも呼び出すことができます。ovr_BeginFrameおよびovr_EndFrameに渡されている。void MainThreadProcessing()
{
frameIndex++;
ovrResult result = ovr_WaitToBeginFrame(session, frameIndex);
// Ask the API for the times when this frame is expected to be displayed.
double frameTiming = ovr_GetPredictedDisplayTime(session, frameIndex);
// Get the corresponding predicted pose state.
ovrTrackingState state = ovr_GetTrackingState(session, frameTiming, ovrTrue);
ovrPosef eyePoses[2];
ovr_CalcEyePoses(state.HeadPose.ThePose, hmdToEyeViewOffset, eyePoses);
SetFrameHMDData(frameIndex, eyePoses);
// Do render pre-processing for this frame.
...
}
void RenderThreadProcessing()
{
int frameIndex;
ovrPosef eyePoses[2];
GetFrameHMDData(&frameIndex, eyePoses);
ovrResult result = ovr_BeginFrame(session, frameIndex);
layer.RenderPose[0] = eyePoses[0];
layer.RenderPose[1] = eyePoses[1];
// Execute actual rendering to eye textures.
...
// Submit frame with the one layer we have.
ovrLayerHeader* layers = &layer.Header;
result = ovr_EndFrame(session, frameIndex, nullptr, &layers, 1);
}
ovr_CreateTextureSwapChainDXを呼び出すと、Oculus SDKは呼び出し元から提供されたID3D12CommandQueueを今後の使用のためにキャッシュします。アプリがovr_EndFrameを呼び出すと、SDKがキャッシュしたID3D12CommandQueueのフェンスを削除し、与えられたアイテクスチャーのセットがSDKコンポジターで使用可能になるタイミングを正確に把握できます。ID3D12CommandQueueを使用することが最も簡単です。ただし、それぞれのアイテクスチャーペアに対してCPUレンダリングワークロードを分割したり、陰影、反射マッピングなどのアイテクスチャー以外のレンダリング作業を別のコマンドキューに入れたりする場合もあります。アプリが複数のスレッドからコマンドリストを生成し、実行する場合は、SDKに提供されるID3D12CommandQueueが別のコマンド再生リストで実行されたアイテクスチャーレンダリング作業のシングル結合ノードであることも確認する必要があります。| レイヤーの型 | 説明 |
|---|---|
EyeFov | 前のSDKでも使用した標準の「アイバッファ」。通常、ユーザーの目の位置からレンダリングした仮想シーンのステレオビューです。アイバッファをモノにすることもできますが、不快感が生じることがあります。前のSDKには暗黙的な視野(FOV)やビューポートがありましたが、現在では明示的に提供されており、必要であればアプリでフレームごとに変更することができます。 |
Quad | バーチャル世界で特定のポーズとサイズの矩形で表示されるモノスコピック画像。ヘッドアップディスプレイ、テキスト情報、オブジェクトラベルなどに役立ちます。デフォルトでは、ポーズはユーザーの実際の空間に相対して指定され、クワッドはユーザーの頭や体の動きに合わせて動くのではなく、空間で固定されています。ヘッドロッククワッドでは、以下で説明するように、 ovrLayerFlag_HeadLockedフラグを使用します。 |
キューブマップ | キューブマップは6つの矩形で構成されます。これらの矩形は、キューブが形成するルーム内部にユーザーが座っているかのように、ユーザーを囲んで配置されます。それぞれの壁はアプリが送信するテクスチャーです。キューブマップは無限遠で、原則としてアプリがレンダリングするすべてのオブジェクトの背景として表示されます。キューブマップはユーザーにはキューブとして見えません。無限遠の単なる背景として表示されます。例えば、キューブマップを使用して、VRエクスペリエンスの中ですべての建物の背後に現れる空を作成できます。前面でオブジェクトのオクルージョンを処理する必要はありません。キューブマップを設定するだけで、シーンのあらゆる場所で背景に空が表示されるようにすることができます。 |
EyeMatrix | EyeMatrixレイヤー型はEyeFovレイヤー型と類似しており、従来のSamsung Gear VRアプリとの互換性を保ちます。 |
Cylindrical | シリンドリカルレイヤーを使用すると、平面のクワッドではなく曲線クワッドを作成できます。 ovrLayerCylinder は、中央に再配置された始点(下図のC)に相対して配置された単一の円柱であるovrLayerType_Cylinder型のレイヤーを表しています。以下のシリンドリカルレイヤーパラメーターを参照してください。 |
Depth Buffers | このレイヤーは、カラーテクスチャーに加えて深度テクスチャーを合わせて、モノスコピックビューまたはステレオスコピックビューを指定します。このレイヤーは ovrLayerEyeFovDepth構造体で実装されます。これはovrLayerEyeFovに相当するものですが、DepthTextureとProjectionDescが加えられています。深度バッファは通常、ポジションのタイムワープをサポートするものです。 |
Disabled | コンポジターでは無視され、無効化されたレイヤーがパフォーマンスに影響することはありません。アプリで基本のフラスタムカリングを実行し、ビューの範囲外のレイヤーは無効にすることをおすすめします。ただし、1つのレイヤーをオフにしたときに、アプリでアクティブレイヤーのリストをまとめて再パックする必要はありません。無効にしたままでリストに残しておくだけで十分です。同様に、リストのレイヤーへのポインターをnullに設定できます。 |
ovrLayerCylinderをご覧ください。
ovrLayerType enumのメンバーと、そのレイヤーの表示に必要なデータを保持する構造があります。例えば、EyeFovレイヤーは型番号ovrLayerType_EyeFovであり、ovrLayerEyeFov構造のデータで記述されます。それぞれの構造は類似するパラメーターセットを共有しますが、すべてのレイヤー型がすべてのパラメーターを必要とするわけではありません。| パラメーター | 型 | 説明 |
|---|---|---|
Header.Type | enum ovrLayerType | すべてのレイヤーに設定し、どの型であるかを指定します。 |
Header.Flags | ovrLayerFlagsのビットフィールド | 詳細については、以下を参照してください。 |
ColorTexture | TextureSwapChain | レイヤーにカラーと半透明のデータを提供します。レイヤーは乗算済みアルファを使用して互いにブレンドされます。これにより、線形補間スタイルブレンディングや加算ブレンディング、またはこれらを組み合わせて表現することができます。レイヤーテクスチャーはRGBAまたはBGRA形式であり、ミップマップを持つ場合がありますが、配列やキューブにしたり、MSAAを持つことはできません。アプリでMSAAレンダリングを行いたい場合は、中間のMSAAカラーテクスチャーをレイヤーの非MSAAカラーテクスチャーに解像する必要があります。 |
Viewport | ovrRecti | 実際に使用されるテクスチャーの矩形であり、それぞれ幅/高さとx/yの整数を含む2つの2Dベクターとして矩形のサイズとポジションを示す ovrRectiとして指定されます。ポジションとして与えられたx/yの値は矩形の左下を表しています。理論的に、この領域外のテクスチャーデータはレイヤーには表示されません。ただし、特にミップマップされたテクスチャーなどには、テクスチャーサンプリングの一般的な注意事項が適用されます。表示されている領域周辺のRGBA(0,0,0,0)ピクセル、特に、隣り合わせで同じテクスチャーにパックされている2つのアイバッファの間には境界を残し、「ブリーディング」を避けることが推奨されます。境界のサイズは具体的な使用ケースによって異なりますが、ほとんどは8ピクセル前後で十分です。 |
Fov | ovrFovPort | Eyeレイヤー型でシーンのレンダリングに使用される視野。これはHMDのディスプレイは制御せず、どのFOVがレイヤーのテクスチャーデータのレンダリングに使用されているかをコンポジターに示します。コンポジターはこれを基に、実際のユーザーのFOVに合わせて調整を行います。アプリでは、特殊効果のためにFOVを動的に変更することができます。FOVを削減すると、低速マシンのパフォーマンスを向上させることができますが、通常は、FOV削減の前に解像度を下げる方が効果があります。 |
RenderPose | ovrPosef | Eyeレイヤー型でシーンのレンダリングに使用されるカメラのポーズ。これは通常、SDKとアプリで ovr_GetTrackingStateおよびovr_CalcEyePoses関数を使用して予測されます。コンポジターは、このポーズと、表示時間における実際のアイポーズとの差を使用して、レイヤーにタイムワープを適用します。 |
SensorSampleTime | double | アプリがトラッキングステータスをサンプリングしたときの絶対時間。この値は通常、 ovr_GetTrackingState呼び出しの次にovr_GetTimeInSecondsを呼び出して取得します。SDKではこの値を使用して、パフォーマンスHUDでのMotion-to-Photon遅延を報告します。アプリで一定のフレームにおいて複数のovrLayerType_EyeFovレイヤーを送信した場合、SDKがそれらのレイヤーをスクラブし、遅延の最も少ないタイミングを選択します。特定のフレームでovrLayerType_EyeFovレイヤーが送信されない場合、SDKでは、アプリのMotion-to-Photon遅延時間の代わりに、latencyMarker をovrTrueに設定したovr_GetTrackingStateが呼び出された時間が使用されます。 |
QuadPoseCenter | ovrPosef | Quadレイヤー型の中心点の向きとポジションを指定します。指定される向きはクワッドと垂直のベクトルです。ポジションは、実際の世界(アプリのバーチャル世界ではなく、ユーザーが実際にいる空間)のメートル法で表され、 ovrLayerFlag_HeadLockedフラグが設定されていなければ、ovr_RecenterTrackingOriginまたはovr_SpecifyTrackingOriginで設定された「ゼロ」ポジションに相対します。 |
QuadSize | ovrVector2f | Quadレイヤー型の幅と高さを指定します。ポジションと同様に、これも実際のメートル法で表します。 |
ovrTextureSwapChain、およびそれぞれ別のビューポートを提供します。ovrTextureSwapChainを提供しますが、それぞれに別のビューポートを提供します。こうすることで、アプリは左右のビューを同じテクスチャーバッファにレンダリングできます。前述のとおり、2つのビューの間に少しバッファを追加し、「ブリーディング」を防止します。ovrTextureSwapChainを提供し、それぞれに同じビューポートを提供します。ovrLayerFlag_HighQuality - このレイヤーについて、コンポジターで4倍のアニソトロピックサンプリングが可能です。これにより、特にテクスチャーにミップマップが含まれる場合などの読みやすさが大幅に向上するため、テキストやダイアグラムなどの高フリークエンシーの画像やQuadレイヤー型を使用する場合におすすめします。Eyeレイヤー型の場合も、周辺部に対する忠実度、または推奨される1:1のピクセル密度を超えるテクスチャーに取り込むときの忠実度を向上させます。最適な結果を得るには、特定のレイヤーに関連するテクスチャーにミップマップを作成する場合、テクスチャーサイズが累乗されていることを確認します。ただし、アプリではテクスチャー全体をレンダリングする必要はなく、そのテクスチャーで推奨されるサイズにレンダリングするビューポートによってパフォーマンスと品質の最適な比率が得られます。ovrLayerFlag_TextureOriginAtBottomLeft - レイヤーのテクスチャー始点は左上隅にあるものと想定されます。ただし、一部のエンジン(特にOpenGLを使用するもの)では、このフラグを使用するため始点に左下が使われている場合があります。ovrLayerFlag_HeadLocked - ほとんどのレイヤー型では、そのポーズの向きとポジションはovr_RecenterTrackingOriginを呼び出して定義された「ゼロポジション」に相対して指定されます。ただし、アプリによってはユーザーの顔に相対してレイヤーのポーズを指定する場合もあります。ユーザーが頭を動かすと、レイヤーもそれに従います。これは視線ベースの向き変更や選択に使用するレチクルに有用です。このフラグはすべてのレイヤー型に使用できますが、Direct型では効果はありません。ovrTextureSwapChainをレンダリングし、ovr_CommitTextureSwapChainを呼び出した後、各レイヤーのデータを関連するovrLayerEyeFov / ovrLayerQuad / ovrLayerDirect構造に配置します。次に、各構造の最初のメンバーとして確定しているヘッダーフィールドへのポインターなど、これらのレイヤー構造へのポインターのリストがアプリで作成されます。次に、必要なデータでovrViewScaleDesc構造体が構築され、ovr_WaitToBeginFrame関数、ovr_BeginFrame関数、ovr_EndFrame関数が呼び出されます。ovrResult result = ovr_WaitToBeginFrame(Session, frameIndex);
result = ovr_BeginFrame(Session, frameIndex);
// Create eye layer.
ovrLayerEyeFov eyeLayer;
eyeLayer.Header.Type = ovrLayerType_EyeFov;
eyeLayer.Header.Flags = 0;
for ( int eye = 0; eye < 2; eye++ )
{
eyeLayer.ColorTexture[eye] = EyeBufferSet[eye];
eyeLayer.Viewport[eye] = EyeViewport[eye];
eyeLayer.Fov[eye] = EyeFov[eye];
eyeLayer.RenderPose[eye] = EyePose[eye];
}
// Create HUD layer, fixed to the player's torso
ovrLayerQuad hudLayer;
hudLayer.Header.Type = ovrLayerType_Quad;
hudLayer.Header.Flags = ovrLayerFlag_HighQuality;
hudLayer.ColorTexture = TheHudTextureSwapChain;
// 50cm in front and 20cm down from the player's nose,
// fixed relative to their torso.
hudLayer.QuadPoseCenter.Position.x = 0.00f;
hudLayer.QuadPoseCenter.Position.y = -0.20f;
hudLayer.QuadPoseCenter.Position.z = -0.50f;
hudLayer.QuadPoseCenter.Orientation.x = 0;
hudLayer.QuadPoseCenter.Orientation.y = 0;
hudLayer.QuadPoseCenter.Orientation.z = 0;
hudLayer.QuadPoseCenter.Orientation.w = 1;
// HUD is 50cm wide, 30cm tall.
hudLayer.QuadSize.x = 0.50f;
hudLayer.QuadSize.y = 0.30f;
ID3D11Texture2D* tex = nullptr;
ovr_GetTextureSwapChainBufferDX(Session, TheHudTextureSwapChain, 0, IID_PPV_ARGS(&tex));
D3D11_TEXTURE2D_DESC desc;
tex->GetDesc(&desc);
// Display all of the HUD texture.
hudLayer.Viewport.Pos.x = 0.0f;
hudLayer.Viewport.Pos.y = 0.0f;
hudLayer.Viewport.Size.w = desc.Width;
hudLayer.Viewport.Size.h = desc.Height;
// The list of layers.
ovrLayerHeader *layerList[2];
layerList[0] = &eyeLayer.Header;
layerList[1] = &hudLayer.Header;
// Set up positional data.
ovrViewScaleDesc viewScaleDesc;
viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
viewScaleDesc.HmdToEyeViewOffset[0] = HmdToEyePose[0];
viewScaleDesc.HmdToEyeViewOffset[1] = HmdToEyePose[1];
result = ovr_EndFrame(Session, frameIndex, &viewScaleDesc, layerList, 2);
ovrLayerFlag_HighQualityフラグを組み合わせる場合などのテキスト品質が大幅に向上します。ovr_EndFrameキューを呼び出し、ovrTextureSwapChains内部で決定したテクスチャーのコントロールをコンポジターに送信します。これらのテクスチャーは(コピーではなく)、アプリとコンポジターのスレッド間で共有されており、ovr_EndFrameを呼び出した時点では必ずしも合成が行われないため、十分注意が必要です。テクスチャースワップチェーンへのレンダリングを続行するには、ovr_GetTextureSwapChainCurrentIndexを使用して、次に使用可能なインデックスをレンダリング前に常に取得する必要があります。以下はその例です。ovrResult result = ovr_WaitToBeginFrame(Hmd, frameIndex); result = ovr_BeginFrame(Hmd, frameIndex); // Create two TextureSwapChains to illustrate. ovrTextureSwapChain eyeTextureSwapChain; ovr_CreateTextureSwapChainDX ( ... &eyeTextureSwapChain ); ovrTextureSwapChain hudTextureSwapChain; ovr_CreateTextureSwapChainDX ( ... &hudTextureSwapChain ); // Set up two layers. ovrLayerEyeFov eyeLayer; ovrLayerEyeFov hudLayer; eyeLayer.Header.Type = ovrLayerType_EyeFov; eyeLayer...etc... // set up the rest of the data. hudLayer.Header.Type = ovrLayerType_Quad; hudLayer...etc... // set up the rest of the data. // the list of layers ovrLayerHeader *layerList[2]; layerList[0] = &eyeLayer.Header; layerList[1] = &hudLayer.Header; // Each frame... int currentIndex = 0; ovr_GetTextureSwapChainCurrentIndex(... eyeTextureSwapChain, ¤tIndex); // Render into it. It is recommended the app use ovr_GetTextureSwapChainBufferDX for each index on texture chain creation to cache // textures or create matching render target views. Each frame, the currentIndex value returned can be used to index directly into that. ovr_CommitTextureSwapChain(... eyeTextureSwapChain); ovr_GetTextureSwapChainCurrentIndex(... hudTextureSwapChain, ¤tIndex); // Render into it. It is recommended the app use ovr_GetTextureSwapChainBufferDX for each index on texture chain creation to cache // textures or create matching render target views. Each frame, the currentIndex value returned can be used to index directly into that. ovr_CommitTextureSwapChain(... hudTextureSwapChain); eyeLayer.ColorTexture[0] = eyeTextureSwapChain; eyeLayer.ColorTexture[1] = eyeTextureSwapChain; hudLayer.ColorTexture = hudTextureSwapChain; result = ovr_EndFrame(Hmd, frameIndex, nullptr, layerList, 2);
ovr_GetRenderDesc関数が示すHmdToEyeOffsetベクターによって指定されていました。バージョン1.17以降、HmdToEyeOffsetがHmdToEyePoseと名称変更され、PositionとOrientationを含むovrPosef型を使用してアイポーズに6自由度が得られます。つまり、左右の目のレンダリングフラスタムは現在、SDKによって移動するだけでなく、HMDの元の方向から回転して離れることもできます。このため、アイフラスタムの軸は互いに平行、あるいはHMDの方向軸とは平行ではなくなりました。この概念によって、HMDジオメトリを定義する際のSDKの自由度が大幅に向上しました。ただし、VRアプリ開発者にとっては、これまでの前提、特にレンダリングに関しては慎重になる必要があります。HmdToEyePoseを使用するポイントについて次に示します。HmdToEyeOffsetを使用する場合、代わりにHmdToEyePose.Positionを使用できます。ただし、自分の作業を確実に理解している場合を除き、ポジションと向きを別にするのではなく、HmdToEyePoseを変換全体として扱うほうが好ましいでしょう。
ovrLayerQuadを使用することをおすすめします。ovrFovPort構造から単一のモノスコピックカメラフラスタムを生成します。これは通常、ovrFovPortを使用して実行されます。そうすると、フラスタムの4面すべてで、両目のovrFovPortを最大値にすることができます。ただし、この方法でモノスコピックフラスタムを生成する前に、FovPort::Uncantを呼び出し、ovr_math.hヘッダーにあるovrFovPort値から可能性のある回転を必ず削除してください。FovPort::Uncantの使用方法については、OculusWorldDemoサンプルコードをご覧ください。



