Впервые на русском: Документация Oculus для разработчиков. Руководство ПК SDK для разработчика. Рендеринг в Oculus Rift

Продолжаем публикацию эксклюзивной информации для разработчиков от Oculus. Новый раздел посвящён рендерингу в Oculus Rift. 

Документация Oculus для разработчиков:

  1. Введение в рекомендации
  2. Бинокулярное зрение, стереоскопическая визуализация и индикаторы глубины
  3. Поле зрения и масштаб
  4. Методы рендеринга
  5. Движение
  6. «Болезнь симуляции»
  7. Пользовательский интерфейс
  8. Пользовательский ввод и навигация
  9. Заключение
  10. Введение в SDK для ПК
  11. Руководство разработчика
  12. Руководство по началу работы с ПК SDK
  13. Руководство ПК SDK для разработчика. Интеграция LibOVR
  14. Инициализация и перечень сенсоров
  15. Рендеринг в Oculus Rift

Чтобы оставаться в курсе новостей о виртуальной реальности, подписывайтесь на наш Telegram!

Oculus Rift требует разделённого экрана с коррекцией искажений для каждого глаза для того, чтобы отменить искажения, связанные с линзами.

Рисунок 5. Стерео-рендеринг OculusWorldDemo

Коррекция искажения может быть сложной задачей, при этом параметры искажения могут различаться для разных типов линз и для индивидуальных рельефов глаз. Чтобы облегчить процесс разработки, Oculus SDK автоматически выполняет коррекцию искажений в процессе Oculus Compositor; он также позаботится о снижении времени задержки и предоставит кадры для шлема виртуальной реальности.

Рендеринг в Oculus Rift

Oculus Rift требует, чтобы каждая сцена рендерилась в разделённом на две половины стерео-экране, каждая из которых используется для отдельного глаза.

При использовании Oculus Rift левый глаз видит левую половину экрана, а правый глаз видит правую половину. Несмотря на то, что глаза человека могут отличаться в зависимости от самого человека, они находятся обычно примерно на расстоянии 65 мм друг от друга. Это расстояние также известно как межзрачковое расстояние (IPD). Камеры в приложениях должны быть сконфигурированы с одинаковым разделением.

Примечание: Это перевод камеры, а не вращение, и именно этот перевод (и эффект параллакса, который идет вместе с ним) вызывает стереоскопический эффект. Это означает, что вашему приложению потребуется выполнить рендеринг каждой сцены дважды, один раз с левой виртуальной камерой, и один раз с правой.

Техника рендеринга стереовоспроизведения, которая основана на представлении левого и правого изображения, создаваемого из одного полностью визуализированного образа, обычно не является жизнеспособной в случае со шлемом виртуальной реальности из-за значительных дефектов на краях объекта.

Линзы в Oculus Rift увеличивают изображение таким образом, чтобы обеспечить очень широкое поле зрения (FOV), которое улучшает погружение. Однако этот процесс значительно искажает изображение. Если бы движок показывал исходные изображения в Oculus Rift, то пользователь наблюдал бы их с подушкообразным искажением.

Рисунок 6. Подушкообразное и бочкообразное искажение

Чтобы противодействовать этому искажению, SDK применяет постобработку к визуализированным видам с равным и противоположным бочкообразным искажением таким образом, что они взаимно компенсируют друг друга, приводя к неискаженному виду для каждого глаза. Кроме того, SDK также исправляет хроматическую аберрацию, которая представляет собой эффект цветоделения на краях, что вызывается линзой. Хотя параметры точного искажения зависят от характеристик линз и положения глаз относительно линз, Oculus SDK выполняет все необходимые вычисления при создании искажения сетки.

При рендеринге для в Oculus Rift проекционные оси должны быть параллельны друг другу, как показано на следующем рисунке, а левый и правый виды полностью независимы друг от друга. Это означает, что настройка камеры очень похожа на ту, что используется для обычного не-стерео-рендеринга, за исключением того, что камеры сдвигаются в сторону для регулировки каждого из положений глаз.

Рисунок 7. Конусы глаз шлема виртуальной реальности.

На практике проекции в Oculus Rift часто слегка смещены, потому что наш нос мешает. Но точка остаётся, в которой левый и правый глаза в Oculus Rift полностью отделены друг от друга, в отличие от стереоизображений, создаваемых телевизором или киноэкраном. Это означает, что вы должны быть очень осторожны, если пытаетесь использовать методы, разработанные для этих носителей, потому что они обычно не применяются в виртуальной реальности.

Две виртуальные камеры в одной сцене должны располагаться таким образом, чтобы они указывали в одном направлении (который определяется ориентацией шлема виртуальной реальности в реальном мире), и так, чтобы расстояние между ними было таким же, как расстояние между глазами или межзрачковое расстояние (IPD). Это обычно делается путём добавления вектора трансляции ovrEyeRenderDesc::HmdToEyeOffset к компоненту перевода матрицы представлений.

Хотя для большинства пользователей линзы Oculus Rift располагаются примерно на равном расстоянии друг от друга, они могут не совсем точно соответствовать межзрачкового расстояния пользователя. Однако из-за того, как устроена оптическая система, каждый глаз всё равно будет получать правильный вид. Важно, чтобы программное обеспечение заставляло расстояние между виртуальными камерами соответствовать межзрачковому расстоянию пользователя так, как указано в их профиле (установленном в утилите конфигурации), а не расстоянию между объективами Oculus Rift.

Схема настройки рендеринга

Oculus SDK использует процесс компоновщика для представления кадров и обработки искажений.

Для того, чтобы настроить таргетинг на Oculus Rift, визуализируйте сцену в одну или две текстуры рендеринга, передавая эти текстуры в API. Рантайм Oculus обрабатывает искажение, синхронизацию графического процессора, синхронизацию кадра и представление кадра в шлем виртуальной реальности.

Ниже приведены шаги для рендеринга SDK:

  1. Инициализируйте:
  • Инициализируйте Oculus SDK и создайте объект ovrSession для шлема виртуальной реальности так, как было описано ранее.
  • Выделите объекты ovrTextureSwapChain, используемые для представления буферов глаза, с помощью API-интерфейса: вызовите ovr_CreateTextureSwapChainDX для Direct3D 11 или 12 или ovr_CreateTextureSwapChainGL для OpenGL.
  1. Настройте обработку кадров:
  • Используйте ovr_GetTrackingState и ovr_CalcEyePoses для вычисления позиций глаз, необходимых для визуализации представления на основе информации о времени кадра.
  • Произведите рендеринг для каждого глаза в зависимости от движка, отображая текущую текстуру внутри набора текстур. Текущую текстуру извлекают с помощью ovr_GetTextureSwapChainCurrentIndex() и ovr_GetTextureSwapChainBufferDX() или ovr_GetTextureSwapChainBufferGL(). После того, как рендеринг текстуры завершен, приложение должно вызвать ovr_CommitTextureSwapChain.
  • Вызовите ovr_SubmitFrame, передавая набор(ы) свопов текстур с предыдущего шага в структуре ovrLayerEyeFov. И хотя для отправки кадра требуется один слой, для расширенного рендеринга можно использовать несколько слоев и типов слоев. ovr_SubmitFrame передаёт текстуры слоев в компоновщик, который обрабатывает синхронизацию искажений, таймрап и графическое отображение перед представлением их на шлем виртуальной реальности.
  1. Выключите:
  • Вызовите ovr_DestroyTextureSwapChain для уничтожения буферов текстурных свопов. Вызовите  ovr_DestroyMirrorTexture для того, чтобы уничтожить зеркальную текстуру. Чтобы уничтожить объект ovrSession, вызовите ovr_Destroy.
Инициализация последовательности обмена текстурами

В этом разделе описывается инициализация рендеринга, в том числе создание цепочек обмена текстурой.

Сначала вы определяете рендеринг поля зрения и выделяете требуемую ovrTextureSwapChain. Следующий код показывает, как можно вычислить требуемый размер текстуры:

 

 

// Configure Stereo settings.

Sizei recommenedTex0Size = ovr_GetFovTextureSize(session, ovrEye_Left,

session->DefaultEyeFov[0], 1.0f);

Sizei recommenedTex1Size = ovr_GetFovTextureSize(session, ovrEye_Right,

session->DefaultEyeFov[1], 1.0f);

Sizei bufferSize;

bufferSize.w  = recommenedTex0Size.w + recommenedTex1Size.w;

bufferSize.h = max ( recommenedTex0Size.h, recommenedTex1Size.h );

 

Размер текстуры рендеринга определяется на основе поля зрения и требуемой плотности пикселей в центре глаза. Хотя значения поля зрения и плотности пикселей могут быть изменены для повышения производительности, в этом примере используется рекомендуемое знаничение поля зрения (которое получено из сеанса DefaultEyeFov). Функция ovr_GetFovTextureSize вычисляет желаемый размер текстуры для каждого глаза на основе этих параметров.

API Oculus позволяет приложению использовать одну общую текстуру или две отдельные текстуры для визуализации глаз. В этом примере для простоты используется одна общая текстура, что делает её достаточно большой для того, чтобы она соответствовала визуализации глаз. Как только размер текстуры известен, приложение может 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 вызывающий объект предоставляет ID3D12CommandQueue вместо ID3D12Device для SDK. Обязанностью вызывающего объекта является обеспечение того, чтобы этот экземпляр ID3D12CommandQueue выполнял визуализацию всей текстуры для рендеринга вида для глаза, который исполняется командой. Или его можно использовать в качестве забора типа «соединение-узел» для того, чтобы ждать списков команд, выполняемых другими командами, визуализирующими текстуры для вида глаз в виртуальной реальности.

Как только эти текстуры и цели рендеринга будут успешно созданы, вы можете использовать их для рендеринга текстур глаз. Раздел «Рендеринг кадра» описывает более подробно настройку экрана вида.

Компилятор Oculus обеспечивает sRGB-корректный рендеринг, который позволяет получить более фотореалистичные визуальные эффекты, лучшие MSAA и энергосберегающие текстурирования, что очень важно для приложений для виртуальной реальности. Как показано выше, ожидается, что приложения создадут sRGB-схемы обмена текстурами. Правильная обработка sRGB рендеринга является сложным предметом, и, хотя в этом разделе предоставляется обзор, обширная информация выходит за рамки этого текста.

Существует несколько шагов для того, чтобы обеспечить рендеринг в режиме реального времени приложения с поддержкой sRGB, а также существуют различные способы его достижения. Например, большинство графических процессоров обеспечивают аппаратное ускорение для того, чтобы улучшить гамма-коррекцию затенения для конкретных входных и выходных sRGB поверхностей, в то время как некоторые приложения используют математику шейдеров графических процессоров для того, чтобы получить более индивидуальное управление. Для Oculus SDK, когда приложение проходит в sRGB-пространстве последовательности текстурного свопа, компоновщик полагается на сэмплер графического редактора доя того, чтобы выполнить преобразование sRGB в линейное.

Все цветные текстуры, которые подаются в шейдер графического процессора, должны быть соответствующим образом маркированы с правильным форматом sRGB, таким как OVR_FORMAT_R8G8B8A8_UNORM_SRGB. Эта рекомендация также актуальна для приложений, которые предоставляют статические текстуры в виде четырехслойных текстур для компоновщика Oculus. Если этого не сделать, текстура будет выглядеть намного ярче, чем ожидалось.

Для D3D 11 и 12 формат текстур, предоставляемый в descдля ovr_CreateTextureSwapChainDX, используется компоновщиком искажений для ShaderResourceView при считывании содержимого текстуры. В результате приложение должно запрашивать форматы структуры обмена свопами, которые находятся в sRGB-пространстве (например, OVR_FORMAT_R8G8B8A8_UNORM_SRGB).

Если ваше приложение настроено на визуализацию в текстуру линейного формата (например, OVR_FORMAT_R8G8B8A8_UNORM) и обрабатывает преобразование линейной гаммы с использованием кода HLSL или не заботится о какой-либо гамма-коррекции, то:

  • Запросите цепочку обмена текстурами формата sRGB (например, OVR_FORMAT_R8G8B8A8_UNORM_SRGB).
  • Укажите переменную ovrTextureMisc_DX_Typeless в desc.
  • Создайте RenderTargetView линейного формата (например, DXGI_FORMAT_R8G8B8A8_UNORM).

Примечание: Переменная The ovrTextureMisc_DX_Typeless для форматов буферов глубины (например, OVR_FORMAT_D32) игнорируется, поскольку она всегда преобразуется в непечатную.

Для OpenGL параметр формата ofovr_CreateTextureSwapChainG используется компоновщиком искажений при чтении содержимого текстуры. В результате приложение должно запросить форматы обмена текстурным свопом предпочтительно в пространстве sRGB (например, OVR_FORMAT_R8G8B8A8_UNORM_SRGB).

Кроме того, ваше приложение должно вызывать glEnable(GL_FRAMEBUFFER_SRGB) перед рендерингом в эти текстуры.

И пусть это не рекомендуется, если ваше приложение настроено на обработку текстуры в линейном формате (например, GL_RGBA) и выполняет преобразование линейного формата в гамма-формат в GLSL или не заботится о гамма-коррекции, тогда:

  • Запросите цепочку обмена текстурами формата sRGB (например, OVR_FORMAT_R8G8B8A8_UNORM_SRGB).
  • Не вызывайте glEnable(GL_FRAMEBUFFER_SRGB) при рендеринге в текстуру.

Предоставленный пример кода демонстрирует, как использовать предоставленную переменную 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 обрабатывает искажение и предоставляет его в 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.

 

Этот пример кода демонстрирует, как сначала происходит получение дескрипторов визуализации для каждого глаза с учётом выбранного поля зрения. Возвращённая структура ovrEyeRenderDescstructure содержит полезные значения для рендеринга, включая HmdToEyeOffset для каждого глаза. Просмотр смещения для глаз используется позже для того, чтобы отрегулировать разделение глаз.

Код также инициализирует структуру ovrLayerEyeFov для полноэкранного слоя. Начиная с Oculus SDK 0.6, представление кадров использует слои для комбинирования нескольких изображений или текстурных квадратов друг на друге. В этом примере используется один уровень для визуализации сцены в виртуальной реальности. Для этой цели мы используем 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);

 

В виртуальной реальности визуализированные виды для глаз зависят от положения и ориентации шлема виртуальной реальности в физическом пространстве, они отслеживаются с помощью внутреннего IMU-сенсра и внешних датчиков. Прогнозирование используется для компенсации задержки в системе, что даёт наилучшую оценку того, где будет шлем в тот момент, когда кадр отображается в устройстве. В SDK Oculus эта отслеженная спрогнозированная позиция сообщается в ovr_GetTrackingState.

 

Для того, чтобы сделать точный прогноз, ovr_GetTrackingState необходимо знать, когда будет отображаться текущий кадр. Вышеприведённый код вызывает GetPredictedDisplayTime для получения displayMidpointSeconds для текущего кадра, используя его для вычисления наилучшего прогнозируемого состояния отслеживания. Позиция головы из состояния отслеживания затем передаётся в ovr_CalcEyePoses для того, чтобы вычислить правильные позы визуализации для каждого глаза. Эти позы хранятся непосредственно в массиве layer.RenderPose[2]. Когда позы глаз готовы, мы можем перейти к фактическому рендерингу кадра.

 

if (isVisible)

{

// Get next available index of the texture swap chain

int currentIndex = 0;

ovr_GetTextureSwapChainCurrentIndex(session, textureSwapChain, &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);

 

Этот код выполняет несколько шагов для визуализации сцены:

  • Он применяет текстуру в качестве цели рендеринга и очищает её для рендеринга. В этом случае одна и та же текстура используется для обоих глаз.
  • Затем код вычисляет матрицы вида и проекции и задаёт рендеринг сцены для экранов вида для каждого глаза. В этом примере вычисление визуализации объединяет исходную позу (значения originPos и originRot) с новой позой, вычисленной на основе состояния отслеживания и сохранённой в слое. Там исходные значения могут быть изменены с помощью ввода новой информации для того, чтобы переместить игрока в трёхмерном мире.
  • После того, как рендеринг текстуры завершен, мы вызываем ovr_SubmitFrameдля передачи данных кадра в компоновщик. С этого момента компоновщик берёт на себя доступ к данным текстуры через общую память, искажая её и отправляя её в Oculus Rift.

Ovr_SubmitFrame возвращает только что полученный и вставленный кадр в очередь, и среда выполнения становится доступной для принятия нового кадра. В случае успеха его возвращаемое значение будет ovrSuccess или ovrSuccess_NotVisible.

ovrSuccess_NotVisible возвращается, если кадр не был фактически отображен, что может случиться тогда, когда приложение виртуальной реальности теряет фокус. Наш пример кода обрабатывает этот случай, обновляя переменную isVisible, которая проверяется логикой рендеринга. Хотя кадры не видны, рендеринг должен быть приостановлен для того, чтобы исключить ненужную загрузку графического процессора.

Если вы получили ovrError_DisplayLost, устройство было удалено, а сеанс оказался недействительным. Освободите общие ресурсы (ovr_DestroyTextureSwapChain), уничтожьте сеанс (ovr_Destroy), заново создайте его (ovr_Create) и создайте новые ресурсы (ovr_CreateTextureSwapChainXXX). Существующие частные графические ресурсы приложения не нужно воссоздавать, если новый вызов ovr_Create не возвращает другой GraphicsLuid.

Синхронизация кадров

Oculus SDK передаёт информацию о времени кадра через функцию ovr_GetPredictedDisplayTime, полагаясь на предоставляемый приложением индекс кадра для того, чтобы гарантировать, что правильное время отображается в различных потоках.

Для точного прогнозирования движений головы требуется точная синхронизация кадра и сенсора, что необходимо для хорошего восприятия виртуальной реальности. Для прогнозирования требуется точно знать, когда в будущем на экране появится текущий кадр. Если мы знаем как время сканирования сенсора, так и время демонстрации кадра, мы можем предсказать будущую позу головы и улучшить стабильность изображения. Некорректное вычисление этих значений может привести к недооценке или чрезмерному прогнозированию, ухудшению воспринимаемой задержки и потенциальному возникновению «шатаний» при перерегулировании.

Для обеспечения точного времени Oculus SDK использует точное системное время, которое хранится как двойное, для представления значений времени сенсора и кадра. Текущее точное время возвращается с помощью ovr_GetTimeInSeconds. Однако текущее время должно использоваться редко, поскольку симуляция и прогнозирование движения дадут лучшие результаты при использовании значений времени, возвращаемых функцией ovr_GetPredictedDisplayTime. Эта функция имеет следующую подпись:

 

ovr_GetPredictedDisplayTime(ovrSession session, long long frameIndex);

 

Аргумент frameIndex указывает, какой кадр приложения отображается. Приложения, использующие многопотоковый рендеринг, должны поддерживать внутренний индекс кадра и вручную увеличивать его, передавая его с потоками вместе с данными кадра для обеспечения правильного времени и прогноза. То же значение frameIndex должно быть передано в ovr_SubmitFrame для того, чтобы было получено время для кадра. Подробная информация о многопоточных таймингах рассматривается в следующем разделе «Рендеринг в разных потоках».

Специальное значение frameIndex 0 может использоваться в обеих функциях для того, чтобы запросить  SDK автоматически отслеживать индексы кадров. Тем не менее, это работает только тогда, когда все запросы времени кадра и запрашиваемого представления кадра выполняются в одном и том же потоке.

Рендеринг в разных потоках

В некоторых движках процесс рендеринга распределяется по нескольким потокам.

Например, один поток может выполнять отбор и визуализацию настроек для каждого объекта в сцене (мы будем называть это «основным» потоком), в то время как второй поток выполняет фактические вызовы D3D или OpenGL API (мы назовем это «рендеринговый» поток). Оба эти потока могут нуждаться в точных оценках времени отображения кадров для того, чтобы вычислять наилучшие возможные прогнозы положения головы.

Асинхронный характер этого подхода делает процесс сложным: в то время как «рендеринговый» поток представляет кадр, «основной» поток может обрабатывать следующий кадр. Эта обработка параллельных кадров может быть не синхронизирована ровно одним кадром или долей кадра в зависимости от дизайна игрового движка. Если бы мы использовали глобальное положение по умолчанию для доступа к таймингу кадра, результат GetPredictedDisplayTime мог бы быть отключён на один кадр, в зависимости от того, из какого потока вызывается функция, или, что ещё хуже, может оказаться случайным образом некорректным в зависимости от того, как планируются потоки. Чтобы решить эту проблему, в предыдущем разделе была представлена концепция frameIndex, которая отслеживается приложением и передаётся через потоки вместе с данными кадра.

Для корректности результата многопоточного рендеринга должно выполняться следующее: (a) предсказание представления, вычисленное на основе времени кадра, должно быть согласованным для того же кадра независимо от того, к какому потоку он обращается; (б) позы глаз, которые фактически использовались для визуализации, должны быть переданы в ovr_SubmitFrame вместе с индексом кадра.

Вот краткое описание шагов, которые вы можете предпринять для того, чтобы убедиться в этом:

  1. «Основной» поток должен назначить индекс кадра текущему кадру, обрабатываемому для визуализации. Он будет увеличивать этот индекс до каждого кадра и будет передавать его в GetPredictedDisplayTime для того, чтобы получить правильное время для прогнозирования позы.
  2. «Основной поток» должен вызывать функцию потокобезопасности ovr_GetTrackingState с прогнозируемым значением времени. Он также может вызывать ovr_CalcEyePoses, если это необходимо для настройки рендеринга.
  3. «Основной» поток должен пропускать текущий индекс кадра и глазные позы в «рендеринговый» поток вместе с любыми командами рендеринга или данными кадра, в которых он нуждается.
  4. Когда команды рендеринга выполняются в «рендеринговом» потоке, разработчикам необходимо убедиться, что всё это выполняется:
  • Фактические позы, используемые для рендеринга кадра, сохраняются в RenderPose для слоя.
  • То же значение индекса кадра, что и в «основном» потоке, передается в 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);

}

 

Oculus SDK также поддерживает Direct3D 12, что позволяет отправлять рендеринговую работу графическому процессору из нескольких потоков центрального процессора. Когда приложение вызывает ovr_CreateTextureSwapChainDX, Oculus SDK кэширует ID3D12CommandQueue, предоставленный вызывающим объектом, для будущего использования. Поскольку приложение вызывает ovr_SubmitFrame, SDK бросает «забор» на кэшированный ID3D12CommandQueue для того, чтобы точно знать, когда данный набор текстур глаз готов к набору компоновщика SDK.

Для данного приложения использование одного ID3D12CommandQueue в одном потоке является самым простым. Но он также может разделить рабочую нагрузку рендеринга центрального процессора для каждой пары текстур глаз или направить рендеринговую работу без визуальной текстуры, такую как тени, карты отражения и т.д., в разные очереди команд. Если приложение заполняет и выполняет списки команд из нескольких потоков, оно также должно удостовериться, что ID3D12CommandQueue, предоставленный SDK, является единственным узлом соединения рендеринговой работы текстуры для глаз, которая выполняется через разные очереди команд.

Слои

Подобно тому, как монитор вида может состоять из нескольких окон, дисплей на шлеме виртуальной реальности может состоять из нескольких слоёв. Обычно, по крайней мере, один из этих слоёв будет представлением, создаваемым из виртуальных глазных яблок пользователя, но другими слоями могут быть слои внешнего пользовательского интерфейса, информационные панели, текстовые метки, прикрепленные к элементам в виртуальном мире, сетки и целевые мишени и т.д.

Каждый слой может иметь другое разрешение, может использовать другой формат текстуры, может использовать другое поле зрения или размер и может быть в моно или стерео. Приложение также может быть настроено таким образом, чтобы не обновлять текстуру слоя, если информация в ней не изменилась. Например, он может не обновить его, если текст в информационной панели не изменился с момента последнего кадра или если слой представляет собой изображение в кадре видеопотока с низкой частотой кадров. Приложения могут отправлять промежуточные текстуры на слой, совмещая это вместе с режимом высококачественных искажений, это очень эффективно улучшает читаемость текстовых панелей.

Каждый кадр, все активные слои компонуются от начала до конца с использованием предварительно умноженного альфа-смешивания. Слой 0 – самый далекий слой, слой 1 находится поверх него и так далее; не существует тестирования пересечения буферов слоёв глубины, даже если буфер глубины предоставляется.

Важной особенностью слоёв является то, что каждый может иметь разное разрешение. Это позволяет приложению масштабироваться до более низких систем производительности, понижая разрешение основного рендеринга визуального буфера, который изображает виртуальный мир, но при этом позволяя сохранить важную информацию, такую как текст или карту, на другом уровне с более высоким разрешением.

Доступны слои нескольких типов:

  • EyeFov: стандартный «глазной буфер», знакомый по предыдущим SDK, который обычно представляет собой стереоизображение виртуальной сцены, визуализируемой исходя из положения глаз пользователя. Хотя «глазной буфер» может быть моно, а не стерео, это может вызывать дискомфорт. Предыдущие SDK имели неявные поле зрения (FOV) и область просмотра; теперь они предоставляются явно, и приложение может изменять каждый кадр, если это необходимо.
  • Quad: моноскопическое изображение, которое отображается в виде прямоугольника в заданных позе и размере в виртуальном мире. Это полезно для наружных дисплеев, текстовой информации, меток объектов и т.д. По умолчанию позиция указывается относительно реального пространства пользователя, и Quad останется неподвижным в пространстве, а значит не будет перемещаться вместе с головой пользователя или при движении тела. Для головных Quad используйте переменную ovrLayerFlag_HeadLocked, как описано ниже.
  • EyeMatrix: он похож на тип слоя EyeFov и предоставляется для обеспечения совместимости с приложениями для очков виртуальной реальности Gear VR. Для получения дополнительной информации см. документацию по мобильной версии SDK.
  • Disabled: игнорируемые компоновщиком, отключённые слои не стоят производительности. Мы рекомендуем добиться того, чтобы приложения выполняли базовую выборку и удаляли слои, которые не видны. Тем не менее, нет необходимости в том, чтобы приложение тщательно перепроверяло список активных слоев при выключении одного слоя; отключение его и оставление его в списке является достаточным. Также можно указателю на слой в списке присвоить значение null.

Каждый стиль слоя имеет соответствующую структуру ovrLayerType перечисления и связанную структуру, которая содержит данные, необходимые для отображения этого слоя. Например, слой EyeFov имеет номер типа ovrLayerType_EyeFov и описывается данными в структуре ovrLayerEyeFov. Эти структуры имеют одинаковый набор параметров, хотя не все типы слоёв требуют всех параметров:

  • Header.Type (перечисление ovrLayerType): должен быть установлен всеми слоями для того, чтобы указать, к какому типу они относятся.
  • Header.Flags (бит ovrLayerFlags): см. ниже для получения дополнительной информации.
  • ColorTexture (TextureSwapChain): предоставляет цвета и полупрозрачные данные для слоя. Слои смешиваются друг с другом с использованием предварительно умноженной альфы. Это позволяет выразить lerp смешивание, аддитивное смешение или комбинацию обоих смешиваний. Текстуры слоёв должны иметь формат RGBA или BGRA, они могут иметь мип-карты, но не могут быть массивами, кубами или иметь MSAA. Если приложение хочет выполнить рендеринг MSAA, тогда оно должно разрешить промежуточную цветную текстуру MSAA в не-MSAA ColorTexture.
  • Viewport (ovrRecti): прямоугольник текстуры, который фактически используется, указан в 0-1 текстуре UV координатного пространства (не пиксельном). Теоретически, данные текстуры вне этой области не видны в слое. Тем не менее, обычные оговорки о выборке текстуры применяются, особенно с mipmapped текстурами. Хорошим опытом стало оставлять границу RGBA(0,0,0,0) пикселей вокруг отображаемой области для того, чтобы избежать «кровотечения», особенно между двумя глазными буферами, расположенными бок о бок в одной и той же текстуре. Размер границы зависит от конкретного варианта использования, но около 8 пикселей, как правило, работают хорошо в большинстве случаев.
  • Fov (ovrFovPort): поле зрения, используемое для визуализации сцены в типе слоя Eye. Обратите внимание, что оно не имеет управления над шлемом виртуальной реальности, а просто сообщает компоновщику о том, что поле зрения используется для визуализации данных текстуры в слое – компоновщик затем соответствующим образом отрегулирует все поле зрения для фактического пользователя. Приложения могут изменять поле зрения динамически для специальных эффектов. Сокращение поля зрения может также помочь в производительности на более медленных устройствах, хотя обычно более эффективно сначала снизить разрешение до уменьшения поля зрения.
  • RenderPose (ovrPosef): положение камеры, которое получает приложение, используемое для визуализации сцены в типе слоя Eye. Обычно это прогнозируется SDK и приложением с использованием функций ovr_GetTrackingState и ovr_CalcEyePoses. Разница между этой позицией и фактической позой глаза во время отображения используется компоновщиком для применения таймрапа к слою.
  • SensorSampleTime (double): точное время, когда приложение провело отслеживание состояния. Типичным способом получения этого значения является вызов ovr_GetTimeInSeconds после вызова ovr_GetTrackingState. SDK использует это значение для того, чтобы сообщить о motion-to-photon задержке приложения в Performance HUD. Если приложение имеет более одного слоя ovrLayerType_EyeFov, отправленного в любой из этих кадров, SDK пробирается через эти слои и выбирает тайминги с наименьшей задержкой. В заданном фрейме, если слои ovrLayerType_EyeFov не представлены, SDK будет использовать момент времени, когда ovr_GetTrackingState был вызван с latencyMarkerset для ovrTrue в качестве времени motion-to-photon задержки приложения.
  • QuadPoseCenter (ovrPosef): определяет ориентацию и положение центральной точки слоя типа Quad. Предоставленное направление – это вектор, перпендикулярный квадрату. Позиция обозначается в реальных метрах (не в виртуальном мире приложения, а в реальном мире, в котором находится пользователь) и относится к позиции «ноль», заданной параметром ovr_RecenterTrackingOrigin или ovr_SpecifyTrackingOrigin, если не используется переменная ovrLayerFlag_HeadLocked.
  • QuadSize (ovrVector2f): указывает ширину и высоту слоя типа Quad. Как и в случае с позицией, это измеряется в реальном мире.

Слои, которые берут стерео информацию (все, кроме четырехъядерных), принимают два набора большинства параметров, и их можно использовать тремя различными способами:

  • Стерео данные, отдельные текстуры – приложение предоставляет другую ovrTextureSwapChain для левого и правого глаз и окно просмотра для каждого.
  • Стерео данные, общая текстура – приложение поставляет одну и ту же ovrTextureSwapChain как для левого, так и для правого глаза, но для каждого экрана просмотра оно другое. Это позволяет приложению отображать как левый, так и правый виды в один и тот же текстурный буфер. Не забудьте добавить небольшой буфер между двумя представлениями, чтобы предотвратить «кровотечение», как обсуждалось выше.
  • Моно данные – приложение поставляет одну и ту же ovrTextureSwapChain для обоих левого и правого глаз и один и тот же экран просмотра для каждого.

Для левого и правого глаз текстуры и размеры экрана просмотра могут быть разными, и у каждого из них могут быть разные поля зрения. Однако будьте осторожны при использовании стерео неравенства, так как это может вызвать дискомфорт у ваших пользователей.

Поле Header.Flags, доступное для всех слоев, является логическим/или следующим:

  • ovrLayerFlag_HighQuality: включает 4x анизотропную выборку в компоновщике для этого слоя. Это может обеспечить значительное повышение удобочитаемости, особенно при использовании текстуры, содержащей mip-карты; это рекомендуется для высокочастотных изображений, таких как текст или диаграммы, а также при использовании слоёв типа Quad. Для слоёв типа Eye это также увеличит визуальную точность на периферии или при подаче текстур с плотностью пикселей более 1:1. Для получения наилучших результатов при создании mip-карт для текстур, связанных с определённым слоем, убедитесь, что размер текстуры равен 2. Так или иначе, приложению не нужно отображать всю текстуру; окно просмотра, которое визуализирует рекомендуемый размер текстуры, обеспечит наилучшие соотношения производительности и качества.
  • ovrLayerFlag_TextureOriginAtBottomLeft: предполагается, что начало текстуры слоя находится в верхнем левом углу. Тем не менее, некоторые движки (особенно те, которые используют OpenGL) предпочитают использовать левый нижний угол в качестве источника, и они должны использовать эту переменную.
  • ovrLayerFlag_HeadLocked: большинство типов слоёв имеет свою ориентацию позы и позицию, указанную относительно «нулевой позиции», определяемой вызовом ovr_RecenterTrackingOrigin. Однако приложение может потребовать указать позу слоя относительно лица пользователя. Когда пользователь двигает головой, слой следует за ним. Это полезно для перенастроек, используемых в прицеливании или выборе элементов с использованием взгляда. Эта переменная может использоваться для всех типов слоёв, хотя она и не имеет никакого эффекта при использовании в типе Direct.

В конце каждого кадра после визуализации в зависимости от того, какое приложение будет обновлять и вызывать значение ovr_CommitTextureSwapChain, данные для каждого слоя помещаются в соответствующую структуру ovrLayerEyeFov/ovrLayerQuad/ovrLayerDirect. Затем приложение создает список указателей к этим структурам слоёв, в частности к полю заголовка, который гарантированно является первым членом каждой структуры. Затем приложение создает структуру ovrViewScaleDesc с необходимыми данными и вызывает функцию 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);

 

Компоновщик выполняет коррекцию по времени, искажению и хроматической аберрации на каждом слое отдельно, прежде чем смешивать их вместе. Традиционный метод quad рендеринга в глазном буфере включает в себя два этапа фильтрации (один – в глазном буфере, затем ещё один – во время искажения). При использовании слоёв есть только один шаг фильтрации между изображением слоя и финальным буфером кадров. Это может значительно улучшить качество текста, особенно в сочетании с mip-картами и переменной 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);

 

Асинхронный таймрап

Асинхронный таймрап (ATW) – это метод уменьшения задержки и вибрации в приложениях для виртуальной реальности.

В базовом цикле игры в виртуальной реальности происходит следующее:

  1. Программное обеспечение запрашивает ваше положение головы.
  2. Центральный процессор обрабатывает сцену для каждого глаза.
  3. Графический процессор отображает сцены.
  4. Компоновщик Oculus применяет искажения и отображает сцены на шлеме виртуальной реальности.

Когда частота кадров поддерживается одинаковая, опыт в виртуальной реальности кажется реалистичным и приятным. Когда это не происходит вовремя, то отображается предыдущий кадр, который может дезориентировать.

Когда вы двигаете головой и виртуальный мир не успевает следом, это может сделать погружение в виртуальную реальность резким и прерывающимся

Асинхронный таймрап – это метод, который слегка сдвигает показываемое изображение для того, чтобы приспособиться к изменениям в движении головы. Несмотря на то, что изображение модифицировано, ваша голова не двигается сильно, поэтому изменения незначительны.

Кроме того, чтобы сгладить проблемы с компьютером пользователя, дизайном игры или операционной системой, асинхронный таймрап может помочь исправить «выбоины» или моменты, когда частота кадров неожиданно падает.

В интервале обновления компоновщик применяет таймрап к последнему визуализированному кадру. В результате, кадр после таймрапа всегда будет отображаться пользователю независимо от частоты кадров. Если частота кадров очень плохая, на периферии дисплея будет заметно мерцание. Но изображение всё равно будет стабильным.

Асинхронный таймрап автоматически применяется в Oculus Compositor; вам не нужно включать или настраивать его. Однако, хотя асинхронный таймрап уменьшает задержку, убедитесь, что ваше приложение продолжает осуществлять частоту кадров.

Адаптивная очередь наперёд

Чтобы улучшить параллелизм центрального и графического процессоров и увеличить время, необходимое графическому процессору для обработки кадра, SDK автоматически применяет очередь наперёд до 1 кадра.

Без очереди наперёд центральный процессор начинает обработку следующего кадра сразу же после отображения предыдущего кадра. После окончания работы центрального процессора графический процессор обрабатывает кадр, компоновщик применяет искажение, и кадр отображается пользователю.

Если графический процессор не сможет обработать кадр вовремя для отображения, отобразится предыдущий кадр. Это приводит к дрожанию.

Если очередь установлена наперёд, центральный процессор может стартовать раньше; это обеспечивает графическому процессору больше времени для обработки кадра.

 

Примечание: Данный материал представлен для ознакомления, при перепечатывании ссылка на оригинал обязательна. Если вы хотите принять участие в помощи проекту, пишите на editor@vrgeek.ru

Если вы разработчик и вы хотите продолжить свою карьеру в лучших компаниях России, пишите на editor@vrgeek.ru с пометкой «Работа мечты», и мы поможем вам с этим.