از Depth در برنامه Android NDK خود استفاده کنید

Depth API به دوربین دستگاه کمک می کند تا اندازه و شکل اشیاء واقعی را در یک صحنه درک کند. از دوربین برای ایجاد تصاویر عمق یا نقشه های عمق استفاده می کند و در نتیجه لایه ای از واقعیت واقعیت افزوده را به برنامه های شما اضافه می کند. می‌توانید از اطلاعات ارائه‌شده توسط یک تصویر عمقی استفاده کنید تا اشیاء مجازی را به‌طور دقیق در جلو یا پشت اشیاء دنیای واقعی نشان دهید و تجربه‌های واقعی و واقعی کاربر را امکان‌پذیر کنید.

اطلاعات عمق از حرکت محاسبه می شود و در صورت وجود ممکن است با اطلاعات یک حسگر عمق سخت افزاری مانند سنسور زمان پرواز (ToF) ترکیب شود. یک دستگاه برای پشتیبانی از Depth API به حسگر ToF نیاز ندارد .

پیش نیازها

قبل از ادامه، مطمئن شوید که مفاهیم اساسی AR و نحوه پیکربندی یک جلسه ARCore را درک کرده اید.

دسترسی به دستگاه های پشتیبانی شده از عمق را محدود کنید

اگر برنامه شما به پشتیبانی Depth API نیاز دارد، یا به این دلیل که بخش اصلی تجربه واقعیت افزوده به عمق متکی است، یا به این دلیل که برای بخش‌هایی از برنامه که از عمق استفاده می‌کنند، نسخه بازگشتی خوبی وجود ندارد، می‌توانید توزیع برنامه خود را در Google Play محدود کنید. با افزودن خط زیر به AndroidManifest.xml ، علاوه بر تغییرات AndroidManifest.xml که در راهنمای فعال کردن ARCore توضیح داده شده است، در دستگاه‌هایی که از Depth API پشتیبانی می‌کنند، ذخیره کنید:

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

Depth را فعال کنید

در جلسه ARCore جدید ، بررسی کنید که آیا دستگاه کاربر از عمق پشتیبانی می‌کند یا خیر. همه دستگاه‌های سازگار با 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 در هندسه دنیای واقعی مشاهده شده و یک نقطه 2 بعدی a همان نقطه را در تصویر عمق نشان می دهد، مقدار داده شده توسط Depth API در a برابر است با طول CA پیش بینی شده روی محور اصلی. این را می توان به عنوان مختصات z A نسبت به مبدا دوربین C نیز نام برد. هنگام کار با Depth API، درک این نکته مهم است که مقادیر عمق طول خود پرتو CA نیست، بلکه نمایش آن است.

از عمق در سایه زن استفاده کنید

اطلاعات عمق فریم فعلی را تجزیه کنید

از توابع کمکی DepthGetMillimeters() و DepthGetVisibility() در یک shader قطعه برای دسترسی به اطلاعات عمق برای موقعیت فعلی صفحه استفاده کنید. سپس از این اطلاعات برای مسدود کردن انتخابی بخش هایی از شی رندر شده استفاده کنید.

// 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() ممکن است نسبت ابعاد متفاوتی نسبت به تصاویر عمق داشته باشند. در این حالت، تصویر عمق برشی از تصویر دوربین است و همه پیکسل‌های تصویر دوربین تخمین عمق معتبر مربوطه ندارند.

برای بدست آوردن مختصات عمق تصویر برای مختصات روی تصویر 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];

تست ضربه عمق

تست ضربه به کاربران امکان می دهد اشیاء را در یک مکان واقعی در صحنه قرار دهند. پیش از این، آزمایش‌های ضربه فقط روی هواپیماهای شناسایی‌شده انجام می‌شد و مکان‌ها را به سطوح بزرگ و مسطح محدود می‌کرد، مانند نتایج نشان‌داده‌شده توسط اندرویدهای سبز. تست‌های ضربه عمقی از اطلاعات عمق صاف و خام برای ارائه نتایج دقیق‌تر حتی در سطوح غیرمسطح و با بافت کم بهره می‌برند. این با اندرویدهای قرمز نشان داده شده است.

برای استفاده از تست‌های ضربه فعال با عمق، 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 را بررسی کنید، که راه‌های مختلفی برای دسترسی به داده‌های عمقی را نشان می‌دهد.