Use Depth in your Android NDK app

The Depth API helps a device’s camera to understand the size and shape of the real objects in a scene. It uses the camera to create depth images, or depth maps, thereby adding a layer of AR realism into your apps. You can use the information provided by a depth image to make virtual objects accurately appear in front of or behind real world objects, enabling immersive and realistic user experiences.

Depth information is calculated from motion and may be combined with information from a hardware depth sensor, such as a time-of-flight (ToF) sensor, if available. A device does not need a ToF sensor to support the Depth API.

Prerequisites

Make sure that you understand fundamental AR concepts and how to configure an ARCore session before proceeding.

Restrict access to Depth-supported devices

If your app requires Depth API support, either because a core part of the AR experience relies on depth, or because there's no graceful fallback for the parts of the app that use depth, you may choose to restrict distribution of your app in the Google Play Store to devices that support the Depth API by adding the following line to your AndroidManifest.xml , in addition to the AndroidManifest.xml changes described in the Enable ARCore guide:

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

Enable Depth

In a new ARCore session , check whether a user's device supports Depth. Not all ARCore-compatible devices support the Depth API due to processing power constraints. To save resources, depth is disabled by default on ARCore. Enable depth mode to have your app use the 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 
 ); 

Acquire depth images

Call ArFrame_acquireDepthImage16Bits() to get the depth image for the current frame.

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

The returned image provides the raw image buffer, which can be passed to a fragment shader for usage on the GPU for each rendered object to be occluded. It is oriented in AR_COORDINATES_2D_OPENGL_NORMALIZED_DEVICE_COORDINATES and can be changed to AR_COORDINATES_2D_TEXTURE_NORMALIZED by calling ArFrame_transformCoordinates2d() . Once the depth image is accessible within an object shader, these depth measurements can be accessed directly for occlusion handling.

Understand depth values

Given point A on the observed real-world geometry and a 2D point a representing the same point in the depth image, the value given by the Depth API at a is equal to the length of CA projected onto the principal axis. This can also be referred as the z-coordinate of A relative to the camera origin C . When working with the Depth API, it is important to understand that the depth values are not the length of the ray CA itself, but the projection of it.

Use depth in shaders

Parse depth information for the current frame

Use the helper functions DepthGetMillimeters() and DepthGetVisibility() in a fragment shader to access the depth information for the current screen position. Then use this information to selectively occlude parts of the rendered object.

  // 
  
 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.015 
 f 
 ; 
  
 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.0 
 f 
 ; 
  
 float 
  
 visibility 
  
 = 
  
 max 
 ( 
 max 
 ( 
 visibility_occlusion 
 , 
  
 kOcclusionAlpha 
 ), 
  
 max 
 ( 
 visibility_depth_near 
 , 
  
 visibility_depth_far 
 )); 
  
 return 
  
 visibility 
 ; 
 } 
 

Occlude virtual objects

Occlude virtual objects in the body of the fragment shader. Update the object's alpha channel based on its depth. This will render an occluded object.

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

You can render occlusion using two-pass rendering or per-object, forward-pass rendering. The efficiency of each approach depends on the complexity of the scene and other app-specific considerations.

Per-object, forward-pass rendering

Per-object, forward-pass rendering determines the occlusion of each pixel of the object in its material shader. If the pixels are not visible, they are clipped, typically via alpha blending, thus simulating occlusion on the user’s device.

Two-pass rendering

With two-pass rendering, the first pass renders all of the virtual content into an intermediary buffer. The second pass blends the virtual scene onto the background based on the difference between the real-world depth with the virtual scene depth. This approach requires no additional object-specific shader work and generally produces more uniform-looking results than the forward-pass method.

Converting coordinates between camera images and depth images

Images obtained using ArFrame_acquireCameraImage() may have a different aspect ratio compared to depth images. In this case, the depth image is a crop of the camera image, and not all pixels in the camera image have a corresponding valid depth estimate.

To obtain depth image coordinates for coordinates on the CPU image:

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

To obtain CPU image coordinates for depth image coordinates:

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

Depth hit test

Hit tests allow users to place objects at a real-world location in the scene. Previously, hit tests could only be conducted on detected planes, limiting locations to large, flat surfaces, such as the results shown by the green Androids. Depth hit tests take advantage of both smooth and raw depth information to provide more accurate hit results, even on non-planar and low-texture surfaces. This is shown with the red Androids.

To use depth-enabled hit tests, call ArFrame_hitTest() and check for AR_TRACKABLE_DEPTH_POINT s in the return list.

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

What’s next

Create a Mobile Website
View Site in Mobile | Classic
Share by: