
ovr_WaitToBeginFrame、ovr_BeginFrame 和 ovr_EndFrame 提交给合成器。我们将在本部分详细介绍这一过程。
ovrEyeRenderDesc 结构中的 HmdToEyePose 成员是一个 ovrPosef 6-DOF 变换。这意味着在具有倾斜显示屏的 HMD 中(即显示屏彼此不平行),渲染相机可能会相对于 HMD 姿势进行旋转和平移。因此,用户面向的方向由 HMD 姿势的 Z 轴决定,而眼睛的姿势可能有指向不同方向的 Z 轴。
ovrEyeRenderDesc::HmdToEyePose 指定的眼睛姿态相同的方向上,并且它们之间的距离应该与眼睛之间的距离(即瞳距,IPD)相同。ovrSession 对象,如前面所述。ovrHmdDesc 数据计算所需的 FOV 和纹理大小。ovrTextureSwapChain 对象,这些对象用于表示眼睛缓冲区:对于 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 的组合使您能够在多线程环境中实现性能优化技术,例如通过拆分和重叠多个帧的处理来同时处理它们。在之前的版本中,这三个函数组合成 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 获得)。函数 ovr_GetFovTextureSize 根据这些参数计算每只眼睛所需的纹理大小。ovr_GetSessionPhysicalDeviceVk 以获取与 luid 匹配的当前物理设备。然后,创建一个与返回的物理设备相关联的 VkDevice。OculusRoomTiny_Advanced 示例应用程序中的 Win32_VulkanAppUtil.h 文件。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时,调用者向 SDK 提供一个 ID3D12CommandQueue 而不是 ID3D12Device。调用者有责任确保所有 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 的 desc 中提供的纹理格式会被扭曲合成器在读取纹理内容时用作 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 空间的纹理交换链格式(例如 OVR_FORMAT_R8G8B8A8_UNORM_SRGB),因为合成器会进行 sRGB 正确的渲染。合成器将依赖于 GPU 的硬件采样器来执行 sRGB 到线性的转换。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 版本开始,帧提交使用图层将多个视图图像或纹理四边形相互叠加组合在一起。这个示例使用一个图层来呈现一个虚拟现实场景。为此,我们使用 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 的结果可能会根据调用该函数的线程而相差一帧,或者更糟糕的是,由于线程调度的不同,结果可能会随机出现错误。为了解决这个问题,前面的部分介绍了由应用程序跟踪并随帧数据一起在线程间传递的 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) 和视口;而现在这些参数是明确提供的,并且如果应用程序需要,可以在每一帧中更改它们。 |
四边形 | 一个单眼图像,它在虚拟世界中以给定的位置和大小作为矩形显示。这对于平视显示器、文本信息、对象标签等非常有用。默认情况下,位置是相对于用户的真实世界空间来指定的,并且四边形将在空间中保持固定,而不是随着用户的头部或身体运动而移动。对于头部锁定四边形,请使用下面描述的 ovrLayerFlag_HeadLocked 标志。 |
Cubemap | 一个立方体贴图由六个矩形组成。这些矩形放置在用户的周围,就好像用户坐在一个立方体形状的房间里面一样。每一面墙都是应用程序提交的一个纹理。立方体贴图看起来像是在无限远的距离,它实质上是应用程序渲染的所有其他对象背后的背景。用户看到的立方体贴图并不像一个立方体。而仅仅是看起来像是处于无限远处的背景。例如,可以使用立方体贴图来创建在 VR 体验中将会出现在所有建筑后面的天空。不需要处理前景物体造成的遮挡。只需设置好立方体贴图,它就会在您场景中的任何地方作为背景出现。 |
EyeMatrix | EyeMatrix 图层类型与 EyeFov 图层类型相似,用于帮助与旧版 Samsung Gear VR 应用程序的兼容性。 |
圆柱形 | 可以使用圆柱形图层来创建弯曲的四边形,而不是平坦的四边形。 ovrLayerCylinder 描述了一个类型为 ovrLayerType_Cylinder 的图层,它是一个相对于重新定位的原点(在下面的图示中用 C 表示)进行定位的单个圆柱体。请参阅下面的圆柱形图层参数。 |
深度缓冲区 | 此图层指定一个单色视图或立体视图,除了颜色纹理外,还包括深度纹理。此图层由 ovrLayerEyeFovDepth 结构体实现。它相当于 ovrLayerEyeFov,但额外添加了 DepthTexture 和 ProjectionDesc。深度缓冲区通常用于支持位置时间扭曲。 |
禁用 | 被合成器忽略的禁用图层不会影响性能。建议应用程序执行基本的视锥体裁剪,并禁用不在视图范围内的图层。但在关闭一个图层时,应用程序无需将活动图层的列表紧密地重新打包在一起;只需禁用该图层并将其留在列表中即可。换句话说,也可以将列表中指向该图层的指针设为空值。 |
ovrLayerCylinder。
ovrLayerType 枚举成员,以及一个包含显示该图层所需数据的相关结构。例如,EyeFov 图层是类型为 ovrLayerType_EyeFov 的图层,并由 ovrLayerEyeFov 结构中的数据描述。这些结构共享一组类似的参数,尽管并非所有图层类型都需要所有参数:| 参数 | 类型 | 描述 |
|---|---|---|
Header.Type | 枚举 ovrLayerType | 所有图层都必须设置此参数,以指定它们的类型。 |
Header.Flags | ovrLayerFlags 的位字段 | 参阅下文了解详细信息。 |
ColorTexture | TextureSwapChain | 为图层提供颜色和透明度数据。图层使用预乘 alpha 值相互融合。这允许它们实现线性插值 (lerp) 风格的混合、加法混合,或者这两种混合的组合。图层纹理必须采用 RGBA 或 BGRA 格式,可能包含多级渐远纹理,但不能是阵列、立方体,也不能具有 MSAA。如果应用程序希望进行 MSAA 渲染,那么它必须将中间 MSAA 颜色纹理解析到图层的非 MSAA 颜色纹理中。 |
Viewport | ovrRecti | 实际使用的纹理矩形,由 ovrRecti 结构指定,该结构以两个二维向量提供矩形的大小和位置,分别包含宽度/高度和 x/y 的整数。提供的 x/y 位置值代表矩形左下角的顶点位置。理论上,此区域之外的纹理数据在图层中是不可见的。然而,关于纹理采样的通常注意事项仍然适用,特别是对于使用多级渐远纹理的情况。在显示区域的周围留下一圈 RGBA(0,0,0,0) 像素的边框是一个好习惯,这可以避免“渗色”,特别是在将两个视觉缓冲区并排打包到同一个纹理中时。边框的大小取决于具体的使用情况,但在大多数情况下,大约 8 个像素的边框似乎效果良好。 |
Fov | ovrFovPort | 用于在眼睛图层类型中渲染场景的视场。请注意,这并不会控制 HMD 的显示,它只是告诉合成器在该图层中渲染纹理数据时使用了什么 FOV,然后合成器会根据用户的实际 FOV 进行相应的调整。应用程序可以动态更改 FOV,以实现特殊效果。减小 FOV 也有助于在较慢的机器上提高性能,但通常而言,在减小 FOV 之前降低分辨率会更为有效。 |
RenderPose | ovrPosef | 应用程序在眼睛图层类型中渲染场景时所使用的相机位置和方向。这通常由 SDK 和应用程序使用 ovr_GetTrackingState 和 ovr_CalcEyePoses 函数来预测。合成器会利用这个预测位置与显示时眼睛实际位置之间的差异,来对图层应用时间扭曲。 |
SensorSampleTime | 双精度 | 应用程序采样跟踪状态的绝对时间。获取此值的典型方法是在 ovr_GetTrackingState 调用旁边紧接着调用 ovr_GetTimeInSeconds。SDK 使用此值在性能 HUD 中报告应用程序的运动到光子延迟。如果应用程序在任何给定帧中提交了多个 ovrLayerType_EyeFov 图层,SDK 会遍历这些图层,并选择延迟最小的时间。在给定帧中,如果没有提交 ovrLayerType_EyeFov 图层,SDK 将使用调用 ovr_GetTrackingState 时设置 latencyMarker为ovrTrue 的时间点,作为应用程序运动到光子延迟时间的替代值。 |
QuadPoseCenter | ovrPosef | 指定四边形图层类型中心点的方向和位置。提供的方向是与四边形垂直的向量。位置是以现实世界中的米为单位(不是应用程序的虚拟世界,而是用户所处的实际世界),并且相对于通过 ovr_RecenterTrackingOrigin 或 ovr_SpecifyTrackingOrigin 设置的“零”位置,除非使用了 ovrLayerFlag_HeadLocked 标志。 |
QuadSize | ovrVector2f | 指定四边形图层类型的宽度和高度。与位置一样,这也是以现实世界中的米为单位。 |
ovrTextureSwapChain,并为每个眼睛提供一个视口。ovrTextureSwapChain,但为每个眼睛提供一个不同的视口。这允许应用程序将左眼和右眼的视图都渲染到同一个纹理缓冲区中。如上所述,请记得在两个视图之间添加一个小的缓冲区,以防止“串色”。ovrTextureSwapChain,并且为每个眼睛提供相同的视口。ovrLayerFlag_HighQuality — 为此图层的合成器启用 4 倍异向性采样。这可以显著提升清晰度,特别是当与包含多级渐近纹理的纹理一起使用时;对于文本或图表等高频图像以及在使用四边形图层类型时,建议采用此方式。对于眼球图层类型,它还可以提高边缘的视觉保真度,或者在输入像素密度超过推荐的 1:1 比例的纹理时提高视觉保真度。为了获得最佳效果,在为与特定图层相关联的纹理创建多级渐近纹理时,请确保纹理尺寸是 2 的幂。但应用程序无需对整个纹理进行渲染;一个对纹理中推荐尺寸进行渲染的视口将提供最佳的性能与质量比。ovrLayerFlag_TextureOriginAtBottomLeft — 图层的纹理原点假定为左上角。然而,一些引擎(特别是使用 OpenGL 的引擎)更喜欢使用左下角作为原点,并且它们应该使用此标志。ovrLayerFlag_HeadLocked — 大多数图层类型都会通过调用 ovr_RecenterTrackingOrigin 定义的“零位”来指定其姿态方向和位置。但应用程序可能希望指定一个图层相对于用户面部的姿态。当用户移动头部时,图层会随之移动。这对于基于注视的瞄准或选择中使用的准星很有用。此标志可用于所有图层类型,但在“直接”类型上使用时无效果。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,并使用 ovrPosef 类型,其中包含位置和方向,从而有效地为眼睛姿态提供六个自由度。这意味着除了 SDK 进行平移之外,每只眼睛的渲染视锥现在可以相对于 HMD 的方向进行旋转。因此,眼睛视锥体的轴不再保证彼此平行或与 HMD 的方向轴平行。这种泛化使得 SDK 在定义 HMD 几何形状方面具有更大的自由度。但这也意味着,作为 VR 应用开发者,需要更加小心您之前的假设,尤其是在渲染方面。HmdToEyePose 的要点:HmdToEyeOffset 的向量平移值,可以改用 HmdToEyePose.Position。不过,除非您完全确定自己在做什么,否则很有可能您实际上想要将 HmdToEyePose 作为一个整体的变换来处理,而不是将方向与位置分开。
ovrLayerQuad 来代替。ovrFovPort 结构中生成一个单一的单眼相机视锥体,以便利用各种渲染优化。这通常是通过使用一个 ovrFovPort 来完成的,它取两只眼睛在视锥体四个边上的 ovrFovPort 值的最大值。但在通过这种方式生成单眼视锥体之前,请确保通过调用位于 ovr_math.h 标头中的 FovPort::Uncant 函数,从 ovrFovPort 值中移除任何潜在的旋转。请参阅 OculusWorldDemo 示例代码,了解如何使用 FovPort::Uncant。



