Используйте Depth в своем приложении Android NDK

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

Информация о глубине рассчитывается на основе движения и может быть объединена с информацией от аппаратного датчика глубины, такого как датчик времени полета (ToF), если таковой имеется. Для поддержки Depth API устройству не требуется датчик ToF .

Предварительные условия

Прежде чем продолжить, убедитесь, что вы понимаете фундаментальные концепции AR и то, как настроить сеанс ARCore .

Ограничить доступ к устройствам с поддержкой Depth

Если вашему приложению требуется поддержка Depth API, либо потому, что основная часть AR-интерфейса зависит от глубины, либо потому, что для частей приложения, использующих глубину, не существует корректного резервного варианта, вы можете ограничить распространение своего приложения в Google Play. Сохраните на устройствах, поддерживающих Depth API, добавив следующую строку в AndroidManifest.xml в дополнение к изменениям AndroidManifest.xml , описанным в руководстве по включению ARCore :

<uses-feature android:name="com.google.ar.core.depth" />

Включить глубину

В новом сеансе ARCore проверьте, поддерживает ли устройство пользователя Depth. Не все ARCore-совместимые устройства поддерживают Depth API из-за ограничений вычислительной мощности. Для экономии ресурсов глубина в ARCore по умолчанию отключена. Включите режим глубины, чтобы ваше приложение использовало Depth API.

// Check whether the user's device supports the Depth API.
int32_t is_depth_supported
= 0;
ArSession_isDepthModeSupported(ar_session, AR_DEPTH_MODE_AUTOMATIC,
                               
&is_depth_supported);

// Configure the session for AR_DEPTH_MODE_AUTOMATIC.
ArConfig* ar_config = NULL;
ArConfig_create(ar_session, &ar_config);
if (is_depth_supported) {
 
ArConfig_setDepthMode(ar_session, ar_config, AR_DEPTH_MODE_AUTOMATIC);
}
CHECK
(ArSession_configure(ar_session, ar_config) == AR_SUCCESS);
ArConfig_destroy(ar_config);

Получение изображений глубины

Вызовите ArFrame_acquireDepthImage16Bits() , чтобы получить изображение глубины для текущего кадра.

// Retrieve the depth image for the current frame, if available.
ArImage* depth_image = NULL;
// If a depth image is available, use it here.
if (ArFrame_acquireDepthImage16Bits(ar_session, ar_frame, &depth_image) !=
    AR_SUCCESS
) {
 
// No depth image received for this frame.
 
// This normally means that depth data is not available yet.
 
// Depth data will not be available if there are no tracked
 
// feature points. This can happen when there is no motion, or when the
 
// camera loses its ability to track objects in the surrounding
 
// environment.
 
return;
}

Возвращенное изображение предоставляет буфер необработанного изображения, который можно передать во фрагментный шейдер для использования на графическом процессоре для каждого визуализированного объекта, который необходимо перекрыть. Он ориентирован на AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES и может быть изменен на AR_COORDINATES_2D_TEXTURE_NORMALIZED путем вызова ArFrame_transformCoordinates2d() . Как только изображение глубины становится доступным в объектном шейдере, к этим измерениям глубины можно получить прямой доступ для обработки окклюзии.

Понимание значений глубины

Учитывая точку A на наблюдаемой геометрии реального мира и двумерную точку a представляющую ту же точку на изображении глубины, значение, заданное Depth API в a , равно длине CA , проецируемой на главную ось. Это также можно назвать координатой Z A относительно начала координат камеры C . При работе с Depth API важно понимать, что значения глубины — это не длина самого CA луча, а его проекция .

Используйте глубину в шейдерах

Анализ информации о глубине для текущего кадра

Используйте вспомогательные функции DepthGetMillimeters() и DepthGetVisibility() во фрагментном шейдере, чтобы получить доступ к информации о глубине для текущей позиции экрана. Затем используйте эту информацию для выборочного закрытия частей визуализируемого объекта.

// Use DepthGetMillimeters() and DepthGetVisibility() to parse the depth image
// for a given pixel, and compare against the depth of the object to render.
float DepthGetMillimeters(in sampler2D depth_texture, in vec2 depth_uv) {
 
// Depth is packed into the red and green components of its texture.
 
// The texture is a normalized format, storing millimeters.
  vec3 packedDepthAndVisibility
= texture2D(depth_texture, depth_uv).xyz;
 
return dot(packedDepthAndVisibility.xy, vec2(255.0, 256.0 * 255.0));
}

// Return a value representing how visible or occluded a pixel is relative
// to the depth image. The range is 0.0 (not visible) to 1.0 (completely
// visible).
float DepthGetVisibility(in sampler2D depth_texture, in vec2 depth_uv,
                         
in float asset_depth_mm) {
 
float depth_mm = DepthGetMillimeters(depth_texture, depth_uv);

 
// Instead of a hard Z-buffer test, allow the asset to fade into the
 
// background along a 2 * kDepthTolerancePerMm * asset_depth_mm
 
// range centered on the background depth.
 
const float kDepthTolerancePerMm = 0.015f;
 
float visibility_occlusion = clamp(0.5 * (depth_mm - asset_depth_mm) /
   
(kDepthTolerancePerMm * asset_depth_mm) + 0.5, 0.0, 1.0);

 
// Use visibility_depth_near to set the minimum depth value. If using
 
// this value for occlusion, avoid setting it too close to zero. A depth value
 
// of zero signifies that there is no depth data to be found.
 
float visibility_depth_near = 1.0 - InverseLerp(
      depth_mm
, /*min_depth_mm=*/150.0, /*max_depth_mm=*/200.0);

 
// Use visibility_depth_far to set the maximum depth value. If the depth
 
// value is too high (outside the range specified by visibility_depth_far),
 
// the virtual object may get inaccurately occluded at further distances
 
// due to too much noise.
 
float visibility_depth_far = InverseLerp(
      depth_mm
, /*min_depth_mm=*/7500.0, /*max_depth_mm=*/8000.0);

 
const float kOcclusionAlpha = 0.0f;
 
float visibility =
      max
(max(visibility_occlusion, kOcclusionAlpha),
          max
(visibility_depth_near, visibility_depth_far));

 
return visibility;
}

Закрывать виртуальные объекты

Окклюдируйте виртуальные объекты в теле фрагментного шейдера. Обновите альфа-канал объекта в зависимости от его глубины. Это отобразит закрытый объект.

// Occlude virtual objects by updating the object’s alpha channel based on its depth.
const float kMetersToMillimeters = 1000.0;

float asset_depth_mm = v_ViewPosition.z * kMetersToMillimeters * -1.;

// Compute the texture coordinates to sample from the depth image.
vec2 depth_uvs
= (u_DepthUvTransform * vec3(v_ScreenSpacePosition.xy, 1)).xy;

gl_FragColor
.a *= DepthGetVisibility(u_DepthTexture, depth_uvs, asset_depth_mm);

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

Пообъектный рендеринг вперед

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

Двухпроходный рендеринг

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

Преобразование координат между изображениями камеры и изображениями глубины

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

Чтобы получить координаты изображения глубины для координат на изображении ЦП:

const float cpu_image_coordinates[] = {(float)cpu_coordinate_x,
                                 
(float)cpu_coordinate_y};
float texture_coordinates[2];
ArFrame_transformCoordinates2d(
    ar_session
, ar_frame, AR_COORDINATES_2D_IMAGE_PIXELS, 1,
    cpu_image_coordinates
, AR_COORDINATES_2D_TEXTURE_NORMALIZED,
    texture_coordinates
);
if (texture_coordinates[0] < 0 || texture_coordinates[1] < 0) {
 
// There are no valid depth coordinates, because the coordinates in the CPU
 
// image are in the cropped area of the depth image.
} else {
 
int depth_image_width = 0;
 
ArImage_getWidth(ar_session, depth_image, &depth_image_width);
 
int depth_image_height = 0;
 
ArImage_getHeight(ar_session, depth_image, &depth_image_height);

 
int depth_coordinate_x = (int)(texture_coordinates[0] * depth_image_width);
 
int depth_coordinate_y = (int)(texture_coordinates[1] * depth_image_height);
}

Чтобы получить координаты изображения ЦП для координат изображения глубины:

int depth_image_width = 0;
ArImage_getWidth(ar_session, depth_image, &depth_image_width);
int depth_image_height = 0;
ArImage_getHeight(ar_session, depth_image, &depth_image_height);

float texture_coordinates[] = {
   
(float)depth_coordinate_x / (float)depth_image_width,
   
(float)depth_coordinate_y / (float)depth_image_height};
float cpu_image_coordinates[2];
ArFrame_transformCoordinates2d(
    ar_session
, ar_frame, AR_COORDINATES_2D_TEXTURE_NORMALIZED, 1,
    texture_coordinates
, AR_COORDINATES_2D_IMAGE_PIXELS,
    cpu_image_coordinates
);

int cpu_image_coordinate_x = (int)cpu_image_coordinates[0];
int cpu_image_coordinate_y = (int)cpu_image_coordinates[1];

Тест на глубину

Хит-тесты позволяют пользователям размещать объекты в реальных местах сцены. Раньше тесты на попадание можно было проводить только на обнаруженных самолетах, ограничивая локации большими плоскими поверхностями, как, например, результаты, показанные зелеными андроидами. В тестах на удар по глубине используются как гладкие, так и необработанные данные о глубине, чтобы обеспечить более точные результаты удара даже на неплоских и слаботекстурированных поверхностях. Это показано красными Андроидами.

Чтобы использовать тесты попадания с поддержкой глубины, вызовите ArFrame_hitTest() и проверьте наличие AR_TRACKABLE_DEPTH_POINT в возвращаемом списке.

// Create a hit test using the Depth API.
ArHitResultList* hit_result_list = NULL;
ArHitResultList_create(ar_session, &hit_result_list);
ArFrame_hitTest(ar_session, ar_frame, hit_x, hit_y, hit_result_list);

int32_t hit_result_list_size
= 0;
ArHitResultList_getSize(ar_session, hit_result_list, &hit_result_list_size);

ArHitResult* ar_hit_result = NULL;
for (int32_t i = 0; i < hit_result_list_size; ++i) {
 
ArHitResult* ar_hit = NULL;
 
ArHitResult_create(ar_session, &ar_hit);
 
ArHitResultList_getItem(ar_session, hit_result_list, i, ar_hit);

 
ArTrackable* ar_trackable = NULL;
 
ArHitResult_acquireTrackable(ar_session, ar_hit, &ar_trackable);
 
ArTrackableType ar_trackable_type = AR_TRACKABLE_NOT_VALID;
 
ArTrackable_getType(ar_session, ar_trackable, &ar_trackable_type);
 
// Creates an anchor if a plane or an oriented point was hit.
 
if (AR_TRACKABLE_DEPTH_POINT == ar_trackable_type) {
   
// Do something with the hit result.
 
}
 
ArTrackable_release(ar_trackable);
 
ArHitResult_destroy(ar_hit);
}

ArHitResultList_destroy(hit_result_list);

Что дальше

  • Обеспечьте более точное измерение с помощью API Raw Depth .
  • Посетите лабораторию ARCore Depth Lab , где демонстрируются различные способы доступа к данным о глубине.