Пространственное сопоставление в DirectX
Примечание
Эта статья относится к устаревшим собственным API-интерфейсам WinRT. Для новых проектов собственных приложений рекомендуется использовать API OpenXR.
В этом разделе описывается, как реализовать пространственное сопоставление в приложении DirectX, включая подробное описание примера приложения пространственного сопоставления, упаковав его с пакетом SDK для универсальная платформа Windows.
В этом разделе используется код из примера кода UWP HolographicSpatialMapping .
Примечание
Фрагменты кода в этой статье в настоящее время демонстрируют использование C++/CX вместо C++17-совместимого C++/WinRT, как используется в шаблоне голографического проекта C++. Эти понятия эквивалентны для проекта C++/WinRT, хотя вам потребуется перевести код.
Поддержка устройств
Компонент | HoloLens (1-го поколения) | HoloLens 2 | Иммерсивные гарнитуры |
пространственное сопоставление | ✔️ | ✔️ | ❌ |
Обзор разработки в DirectX
Разработка собственных приложений для пространственного сопоставления использует API в пространстве имен Windows.Perception.Spatial . Эти API предоставляют полный контроль над функциональностью пространственного сопоставления так же, как и API пространственного сопоставления, предоставляемые Unity.
API восприятия
Ниже перечислены основные типы, предоставляемые для разработки пространственного сопоставления.
- SpatialSurfaceObserver предоставляет сведения о поверхностях в указанных приложением областях пространства рядом с пользователем в виде объектов SpatialSurfaceInfo.
- SpatialSurfaceInfo описывает одну храняющуюся пространственную поверхность, включая уникальный идентификатор, ограничивающий объем и время последнего изменения. Он предоставляет SpatialSurfaceMesh асинхронно по запросу.
- SpatialSurfaceMeshOptions содержит параметры, используемые для настройки объектов SpatialSurfaceMesh, запрашиваемых из SpatialSurfaceInfo.
- SpatialSurfaceMesh представляет данные сетки для одной пространственной поверхности. Данные о положениях вершин, нормали вершин и индексах треугольников содержатся в объектах-членах SpatialSurfaceMeshBuffer.
- SpatialSurfaceMeshBuffer заключает в оболочку один тип данных сетки.
При разработке приложения с помощью этих API базовый поток программы будет выглядеть следующим образом (как показано в примере приложения, описанном ниже):
- Настройка spatialSurfaceObserver
- Вызовите RequestAccessAsync, чтобы убедиться, что пользователь предоставил приложению разрешение на использование возможностей пространственного сопоставления устройства.
- Создайте экземпляр объекта SpatialSurfaceObserver.
- Вызовите SetBoundingVolumes , чтобы указать области пространства, в которых требуется информация о пространственных поверхностях. Вы можете изменить эти регионы в будущем, повторно вызвав эту функцию. Каждая область указывается с помощью SpatialBoundingVolume.
- Зарегистрируйтесь для события ObservedSurfacesChanged , которое срабатывает при появлении новых сведений о пространственных поверхностях в указанных областях пространства.
- Обработка событий ObservedSurfacesChanged
- В обработчике событий вызовите Метод GetObservedSurfaces , чтобы получить карту объектов SpatialSurfaceInfo. С помощью этой карты можно обновить записи о том, какие пространственные поверхности существуют в среде пользователя.
- Для каждого объекта SpatialSurfaceInfo можно запросить TryGetBounds , чтобы определить пространственные экстенты поверхности, выраженные в выбранной пространственной системе координат .
- Если вы решили запросить сетку для пространственной поверхности, вызовите TryComputeLatestMeshAsync. Можно указать параметры, указывающие плотность треугольников и формат возвращаемых данных сетки.
- Сетка получения и обработки
- Каждый вызов TryComputeLatestMeshAsync асинхронно возвращает один объект SpatialSurfaceMesh.
- Из этого объекта можно получить доступ к содержащимся объектам SpatialSurfaceMeshBuffer, которые предоставляют доступ к индексам треугольников, позициям вершин и нормалий вершин сетки при их запросе. Эти данные будут иметь формат, непосредственно совместимый с API Direct3D 11 , используемыми для отрисовки сеток.
- Отсюда приложение может при необходимости анализировать или обрабатывать данные сетки и использовать их для отрисовки , физического вещания и столкновения.
- Важно отметить, что необходимо применить масштаб к позициям вершин сетки (например, в вершинном шейдере, используемом для отрисовки сеток), чтобы преобразовать их из оптимизированных целочисленных единиц, в которых они хранятся в буфере, в метры. Этот масштаб можно получить, вызвав Метод VertexPositionScale.
Устранение неполадок
- Не забудьте масштабировать позиции вершин сетки в шейдере вершин с помощью шкалы, возвращаемой SpatialSurfaceMesh.VertexPositionScale
Пошаговое руководство по примеру кода пространственного сопоставления
Пример кода для голографического пространственного сопоставления включает код, который можно использовать для начала загрузки сеток поверхности в приложение, включая инфраструктуру для управления и отрисовки сеток поверхности.
Теперь мы рассмотрим, как добавить возможность сопоставления поверхностей в приложение DirectX. Этот код можно добавить в проект шаблона приложения Windows Holographic или просмотреть приведенный выше пример кода. Этот пример кода основан на шаблоне приложения Windows Holographic.
Настройка приложения для использования возможности spatialPerception
Приложение может использовать функцию пространственного сопоставления. Это необходимо, так как пространственная сетка представляет среду пользователя, которая может считаться частными данными. Объявите эту возможность в файле package.appxmanifest для приложения. Ниже приведен пример:
<Capabilities>
<uap2:Capability Name="spatialPerception" />
</Capabilities>
Эта возможность поступает из пространства имен uap2 . Чтобы получить доступ к этому пространству имен в манифесте, включите его в качестве атрибута xlmns в <элемент Package> . Ниже приведен пример:
<Package
xmlns="https://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="https://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="https://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap2="https://schemas.microsoft.com/appx/manifest/uap/windows10/2"
IgnorableNamespaces="uap uap2 mp"
>
Проверка поддержки функций пространственного сопоставления
Windows Mixed Reality поддерживает широкий спектр устройств, включая устройства, которые не поддерживают пространственное сопоставление. Если приложение может использовать пространственное сопоставление или должно использовать пространственное сопоставление для предоставления функциональных возможностей, оно должно проверка, чтобы убедиться, что пространственное сопоставление поддерживается, прежде чем пытаться использовать его. Например, если приложение смешанной реальности требует пространственного сопоставления, оно должно отобразить сообщение об этом, если пользователь пытается запустить его на устройстве без пространственного сопоставления. Кроме того, приложение может отрисовывать собственную виртуальную среду вместо среды пользователя, обеспечивая взаимодействие, аналогичное тому, что произошло бы при наличии пространственного сопоставления. В любом случае этот API позволяет приложению знать, когда оно не получит данные пространственного сопоставления, и реагировать соответствующим образом.
Чтобы проверка текущее устройство для поддержки пространственного сопоставления, сначала убедитесь, что контракт UWP находится на уровне 4 или выше, а затем вызовите SpatialSurfaceObserver::IsSupported(). Вот как это сделать в контексте примера кода голографического пространственного сопоставления . Поддержка проверяется непосредственно перед запросом доступа.
API SpatialSurfaceObserver::IsSupported() доступен начиная с пакета SDK версии 15063. При необходимости перед использованием этого API перенаправьте проект на платформу версии 15063.
if (m_surfaceObserver == nullptr)
{
using namespace Windows::Foundation::Metadata;
if (ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 4))
{
if (!SpatialSurfaceObserver::IsSupported())
{
// The current system does not have spatial mapping capability.
// Turn off spatial mapping.
m_spatialPerceptionAccessRequested = true;
m_surfaceAccessAllowed = false;
}
}
if (!m_spatialPerceptionAccessRequested)
{
/// etc ...
Если контракт UWP меньше уровня 4, приложение должно работать так, как будто устройство может выполнять пространственное сопоставление.
Запрос доступа к данным пространственного сопоставления
Ваше приложение должно запросить разрешение на доступ к данным пространственного сопоставления, прежде чем пытаться создать каких-либо наблюдателей поверхности. Ниже приведен пример кода, основанный на примере кода Surface Mapping, с дополнительными сведениями, приведенными далее на этой странице:
auto initSurfaceObserverTask = create_task(SpatialSurfaceObserver::RequestAccessAsync());
initSurfaceObserverTask.then([this, coordinateSystem](Windows::Perception::Spatial::SpatialPerceptionAccessStatus status)
{
if (status == SpatialPerceptionAccessStatus::Allowed)
{
// Create a surface observer.
}
else
{
// Handle spatial mapping unavailable.
}
}
Создание surface observer
Пространство имен Windows::P erception::Spatial::Surfaces включает класс SpatialSurfaceObserver , который наблюдает за одним или несколькими томами, указанными в SpatialCoordinateSystem. Используйте экземпляр SpatialSurfaceObserver для доступа к данным сетки поверхности в режиме реального времени.
Из AppMain.h:
// Obtains surface mapping data from the device in real time.
Windows::Perception::Spatial::Surfaces::SpatialSurfaceObserver^ m_surfaceObserver;
Windows::Perception::Spatial::Surfaces::SpatialSurfaceMeshOptions^ m_surfaceMeshOptions;
Как отмечалось в предыдущем разделе, необходимо запросить доступ к данным пространственного сопоставления, прежде чем приложение сможет их использовать. Этот доступ предоставляется автоматически на Устройстве HoloLens.
// The surface mapping API reads information about the user's environment. The user must
// grant permission to the app to use this capability of the Windows Mixed Reality device.
auto initSurfaceObserverTask = create_task(SpatialSurfaceObserver::RequestAccessAsync());
initSurfaceObserverTask.then([this, coordinateSystem](Windows::Perception::Spatial::SpatialPerceptionAccessStatus status)
{
if (status == SpatialPerceptionAccessStatus::Allowed)
{
// If status is allowed, we can create the surface observer.
m_surfaceObserver = ref new SpatialSurfaceObserver();
Затем необходимо настроить наблюдатель поверхности для наблюдения за определенным ограничивающим объемом. Здесь мы наблюдаем прямоугольник с координатами 20x20x5 метров по центру в начале системы координат.
// The surface observer can now be configured as needed.
// In this example, we specify one area to be observed using an axis-aligned
// bounding box 20 meters in width and 5 meters in height and centered at the
// origin.
SpatialBoundingBox aabb =
{
{ 0.f, 0.f, 0.f },
{20.f, 20.f, 5.f },
};
SpatialBoundingVolume^ bounds = SpatialBoundingVolume::FromBox(coordinateSystem, aabb);
m_surfaceObserver->SetBoundingVolume(bounds);
Вместо этого можно задать несколько ограничивающих томов.
Это псевдокод:
m_surfaceObserver->SetBoundingVolumes(/* iterable collection of bounding volumes*/);
Кроме того, можно использовать другие ограничивающие фигуры, например фрезум представления или ограничивающий прямоугольник, который не выровнен по оси.
Это псевдокод:
m_surfaceObserver->SetBoundingVolume(
SpatialBoundingVolume::FromFrustum(/*SpatialCoordinateSystem*/, /*SpatialBoundingFrustum*/)
);
Если приложению нужно что-то делать по-другому, если данные сопоставления поверхности недоступны, можно написать код для реагирования на случай, когда SpatialPerceptionAccessStatus не разрешено . Например, он не будет разрешен на компьютерах с подключенными иммерсивными устройствами, так как на этих устройствах нет оборудования для пространственного сопоставления. Для этих устройств следует полагаться на пространственный этап для получения сведений о среде пользователя и конфигурации устройства.
Инициализация и обновление коллекции сетки Surface
Если наблюдатель поверхности был успешно создан, можно продолжить инициализацию коллекции surface mesh. Здесь мы используем API модели извлечения для получения текущего набора наблюдаемых поверхностей сразу:
auto mapContainingSurfaceCollection = m_surfaceObserver->GetObservedSurfaces();
for (auto& pair : mapContainingSurfaceCollection)
{
// Store the ID and metadata for each surface.
auto const& id = pair->Key;
auto const& surfaceInfo = pair->Value;
m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
}
Существует также модель push-уведомлений, доступная для получения данных сетки поверхности. Вы можете спроектировать приложение так, чтобы использовать только модель извлечения при желании. В этом случае вы будете опрашивать данные каждый раз , скажем, один раз на кадр, или в течение определенного периода времени, например во время настройки игры. Если да, то вам потребуется приведенный выше код.
В нашем примере кода мы решили продемонстрировать использование обеих моделей в педагогических целях. Здесь мы подписываемся на событие, чтобы получать актуальные данные сетки surface всякий раз, когда система распознает изменения.
m_surfaceObserver->ObservedSurfacesChanged += ref new TypedEventHandler<SpatialSurfaceObserver^, Platform::Object^>(
bind(&HolographicDesktopAppMain::OnSurfacesChanged, this, _1, _2)
);
Наш пример кода также настроен для реагирования на эти события. Давайте рассмотрим, как мы это делаем.
ПРИМЕЧАНИЕ: Возможно, это не самый эффективный способ обработки данных сетки для приложения. Этот код написан для ясности и не оптимизирован.
Данные сетки поверхности предоставляются на карте, доступной только для чтения, в котором в качестве ключевых значений хранятся объекты SpatialSurfaceInfo , использующие Platform::Guids.
IMapView<Guid, SpatialSurfaceInfo^>^ const& surfaceCollection = sender->GetObservedSurfaces();
Для обработки этих данных сначала мы ищем значения ключей, которых нет в нашей коллекции. Сведения о том, как данные хранятся в нашем примере приложения, будут представлены далее в этом разделе.
// Process surface adds and updates.
for (const auto& pair : surfaceCollection)
{
auto id = pair->Key;
auto surfaceInfo = pair->Value;
if (m_meshCollection->HasSurface(id))
{
// Update existing surface.
m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
}
else
{
// New surface.
m_meshCollection->AddOrUpdateSurface(id, surfaceInfo);
}
}
Кроме того, необходимо удалить поверхностные сетки, которые находятся в нашей коллекции поверхностных сеток, но больше не находятся в системной коллекции. Для этого нам нужно сделать что-то сродни тому, что мы только что показали для добавления и обновления сеток; Мы циклически просматриваем коллекцию нашего приложения и проверка, чтобы узнать, находится ли у нас guid в системной коллекции. Если его нет в системной коллекции, мы удаляем его из нашей коллекции.
Из нашего обработчика событий в AppMain.cpp:
m_meshCollection->PruneMeshCollection(surfaceCollection);
Реализация обрезки сетки в RealtimeSurfaceMeshRenderer.cpp:
void RealtimeSurfaceMeshRenderer::PruneMeshCollection(IMapView<Guid, SpatialSurfaceInfo^>^ const& surfaceCollection)
{
std::lock_guard<std::mutex> guard(m_meshCollectionLock);
std::vector<Guid> idsToRemove;
// Remove surfaces that moved out of the culling frustum or no longer exist.
for (const auto& pair : m_meshCollection)
{
const auto& id = pair.first;
if (!surfaceCollection->HasKey(id))
{
idsToRemove.push_back(id);
}
}
for (const auto& id : idsToRemove)
{
m_meshCollection.erase(id);
}
}
Получение и использование буферов данных сетки поверхности
Получить сведения о сетке поверхности было так же просто, как получить сбор данных и обработать обновления в этой коллекции. Теперь мы подробно рассмотрим, как можно использовать данные.
В нашем примере кода мы решили использовать сетки поверхности для отрисовки. Это распространенный сценарий для исключения голограмм за реальными поверхностями. Вы также можете отобразить сетки или обработанные их версии, чтобы показать пользователю, какие области комнаты сканируются, прежде чем вы начнете предоставлять функциональные возможности приложения или игры.
Пример кода запускает процесс при получении обновлений сетки surface от обработчика событий, описанного в предыдущем разделе. Важной строкой кода в этой функции является вызов для обновления сетки поверхности: к этому времени мы уже обработали сведения о сетке, и мы собираемся получить данные вершины и индекса для использования по мере необходимости.
Из RealtimeSurfaceMeshRenderer.cpp:
void RealtimeSurfaceMeshRenderer::AddOrUpdateSurface(Guid id, SpatialSurfaceInfo^ newSurface)
{
auto options = ref new SpatialSurfaceMeshOptions();
options->IncludeVertexNormals = true;
auto createMeshTask = create_task(newSurface->TryComputeLatestMeshAsync(1000, options));
createMeshTask.then([this, id](SpatialSurfaceMesh^ mesh)
{
if (mesh != nullptr)
{
std::lock_guard<std::mutex> guard(m_meshCollectionLock);
'''m_meshCollection[id].UpdateSurface(mesh);'''
}
}, task_continuation_context::use_current());
}
Наш пример кода разработан таким образом, что класс данных SurfaceMesh обрабатывает и отрисовывает данные сетки. Эти сетки на самом деле хранят карту RealtimeSurfaceMeshRenderer . Каждый из них имеет ссылку на SpatialSurfaceMesh, из которого он получен, поэтому вы можете использовать его в любое время, когда вам нужно получить доступ к вершинам сетки или буферам индекса, или получить преобразование для сетки. Сейчас мы помечаем сетку как нуждающуюся в обновлении.
Из SurfaceMesh.cpp:
void SurfaceMesh::UpdateSurface(SpatialSurfaceMesh^ surfaceMesh)
{
m_surfaceMesh = surfaceMesh;
m_updateNeeded = true;
}
В следующий раз, когда сетке будет предложено нарисовать себя, она сначала проверка флаг. Если требуется обновление, буферы вершин и индексов будут обновлены на GPU.
void SurfaceMesh::CreateDeviceDependentResources(ID3D11Device* device)
{
m_indexCount = m_surfaceMesh->TriangleIndices->ElementCount;
if (m_indexCount < 3)
{
// Not enough indices to draw a triangle.
return;
}
Сначала мы получаем необработанные буферы данных:
Windows::Storage::Streams::IBuffer^ positions = m_surfaceMesh->VertexPositions->Data;
Windows::Storage::Streams::IBuffer^ normals = m_surfaceMesh->VertexNormals->Data;
Windows::Storage::Streams::IBuffer^ indices = m_surfaceMesh->TriangleIndices->Data;
Затем мы создадим буферы устройств Direct3D с данными сетки, предоставляемыми HoloLens:
CreateDirectXBuffer(device, D3D11_BIND_VERTEX_BUFFER, positions, m_vertexPositions.GetAddressOf());
CreateDirectXBuffer(device, D3D11_BIND_VERTEX_BUFFER, normals, m_vertexNormals.GetAddressOf());
CreateDirectXBuffer(device, D3D11_BIND_INDEX_BUFFER, indices, m_triangleIndices.GetAddressOf());
// Create a constant buffer to control mesh position.
CD3D11_BUFFER_DESC constantBufferDesc(sizeof(SurfaceTransforms), D3D11_BIND_CONSTANT_BUFFER);
DX::ThrowIfFailed(
device->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_modelTransformBuffer
)
);
m_loadingComplete = true;
}
ПРИМЕЧАНИЕ: Сведения о вспомогательной функции CreateDirectXBuffer, используемой в предыдущем фрагменте кода, см. в примере кода Surface Mapping: SurfaceMesh.cpp, GetDataFromIBuffer.h. Теперь создание ресурса устройства завершено, и сетка считается загруженной и готовой к обновлению и отрисовки.
Обновление и отрисовка сеток поверхности
Наш класс SurfaceMesh имеет специализированную функцию обновления. Каждый Объект SpatialSurfaceMesh имеет собственное преобразование, и в нашем примере используется текущая система координат для объекта SpatialStationaryReferenceFrame для получения преобразования. Затем обновляется буфер констант модели на GPU.
void SurfaceMesh::UpdateTransform(
ID3D11DeviceContext* context,
SpatialCoordinateSystem^ baseCoordinateSystem
)
{
if (m_indexCount < 3)
{
// Not enough indices to draw a triangle.
return;
}
XMMATRIX transform = XMMatrixIdentity();
auto tryTransform = m_surfaceMesh->CoordinateSystem->TryGetTransformTo(baseCoordinateSystem);
if (tryTransform != nullptr)
{
transform = XMLoadFloat4x4(&tryTransform->Value);
}
XMMATRIX scaleTransform = XMMatrixScalingFromVector(XMLoadFloat3(&m_surfaceMesh->VertexPositionScale));
XMStoreFloat4x4(
&m_constantBufferData.vertexWorldTransform,
XMMatrixTranspose(
scaleTransform * transform
)
);
// Normals don't need to be translated.
XMMATRIX normalTransform = transform;
normalTransform.r[3] = XMVectorSet(0.f, 0.f, 0.f, XMVectorGetW(normalTransform.r[3]));
XMStoreFloat4x4(
&m_constantBufferData.normalWorldTransform,
XMMatrixTranspose(
normalTransform
)
);
if (!m_loadingComplete)
{
return;
}
context->UpdateSubresource(
m_modelTransformBuffer.Get(),
0,
NULL,
&m_constantBufferData,
0,
0
);
}
Когда пришло время отрисовки сеток поверхности, мы делаем некоторые работы по подготовке перед отрисовкой коллекции. Мы настроили конвейер шейдера для текущей конфигурации отрисовки и настроили этап сборщика входных данных. Вспомогательный класс голографической камеры CameraResources.cpp уже настроил буфер констант представления или проекции.
Из RealtimeSurfaceMeshRenderer::Render:
auto context = m_deviceResources->GetD3DDeviceContext();
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetInputLayout(m_inputLayout.Get());
// Attach our vertex shader.
context->VSSetShader(
m_vertexShader.Get(),
nullptr,
0
);
// The constant buffer is per-mesh, and will be set as such.
if (depthOnly)
{
// Explicitly detach the later shader stages.
context->GSSetShader(nullptr, nullptr, 0);
context->PSSetShader(nullptr, nullptr, 0);
}
else
{
if (!m_usingVprtShaders)
{
// Attach the passthrough geometry shader.
context->GSSetShader(
m_geometryShader.Get(),
nullptr,
0
);
}
// Attach our pixel shader.
context->PSSetShader(
m_pixelShader.Get(),
nullptr,
0
);
}
Как только это будет сделано, мы циклически на наших сетках и говорим каждому из них, чтобы нарисовать себя. ПРИМЕЧАНИЕ: Этот пример кода не оптимизирован для использования какого-либо типа отбраковки frustum, но эту функцию следует включить в приложение.
std::lock_guard<std::mutex> guard(m_meshCollectionLock);
auto device = m_deviceResources->GetD3DDevice();
// Draw the meshes.
for (auto& pair : m_meshCollection)
{
auto& id = pair.first;
auto& surfaceMesh = pair.second;
surfaceMesh.Draw(device, context, m_usingVprtShaders, isStereo);
}
Отдельные сетки отвечают за настройку буфера вершин и индексов, а также буфера констант преобразования модели и шага. Как и в случае с вращающимися кубами в шаблоне приложения Windows Holographic, мы отрисовываем данные в стереоскопических буферах с помощью instancing.
Из SurfaceMesh::D raw:
// The vertices are provided in {vertex, normal} format
const auto& vertexStride = m_surfaceMesh->VertexPositions->Stride;
const auto& normalStride = m_surfaceMesh->VertexNormals->Stride;
UINT strides [] = { vertexStride, normalStride };
UINT offsets [] = { 0, 0 };
ID3D11Buffer* buffers [] = { m_vertexPositions.Get(), m_vertexNormals.Get() };
context->IASetVertexBuffers(
0,
ARRAYSIZE(buffers),
buffers,
strides,
offsets
);
const auto& indexFormat = static_cast<DXGI_FORMAT>(m_surfaceMesh->TriangleIndices->Format);
context->IASetIndexBuffer(
m_triangleIndices.Get(),
indexFormat,
0
);
context->VSSetConstantBuffers(
0,
1,
m_modelTransformBuffer.GetAddressOf()
);
if (!usingVprtShaders)
{
context->GSSetConstantBuffers(
0,
1,
m_modelTransformBuffer.GetAddressOf()
);
}
context->PSSetConstantBuffers(
0,
1,
m_modelTransformBuffer.GetAddressOf()
);
context->DrawIndexedInstanced(
m_indexCount, // Index count per instance.
isStereo ? 2 : 1, // Instance count.
0, // Start index location.
0, // Base vertex location.
0 // Start instance location.
);
Варианты отрисовки с помощью Surface Mapping
Пример кода Surface Mapping предлагает код для отрисовки данных сетки поверхности только для окклюзии, а также для отрисовки данных сетки surface на экране. Выбранный вами путь зависит от приложения. В этом документе мы рассмотрим обе конфигурации.
Отрисовка буферов окклюзии для голографического эффекта
Начните с очистки целевого представления отрисовки для текущей виртуальной камеры.
Из AppMain.cpp:
context->ClearRenderTargetView(pCameraResources->GetBackBufferRenderTargetView(), DirectX::Colors::Transparent);
Это проход предварительной отрисовки. Здесь мы создаем буфер окклюзии, запрашивая отрисовщик сетки отрисовщик только глубину. В этой конфигурации мы не присоединяем целевое представление отрисовки, а отрисовщик сетки устанавливает для этапа шейдера пикселей значение nullptr , чтобы GPU не беспокоили рисование пикселей. Геометрия будет растеризована в буфер глубины, и графический конвейер остановится на этом.
// Pre-pass rendering: Create occlusion buffer from Surface Mapping data.
context->ClearDepthStencilView(pCameraResources->GetSurfaceDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
// Set the render target to null, and set the depth target occlusion buffer.
// We will use this same buffer as a shader resource when drawing holograms.
context->OMSetRenderTargets(0, nullptr, pCameraResources->GetSurfaceOcclusionDepthStencilView());
// The first pass is a depth-only pass that generates an occlusion buffer we can use to know which
// hologram pixels are hidden behind surfaces in the environment.
m_meshCollection->Render(pCameraResources->IsRenderingStereoscopic(), true);
Мы можем рисовать голограммы с дополнительным тестом глубины в буфере окклюзии Surface Mapping. В этом примере кода мы отрисовывем пиксели на кубе другого цвета, если они находятся за поверхностью.
Из AppMain.cpp:
// Hologram rendering pass: Draw holographic content.
context->ClearDepthStencilView(pCameraResources->GetHologramDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
// Set the render target, and set the depth target drawing buffer.
ID3D11RenderTargetView *const targets[1] = { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, pCameraResources->GetHologramDepthStencilView());
// Render the scene objects.
// In this example, we draw a special effect that uses the occlusion buffer we generated in the
// Pre-Pass step to render holograms using X-Ray Vision when they are behind physical objects.
m_xrayCubeRenderer->Render(
pCameraResources->IsRenderingStereoscopic(),
pCameraResources->GetSurfaceOcclusionShaderResourceView(),
pCameraResources->GetHologramOcclusionShaderResourceView(),
pCameraResources->GetDepthTextureSamplerState()
);
На основе кода из SpecialEffectPixelShader.hlsl:
// Draw boundaries
min16int surfaceSum = GatherDepthLess(envDepthTex, uniSamp, input.pos.xy, pixelDepth, input.idx.x);
if (surfaceSum <= -maxSum)
{
// The pixel and its neighbors are behind the surface.
// Return the occluded 'X-ray' color.
return min16float4(0.67f, 0.f, 0.f, 1.0f);
}
else if (surfaceSum < maxSum)
{
// The pixel and its neighbors are a mix of in front of and behind the surface.
// Return the silhouette edge color.
return min16float4(1.f, 1.f, 1.f, 1.0f);
}
else
{
// The pixel and its neighbors are all in front of the surface.
// Return the color of the hologram.
return min16float4(input.color, 1.0f);
}
Примечание: Для нашей процедуры GatherDepthLess см. пример кода surface Mapping: SpecialEffectPixelShader.hlsl.
Отрисовка данных сетки поверхности на дисплее
Кроме того, мы можем просто нарисовать сетки поверхности в буферах стерео display. Мы решили рисовать полные лица с помощью освещения, но вы можете рисовать каркас, обрабатывать сетки перед отрисовкой, применять карту текстур и т. д.
Здесь наш пример кода указывает отрисовщику сетки нарисовать коллекцию. На этот раз мы не указываем проход только для глубины, он подключит пиксельный шейдер и завершит конвейер отрисовки, используя целевые объекты, указанные для текущей виртуальной камеры.
// Spatial Mapping mesh rendering pass: Draw Spatial Mapping mesh over the world.
context->ClearDepthStencilView(pCameraResources->GetSurfaceOcclusionDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
// Set the render target to the current holographic camera's back buffer, and set the depth buffer.
ID3D11RenderTargetView *const targets[1] = { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, pCameraResources->GetSurfaceDepthStencilView());
// This drawing pass renders the surface meshes to the stereoscopic display. The user will be
// able to see them while wearing the device.
m_meshCollection->Render(pCameraResources->IsRenderingStereoscopic(), false);