Oculus Rift 渲染

Oculus Rift需求使用失真矫正分屏为每个眼睛来消除镜头有关失真。


修正失真是有挑战的。用各种失真参数为不同的镜头类型和个别的眼睛减轻。为了使开发简单,Oculus SDK句柄自动失真修正在Oculus排字处理;它也关心消减延迟时间扭曲和呈现帧为头戴。

Oculus SDK做了很多工作,应用的主要工作是执行模拟和渲染立体世界基于追踪姿态。立体视图能被渲染进一个或两个个别的纹理和被提交到排字通过调用ovr_SubmitFrame.我们在这节里详细讨论这个处理。

渲染到Oculus Rift

Oculus Rift需求场景被渲染进用于每个眼睛的屏幕的一半的立体分屏。

当使用Rift,左眼看到屏幕的左边一半,右眼看到右边一半。虽然有各种各样的人,人们的眼睛瞳孔相距近似于65mm。这就是瞳孔间的距离(IPD)。应用内摄像机应该用相同的间距配置。

注意:

这是一个摄像机的转换,不是一个旋转,这个转换导致立体效果。这意味着你的应用会需要渲染整个场景两次,一次用于左虚拟摄像机,一次用于右摄像机。

二次重影立体渲染技术,依赖左右视图被生成从一个单个全渲染视图,通常是不可行的对一个HMD因为重大的人工产品在对象边缘。

在Rfit中的透镜组放大图像来提供一个宽泛的视图域提高沉浸式。然而,这个处理使图像失真。如果引擎显示原始图像在Rift,用户会观察他们用针插失真。

为了抵消这个失真,SDK应用后处理来渲染视图用一个相等和相反的桶失真所以两个互相取消,产生一个不失真的视图为每个眼。更多的,SDK也改正色彩失真,这是一个在边缘的由镜头导致的颜色分离效果。尽管精确的失真参数依赖镜头特征和相对于镜头的眼位置,OculusSDK关心所有必要计算当产生失真网眼。

当为Rift渲染时,每个工程轴应该是平行的就像在下面图中的,左和右视图是完全独立于另一个的。这意味着摄像机设置是非常小的对于不是立体渲染的使用的摄像机,期望摄像机是被移动到一旁来适应每个眼睛的位置。

实际上,在Rift中的投射是经常稍微偏离中心的因为我们的鼻子妨碍!但是点残余,Rift中的左右视图是整个从每个分离,不像立体视图通过电视或一个摄像机屏幕产生。这意味着你应该非常小心尝试使用方法开发为这些媒介因为他们通常不应用在VR中。

在场景中的两个虚拟摄像机应该安放以便他们正指向相同方向,它们之间的距离和双眼之间的距离相同,或者瞳孔之间的距离。这个代表性动作通过添加ovrEyeRenderDese::HmdToEyeOffset转换向量来转换视图矩阵的组件。

尽管Rift的透视镜是近似于正确距离与大部分用户的不同,他们可能不是精确匹配用户的IPD。然而,因为被设计的光学方式,每个眼会一直看到正确的视图。这是重要的软件使虚拟摄像机之间的距离匹配用户的IPD就像在他们配置中的一样,而不是Rift的透视镜中的距离。

渲染设置概要

Oculus SDK使用一个排字处理来展示帧和处理失真。

达到Rift的目标,你渲染场景到一个或两个渲染纹理,传递这些纹理进API。Oculus运行时处理失真渲染,GPU同步,帧定时和帧展示到HMD。

接下来是SDK渲染的步骤:

1.初始化:

a.初始化OculusSDK和创建一个ovrSession对象为头戴像前面描述的。

b.计算期望FOV和纹理大小基于ovrHmdDes数据。

c.分配ovrTextureSwapChain对象,用于代表眼缓冲,在一个API特殊方式:调用ovr_CreateTextureSwapChainDX为Direct3D 11或12或ovr_CreateTextureSwapChainGL为OpenGL。

2.设置帧处理:

a.使用ovr_GetTrackingState和ovr_CalcEyePoses来计算需要视图渲染的眼姿势基于帧定时信息。

b.为每个眼睛执行渲染在一个引擎特殊方式,渲染进当前纹理在纹理设置之内。当前纹理是使用ovr_GetTextureSwapChainCurrentIndex()和ovr_GetTextureSwapChainBufferDX()或ovr_GetTextureSwapChainBufferGL()来恢复。渲染纹理完成后,应用必须调用ovr_CommitTextureSwapChain.

c.调用ovr_SubmitFrame,传递交换纹理set(s)从前面步骤在一个ovrLayerEyeFov结构体。虽然一个单一层是需要的去提交一个帧,你能使用多层和层类型为了高级渲染。ovr_SubmitFrame传递层纹理给排版处理失真,时间异常和GPU同步在表现它给头戴之前。

3.关闭:

a.调用ovr_DestroyTextureSwapChain去销毁交换纹理缓冲。调用ovr_DestroyMirrorTexture去销毁一个镜像纹理。为了销毁ovrSession对象,调用ovr_Destroy.

纹理交换链初始化

这段描述渲染初始化,包括纹理交换链的创建。

初始化,你判定渲染FOV和分配需求的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 );
渲染纹理大小被判定基于FOV和描述的像素密度在眼睛中间。虽然FOV和像素密度值能被修改来改善性能,这个例子使用推荐的FOV。这个函数ovr_GetFovTextureSize计算每个眼睛的纹理大小基于这些参数。
OculusAPI允许应用使用一个共享纹理或两个独立纹理为眼睛渲染。这个例子使用一个单独的共享纹理为简易的,使它足够大来填充两个眼睛渲染。因为纹理大小是已知的,应用能调用ovr_CreateTextureSwapChainGL或ovr_CreateTextureSwapChainDX来分配纹理交换链在一个API特殊方式中。这是一个纹理交换链怎样被创建和访问在OpenGL下:
	
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);
    ...
}
这儿是一个简单的使用Direct3D 11纹理交换链创建和访问的例子:
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();
     }
 }
这是一个简单的代码由OculusRoomTiny示例提供的运行在Direct3D 12:
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,调用者提供一个ID3D12 命令队列替换一个ID3D12设备为SDK。调用者的责任是确保ID3D12命令队列示例是所有VR眼睛纹理渲染是可执行的。或者,它能被用于作为一个加入节点防护等待命令行列表执行同其他命令行队列渲染VR眼睛纹理。

一旦这些纹理和渲染目标被成功创建,你能使用他们去执行眼睛纹理渲染。帧渲染段描述视口设置在更多的细节。

Oculus排字提供sRGB正确渲染,这会产生更逼真的视图,更好的MSAA,和积极保存纹理示例,这是非常重要的为VR应用。就像前面展示的,应用期望建立sRGB纹理交换链。合适的sRGB的处理渲染是一个复杂的科目,虽然这段提供一个概览,珍贵的信息是外部的这个文档的范围。

这儿有一些步骤去确保一个实时渲染应用达到sRGB正确底纹和不同的方式去达到它。例如。很多GPU提供硬件加速去改善伽马正确底纹为sRGB特殊输入和输出表面,当一些应用使用GPU着色器数学为更多的自定义控制,排版依赖GPU的采样器去做sRGB线性转换。

所有的颜色纹理在一个GPU着色器中应该被标记近似于sRGB正确格式,就像OVR_FORMAT_R8G8B8A8_UNORM_SRGB.这也被推荐给应用他们提供一个静态纹理作为四方层纹理为Oculus排版。失败会导致纹理看起来更明亮比预期的。

为D3D 11 和12 纹理格式提供降序为通过失真排版被使用的ovr_CreateTextureSwapChainDX为ShaderResourceView当读取纹理的内容时。作为结果,应用应该请求纹理交换链格式是在sRGB-空间中。

如果你的应用是配置来渲染进一个线性格式纹理和处理线到伽马转换使用HLSL代码,或者不关心任何伽马修正,随后:

。请求一个sRGB格式纹理转换链。

。详细说明ovrTextureMisc_DX_Typeless标志在降序中。

。创建一个线性格式的RenderTargetView。

注意:ovrTextureMisc_DX_Typeless标志为深度缓冲区格式被忽略就像他们一直被转换为无类型的。

为OpenGL,ovr_CreateTextureSwapChianGL的格式参数被使用通过失真排版当读取纹理的内容时。作为结果,应用应该请求纹理转换链格式在sRGB空间中。更多的,你得应用应该调用glEnable;在渲染这些纹理之前。

虽然这不是被推荐的,如果你的应用被配置来处理纹理作为一个线性格式和执行线性到伽马转换在GLSL或不关心伽马修正,所以:

。请求一个sRGB格式纹理转换链。

。不用调用glenable;当渲染纹理时。

提供代码示例证明怎么使用提供的ovrTextureMisc_DX_Typeless标志在D3D11:

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();
    }
为了添加sRGB,这些观念也应用镜像纹理创建,为了更多信息,涉及函数文档提供ovr_CreateMirrorTextureDX和ovr_CreateMirrorTextureGL为D3D和OpenGL,各自的。

帧渲染

帧渲染典型的包含一些步骤:获取预期的眼姿势基于头戴追踪姿势,为每个眼睛渲染视图和最终的,提交眼纹理给排版通过ovr_SubmitFrame。帧提交之后,Oculus排版处理失真和呈现它在Rift。

在渲染帧之前这是有用的去初始化一些能被通过帧共享的数据结构。作为一个例子,我们质疑眼描述符号和初始化层结构在渲染循环之外:

 // Initialize VR structures, filling out description.
ovrEyeRenderDesc eyeRenderDesc[2];
ovrVector3f      hmdToEyeViewOffset[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]);
hmdToEyeViewOffset[0] = eyeRenderDesc[0].HmdToEyeOffset;
hmdToEyeViewOffset[1] = eyeRenderDesc[1].HmdToEyeOffset;

// 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.
这个代码例子首先获取为每个眼获取渲染描述符,获取选择的FOV。返回的vorEyeRenderDesc结构包含渲染有用值,包含每个眼的HmdToEyeOffset。眼视图偏移随后用于去适应眼分离。

这个代码也初始化ovrLayerEyeFov结构为全屏层。从OculusSDK 0.6开始,帧提交使用层给排版多视图图像或纹理四元组在每个上面智商。这个例子使用一个单一层去呈现一个VR场景。为了这个意图,我们使用ovrLayerEyeFov,它描述一个双眼层这覆盖整个眼视图域。因为我们使用相同的纹理设置两个眼,我们初始化两眼颜色纹理来pTextureSet和配置视口去描绘这个纹理的左和右边,独自的。

注意:尽管它经常去初始化视口在开始,特别的他们做为一个被提交的层结构的一部分每个帧运行应用去动态变换渲染目标大小,像期望的。这是有用的去最优化渲染性能。

在步骤完成之后,应用能运行渲染循环。首先,我们需要获取眼姿势去渲染左和右视图。

// Get both eye poses simultaneously, with IPD offset already included.
double displayMidpointSeconds = GetPredictedDisplayTime(session, 0);
ovrTrackingState hmdState = ovr_GetTrackingState(session, displayMidpointSeconds, ovrTrue);
ovr_CalcEyePoses(hmdState.HeadPose.ThePose, hmdToEyeViewOffset, layer.RenderPose)
在VR中,渲染眼视图依赖头戴在物理空间中的位置和方向,追踪用内部IMU的帮助和外部的传感器。在系统中预期是用于补偿延迟,给出最好头戴会在哪儿的估算当帧被显示在头戴上。在OculusSDK中,这个追踪,预期姿势被表示通过ovr_GetTrackingState.

为了精确的预期,ovr_GetTrakcingState需要知道当当前帧会真实被显示。上面的代码调用GetPredictedDisplayTime去获取显示器MidpointSeconds为当前帧,使用它去计算最佳预期追踪状态。来自追踪状态的头部姿势是被传递给ovr_CalcEyePoses去计算每个眼的正确视图姿势。RenderPose数组。用准备好的眼姿势,我们能处理真实帧渲染。

if (isVisible)
{
    // Get next available index of the texture swap chain
	int currentIndex = 0;
    ovr_GetTextureSwapChainCurrentIndex(session, textureSwapChain, &currentIndex);
    
    // Clear and set up render-target.            
    DIRECTX.SetAndClearRenderTarget(pTexRtv[currentIndex], pEyeDepthBuffer);

    // Render Scene to Eye Buffers
    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;
ovrResult       result = ovr_SubmitFrame(session, 0, nullptr, &layers, 1);
isVisible = (result == ovrSuccess);
这些代码花费一些步骤去渲染场景:
	。它应用文理作为一个渲染目标和清除它为渲染。在这个例子中,两眼使用相同的纹理。
	。这个代码计算视图和工程矩阵和设置视口场景渲染为每个眼。在这个例子中,视图计算合并原始姿势和计算的新姿势基于追踪状态和存储在层中。原始值能被修改通过输入去移动玩家在3D世界中。
	。在完成纹理渲染后,我们调研ovr_SubmitFrame去传递帧数据给排版。排版接收通过访问纹理数据通过共享内存,扭曲它,和呈现它在Rift中。
	ovr_SubmitFrame返回因为提交帧是排队等候和运行时是有效的去接受一个新帧。当成功后,它的返回值是ovrSuccess或ovrSuccess_NotVisible.
	ovrSuccess_NotVisible被返回如果帧不是真实显示,它能发生当VR应用失去焦点。我们示例代码处理这个问题通过更新isVisible标志,检查通过渲染逻辑。当帧不是可见的,渲染应该被停止来消除不必要的GPU加载。
	如果你接收ovrError_DisplayLost,设备会被移除和回话是无效的。释放共享资源,销毁回话,重建它和创建新资源。应用的现存的私有图形资源不需要被重建除非新ovr_Create调用返回一个不同的GraphicsLuid.
帧定时
Oculus SDK通过ovr_GetPredictedDisplayTime函数报告帧定时信息,依赖应用提供的帧索引来确保报告正确的时间通过不同的线程。
精确的帧和传感器时间需要精确的头部运动预期,这是一个好的VR体验的基本。预期需要知道精确的在未来当前帧会出现在屏幕上。如果我们知道传感器和显示器扫描时间,我们能预测未来头姿势和改进图像稳定性。计算出这些正确值会导致低或超过预期,减少感知延迟和潜在的导致目标摇晃。
为了确保精确时间,Oculus SDK使用绝对系统时间,用一个double值存储,来重先传感器和帧定时值。当前的绝对值时间通过ovr_GetTimeInSeconds返回。当前时间应该很少被使用,然而,因为模拟器和运动预测会减少好结果当依赖ovr_GetPredictedDisplayTime返回的时间值。这个函数有下面的特征:
ovr_GetPredictedDisplayTime(ovrSession session,long long frameIndex);
frameIndex参数是我们正在渲染的应用帧。应用使用多线程渲染必需保持一个内部帧索引和手动递增它,穿过线程传统它和帧数据来确保正确的时间和预测。传递给ovr_SubmitFrame的帧索引值必须和用来获取帧定时的相同。多线程时间的细节在下一节,渲染在不同的线程。
一个指定的帧索引 0 值可以被使用在函数中去请求SDK保持帧索引自动追踪。然而,这只会在所有帧定时请求和渲染提交后再相同的线程中工作。
渲染在不同的线程
在一些引擎中,渲染是通过不止一个线程完成的。
例如,一个线程可能执行选择和为每个对象渲染设置在场景中,另一个线程使用真实D3D或OpenGL API调用。两个线程可能需要精确的帧显示时间估算,来计算出最可能的头部资深预测。
异步的性质是一个挑战:当渲染线程正在渲染一个帧,主线程可能正在处理下一个帧。平行帧处理可能会超出同步精确的一个帧或者一帧的小部分,依赖于游戏引擎设计。如果我们使用默认全局状态去访问帧定时,GetPredictedDisplayTime的结果可能是关闭的通过一帧依赖线程函数调用,或者更糟的,可能是随机修正依赖于线程调度。处理这个问题,前面节介绍的帧索引概念通过应用被追踪和穿过线程被传递和帧数据。
为了多线程渲染结果正确,下面必须是对的;(a)姿势预测,基于帧定时的计算,必须始终是相同帧不管哪个线程访问;(b)真实被用来渲染的眼位置必须被传递进ovr_SubmitFrame和帧索引一起。
下面是一些步骤概要,你能采取来确保这写情况:
	1.主线程需要赋值一个帧索引给当前帧来渲染处理。它会增加每帧的索引和传递它给GetPredictedDisplayTime去获得姿势预测的正确时间。
	2.主线程应该调用线程安全函数ovr_GetTrackingState来预测时间值。它也可以调用ovr_CalcEyePoses如果必要的为渲染阶段。
	3.主线程需要传递当前帧索引和眼姿势给渲染线程,和任何它需要的渲染命令或帧数据。
	4.当渲染命令在渲染线程中执行时,开发者需要保持下面的事情:
		a.真实的用于帧渲染的姿势存储进RenderPose为层。
		b.帧索引的值和主线程用于传递给ovr_SubmitFrame相同。
下面的代码展示了更多的细节:
void MainThreadProcessing()
{
    frameIndex++;
        
    // Ask the API for the times when this frame is expected to be displayed. 
    double frameTiming = 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);
    layer.RenderPose[0] = eyePoses[0];
    layer.RenderPose[1] = eyePoses[1];
    
    // Execute actual rendering to eye textures.
    ...    
    
   // Submit frame with one layer we have.
   ovrLayerHeader* layers = &layer.Header;
   ovrResult       result = ovr_SubmitFrame(session, frameIndex, nullptr, &layers, 1);
}

OculusSDK也支持Direct3D 12,它允许从多CPU线程提交渲染工作给GPU。当应用调用ovr_CreateTextureSwapChainDX,OculusSDK缓冲离开ID3D12CommandQueue提供的通过调用者给未来用例。作为应用调用ovr_SubmitFrame,SDK丢弃一个栅栏在缓冲的ID3D12CommandQueue来知道精确的当一个被给的眼纹理集合已经准备好了为SDK排版。
一个给定的应用,使用一个单独的ID3D12CommandQueue在一个单线程上是最简单的。但是,它可能分离CPU渲染工作加载为每个眼纹理对或推送没有眼纹理渲染工作,就像阴影,映像地图等等,在不同的命令队列上。如果应用从多线程执行,它也会确保ID3D12CommandQueue提供给SDK的是单一加入节点给眼纹理渲染工作执行通过不同的命令队列。
类似于一个监视器视图能被多窗口组成,头戴的显示器也能由多个窗口组成。至少这些层的一个会是一个由用户的虚拟眼球来渲染的视图,但是其他层可以是HUD层,信息面板,文本标签附加于名目在世界中,忽略光标,等等。
每个层能有一个不同的分辨率,使用不同的纹理格式,能是一个不同的视图域或大小,也可以是单通道的或立体的。应用也能被配置不更新一个层的纹理如果它里面的信息没有改变。例如,它不会更新如果一个文本在一个信息面板中没有改变因为上一帧或者层是一个低帧率视频流的画中画视图。应用支持给一个层细化纹理,和一个高质量的失真模式一起,这是非常有效果的在改进文本面板的可读性上。
每一帧,所有活跃层是复合的从后到前使用预繁衍alpha混合。层0是最远的层,层1在它上面,等等;这里没有层的深度缓冲交叉测试,虽然一个深度缓冲是支持的。
一个强有力的层的特征是每个层都有一个分辨率。这允许一个应用去缩小性能通过丢弃分辨率在主眼缓冲区显示在虚拟世界中的渲染,但是保持基本的信息,像文本或一个地图,在一个不同层中用高分辨率。
这儿有一些层类型变量:
  
  
  
  
EyeFov The standard "eye buffer" familiar from previous SDKs, which is typically a stereo view of a virtual scene rendered from the position of the user's eyes. Although eye buffers can be mono, this can cause discomfort. Previous SDKs had an implicit field of view (FOV) and viewport; these are now supplied explicitly and the application can change them every frame, if desired.
Quad A monoscopic image that is displayed as a rectangle at a given pose and size in the virtual world. This is useful for heads-up-displays, text information, object labels and so on. By default the pose is specified relative to the user's real-world space and the quad will remain fixed in space rather than moving with the user's head or body motion. For head-locked quads, use the ovrLayerFlag_HeadLocked flag as described below.
EyeMatrix The EyeMatrix layer type is similar to the EyeFov layer type and is provided to assist compatibility with Gear VR applications. For more information, refer to the Mobile SDK documentation.
Disabled Ignored by the compositor, disabled layers do not cost performance. We recommend that applications perform basic frustum-culling and disable layers that are out of view. However, there is no need for the application to repack the list of active layers tightly together when turning one layer off; disabling it and leaving it in the list is sufficient. Equivalently, the pointer to the layer in the list can be set to null.

Each layer style has a corresponding member of the ovrLayerType enum, and an associated structure holding the data required to display that layer. For example, the EyeFov layer is type number ovrLayerType_EyeFov and is described by the data in the structure ovrLayerEyeFov. These structures share a similar set of parameters, though not all layer types require all parameters:

Parameter Type Description
 
Header.Type enum ovrLayerType Must be set by all layers to specify what type they are.
Header.Flags A bitfield ofovrLayerFlags See below for more information.
ColorTexture TextureSwapChain Provides color and translucency data for the layer. Layers are blended over one another using premultiplied alpha. This allows them to express either lerp-style blending, additive blending, or a combination of the two. Layer textures must be RGBA or BGRA formats and might have mipmaps, but cannot be arrays, cubes, or have MSAA. If the application desires to do MSAA rendering, then it must resolve the intermediate MSAA color texture into the layer's non-MSAA ColorTexture.
Viewport ovrRecti The rectangle of the texture that is actually used, specified in 0-1 texture "UV" coordinate space (not pixels). In theory, texture data outside this region is not visible in the layer. However, the usual caveats about texture sampling apply, especially with mipmapped textures. It is good practice to leave a border of RGBA(0,0,0,0) pixels around the displayed region to avoid "bleeding," especially between two eye buffers packed side by side into the same texture. The size of the border depends on the exact usage case, but around 8 pixels seems to work well in most cases.
Fov ovrFovPort The field of view used to render the scene in an Eye layer type. Note this does not control the HMD's display, it simply tells the compositor what FOV was used to render the texture data in the layer - the compositor will then adjust appropriately to whatever the actual user's FOV is. Applications may change FOV dynamically for special effects. Reducing FOV may also help with performance on slower machines, though typically it is more effective to reduce resolution before reducing FOV.
RenderPose ovrPosef The camera pose the application used to render the scene in an Eye layer type. This is typically predicted by the SDK and application using theovr_GetTrackingState and ovr_CalcEyePosesfunctions. The difference between this pose and the actual pose of the eye at display time is used by the compositor to apply timewarp to the layer.
SensorSampleTime double The absolute time when the application sampled the tracking state. The typical way to acquire this value is to have an ovr_GetTimeInSeconds call right next to the ovr_GetTrackingState call. The SDK uses this value to report the application's motion-to-photon latency in the Performance HUD. If the application has more than oneovrLayerType_EyeFov layer submitted at any given frame, the SDK scrubs through those layers and selects the timing with the lowest latency. In a given frame, if no ovrLayerType_EyeFov layers are submitted, the SDK will use the point in time whenovr_GetTrackingState was called with thelatencyMarkerset to ovrTrue as the substitute application motion-to-photon latency time.
QuadPoseCenter ovrPosef Specifies the orientation and position of the center point of a Quad layer type. The supplied direction is the vector perpendicular to the quad. The position is in real-world meters (not the application's virtual world, the actual world the user is in) and is relative to the "zero" position set byovr_RecenterTrackingOrigin unless theovrLayerFlag_HeadLocked flag is used.
QuadSize ovrVector2f Specifies the width and height of a Quad layer type. As with position, this is in real-world meters.
带有立体信息的层花费两个参数的集合,它们能被用在3个不能的方式:
。立体数据,独立纹理--应用支持一个不同的ovrTextureSwapChain为左和右眼,为每个眼一个视口。
。立体数据,共享纹理--应用尺寸相同的ovrTextureSwapChian为两个眼睛,但是为每个有一个不同的视口。这允许应用渲染左和右视图用相同的纹理缓冲区。记住添加一个小缓冲区在两个视图之间来预防“渗色”,作为上面讨论的。
。单通道数据--应用支持给左和右眼提供相同的ovrTextureSwapChain和相同的视口。
纹理和视口大小可能是左右眼不同的,甚至每个有不同的视图域。然而当心导致立体不一致和你们的用户不舒服。
在下面中,跟所有层的头部标志域变量是逻辑或的关系
。ovrLayerFlag_HighQuality--能使4x各异性取样在层的排版中。这能提供一个有影响的增长在易读性,特别是在一个包含变频解码纹理中使用;这被推荐给高频率图像就像文本或图表当用Quad层类型使用时。眼睛层类型,它也会增加朝向边缘的虚拟保真度,当建立变频解码为纹理关联到特殊层,确保纹理大小是2的幂。然而,应用不需要渲染整个纹理;一个视口渲染在纹理中的推荐大小会提供最好的性能到质量的比率。
。ovrLayerFlag_TextureOriginAtBottomLeft--一个层纹理的原点假设在左上角。然而,一些引擎喜欢使用左下角作为原点,他们应该使用这个标志。
。ovrLayerFlag_HeadLocked--大多数层类型有他们自己姿势方向和位置指定相对于“0位置”通过调用ovr_RecenterTrackingOrigin定义。然而应用可以希望指定一个层的姿势相对于用户的脸。当用户移动他们的头,层跟着移动。这是有用的为光标使用在凝视基于瞄准或选择。这个标志被用于所有的层类型,虽然它没有影响在使用Direct类型上。
在每帧结束时,渲染完任何一个ovrTextureSwapChain后应用希望更新和调用ovr_CommitTextureSwapChain,每层的数据是放进相关的ovrLayerEyeFov/ovrLayerQuad/ovrLayerDirect结构中。应用随后建立一个指向这些结构的指针列表,指出Header域(保证它是每个结构的第一个成员)。然后应用建立一个ovrViewScaleDesd结构来请求数据和调用ovr_SubmitFrame函数。
// 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;
// Display all of the HUD texture.
hudLayer.Viewport.Pos.x = 0.0f;
hudLayer.Viewport.Pos.y = 0.0f;
hudLayer.Viewport.Size.w = 1.0f;
hudLayer.Viewport.Size.h = 1.0f;

// 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] = HmdToEyeOffset[0];
viewScaleDesc.HmdToEyeViewOffset[1] = HmdToEyeOffset[1];

ovrResult result = ovr_SubmitFrame(Session, 0, &viewScaleDesc, layerList, 2);
排版执行时间异常,失真和色彩失常修正在每个独立层在一起混合他们之前。渲染一个四方组给眼缓冲的传统方法包含两个过滤步骤。使用层,这儿只是一个单一过滤步骤在层图像和最终的帧缓冲之间。这能提供大量的改进在文本质量上,特别是当合并变频解码和ovrLayerFlag_HighQuality标志。
一个当前层的缺点是不传递处理能被执行在最终的复合的图像,就像舒服聚焦效果,光晕效果或者Z轴层数据的交叉。这些效果能被执行在层内容上和类似的视觉结果。
调用ovr_SubmitFrame显示队列层,和转换提交纹理的控制在ovrTextureSwapChains内为排版。这是重要的去理解这些纹理是共享的在应用和排版线程之间,排版不必发生在ovr_SubmitFrame调用时,所以必须关心。为了继续渲染进一个纹理交互链应用应该一直获取下一个有效的所以调用ovr_GetTextureSwapChainCurrentIndex在渲染之前。例如:

// 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, &currentIndex);
// 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, &currentIndex);
// 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;
ovr_SubmitFrame(Hmd, 0, nullptr, layerList, 2);

异步时间异常
异步时间异常时一个技术去减少延迟和颤抖在VR应用和体验中。
在一个基本的VR游戏循环中,下面发生:
1.软件请求你的头部位置。
2.CPU处理每个眼的场景。
3.GPU渲染场景。
4.Oculus排版使用失真和显示场景在头戴上。
下面显示一个基本的游戏循环例子:

保持帧速率,体验感觉真实和被享受的。当它不能及时发生,前面显示的帧能是被令人迷惑的。下面的图形显示了一个抖动的例子在基本游戏循环期间:

当你移动你们的头和世界不能保持,这是不和谐的和打破沉寂。
ATW是一个技术是稍微移动渲染图像去适应在头部运动中的变化。虽然图像被修改了,你的头不是移动喝多,所以只是稍微变化。
另外的,为平滑问题用用户的计算机,游戏设计或操作系统,ATW能帮助修补凹坑或移动当帧速率不期望丢失。
下面的图形显示一个帧丢失的例子当应用ATW时:

在内部刷新时,排版应用时间异常来渲染上一个帧。作为结构,一个时间异常帧会一直被显示给用户,不管帧速率。如果帧速率非常糟糕,闪烁会是显而易见的在显示器的外围。但是,图像会一直是稳定的。
ATW是自动应用通过Oculus Compositor;你不需要使能或调整它,虽然ATW减少延迟,确保你的应用或体验帧速率。
适应队列头
为了改进CPU和GPU平行和增加时间数量,GPU需要处理一帧,SDK自动应用队列头到1帧。
没有队列头,CPU开始处理下一帧立即的在前一帧显示后。CPU结束后,GPU处理帧,排版应用失真,帧显示给用户。下面的图形显示CPU和GPU未用队列头初始化:

GPU不能为显示器及时处理帧,前面帧显示。这个结果是颤抖的。
用了队列头,CPU能早点开始;给GPU提供更多时间去处理帧。下面图形显示CPU和GPU用队列头初始化:

性能指示器
异步时间异常能掩饰延迟和颤抖问题正常上是显然的。为了帮助你识别当你的应用或体验没有表现和测试你的游戏或体验在提交它之前,Oculus提供了性能指示器。
当可以时,一个字母代码显示在头戴的右上方每当应用正在测试一个性能问题。下面的图显示一个性能指示器的例子用L和F显示:

为了使用性能指示器,设置下面的注册键:
HKLM\SOFTWARE\Oculus VR, LLC\LibOVR
FrameDropHUDEnable (DWORD)

Enable = 1
Disable = 0

性能指示器能返回下面的代码:
。L--一个延迟问题是事件;多余一个队列头的帧是被应用的。
。F--应用没有保持帧速率。
。C--排版没有保持帧速率。可能导致包含:
	。程序,就像反病毒软件,超负荷CPU。
	。CPU不能处理大量线程。
	。CPU或GPU没有匹配推荐规定。
	。GPU的特定模式用例,就像巨大的阴影的用例和棋盘行布置,影响帧速率。
	。GPU驱动有问题。
	。未知的硬件问题。
。U--一个未知错误事件。
每个警告持续一帧。所以,如果L保持可见,应用有连续不断的延迟问题。

你可能感兴趣的:(Oculus Rift 渲染)