
ovr_WaitToBeginFrame, ovr_BeginFrame und ovr_EndFrame an den Compositor gesendet. Dieser Prozess wird in diesem Abschnitt detailliert beschrieben.
HmdToEyePose-Mitglied des ovrEyeRenderDesc-Struct ist eine ovrPosef-6-DOF-Transformation. Das bedeutet, dass bei HMDs mit geneigten Displays (Displays nicht parallel zueinander) die Rendering-Kameras von der HMD-Haltung weg rotiert und translatiert werden können. Die vorwärtsgewandte Richtung des*der Nutzer*in wird von der Z-Achse der HMD-Haltung vorgegeben, während die Augenhaltungen gegebenenfalls Z-Achsen aufweisen, die in unterschiedliche Richtungen zeigen.
ovrEyeRenderDesc::HmdToEyePose spezifiziert werden. Dabei sollte der Abstand zwischen beiden Kameras dem Augenabstand (IPD) entsprechen.ovrSession-Objekt für das Headset, wie bereits beschrieben.ovrHmdDesc-Daten.ovrTextureSwapChain-Objekte, die für Eye-Render-Buffer stehen, auf API-spezifische Weise zu: Rufe ovr_CreateTextureSwapChainDX für Direct3D 11 oder 12 ab, ovr_CreateTextureSwapChainGL für OpenGL oder ovr_CreateTextureSwapChainVk für Vulkan.VK_PRESENT_MODE_IMMEDIATE_KHR für AMD und VK_PRESENT_MODE_MAILBOX_KHR für nVidia. Dadurch wird ein Problem vermieden, bei dem der Loop vor dem Rendern des nächsten Einzelbilds auf Vsync am Hauptmonitor wartet, wodurch Latenz entsteht und die Leistung verschlechtert wird.ovr_GetTrackingState und ovr_CalcEyePoses, um die Augenhaltungen zu berechnen, die für das Ansichtsrendering auf Grundlage von Einzelbild-Timinginformationen erforderlich sind.ovr_GetTextureSwapChainCurrentIndex und ovr_GetTextureSwapChainBufferDX, ovr_GetTextureSwapChainBufferGL oder ovr_GetTextureSwapChainBufferVk. Nachdem das Rendering in die Textur abgeschlossen ist, muss die App ovr_CommitTextureSwapChain aufrufen.ovr_WaitToBeginFrame auf, und wenn deine App bereit ist, mit dem Rendering des Einzelbilds zu beginnen, rufe ovr_BeginFrame auf. Wenn deine App bereit ist, das Einzelbild zu senden, rufe ovr_EndFrame auf und gib die Swap-Texturauswahl(en) aus dem vorherigen Schritt innerhalb einer ovrLayerEyeFov-Struktur weiter. Zwar ist nur ein einzelnes Layer erforderlich, um ein Einzelbild zu senden, aber du kannst für fortgeschrittenes Rendering auch mehrere Layers und Layer-Typen verwenden. ovr_EndFrame gibt Layer-Texturen an den Compositor weiter, der sich um Verzeichnung, Timewarp und GPU-Synchronisierung kümmert, bevor er sie dem Headset präsentiert. Beachte, dass die Kombination von ovr_WaitToBeginFrame, ovr_BeginFrame und ovr_EndFrame es dir ermöglicht, Techniken zur Leistungsoptimierung in Multi-Thread-Umgebungen zu implementieren, zum Beispiel durch eine aufgeteilte und überlappende Verarbeitung mehrerer Einzelbilder zur gleichen Zeit. In früheren Versionen waren diese Funktionen in ovr_SubmitFrame kombiniert, aber dieser Aufruf wurde jetzt eingestellt. Verwende bitte stattdessen ovr_WaitToBeginFrame, ovr_BeginFrame und ovr_EndFrame.ovr_DestroyTextureSwapChain auf, um Swap-Texturpuffer zu löschen. Rufe ovr_DestroyMirrorTexture, um eine Spiegeltextur zu löschen. Rufe ovr_Destroy ab, um das ovrSession-Objekt zu löschen.ovrTextureSwapChain zu. Der folgende Code zeigt, wie sich die erforderliche Texturgröße berechnen lässt:// 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). Die Funktion ovr_GetFovTextureSize berechnet die gewünschte Texturgröße für jedes Auge auf Grundlage dieser Parameter.ovr_GetSessionPhysicalDeviceVk ab, um das aktuelle physische Gerät mit übereinstimmender LUID zu erhalten. Erstelle dann ein mit dem zurückgegebenen physischen Gerät verknüpftes VkDevice.Win32_VulkanAppUtil.h, das der Beispiel-App OculusRoomTiny_Advanced beiliegt, die mit dem Oculus-SDK geliefert wird.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 auf, um die Warteschlange zu identifizieren.ovr_CreateTextureSwapChainGL, ovr_CreateTextureSwapChainDX oder ovr_CreateTextureSwapChainVk aufrufen, um die Textur-Swapchains auf API-spezifische Weise zuzuweisen.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]);
}
Hinweis: Für Direct3D 12 stellt der Aufrufer beim Aufruf vonovr_CreateTextureSwapChainDXeine ID3D12CommandQueue statt eines ID3D12Device für das SDK bereit. Es liegt in der Verantwortung des Aufrufers, sicherzustellen, dass sämtliches VR-Augentextur-Rendering in dieser ID3D12CommandQueue-Instanz ausgeführt wird. Alternativ kann sie als „Join-Node“-Fence verwendet werden, um auf die Befehlslisten zu warten, die durch andere Befehlswarteschlangen ausgeführt werden, welche die VR-Augentexturen rendern.
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. Dies wird auch für Apps empfohlen, die statische Texturen als Quad-Layer-Texturen für den Compositor bereitstellen. Wird dies unterlassen, sieht die Textur viel heller aus als erwartet.ovr_CreateTextureSwapChainDX bereitgestellte Texturformat vom Verzeichnungs-Compositor für ShaderResourceView verwendet, wenn der Inhalt der Textur gelesen wird. Demzufolge sollte die App Textur-Swapchain-Formate im sRGB-Raum anfordern (z. B. OVR_FORMAT_R8G8B8A8_UNORM_SRGB).OVR_FORMAT_R8G8B8A8_UNORM) und die Linear-Gamma-Konvertierung mittels HLSL-Code handhabt oder jegliche Gammakorrektur ignoriert, dann:OVR_FORMAT_R8G8B8A8_UNORM_SRGB).ovrTextureMisc_DX_Typeless-Flag in Desc.DXGI_FORMAT_R8G8B8A8_UNORM)Hinweis: DasovrTextureMisc_DX_Typeless-Flag für Tiefepufferformate (z. B. OVR_FORMAT_D32) wird ignoriert, da diese immer als Typeless konvertiert werden.
ovrTextureMisc_DX_Typeless-Flag in D3D 11 verwendet wird: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 beim Lesen des Texturinhalts. Demzufolge sollte die App Textur-Swapchain-Formate vorzugsweise im sRGB-Raum anfordern (z. B. OVR_FORMAT_R8G8B8A8_UNORM_SRGB). Außerdem sollte deine App glEnable (GL_FRAMEBUFFER_SRGB) aufrufen, bevor sie in diese Texturen rendert.OVR_FORMAT_R8G8B8A8_UNORM_SRGB).GL_FRAMEBUFFER_SRGB) auf, wenn in die Textur gerendert wird.ovr_CreateTextureSwapChainVk beim Lesen des Texturinhalts. Deine App sollte Textur-Swapchainformate im sRGB-Raum (z. B. OVR_FORMAT_R8G8B8A8_UNORM_SRGB) anfordern, da der Compositor sRGB-korrekt rendert. Der Compositor greift auf den GPU-Hardwaresampler zurück, um die sRGB-Linear-Konvertierung durchzuführen.OVR_FORMAT_R8G8B8A8_UNORM) und die Linear-Gamma-Konvertierung per SPIRV-Code handhabt, muss die App dennoch das entsprechende sRGB-Format anfordern und auch ovrTextureMisc_DX_Typeless im Flag-Feld von ovrTextureSwapChainDesc verwenden. Dies gestattet der App, ein RenderTargetView im linearen Format zu generieren, während der Compositor es als sRGB behandeln kann. Wird dies unterlassen, treten unerwartete Gammakurven-Artefakte auf. Das ovrTextureMisc_DX_Typeless-Flag für Tiefepufferformate (z. B. OVR_FORMAT_D32_FLOAT) wird ignoriert, da diese immer als Typeless konvertiert werden.ovr_CreateMirrorTextureDX, ovr_CreateMirrorTextureGL und ovr_CreateMirrorTextureWithOptionsVk für D3D, OpenGL beziehungsweise Vulkan.ovr_WaitToBeginFrame, ovr_BeginFrame und ovr_EndFrame. Nachdem das Einzelbild übermittelt wurde, kümmert sich der Compositor um Verzeichnung, Timewarp und GPU-Synchronisierung, bevor er es dem Headset präsentiert.// 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-Struktur enthält nützliche Werte für das Rendering, einschließlich der HmdToEyePose für jedes Auge. Die Augenansicht-Offsets werden später in Verbindung mit der Augentrennung berücksichtigt.ovrLayerEyeFovStruktur für ein Vollbild-Layer. Ab Oculus-SDK 0.6 werden bei der Einzelbildeinreichung Layers verwendet, um mehrere Ansichtsbilder oder Texture-Quads übereinander zu kombinieren. In diesem Beispiel wird ein einzelnes Layer verwendet, um eine VR-Szene zu präsentieren. Zu diesem Zweck verwenden wir ovrLayerEyeFov, das ein Dual-Eye-Layer beschreibt, welches das gesamte Sichtfeld ausfüllt. Da wir dasselbe Texturset für beide Augen verwenden, initialisieren wir beide Augenfarbtexturen zu pTextureSet und konfigurieren Viewports, um jeweils links und rechts von dieser gemeinsamen Textur zu zeichnen.// 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 gemeldet.ovr_GetTrackingState wissen, wann das aktuelle Einzelbild wirklich angezeigt wird. Der obige Code ruft ovr_GetPredictedDisplayTime auf, um displayMidpointSeconds für das aktuelle Einzelbild zu erhalten und daraus den bestmöglich prognostizierten Trackingstatus zu berechnen. Die Kopfhaltung aus dem Trackingstatus wird dann an ovr_CalcEyePoses weitergegeben, um die richtigen Ansichtshaltungen für jedes Auge zu berechnen. Diese Haltungen werden direkt im Array layer.RenderPose[2] gespeichert. Wenn die Augenhaltungen bereit sind, können wir mit dem eigentlichen Einzelbild-Rendering fortfahren.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 und originRot) mit der neuen Haltung, die auf Grundlage des Trackingstatus berechnet und im Layer gespeichert wurde. Diese Originalwerte können durch Eingaben modifiziert werden, um den*die Spieler*in in der 3D-Welt zu bewegen.ovr_EndFrame auf, um Einzelbilddaten an den Compositor weiterzugeben. Ab diesem Moment übernimmt der Compositor, indem er die Texturdaten über den gemeinsamen Speicher abruft, sie verzeichnet und auf dem Rift präsentiert.ovr_EndFrame gibt einen Wert zurück, sobald das gesendete Einzelbild in die Warteschlange eingereiht ist und die Runtime bereit ist, ein neues Einzelbild anzunehmen. Wenn erfolgreich, ist der zurückgegebene Wert entweder ovrSuccess oder ovrSuccess_NotVisible.ovrSuccess_NotVisible wird zurückgegeben, wenn das Einzelbild nicht angezeigt wurde; das kann passieren, wenn die VR-App den Fokus verliert. Unser Beispielcode handhabt diesen Fall durch Aktualisierung des isVisible-Flags, das durch die Renderinglogik geprüft wird. Während die Einzelbilder nicht zu sehen sind, sollte das Rendering pausiert werden, um unnötige GPU-Last zu vermeiden.ovrError_DisplayLost erhältst, wurde das Gerät entfernt und die Session ist ungültig. Gib die gemeinsamen Ressourcen frei (ovr_DestroyTextureSwapChain), lösche die Session (ovr_Destroy), erstelle sie neu (ovr_Create) und erstelle neue Ressourcen (ovr_CreateTextureSwapChainXXX). Die vorhandenen privaten Grafikressourcen der App müssen nicht neu erstellt werden, es sei denn, der neue ovr_Create-Aufruf gibt eine abweichende GraphicsLuid zurück.ovr_GetPredictedDisplayTime und verlässt sich dabei auf den von der App bereitgestellten Einzelbildindex, um korrekte Timing-Meldungen für unterschiedliche Threads zu gewährleisten.ovr_GetTimeInSeconds zurückgegeben. Die aktuelle Zeit sollte jedoch selten verwendet werden, da die Simulation und die Bewegungsprognose bessere Ergebnisse zeigen, wenn sie auf die durch ovr_GetPredictedDisplayTime zurückgegebenen Timingwerten zurückgreifen. Diese Funktion hat die folgende Signatur:ovr_GetPredictedDisplayTime(ovrSession session, long long frameIndex);
ovr_WaitToBeginFrame, ovr_BeginFrame und ovr_EndFrame weitergegeben werden. Details zum Multi-Thread-Timing findest du im nächsten Abschnitt Rendering auf verschiedenen Threads.ovr_GetPredictedDisplayTime entweder um ein Einzelbild abweichen, je nachdem, von welchem Thread die Funktion aufgerufen wird, oder sogar zufällige Fehler aufweisen, abhängig davon, wie die Threads geplant werden. Um dieses Problem anzugehen, wurde im vorherigen Abschnitt das Konzept des frameIndex vorgestellt, der von der App getrackt und zusammen mit Einzelbilddaten über Threads weitergegeben wird.ovr_EndFrame weitergegeben werden. (Dies muss geschehen, nachdem ovr_WaitToBeginFrame und ovr_BeginFrame aufgerufen wurden.)ovr_GetPredictedDisplayTime weiter, um das richtige Timing für die Haltungsprognose zu erhalten.ovr_GetTrackingState mit dem prognostizierten Zeitwert aufrufen. Er kann auch ovr_CalcEyePoses aufrufen, falls dies für die Rendering-Konfiguration nötig ist.ovr_BeginFrame und ovr_EndFrame weitergegeben.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 aufruft, speichert das Oculus-SDK die vom Aufrufer bereitgestellte ID3D12CommandQueue zur zukünftigen Verwendung im Cache. Wenn die App ovr_EndFrame aufruft, legt das SDK ein Fence um die zwischengespeicherte ID3D12CommandQueue an, um genau zu wissen, wann eine bestimmte Auswahl von Augentexturen bereit für den SDK-Compositor ist.ID3D12CommandQueue auf einem einzigen Thread zu verwenden. Sie könnte aber auch die CPU-Rendering-Last für jedes Augentexturpaar aufteilen oder die Rendering-Aufgaben ohne Bezug zu Augentexturen, wie Schatten, Reflexionskarten und so weiter, auf unterschiedliche Befehlswarteschlangen verteilen. Wenn die App Befehlslisten aus mehreren Threads befüllt und ausführt, muss sie auch dafür sorgen, dass die dem SDK bereitgestellte ID3D12CommandQueue als einziger Join-Node für die Augentextur-Renderingaufgaben dient, die durch unterschiedliche Befehlswarteschlangen ausgeführt werden.| Layer-Typ | Beschreibung |
|---|---|
EyeFov | Der standardmäßige Eye-Render-Buffer, der aus früheren SDKs bekannt ist. Typischerweise handelt es sich um ein Stereobild einer virtuellen Szene, die aus der Augenposition des*der Nutzer*in gerendert wird. Eye-Render-Buffer sind auch als Monobilder möglich, dies kann aber Unwohlsein verursachen. Frühere SDKs hatten ein implizites Sichtfeld (FOV) und Viewport. Diese werden nun explizit bereitgestellt und die App kann sie für jedes Einzelbild ändern, wenn gewünscht. |
Quad | Ein monoskopisches Bild, das als Rechteck mit einer bestimmten Haltung und Größe in der virtuellen Welt angezeigt wird. Dies ist nützlich für Heads-Up-Displays, Textinformationen, Objektlabels und so weiter. Standardmäßig wird die Haltung relativ zum physischen Raum des*der Nutzer*in spezifiziert. Das Quad bleibt im Raum fixiert, statt sich mit der Kopf- oder Körperbewegung des*der Nutzer*in mitzubewegen. Für kopffixierte Quads verwendest du das ovrLayerFlag_HeadLocked-Flag wie unten beschrieben. |
Cubemap | Eine Cubemap besteht aus sechs Rechtecken. Diese Rechtecke werden um den*die Nutzer*in herum angeordnet, als ob er*sie in einem würfelförmigen Raum sitzen würde. Jede Wand ist eine Textur, die von deiner App gesendet wird. Die Cubemap erscheint wie in unendlicher Entfernung und stellt im Wesentlichen den Hintergrund hinter allen anderen Objekten dar, die von deiner App gerendert werden. Die Cubemap sieht für den*die Nutzer*in nicht wie ein Würfel aus. Vielmehr wirkt sie einfach wie der Hintergrund in unendlicher Entfernung. Du kannst Cubemaps zum Beispiel verwenden, um den Himmel zu erstellen, der in deinem VR-Erlebnis hinter allen Gebäuden zu sehen sein wird. Du brauchst dich nicht um Verdeckungen durch Objekte im Vordergrund zu kümmern. Richte einfach die Cubemap ein, dann erscheint sie überall in deiner Szene im Hintergrund. |
EyeMatrix | Der Layer-Typ EyeMatrix ähnelt dem Layer-Typ EyeFov und wird bereitgestellt, um die Kompatibilität mit veralteten Samsung Gear VR-Apps zu unterstützen. |
Zylindrisch | Du kannst zylindrische Layers verwenden, um statt flachen Quads gebogene Quads zu erstellen. ovrLayerCylinder beschreibt ein Layer des Typs ovrLayerType_Cylinder: ein einzelner Zylinder, der relativ zu einem neu zentrierten Ursprungspunkt positioniert wird (in der Abbildung unten durch C dargestellt). Siehe Parameter für zylindrische Layers unten. |
Tiefepuffer | Dieses Layer spezifiziert eine monoskopische oder stereoskopische Ansicht, mit Tiefetexturen zusätzlich zu Farbtexturen. Dieses Layer wird durch das ovrLayerEyeFovDepth-Struct implementiert. Es ist äquivalent zu ovrLayerEyeFov, jedoch mit zusätzlichem DepthTexture und ProjectionDesc. Tiefepuffer werden in der Regel verwendet, um positionellen Timewarp zu unterstützen. |
Deaktiviert | Wird vom Compositor ignoriert, deaktivierte Layers nehmen keine Leistung in Anspruch. Wir empfehlen, dass Apps grundlegendes Frustum Culling durchführen und Layers außerhalb des Sichtfelds deaktivieren. Es ist jedoch nicht erforderlich, dass die App die Liste der aktiven Layers neu packt, wenn ein Layer deaktiviert wird; es reicht aus, es zu deaktivieren und in der Liste zu belassen. Gleichermaßen kann der Zeiger auf das Layer in der Liste auf Null gesetzt werden. |
ovrLayerCylinder in der Referenzdokumentation.
ovrLayerType-Enum sowie eine verknüpfte Struktur, welche die für die Anzeige dieses Layers benötigten Daten beinhaltet. So ist das Layer EyeFov vom Typ Nummer ovrLayerType_EyeFov und wird durch die Daten in der Struktur ovrLayerEyeFov beschrieben. Diese Strukturen verwenden eine gemeinsame Parameterauswahl, auch wenn nicht alle Layer-Typen alle Parameter erfordern:| Parameter | Typ | Beschreibung |
|---|---|---|
Header.Type | Enum ovrLayerType | Muss durch alle Layers festgelegt werden, um ihren Typ zu spezifizieren. |
Header.Flags | Ein Bitfield von ovrLayerFlags | Weitere Informationen dazu findest du unten. |
ColorTexture | TextureSwapChain | Stellt Farb- und Lichtdurchlässigkeitsdaten für das Layer bereit. Layers werden mittels prämultipliziertem Alpha überblendet. Dadurch können sie entweder lerp-Blending, additives Blending oder eine Kombination aus beidem ausdrücken. Die Layertexturen müssen als RGBA oder BGRA vorliegen und können Mip-Mapping aufweisen, dürfen aber keine Arrays oder Cubes sein oder MSAA aufweisen. Wenn die App MSAA-Rendering ausführen will, muss sie die MSAA-Zwischen-Farbtextur in die nicht-MSAA-ColorTexture umwandeln. |
Viewport | ovrRecti | Das Rechteck der tatsächlich verwendeten Textur, angegeben als ovrRecti-Struktur, die Größe und Position des Rechtecks in Form von zwei 2D-Vektoren bereitstellt, die jeweils Ganzzahlen für Breite/Höhe und x/y enthalten. Die für die Position bereitgestellten x/y-Werte geben die linke untere Ecke des Rechtecks an. Theoretisch sind Texturdaten außerhalb dieses Bereichs nicht im Layer sichtbar. Allerdings gelten die üblichen Einschränkungen in Verbindung mit der Texturabtastung, besonders bei Texturen mit Mip-Mapping. Es hat sich bewährt, einen Rand aus RGBA(0,0,0,0)-Pixeln um den angezeigten Bereich herum zu belassen, um ein „Ineinanderfließen“ zu verhindern, vor allem zwischen zwei nebeneinander in dieselbe Textur gepackten Eye-Render-Buffers. Die Größe des Rands ist vom genauen Anwendungsfall abhängig, aber in den meisten Fällen scheint es mit etwa 8 Pixeln gut zu funktionieren. |
Fov | ovrFovPort | Das Sichtfeld, das verwendet wird, um die Szene in einem Eye-Layer-Typ zu rendern. Beachte, dass dies nicht die HMD-Anzeige steuert, sondern einfach nur dem Compositor mitteilt, welches FOV zum Rendern der Texturdaten im Layer verwendet wurde. Der Compositor nimmt dann entsprechend dem tatsächlichen FOV des*der Nutzer*in geeignete Anpassungen vor. Apps können das FOV für Spezialeffekte dynamisch verändern. Die Verkleinerung des FOV kann außerdem bei langsameren Rechnern die Leistung verbessern, auch wenn es in der Regel wirksamer ist, die Auflösung zu senken, bevor das FOV verkleinert wird. |
RenderPose | ovrPosef | Die Kamerahaltung, die von der App verwendet wurde, um die Szene in einem Eye-Layer-Typ zu rendern. Wird in der Regel von SDK und App mithilfe der Funktionen ovr_GetTrackingState und ovr_CalcEyePoses prognostiziert. Die Differenz zwischen dieser Haltung und der tatsächlichen Haltung des Auges zum Anzeigezeitpunkt wird vom Compositor verwendet, um Timewarp auf das Layer anzuwenden. |
SensorSampleTime | Double | Die absolute Zeit zum Zeitpunkt der Abtastung des Tracking-Status durch die App. Dieser Wert wird in der Regel durch einen ovr_GetTimeInSeconds-Aufruf gleich neben einem ovr_GetTrackingState-Aufruf erhalten. Das SDK verwendet diesen Wert, um die Bewegung-zu-Photon-Latenz der App im Performance HUD zu melden. Wenn die App an einem beliebigen Einzelbild mehr als ein ovrLayerType_EyeFov-Layer eingereicht hat, bereinigt das SDK diese Layers und wählt das Timing mit der geringsten Latenz. Wenn in einem Einzelbild keine ovrLayerType_EyeFov-Layers eingereicht werden, verwendet das SDK den Zeitpunkt, zu dem ovr_GetTrackingState aufgerufen wurde, wobei der latencyMarker als Ersatz für die Bewegung-zu-Photon-Latenzzeit der App auf ovrTrue eingestellt ist. |
QuadPoseCenter | ovrPosef | Spezifiziert Ausrichtung und Position des Mittelpunkts eines Quad-Layer-Typs. Die angegebene Richtung ist der senkrecht zum Quad stehende Vektor. Die Position wird in echten Metern angegeben (keine Meter in der virtuellen App-Welt, sondern in der physischen Welt des*der Nutzer*in) und ist relativ zur Null-Position, die durch ovr_RecenterTrackingOrigin oder ovr_SpecifyTrackingOrigin festgelegt wird, es sei denn, das ovrLayerFlag_HeadLocked-Flag wird verwendet. |
QuadSize | ovrVector2f | Spezifiziert Breite und Höhe eines Quad-Layer-Typs. Wie bei der Position handelt es sich um Meter in der echten Welt. |
ovrTextureSwapChain für das linke und das rechte Auge sowie einen Viewport für jedes Auge bereit.ovrTextureSwapChain für das linke und das rechte Auge sowie einen unterschiedlichen Viewport für jedes Auge bereit. Dadurch kann die App sowohl linke als auch rechte Ansichten in denselben Textur-Buffer rendern. Denke daran, wie oben besprochen zwischen den beiden Ansichten einen kleinen Buffer einzufügen, um „Ineinanderfließen“ zu verhindern.ovrTextureSwapChain für das linke und das rechte Auge sowie denselben Viewport für jedes Auge bereit.ovrLayerFlag_HighQuality – ermöglicht vierfache anisotrope Filterung im Compositor für dieses Layer. Das kann eine erheblich verbesserte Lesbarkeit bewirken, besonders bei Verwendung mit einer Textur mit Mip-Mapping; dies wird für hochfrequente Bilder wie Texte oder Diagramme sowie bei Verwendung mit den Quad-Layer-Typen empfohlen. Für Eye-Layer-Typen steigert dies auch die visuelle Wiedergabetreue in Richtung Peripherie beziehungsweise beim Einspeisen von Texturen, die die empfohlene Pixeldichte von 1:1 überschreiten. Stelle für beste Ergebnisse sicher, dass es sich bei den Texturgrößen um Zweierpotenzen handelt, wenn du Mipmaps für die mit dem jeweiligen Layer verknüpften Texturen erstellst. Die App muss allerdings nicht die gesamte Textur rendern. Ein Viewport, der in der Textur auf die empfohlene Größe rendert, liefert das beste Verhältnis von Leistung zu Qualität.ovrLayerFlag_TextureOriginAtBottomLeft – es wird davon ausgegangen, dass der Ursprungspunkt einer Layer-Textur in der oberen linken Ecke liegt. Manche Engines (vor allem jene, die OpenGL verwenden) bevorzugen allerdings die untere linke Ecke als Ursprungspunkt und sollten dieses Flag verwenden.ovrLayerFlag_HeadLocked – für die meisten Layer-Typen wird die Haltung, Ausrichtung und Position relativ zur Null-Position spezifiziert, die durch Aufruf von ovr_RecenterTrackingOrigin definiert wird. Unter Umständen soll die App aber die Haltung eines Layers relativ zum Gesicht des*der Nutzer*in spezifizieren. Wenn der*die Nutzer*in den Kopf bewegt, folgt das Layer. Das ist nützlich für Fadenkreuze, die zum blickbasierten Zielen oder zur Auswahl verwendet werden. Dieses Flag kann für alle Layer-Typen verwendet werden, auch wenn es beim Direkt-Typ keinen Effekt hat.ovrTextureSwapChain gerendert wurde, die die App aktualisieren will, und ovr_CommitTextureSwapChain aufgerufen wurde, werden die Daten für jedes Layer in die relevante ovrLayerEyeFov-/ovrLayerQuad-/ovrLayerDirect-Struktur eingefügt. Die App erstellt dann eine Liste mit Zeigern auf diese Layer-Strukturen, insbesondere auf das Header-Feld, das garantiert das erste Mitglied jeder Struktur ist. Dann erstellt die App ein ovrViewScaleDesc-Struct mit den erforderlichen Daten und ruft die Funktionen ovr_WaitToBeginFrame, ovr_BeginFrame und ovr_EndFrame auf.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-Flag.ovr_EndFrame werden die Layers zur Anzeige eingereiht, zudem wird die Steuerung der übergebenen Texturen in der ovrTextureSwapChains an den Compositor übertragen. Es ist wichtig zu verstehen, dass diese Texturen von App und Compositor-Threads geteilt (und nicht kopiert) werden und dass die Komposition nicht unbedingt zum selben Zeitpunkt erfolgt, zu dem ovr_EndFrame aufgerufen wird. Gehe also sorgfältig vor. Um mit dem Rendering in eine Textur-Swapchain fortzufahren, sollte die App immer den nächsten verfügbaren Index mit ovr_GetTextureSwapChainCurrentIndex abrufen, bevor sie mit dem Rendering beginnt. Beispiel: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);
HmdToEyeOffset-Vektor spezifiziert, der von der ovr_GetRenderDesc-Funktion bereitgestellt wurde. Ab Version 1.17 wurde HmdToEyeOffset in HmdToEyePose umbenannt, wobei der Typ ovrPosef verwendet wird, der Position und Orientation enthält und Augenhaltungen so im Endeffekt sechs Freiheitsgrade verleiht. Das bedeutet, dass das Render Frustum jedes Auges jetzt zusätzlich zu seiner Translation durch das SDK auch von der HMD-Ausrichtung weg rotiert werden kann. Aus diesem Grund sind die Eye-Frustum-Achsen nicht mehr garantiert parallel zueinander oder zu den Achsen der HMD-Ausrichtung. Diese Generalisierung gibt dem SDK größeren Spielraum bei der Definition der HMD-Geometrie. Das heißt aber auch, dass du als VR-App-Entwickler*in in Bezug auf deine vorherigen Annahmen vorsichtiger sein musst, besonders wenn es ums Rendering geht.HmdToEyePose richtig verwendet:HmdToEyeOffset (vor Version 1.17) benötigt, kannst du stattdessen HmdToEyePose.Position verwenden. Wenn du dir mit deiner Vorgehensweise allerdings nicht absolut sicher bist, solltest du HmdToEyePose wahrscheinlich eher als vollständiges Transform behandeln, statt die Ausrichtung von der Position zu trennen.
ovrLayerQuad.ovrFovPort-Strukturen beider Augen, um die Vorteile verschiedener Renderingoptimierungen zu nutzen. Das geschieht normalerweise mithilfe einer ovrFovPort, die das Maximum der ovrFovPort-Werte für beide Augen auf allen vier Seiten des Frustums übernimmt. Bevor du das monoskopische Frustum jedoch auf diese Weise generierst, stelle sicher, dass du die ovrFovPort-Werte um jede potenzielle Rotation bereinigt hast, indem du FovPort::Uncant aufrufst, das sich im ovr_math.h-Header befindet. Siehe den OculusWorldDemo-Beispielcode zur Verwendung von FovPort::Uncant.



