Oculus Developer Guide
Oculus Developer Guide
Oculus Developer Guide
Version 0.6.0.0
2|Introduction|Oculus Rift
OCULUS VR, OCULUS, and RIFT are trademarks of Oculus VR, LLC. (C) Oculus VR, LLC. All rights reserved.
BLUETOOTH is a registered trademark of Bluetooth SIG, Inc. All other trademarks are the property of their
respective owners. Certain materials included in this publication are reprinted with the permission of the
copyright holder.
2||
Oculus Rift|Contents|3
Contents
LibOVR Integration.............................................................................................. 4
Overview of the SDK.........................................................................................................................................4
Integrating LibOVR............................................................................................................................................ 5
Chromatic Aberration.........................................................................................32
Chromatic Aberration...................................................................................................................................... 32
Sub-Channel Aberration.................................................................................................................................. 32
Since
Since
Since
Since
Release
Release
Release
Release
0.2............................................................................................................................. 35
0.3............................................................................................................................. 36
0.4............................................................................................................................. 37
0.5............................................................................................................................. 38
LibOVR Integration
The Oculus SDK is designed to be as easy to integrate as possible. This guide outlines a basic Oculus
integration with a C/C++ game engine or application.
Well discuss initializing the LibOVR, HMD device enumeration, head tracking, frame timing, and rendering for
the Rift.
Many of the code samples below are taken directly from the OculusRoomTiny demo source code (available in
Oculus/LibOVR/Samples/OculusRoomTiny). OculusRoomTiny and OculusWorldDemo are great places to
view sample integration code when in doubt about a particular system or feature.
Integrating LibOVR
To add Oculus support to a new application, do the following:
1. Initialize LibOVR.
2. Enumerate Oculus devices, create the ovrHmd object, and start sensor input.
3. Integrate head-tracking into your applications view and movement code. This involves:
a. Obtaining predicted headset orientation for the frame through a combination of the
ovrHmd_GetFrameTiming and ovrHmd_GetTrackingState calls.
b. Applying Rift orientation and position to the camera view, while combining it with other application
controls.
c. Modifying movement and game play to consider head orientation.
4. Initialize rendering for the HMD.
a. Select rendering parameters such as resolution and field of view based on HMD capabilities.
b. Configure rendering by creating D3D/OpenGL-specific swap texture sets to present data to the headset.
5. Modify application frame rendering to integrate HMD support and proper frame timing:
a. Make sure your engine supports rendering stereo views.
b. Add frame timing logic into the render loop to ensure that motion prediction and timewarp work
correctly.
c. Render each eyes view to intermediate render targets.
d. Submit the rendered frame to the headset by calling ovrHmd_SubmitFrame. This and other details of
frame rendering are covered later in the section on Frame Rendering.
6. Customize UI screens to work well inside of the headset.
ovr_Shutdown();
As you can see, ovr_Initialize is called before any other API functions and ovr_Shutdown is called to
shut down the library before you exit the program. In between these function calls, you are free to create HMD
objects, access tracking state, and perform application rendering.
In this example, ovrHmd_Create(0, &hmd) creates the first available HMD. ovrHmd_Create accesses
HMDs by index, which is an integer ranging from 0 to the value returned by ovrHmd_Detect. Users can call
ovrHmd_Detect any time after library initialization to re-enumerate the connected Oculus devices. Finally,
ovrHmd_Destroy must be called to clear the HMD before shutting down the library.
If no Rift is plugged in during detection, ovrHmd_Create(0, &hmd) fills in a null handle. In this case, you
can use ovrHmd_CreateDebug to create a virtual HMD of the specified type. Although the virtual HMD will
not provide any sensor input, it can be useful for debugging Rift-compatible rendering code and for general
development without a physical device.
TheovrHmd handle is actually a pointer to an ovrHmdDesc struct that contains information about the HMD and
its capabilities, which is used to set up rendering. The following table describes the fields:
Field
Type
Description
Type
ovrHmdType
ProductName
const char*
Manufacturer
const char*
VendorId
short
ProductId
short
Field
Type
Description
SerialNumber
char[]
FirmwareMajor
short
FirmwareMinor
short
CameraFrustumHFovInRadians float
CameraFrustumVFovInRadians float
CameraFrustumNearZInMeters float
CameraFrustumNearZInMeters float
HmdCaps
unsigned int
TrackingCaps
unsigned int
DefaultEyeFov
ovrFovPort[]
MaxEyeFov
ovrFovPort[]
EyeRenderOrder
ovrEyeType[]
Resolution
ovrSizei
ovrHmd_ConfigureTracking takes two sets of capability flags as input. These both use flags declared in
ovrTrackingCaps. supportedTrackingCaps describes the HMD tracking capabilities that the should
use when available. requiredTrackingCaps specifies capabilities that must be supported by the HMD
at the time of the call for the application to operate correctly. If the required capabilities are not present,
ovrHmd_ConfigureTracking will fail.
After tracking is initialized, you can poll sensor fusion for head position and orientation by calling
ovrHmd_GetTrackingState. These calls are demonstrated by the following code:
// Start the sensor which provides the Rifts pose and motion.
ovrHmd_ConfigureTracking(hmd, ovrTrackingCap_Orientation |
ovrTrackingCap_MagYawCorrection |
ovrTrackingCap_Position, 0);
// Query the HMD for the current tracking state.
ovrTrackingState ts = ovrHmd_GetTrackingState(hmd, ovr_GetTimeInSeconds());
if (ts.StatusFlags & (ovrStatus_OrientationTracked | ovrStatus_PositionTracked))
{
Posef pose = ts.HeadPose;
...
}
This example initializes the sensors with orientation, yaw correction, and position tracking capabilities if
available, while only requiring basic orientation tracking. This means that the code will work for DK1, but will
automatically use DK2 tracker-based position tracking. If you are using a DK2 headset and the DK2 tracker is
not available during the time of the call, but is plugged in later, the tracker is automatically enabled by the SDK.
After the sensors are initialized, the sensor state is obtained by calling ovrHmd_GetTrackingState.
This state includes the predicted head pose and the current tracking state of the HMD as described by
StatusFlags. This state can change at runtime based on the available devices and user behavior. For
example with DK2, the ovrStatus_PositionTracked flag is only reported when HeadPose includes the
absolute positional tracking data from the tracker.
The reported ovrPoseStatef includes full six degrees of freedom (6DoF) head tracking data including
orientation, position, and their first and second derivatives. The pose value is reported for a specified absolute
point in time using prediction, typically corresponding to the time in the future that this frames image will be
displayed on screen. To facilitate prediction, ovrHmd_GetTrackingState takes absolute time, in seconds, as
a second argument. The current value of absolute time can be obtained by calling ovr_GetTimeInSeconds.
If the time passed into ovrHmd_GetTrackingState is the current time or earlier, the tracking state returned
will be based on the latest sensor readings with no prediction. In a production application, however, you should
use the real-time computed value returned by ovrHmd_GetFrameTiming. Prediction is covered in more detail
in the section on Frame Timing.
As already discussed, the reported pose includes a 3D position vector and an orientation quaternion. The
orientation is reported as a rotation in a right-handed coordinate system, as illustrated in the following figure.
Figure 1: Rift Coordinate System
The x-z plane is aligned with the ground regardless of camera orientation.
As seen from the diagram, the coordinate system uses the following axis definitions:
Y is positive in the up direction.
X is positive to the right.
Z is positive heading backwards.
Rotation is maintained as a unit quaternion, but can also be reported in yaw-pitch-roll form. Positive rotation is
counter-clockwise (CCW, direction of the rotation arrows in the diagram) when looking in the negative direction
of each axis, and the component rotations are:
Pitch is rotation around X, positive when pitching up.
Yaw is rotation around Y , positive when turning left.
Roll is rotation around Z, positive when tilting to the left in the XY plane.
The simplest way to extract yaw-pitch-roll from ovrPose is to use the C++ OVR Math helper classes that are
included with the library. The following example uses direct conversion to assign ovrPosef to the equivalent C
++ Posef class. You can then use the Quatf::GetEulerAngles<> to extract the Euler angles in the desired
axis rotation order.
All simple C math types provided by OVR such as ovrVector3f and ovrQuatf have corresponding C++
types that provide constructors and operators for convenience. These types can be used interchangeably.
Position Tracking
The frustum is defined by the horizontal and vertical FOV, and the distance to the front and back frustum
planes.
Approximate values for these parameters can be accessed through the ovrHmdDesc struct as follows:
ovrHmd hmd;
ovrHmd_Create(0, &hmd);
if (hmd)
{
// Extract tracking frustum parameters.
float frustomHorizontalFOV = hmd->CameraFrustumHFovInRadians;
...
The following figure shows the DK2 position tracker mounted on a PC monitor and a representation of the
resulting tracking frustum.
Figure 2: Position Tracking Camera and Tracking Frustum
Type
Typical Value
CameraFrustumHFovInRadians float
CameraFrustumVFovInRadians float
CameraFrustumNearZInMeters float
0.4m
CameraFrustumFarZInMeters
2.5m
float
These parameters are provided to enable application developers to provide a visual representation of the
tracking frustum. The previous figure also shows the default tracking origin and associated coordinate system.
Note: Although the tracker axis (and hence the tracking frustum) are shown tilted downwards slightly,
the tracking coordinate system is always oriented horizontally such that the and axes are parallel to the
ground.
By default, the tracking origin is located one meter away from the tracker in the direction of the optical axis but
with the same height as the tracker. The default origin orientation is level with the ground with the negative
axis pointing towards the tracker. In other words, a headset yaw angle of zero corresponds to the user looking
towards the tracker.
Note: This can be modified using the API call ovrHmd_RecenterPose which resets the tracking origin
to the headsets current location, and sets the yaw origin to the current headset yaw value.
Note: The tracking origin is set on a per application basis; switching focus between different VR apps
also switches the tracking origin.
The head pose is returned by calling ovrHmd_GetTrackingState. The returned ovrTrackingState struct
contains several items relevant to position tracking:
HeadPoseincludes both head position and orientation.
CameraPosethe pose of the tracker relative to the tracking origin.
LeveledCameraPose the pose of the tracker relative to the tracking origin but with roll and pitch zeroed
out. You can use this as a reference point to render real-world objects in the correct place.
The StatusFlags variable contains three status bits relating to position tracking:
ovrStatus_PositionConnectedset when the position tracker is connected and functioning properly.
ovrStatus_PositionTrackedflag that is set only when the headset is being actively tracked.
ovrStatus_CameraPoseTrackedset after the initial tracker calibration has taken place. Typically this
requires the headset to be reasonably stationary within the view frustum for a second or so at the start of
tracking. It may be necessary to communicate this to the user if the ovrStatus_CameraPoseTracked flag
doesnt become set quickly after entering VR.
There are several conditions that may cause position tracking to be interrupted and for the flag to become
zero:
The headset moved wholly or partially outside the tracking frustum.
The headset adopts an orientation that is not easily trackable with the current hardware (for example facing
directly away from the tracker).
The exterior of the headset is partially or fully occluded from the trackers point of view (for example by hair
or hands).
The velocity of the headset exceeds the expected range.
Following an interruption, assuming the conditions above are no longer present, tracking normally resumes
quickly and the ovrStatus_PositionTracked flag is set.
To provide the most comfortable, intuitive, and usable interface for the player, head tracking should be
integrated with an existing control scheme for most applications.
For example, in a first person shooter (FPS) game, the player generally moves forward, backward, left, and
right using the left joystick, and looks left, right, up, and down using the right joystick. When using the Rift, the
player can now look left, right, up, and down, using their head. However, players should not be required to
frequently turn their heads 180 degrees since this creates a bad user experience. Generally, they need a way to
reorient themselves so that they are always comfortable (the same way in which we turn our bodies if we want
to look behind ourselves for more than a brief glance).
To summarize, developers should carefully consider their control schemes and how to integrate head-tracking
when designing applications for VR. The OculusRoomTiny application provides a source code sample that
shows how to integrate Oculus head tracking with the aforementioned standard FPS control scheme.
For more information about good and bad practices, refer to the Oculus Best Practices Guide.
Setting the value of HSWToggleEnabled to 1 enables the Disable Health and Safety Warning check box in the
Advanced Configuration panel of the Oculus Configuration Utility. For non-Windows builds, you must create an
environment variable named Oculus LibOVR HSWToggleEnabled with the value of 1.
Correcting for distortion can be challenging, with distortion parameters varying for different lens types and
individual eye relief. To make development easier, Oculus SDK handles distortion correction automatically
within the Oculus Compositor process; it also takes care of latency-reducing timewarp and presents frames to
the headset.
With Oculus SDK doing a lot of the work, the main job of the application is to perform simulation and render
stereo world based on the tracking pose. Stereo views can be rendered into either one or two individual
textures and are submitted to the compositor by calling ovrHmd_SubmitFrame. We cover this process in
detail in this section.
The lenses in the Rift magnify the image to provide a very wide field of view (FOV) that enhances immersion.
However, this process distorts the image significantly. If the engine were to display the original images on the
Rift, then the user would observe them with pincushion distortion.
Figure 4: Pincushion and Barrel Distortion
To counteract this distortion, the SDK applies post-processing to the rendered views with an equal and
opposite barrel distortion so that the two cancel each other out, resulting in an undistorted view for each eye.
Furthermore, the SDK also corrects chromatic aberration, which is a color separation effect at the edges caused
by the lens. Although the exact distortion parameters depend on the lens characteristics and eye position
relative to the lens, the Oculus SDK takes care of all necessary calculations when generating the distortion
mesh.
When rendering for the Rift, projection axes should be parallel to each other as illustrated in the following
figure, and the left and right views are completely independent of one another. This means that camera setup
is very similar to that used for normal non-stereo rendering, except that the cameras are shifted sideways to
adjust for each eye location.
Figure 5: HMD Eye View Cones
In practice, the projections in the Rift are often slightly off-center because our noses get in the way! But the
point remains, the left and right eye views in the Rift are entirely separate from each other, unlike stereo views
generated by a television or a cinema screen. This means you should be very careful if trying to use methods
developed for those media because they do not usually apply in VR.
The two virtual cameras in the scene should be positioned so that they are pointing in the same direction
(determined by the orientation of the HMD in the real world), and such that the distance between them is the
same as the distance between the eyes, or interpupillary distance (IPD). This is typically done by adding the
ovrEyeRenderDesc::HmdToEyeViewOffset translation vector to the translation component of the view
matrix.
Although the Rifts lenses are approximately the right distance apart for most users, they may not exactly match
the users IPD. However, because of the way the optics are designed, each eye will still see the correct view. It
is important that the software makes the distance between the virtual cameras match the users IPD as found in
their profile (set in the configuration utility), and not the distance between the Rifts lenses.
This section describes rendering initialization, including creation of swap texture sets.
Initially, you determine the rendering FOV and allocate the required ovrSwapTextureSet. The following code
shows how the required texture size can be computed:
// Configure Stereo settings.
Sizei recommenedTex0Size = ovrHmd_GetFovTextureSize(hmd, ovrEye_Left,
hmd->DefaultEyeFov[0], 1.0f);
Sizei recommenedTex1Size = ovrHmd_GetFovTextureSize(hmd, ovrEye_Right,
hmd->DefaultEyeFov[1], 1.0f);
Sizei bufferSize;
bufferSize.w = recommenedTex0Size.w + recommenedTex1Size.w;
bufferSize.h = max ( recommenedTex0Size.h, recommenedTex1Size.h );
Render texture size is determined based on the FOV and the desired pixel density at the center of the eye.
Although both the FOV and pixel density values can be modified to improve performance, this example uses
As can be seen from this example, ovrSwapTextureSet contains an array of ovrTexture objects, each
wrapping either a D3D texture handle or OpenGL texture ID that can be used for rendering. Here's a similar
example of texture set creation and access using Direct3D:
ovrSwapTextureSet *
pTextureSet = 0;
ID3D11RenderTargetView * pTexRtv[3];
D3D11_TEXTURE2D_DESC dsDesc;
dsDesc.Width
= bufferSize.w;
dsDesc.Height
= bufferSize.h;
dsDesc.MipLevels
= 1;
dsDesc.ArraySize
= 1;
dsDesc.Format
= DXGI_FORMAT_B8G8R8A8_UNORM;
dsDesc.SampleDesc.Count
= 1;
dsDesc.SampleDesc.Quality = 0;
dsDesc.Usage
= D3D11_USAGE_DEFAULT;
dsDesc.CPUAccessFlags
= 0;
dsDesc.MiscFlags
= 0;
dsDesc.BindFlags
= D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
if (ovrHmd_CreateSwapTextureSetD3D11(hmd, DIRECTX.Device, &dsDesc, &pTextureSet) == ovrSuccess)
{
for (int i = 0; i < pTextureSet->TextureCount; ++i)
{
ovrD3D11Texture* tex = (ovrD3D11Texture*)&pTextureSet->Textures[i];
DIRECTX.Device->CreateRenderTargetView(tex->D3D11.pTexture, NULL, &pTexRtv[i]);
}
}
In this case, newly created render target views can be used to perform eye texture rendering. The Frame
Rendering section of this guide describes viewport setup in more detail.
Frame Rendering
Frame rendering typically involves several steps: obtaining predicted eye poses based on the headset
tracking pose, rendering the view for each eye and, finally, submitting eye textures to the compositor through
ovrHmd_SubmitFrame. After the frame is submitted, the Oculus compositor handles distortion and presents it
on the Rift.
Before rendering frames it is helpful to initialize some data structures that can be shared across frames. As an
example, we query eye descriptors and initialize the layer structure outside of the rendering loop:
// Initialize VR structures, filling out description.
ovrEyeRenderDesc eyeRenderDesc[2];
ovrVector3f
hmdToEyeViewOffset[2];
eyeRenderDesc[0]
= ovrHmd_GetRenderDesc(hmd, ovrEye_Left, hmd->DefaultEyeFov[0]);
eyeRenderDesc[1]
= ovrHmd_GetRenderDesc(hmd, ovrEye_Right, hmd->DefaultEyeFov[1]);
hmdToEyeViewOffset[0] = eyeRenderDesc[0].HmdToEyeViewOffset;
hmdToEyeViewOffset[1] = eyeRenderDesc[1].HmdToEyeViewOffset;
// Initialize our single full screen Fov layer.
ovrLayerEyeFov layer;
layer.Header.Type
= ovrLayerType_EyeFov;
layer.Header.Flags
= 0;
layer.ColorTexture[0] = pTextureSet;
layer.ColorTexture[1] = pTextureSet;
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 is updated later per frame.
This code example first gets rendering descriptors for each eye, given the chosen FOV. The returned
ovrEyeRenderDescstructure contains useful values for rendering, including the HmdToEyeViewOffset for
each eye. Eye view offsets are used later to adjust for eye separation.
The code also initializes the ovrLayerEyeFov structure for a full screen layer. Starting with Oculus SDK
0.6, frame submission uses layers to composite multiple view images or texture quads on top of each other.
This example uses a single layer to present a VR scene. For this purpose, we use ovrLayerEyeFov, which
describes a dual-eye layer that covers the entire eye field of view. Since we are using the same texture set for
both eyes, we initialize both eye color textures to pTextureSet and configure viewports to draw to the left
and right sides of this shared texture, respectively.
Note: Although it is often enough to initialize viewports once in the beginning, specifying them as a
part of the layer structure that is submitted every frame allows applications to change render target size
dynamically, if desired. This is useful for optimizing rendering performance.
After setup completes, the application can begin generating the rendering loop. First, we need to get the eye
poses to render the left and right views.
// Get both eye poses simultaneously, with IPD offset already included.
ovrFrameTiming
ftiming = ovrHmd_GetFrameTiming(hmd, 0);
ovrTrackingState hmdState = ovrHmd_GetTrackingState(hmd, ftiming.DisplayMidpointSeconds);
ovr_CalcEyePoses(hmdState.HeadPose.ThePose, hmdToEyeViewOffset, layer.RenderPose);
In VR, rendered eye views depend on the headset position and orientation in the physical space, tracked with
the help of internal IMU and external trackers. Prediction is used to compensate for the latency in the system,
giving the best estimate for where the headset will be when the frame is displayed on the headset. In the
Oculus SDK, this tracked, predicted pose is reported by ovrHmd_GetTrackingState.
To do accurate prediction, ovrHmd_GetTrackingState needs to know when the current frame will actually
be displayed. The code above calls ovrHmd_GetFrameTiming to obtain DisplayMidpointSeconds for
the current frame, using it to compute the best predicted tracking state. The head pose from the tracking state
is then passed to ovr_CalcEyePoses to calculate correct view poses for each eye. These poses are stored
directly into the layer.RenderPose[2] array. With eye poses ready, we can proceed onto the actual frame
rendering.
if (isVisible)
{
// Increment to use next texture, just before writing
pTextureSet->CurrentIndex = (pTextureSet->CurrentIndex + 1) % pTextureSet->TextureCount;
// Clear and set up render-target.
DIRECTX.SetAndClearRenderTarget(pTexRtv[pTextureSet->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);
Frame Timing
The Oculus SDK reports frame timing information through the ovrHmd_GetFrameTiming function, relying on
the application-provided frame index to ensure correct timing is reported across different threads.
Accurate frame and sensor timing are required for accurate head motion prediction, which is essential for a
good VR experience. Prediction requires knowing exactly when in the future the current frame will appear
on the screen. If we know both sensor and display scanout times, we can predict the future head pose and
improve image stability. Computing these values incorrectly can lead to under or over-prediction, degrading
perceived latency, and potentially causing overshoot wobbles.
To ensure accurate timing, the Oculus SDK uses absolute system time, stored as a double, to represent sensor
and frame timing values. The current absolute time is returned by ovr_GetTimeInSeconds. Current time
should rarely be used, however, since simulation and motion prediction will prodice better results when relying
on the timing values returned by ovrHmd_GetFrameTiming. This function has the following signature:
ovrFrameTiming ovrHmd_GetFrameTiming(ovrHmd hmd, unsigned int frameIndex);
The frameIndex argument specifies which application frame we are rendering. Applications that make use
of multi-threaded rendering must keep an internal frame index and manually increment it, passing it across
threads along with frame data to ensure correct timing and prediction. The same frameIndex value must be
passed to ovrHmd_SubmitFrame as was used to obtain timing for the frame. The details of multi-threaded
timing are covered in the next section, Rendering on Different Threads on page 19.
A special frameIndex value of 0 can be used in both functions to request that the SDK keep track of frame
indices automatically. This, however, only works when all frame timing requests and render submission is done
on the same thread.
ovrFrameTiming provides the following set of absolute times values associated with the current frame:
DisplayMidpointSeconds
double
FrameIntervalSeconds
double
AppFrameIndex
unsigned int
DisplayFrameIndex
unsigned int
The most important value is DisplayMidpointSeconds, which is the predicted time frame will be displayed.
This value can be used for both simulation and rendering prediction. Other values can be used to make
estimates about future frames to detect if display frames were actually skipped.
In some engines, render processing is distributed across more than one thread.
For example, one thread may perform culling and render setup for each object in the scene (we'll call this the
main thread), while a second thread makes the actual D3D or OpenGL API calls (we'll call this the render
thread). Both of these threads may need accurate estimates of frame display time, so as to compute best
possible predictions of head pose.
The asynchronous nature of this approach makes this challenging: while the render thread is rendering a
frame, the main thread might be processing the next frame. This parallel frame processing may be out of
sync by exactly one frame or a fraction of a frame, depending on game engine design. If we used the default
global state to access frame timing, the result of ovrHmd_GetFrameTiming could either be off by one frame
depending which thread the function is called from, or worse, could be randomly incorrect depending on how
threads are scheduled. To addess this issue, previous section introduced the concept of frameIndex that is
tracked by the application and passed across threads along with frame data.
For multi-threaded rendering result to be correct, the following must be true: (a) pose prediction, computed
based on frame timing, must be consistent for the same frame regardless of which thread it is accessed from;
and (b) eye poses that were actually used for rendering must be passed into ovrHmd_SubmitFrame, along
with the frame index.
Here is a summary of steps you can take to ensure this is the case:
1. The main thread needs to assign a frame index to the current frame being processed for rendering. It would
increment this index each frame and pass it to ovrHmd_GetFrameTiming to obtain the correct timing for
pose prediction.
2. The main thread should call the thread safe function ovrHmd_GetTrackingState with the predicted time
value. It can also call ovr_CalcEyePoses if necessary for rendering setup.
3. Main thread needs to pass the current frame index and eye poses to the render thread, along with any
rendering commands or frame data it needs.
4. When the rendering commands executed on the render thread, developers need to make sure these things
hold:
a. The actual poses used for frame rendering are stored into the RenderPose for the layer.
b. The same value of frameIndex as was used on the main thead is passed into ovrHmd_SubmitFrame.
The following code illustrates this in more detail:
void MainThreadProcessing()
{
frameIndex++;
// Ask the API for the times when this frame is expected to be displayed.
ovrFrameTiming frameTiming = ovrHmd_GetFrameTiming(hmd, frameIndex);
// Get the corresponding predicted pose state.
ovrTrackingState state = ovrHmd_GetTrackingState(hmd, frameTiming.DisplayMidpointSeconds);
ovrPosef
eyePoses[2];
ovr_CalcEyePoses(state.HeadPose.ThePose, hmdToEyeViewOffset, eyePoses);
SetFrameHMDData(frameIndex, eyePoses);
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.
...
Layers
Similar to the way a monitor view can be composed of multiple windows, the display on the headset can be
composed of multiple layers. Typically at least one of these layers will be a view rendered from the user's virtual
eyeballs, but other layers may be HUD layers, information panels, text labels attached to items in the world,
aiming reticles, and so on.
Each layer can have a different resolution, can use a different texture format, can use a different field of view or
size, and might be in mono or stereo. The application can also be configured to not update a layer's texture if
the information in it has not changed. For example, it might not update if the text in an information panel has
not changed since last frame or if the layer is a picture-in-picture view of a video stream with a low framerate.
Applications can supply mipmapped textures to a layer and, together with a high-quality distortion mode, this
is very effective at improving the readability of text panels.
Every frame, all active layers are composited from back to front using pre-multiplied alpha blending. Layer 0 is
the furthest layer, layer 1 is on top of it, and so on; there is no depth-buffer intersection testing of layers, even if
a depth-buffer is supplied.
A powerful feature of layers is that each can be a different resolution. This allows an application to scale to
lower performance systems by dropping resolution on the main eye-buffer render that shows the virtual world,
but keeping essential information, such as text or a map, in a different layer at a higher resolution.
There are several layer types available:
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
mono eye buffers can be mono, they 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.
EyeFovDepth
An eye buffer render with depth buffer information for use with positional
timewarp (PTW). Our current support for depth-based PTW is experimental. It is
a rendering enhancement that is still in development and drops below full frame
rate; we welcome developer feedback on this layer type.
Note: The depth buffer is only used for timewarp correction and is not
used for occlusion (Z testing) between layer types. Additionally, in the
current version of the SDK, only layer #0 can be of this type and it only
reliably supports a D32F format.
QuadInWorld
QuadHeadLocked
Similar to the QuadInWorld type, but the pose is specified relative to the user's
face. When the user moves their head, the quad follows. This quad type is
useful for reticles used in gaze-based aiming or selection. However, it can be
uncomfortable if used for text information and it is usually preferable to use a
QuadInWorld layer type for this purpose.
Direct
Disabled
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
Parameter
Type
Description
Header.Type
Header.Flags
A bitfield of ovrLayerFlags.
ColorTexture
ovrSwapTextureSet
DepthTexture
ovrSwapTextureSet
ProjectionDesc
Viewport
Fov
Parameter
Type
Description
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
QuadPoseCenter
QuadSize
Layers that take stereo information (all those except Quad layer types) take two sets of most parameters, and
these can be used in three different ways:
Stereo data, separate texturesthe app supplies a different ovrSwapTextureSet for the left and right
eyes, and a viewport for each.
Stereo data, shared texturethe app supplies the same ovrSwapTextureSet for both left and right eyes,
but a different viewport for each. This allows the application to render both left and right views to the same
texture buffer.
Mono datathe app supplies the same ovrSwapTextureSet for both left and right eyes, and the same
viewport for each.
Texture and viewport sizes may be different for the left and right eyes, and each can even have different fields
of view. However beware of causing stereo disparity and discomfort in your users.
The flags available for all layers are a logical-or of the following:
layers.
*layerList[2];
&eyeLayer.Header;
&hudLayer.Header;
The compositor performs timewarp, distortion, and chromatic aberration correction on each layer separately
before blending them together; there is no intermediate filtering step between sampling the layer image and
the final framebuffer. This can provide a substantial improvement in text quality over the traditional method of
rendering the layer as a quad to the eye buffer, which involves a double-filter step (once to the eye buffer, then
once during distortion), especially when combined with the high-quality distortion flag.
One current disadvantage of layers is that no post-processing can be performed on the final composited
image, such as soft-focus effects, light-bloom effects, or the Z intersection of layer data.
Calling ovrHmd_SubmitFrame queues the layers for display, and transfers control of the CurrentIndex
texture inside the ovrSwapTextureSet to the compositor. It is important to understand that these textures
are being shared (rather than copied) between the application and the compositor threads, and that
composition does not necessarily happen at the time ovrHmd_SubmitFrame is called, so care must be taken.
Oculus strongly recommends that the application should not try to use or render to any of the textures and
indices that were submitted in the most recent ovrHmd_SubmitFrame call. For example:
// Create two SwapTextureSets to illustrate. Each will have two textures, [0] and [1].
ovrSwapTextureSet *eyeSwapTextureSet;
ovrHmd_CreateSwapTextureSetD3D11 ( ... &eyeSwapTextureSet );
ovrSwapTextureSet *hudSwapTextureSet;
ovrHmd_CreateSwapTextureSetD3D11 ( ... &hudSwapTextureSet );
// 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_QuadInWorld;
hudLayer...etc... // set up the rest of the data.
// the list of
ovrLayerHeader
layerList[0] =
layerList[1] =
//
//
//
//
//
layers
*layerList[2];
&eyeLayer.Header;
&hudLayer.Header;
// Frame 1.
eyeSwapTextureSet->CurrentIndex = 0;
hudSwapTextureSet->CurrentIndex = 0;
eyeLayer.ColorTexture[0] = eyeSwapTextureSet;
eyeLayer.ColorTexture[1] = eyeSwapTextureSet;
hudLayer.ColorTexture = hudSwapTextureSet;
ovrHmd_SubmitFrame(Hmd, 0, nullptr, layerList, 2);
//
//
//
//
//
Now,
eyeSwapTextureSet->Textures[0]:
eyeSwapTextureSet->Textures[1]:
hudSwapTextureSet->Textures[0]:
hudSwapTextureSet->Textures[1]:
in use by compositor
available
in use by compositor
available
// Frame 2.
eyeSwapTextureSet->CurrentIndex = 1;
AppRenderScene ( eyeSwapTextureSet->Textures[1] );
// App does not render to the HUD, does not change the layer setup.
ovrHmd_SubmitFrame(Hmd, 0, nullptr, layerList, 2);
//
//
//
//
//
Now,
eyeSwapTextureSet->Textures[0]:
eyeSwapTextureSet->Textures[1]:
hudSwapTextureSet->Textures[0]:
hudSwapTextureSet->Textures[1]:
available
in use by compositor
in use by compositor
available
// Frame 3.
eyeSwapTextureSet->CurrentIndex = 0;
AppRenderScene ( eyeSwapTextureSet->Textures[0] );
// App hides the HUD
hudLayer.Header.Type = ovrLayerType_Disabled;
ovrHmd_SubmitFrame(Hmd, 0, nullptr, layerList, 2);
//
//
//
//
//
Now,
eyeSwapTextureSet->Textures[0]:
eyeSwapTextureSet->Textures[1]:
hudSwapTextureSet->Textures[0]:
hudSwapTextureSet->Textures[1]:
in use by compositor
available
available
available
In other words, if the texture was used by the last ovrHmd_SubmitFrame call, don't try to render to it. If it
wasn't, you can.
=
=
=
=
Vector2i(0,0);
Sizei(renderTargetSize.w / 2, renderTargetSize.h);
Vector2i((renderTargetSize.w + 1) / 2, 0);
EyeRenderViewport[0].Size;
For SDK distortion rendering, this data is passed into ovrHmd_ConfigureRendering as follows (code shown
is for the D3D11 API):
ovrEyeRenderDesc eyeRenderDesc[2];
ovrBool result = ovrHmd_ConfigureRendering(hmd, &d3d11cfg.Config,
ovrDistortion_Chromatic | ovrDistortion_TimeWarp,
eyeFov, eyeRenderDesc);
You are free to choose the render target texture size and left and right eye viewports as you like, provided
that you specify these values when calling ovrHmd_EndFrame using the ovrTexture. However, using
ovrHmd_GetFovTextureSize will ensure that you allocate the optimum size for the particular HMD in use.
The following sections describe how to modify the default configurations to make quality and performance
trade-offs. You should also note that the API supports using different render targets for each eye if that is
required by your engine (although using a single render target is likely to perform better since it will reduce
context switches). OculusWorldDemo allows you to toggle between using a single combined render target
versus separate ones for each eye, by navigating to the settings menu (press the Tab key) and selecting the
Share RenderTarget option.
53.6 degrees up
58.9 degrees down
50.3 degrees inwards (towards the nose)
58.7 degrees outwards (away from the nose)
In the code and documentation, these are referred to as half angles because traditionally a FOV is expressed
as the total edge-to-edge angle. In this example, the total horizontal FOV is 50.3+58.7 = 109.0 degrees, and
the total vertical FOV is 53.6+58.9 = 112.5 degrees.
The recommended and maximum fields of view can be accessed from the HMD as shown below:
ovrFovPort defaultLeftFOV = hmd->DefaultEyeFov[ovrEye_Left];
ovrFovPort maxLeftFOV = hmd->MaxEyeFov[ovrEye_Left];
DefaultEyeFov refers to the recommended FOV values based on the current users profile settings (IPD, eye
relief etc). MaxEyeFov refers to the maximum FOV that the headset can possibly display, regardless of profile
settings.
The default values provide a good user experience with no unnecessary additional GPU load. If your application
does not consume significant GPU resources, you might want to use the maximum FOV settings to reduce
reliance on the accuracy of the profile settings. You might provide a slider in the application control panel
that enables users to choose interpolated FOV settings between the default and the maximum. But, if your
application is heavy on GPU usage, you might want to reduce the FOV below the default values as described in
Improving Performance by Decreasing Field of View on page 30.
The chosen FOV values should be passed into ovrHmd_ConfigureRendering.
The FOV angles for up, down, left, and right (expressed as the tangents of the half-angles), is the most
convenient form to set up culling or portal boundaries in your graphics engine. The FOV values are also used
to determine the projection matrix used during left and right eye scene rendering. We provide an API utility
function ovrMatrix4f_Projection for this purpose:
ovrFovPort fov;
// Determine fov.
...
ovrMatrix4f projMatrix = ovrMatrix4f_Projection(fov, znear, zfar, isRightHanded);
It is common for the top and bottom edges of the FOV to not be the same as the left and right edges when
viewing a PC monitor. This is commonly called the aspect ratio of the display, and very few displays are
square. However, some graphics engines do not support off-center frustums. To be compatible with these
engines, you will need to modify the FOV values reported by the ovrHmdDesc struct. In general, it is better to
grow the edges than to shrink them. This will put a little more strain on the graphics engine, but will give the
user the full immersive experience, even if they wont be able to see some of the pixels being rendered.
Some graphics engines require that you express symmetrical horizontal and vertical fields of view, and some
need an even less direct method such as a horizontal FOV and an aspect ratio. Some also object to having
frequent changes of FOV, and may insist that both eyes be set to the same. The following is a an example of
code for handling this restrictive case:
= fovBoth;
= fovBoth;
...
// Compute the parameters to feed to the rendering engine.
// In this case we are assuming it wants a horizontal FOV and an aspect ratio.
float horizontalFullFovInRadians = 2.0f * atanf ( combinedTanHalfFovHorizontal );
float aspectRatio = combinedTanHalfFovHorizontal / combinedTanHalfFovVertical;
GraphicsEngineSetFovAndAspect ( horizontalFullFovInRadians, aspectRatio );
...
Note: You will need to determine FOV before creating the render targets, since FOV affects the size of
the recommended render target required for a given quality.
Although you can set the parameter to a value larger than 1.0 to produce a higher-resolution intermediate
render target, Oculus hasn't observed any useful increase in quality and it has a high performance cost.
OculusWorldDemo allows you to experiment with changing the render target pixel density. Navigate to the
settings menu (press the Tab key) and select Pixel Density. Press the up and down arrow keys to adjust the pixel
density at the center of the eye projection. A value of 1.0 sets the render target pixel density to the display
surface 1:1 at this point on the display. A value of 0.5 means sets the density of the render target pixels to half
of the display surface. Additionally, you can select Dynamic Res Scaling which will cause the pixel density to
automatically adjust between 0 to 1.
= newFovLeft;
= newFovRight;
...
// Determine projection matrices.
ovrMatrix4f projMatrixLeft = ovrMatrix4f_Projection(newFovLeft, znear, zfar, isRightHanded);
ovrMatrix4f projMatrixRight = ovrMatrix4f_Projection(newFovRight, znear, zfar, isRightHanded);
It might be interesting to experiment with non-square fields of view. For example, clamping the up and down
ranges significantly (e.g. 70 degrees FOV) while retaining the full horizontal FOV for a Cinemascope feel.
OculusWorldDemo allows you to experiment with reducing the FOV below the defaults. Navigate to the
settings menu (press the Tab key) and select the Max FOV value. Pressing the up and down arrows to change
the maximum angle in degrees.
Chromatic Aberration
Chromatic aberration is a visual artifact seen when viewing images through lenses.
The phenomenon causes colored fringes to be visible around objects, and is increasingly more apparent as
our view shifts away from the center of the lens. The effect is due to the refractive index of the lens varying for
different wavelengths of light (shorter wavelengths towards the blue end of the spectrum are refracted less than
longer wavelengths towards the red end). Since the image displayed on the Rift is composed of individual red,
green, and blue pixels,2 it is susceptible to the unwanted effects of chromatic aberration. The manifestation,
when looking through the Rift, is that the red, green, and blue components of the image appear to be scaled
out radially, and by differing amounts. Exactly how apparent the effect is depends on the image content and to
what degree users are concentrating on the periphery of the image versus the center.
Chromatic Aberration
Fortunately, programmable GPUs enable you to significantly reduce the degree of visible chromatic aberration,
albeit at some additional GPU expense.
To do this, pre-transform the image so that the chromatic aberration of the lens will result in a more normal
looking image. This is analogous to the way in which we pre-distort the image to cancel out the distortion
effects generated by the lens.
Sub-Channel Aberration
Although we can reduce the artifacts through the use of distortion correction, we cannot completely remove
them for an LCD display panel.
This is due to the fact that each color channel is actually comprised of a range of visible wavelengths, each of
which is refracted by a different amount when viewed through the lens. As a result, although we are able to
distort the image for each channel to bring the peak frequencies back into spatial alignment, it is not possible
to compensate for the aberration that occurs within a color channel. Typically, when designing optical systems,
chromatic aberration across a wide range of wavelengths is managed by carefully combining specific optical
elements (in other texts, for example, look for achromatic doublets).
Accelerometer;
Gyro;
Magnetometer;
Temperature;
TimeInSeconds;
//
//
//
//
//
Over long periods of time, a discrepancy will develop between Q (the current head pose estimate) and the true
orientation of the Rift. This problem is called drift error, which described more in the following section. Errors in
pitch and roll are automatically reduced by using accelerometer data to estimate the gravity vector.
Errors in yaw are reduced by magnetometer data. For many games, such as a standard First Person Shooter
(FPS), the yaw direction is frequently modified by the game controller and there is no problem. However,
in many other games or applications, the yaw error will need to be corrected. For example, if you want to
maintain a cockpit directly in front of the player. It should not unintentionally drift to the side over time. Yaw
error correction is enabled by default.
Initialization
OVR::System::Init,
DeviceManager, HMDDevice,
HMDInfo.
ovr_Initialize,
ovrHmd_Create, ovrHmd handle
and ovrHmdDesc.
Sensor Interaction
OVR::SensorFusion class,
with GetOrientation returning
Quatf. Prediction amounts are
specified manually relative to the
current time.
ovrHmd_ConfigureTracking,
ovrHmd_GetTrackingState
returning ovrTrackingState.
ovrHmd_GetEyePoses returns
head pose based on correct timing.
Rendering Setup
Util::Render::StereoConfig
helper class creating
StereoEyeParams, or manual
setup based on members of
HMDInfo.
ovrHmd_ConfigureRendering
populates ovrEyeRenderDesc
based on the field of
view. Alternatively,
ovrHmd_GetRenderDesc
supports rendering setup for client
distortion rendering.
Distortion Rendering
Functionality
Frame Timing
Removed ovrTrackingState::LastVisionProcessingTime.
Removed ovrTrackingState::LastVisionFrameLatency.
ovr_Initialize now takes a params argument. See the in-code documentation for details.
ovr_Initialize now returns false for additional reasons.
No API functions can be called after ovr_Shutdown except ovr_Initialize.
The hmdToEyeViewOffset argument for ovrHmd_GetEyePosess is now const.
Added the ovrQuatf playerTorsoMotion argument to ovrHmd_GetEyeTimewarpMatricesDebug.
Added ovr_TraceMessage.
ovrHmdDesc no longer contains display device information, as the service-based compositor now handles
the display device.
Simplified ovrFrameTiming to only return the DisplayMidpointSeconds prediction timing value. All
other timing information is now available though the thread-safe ovrHmd_GetFrameTiming. The
ovrHmd_BeginFrameTiming and EndFrameTiming functions were removed.
Removed the LatencyTest functions (e.g. ovrHmd_GetLatencyTestResult).
Removed the PerfLog functions (e.g. ovrHmd_StartPerfLog), as these are effectively replaced by
ovrLogCallback (introduced in SDK 0.5).
Removed the health-and-safety-warning related functions (e.g. ovrHmd_GetHSWDisplayState). The HSW
functionality is now handled automatically.
Removed support for automatic HMD mirroring. Applications can now create a mirror texture (e.g. with
ovrHmd_CreateMirrorTextureD3D11 / ovrHmd_DestroyMirrorTexture) and manually display it in a desktop
window instead. This gives developers flexibility to use the application window in a manner that best suits
their needs, and removes the OpenGL problem with previous SDKs in which the application back-buffer
limited the HMD render size.
Added ovrInitParams::ConnectionTimeoutMS, which allows the specification of a timeout for ovr_Initialize to
successfully complete.
Removed ovrHmd_GetHmdPosePerEye and added ovr_CalcEyePoses.
Bugs Fixed Since the Last Release
The following are bugs fixed since 0.5:
HmdToEyeViewOffset provided the opposite of the expected result; it now properly returns a vector to each
eye's position from the center.
If both the left and right views are rendered to the same texture, there is less "bleeding" between the two.
Apps still need to keep a buffer zone between the two regions to prevent texture filtering from picking
up data from the adjacent eye, but the buffer zone is much smaller than before. We recommend about 8
pixels, rather than the previously recommended 100 pixels. Because systems vary, feedback on this matter is
appreciated.
Fixed a crash when switching between Direct and Extended Modes
Fixed performance and judder issues in Extended Mode
Switching from Extended Mode to Direct Mode while running Oculus World Demo causes sideways
rendering.
Judder with Oculus Room Tiny Open GL examples in Windows 7