Android NDK アプリで Depth を使用する

Depth 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;
}

返される画像は、未加工の画像バッファを提供します。このバッファをフラグメント シェーダーに渡して、レンダリングされた各オブジェクトを遮断するために GPU で使用できます。AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES の向きで指定されており、ArFrame_transformCoordinates2d() を呼び出すことで AR_COORDINATES_2D_TEXTURE_NORMALIZED に変更できます。オブジェクト シェーダー内で深度画像にアクセスできるようになると、それらの深度測定値に直接アクセスしてオクルージョンを処理できるようになります。

深度値について

観測された実世界のジオメトリ上の点 A と 2D の点 a 深度画像内の同じポイントを表します。Depth a の API の座標は、CA を主軸に投影した長さと等しくなります。 これは、カメラを基準とする A の Z 座標とも呼ばれます。 オリジン: 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 objects 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);

オクルージョンは、2 パス レンダリングまたはオブジェクトごとのフォワードパス レンダリングを使用してレンダリングできます。それぞれの方法の効率は、シーンの複雑さやその他のアプリ固有の考慮事項によって異なります。

オブジェクトごとのフォワードパス レンダリング

オブジェクトごとのフォワードパス レンダリングでは、マテリアル シェーダーでのオブジェクトの各ピクセルのオクルージョンを決定します。ピクセルが見えない場合は、通常はアルファ ブレンディングによってクリップされ、ユーザーのデバイスでオクルージョンをシミュレートします。

2 パス レンダリング

2 パス レンダリングでは、最初のパスですべての仮想コンテンツが中間バッファにレンダリングされます。2 回目のパスでは、現実世界の奥行きと仮想シーンの奥行きの差に基づいて、仮想シーンを背景にブレンドします。この方法では、オブジェクト固有のシェーダーを追加で作業する必要はなく、通常はフォワードパス メソッドよりも均一な結果が得られます。

カメラ画像と奥行き画像の座標変換

ArFrame_acquireCameraImage() を使用して取得した画像は、奥行きのある画像とは異なるアスペクト比になることがあります。 この場合、奥行き画像はカメラ画像から切り抜かれたものであり、カメラ画像内のすべてのピクセルに対応する有効な奥行き推定値があるわけではありません。

CPU 画像上の座標の深度画像座標を取得するには:

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);
}

奥行き画像座標の CPU 画像座標を取得するには:

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];

深度ヒットのテスト

ヒットテストでは、シーンの実際の場所にオブジェクトを配置できます。以前は、ヒットテストは検出された平面上でしか実行できず、場所は大きな平らな面(緑色の Android に表示される結果など)に限定されていました。深度ヒットテストでは、平面以外でもテクスチャの低いサーフェスでも、スムーズな深度情報と未加工の深度情報の両方を利用して、より正確なヒット結果を提供します。これは、赤色の Android で示されています。

<ph type="x-smartling-placeholder">

深度が有効になっているヒットテストを使用するには、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);

次のステップ

  • Raw Depth API を使用すると、より正確な検出が可能になります。
  • ARCore Depth Lab で、深度データにアクセスするさまざまな方法を確認しましょう。