L'API Depth aide l'appareil photo d'un appareil à comprendre la taille et la forme des objets réels d'une scène. Elle utilise l'appareil photo pour créer des images de profondeur, ou cartes de profondeur, et ajoute ainsi un niveau de réalisme de la RA à vos applications. Vous pouvez utiliser les informations fournies par une représentation de profondeur pour faire apparaître avec précision des objets virtuels devant ou derrière des objets du monde réel, offrant ainsi des expériences utilisateur immersives et réalistes.
Les informations de profondeur sont calculées à partir des mouvements et peuvent être combinées à celles d'un capteur matériel de profondeur, tel qu'un capteur de durée de vol, si disponible. Un appareil n'a pas besoin d'un capteur ToF pour être compatible avec l'API Depth.
Prérequis
Assurez-vous de bien maîtriser les concepts fondamentaux de la RA. et comment configurer une session ARCore avant de continuer.
Limiter l'accès aux appareils compatibles avec la fonctionnalité de profondeur
Si votre application nécessite la prise en charge de l'API Depth, soit parce qu'une partie essentielle de
L'expérience de RA repose sur la profondeur, ou parce qu'il n'existe pas de solution de remplacement élégante pour
les parties de l'application qui utilisent la profondeur, vous pouvez choisir de limiter la distribution de votre
sur le Google Play Store pour
appareils compatibles avec l'API Depth en ajoutant
la ligne suivante à votre AndroidManifest.xml
, en plus de
AndroidManifest.xml
modifications décrites dans le
Guide Activer ARCore:
<uses-feature android:name="com.google.ar.core.depth" />
Activer la profondeur
Dans une nouvelle session ARCore, vérifiez si l'appareil de l'utilisateur est compatible avec la fonctionnalité de profondeur. Les appareils compatibles ARCore ne sont pas tous compatibles avec l'API Depth en raison de contraintes de puissance de traitement. Pour économiser des ressources, la profondeur est désactivée par défaut sur ARCore. Activez le mode Profondeur pour que votre application utilise l'API Depth.
Java
Config config = session.getConfig(); // Check whether the user's device supports the Depth API. boolean isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC); if (isDepthSupported) { config.setDepthMode(Config.DepthMode.AUTOMATIC); } session.configure(config);
Kotlin
val config = session.config // Check whether the user's device supports the Depth API. val isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC) if (isDepthSupported) { config.depthMode = Config.DepthMode.AUTOMATIC } session.configure(config)
Obtenir des représentations de profondeur
Appelez Frame.acquireDepthImage16Bits()
pour obtenir la représentation de profondeur pour l'image actuelle.
Java
// Retrieve the depth image for the current frame, if available. Image depthImage = null; try { depthImage = frame.acquireDepthImage16Bits(); // Use the depth image here. } catch (NotYetAvailableException e) { // This 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. } finally { if (depthImage != null) { depthImage.close(); } }
Kotlin
// Retrieve the depth image for the current frame, if available. try { frame.acquireDepthImage16Bits().use { depthImage -> // Use the depth image here. } } catch (e: NotYetAvailableException) { // This 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. }
L'image renvoyée fournit le tampon d'image brut, qui peut être transmis à un nuanceur de fragments afin qu'il soit utilisé sur le GPU pour masquer chaque objet affiché. Il est orienté dans OPENGL_NORMALIZED_DEVICE_COORDINATES
et peut être remplacé par TEXTURE_NORMALIZED
en appelant Frame.transformCoordinates2d()
. Une fois que la représentation de profondeur est accessible dans un nuanceur d'objets, vous pouvez accéder directement aux mesures de profondeur pour gérer l'occlusion.
Comprendre les valeurs de profondeur
Point A
donné sur la géométrie réelle observée et un point 2D a
.
représentant le même point dans la représentation de profondeur, la valeur donnée par le paramètre
L'API à a
est égale à la longueur de CA
projetée sur l'axe principal.
Également appelée coordonnée Z de A
par rapport à la caméra
origine C
. Lorsque vous utilisez l'API Depth, il est important de comprendre que
Les valeurs de profondeur ne correspondent pas à la longueur du rayon CA
lui-même, mais à la projection
du projet.
Utiliser la profondeur dans les nuanceurs
Analyser les informations de profondeur pour l'image actuelle
Utilisez les fonctions d'assistance DepthGetMillimeters()
et DepthGetVisibility()
dans un nuanceur de fragments pour accéder aux informations de profondeur correspondant à la position actuelle de l'écran. Utilisez ensuite ces informations pour masquer de manière sélective certaines parties de l'objet rendu.
// 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;
}
Masquer les objets virtuels
Les objets virtuels sont masqués dans le corps du nuanceur de fragments. Mettez à jour le canal alpha de l'objet en fonction de sa profondeur. Un objet masqué est alors affiché.
// 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);
Vous pouvez effectuer le rendu d'une occlusion à l'aide d'un rendu en deux passes ou d'un rendu par objet en effectuant une passe avant. L'efficacité de chaque approche dépend de la complexité de la scène et d'autres considérations propres à l'application.
Rendu par objet, passage avant
Le rendu avant transmission détermine l'occlusion de chaque pixel de l'objet dans son nuanceur Material. Si les pixels ne sont pas visibles, ils sont rognés, généralement via le mélange alpha, ce qui simule une occlusion sur l'appareil de l'utilisateur.
Rendu en deux passes
Avec le rendu en deux passes, la première passe affiche tout le contenu virtuel dans un tampon intermédiaire. La seconde passe associe la scène virtuelle à l'arrière-plan en fonction de la différence entre la profondeur réelle et la profondeur virtuelle. Cette approche ne nécessite aucun travail supplémentaire du nuanceur spécifique aux objets et produit généralement des résultats d'apparence plus uniformes que la méthode de transmission avant.
Extraire la distance d'une représentation de profondeur
Si vous souhaitez utiliser l'API Depth à d'autres fins que d'occlure des objets virtuels ou de visualiser des données de profondeur, extrayez les informations de la représentation de profondeur.
Java
/** Obtain the depth in millimeters for depthImage at coordinates (x, y). */ public int getMillimetersDepth(Image depthImage, int x, int y) { // The depth image has a single plane, which stores depth for each // pixel as 16-bit unsigned integers. Image.Plane plane = depthImage.getPlanes()[0]; int byteIndex = x * plane.getPixelStride() + y * plane.getRowStride(); ByteBuffer buffer = plane.getBuffer().order(ByteOrder.nativeOrder()); return Short.toUnsignedInt(buffer.getShort(byteIndex)); }
Kotlin
/** Obtain the depth in millimeters for [depthImage] at coordinates ([x], [y]). */ fun getMillimetersDepth(depthImage: Image, x: Int, y: Int): UInt { // The depth image has a single plane, which stores depth for each // pixel as 16-bit unsigned integers. val plane = depthImage.planes[0] val byteIndex = x * plane.pixelStride + y * plane.rowStride val buffer = plane.buffer.order(ByteOrder.nativeOrder()) val depthSample = buffer.getShort(byteIndex) return depthSample.toUInt() }
Convertir des coordonnées entre des images d'appareil photo et des représentations de profondeur
Les images obtenues à l'aide de getCameraImage()
peuvent avoir un format différent de celui des représentations de profondeur.
Dans ce cas, la représentation de profondeur est un recadrage de l'image de l'appareil photo, et tous les pixels de l'image de l'appareil photo n'ont pas une estimation de profondeur valide correspondante.
Pour obtenir les coordonnées de profondeur pour les coordonnées de l'image de processeur:
Java
float[] cpuCoordinates = new float[] {cpuCoordinateX, cpuCoordinateY}; float[] textureCoordinates = new float[2]; frame.transformCoordinates2d( Coordinates2d.IMAGE_PIXELS, cpuCoordinates, Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates); if (textureCoordinates[0] < 0 || textureCoordinates[1] < 0) { // There are no valid depth coordinates, because the coordinates in the CPU image are in the // cropped area of the depth image. return null; } return new Pair<>( (int) (textureCoordinates[0] * depthImage.getWidth()), (int) (textureCoordinates[1] * depthImage.getHeight()));
Kotlin
val cpuCoordinates = floatArrayOf(cpuCoordinateX.toFloat(), cpuCoordinateY.toFloat()) val textureCoordinates = FloatArray(2) frame.transformCoordinates2d( Coordinates2d.IMAGE_PIXELS, cpuCoordinates, Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates, ) if (textureCoordinates[0] < 0 || textureCoordinates[1] < 0) { // There are no valid depth coordinates, because the coordinates in the CPU image are in the // cropped area of the depth image. return null } return (textureCoordinates[0] * depthImage.width).toInt() to (textureCoordinates[1] * depthImage.height).toInt()
Pour obtenir les coordonnées de l'image de processeur pour la profondeur de l'image:
Java
float[] textureCoordinates = new float[] { (float) depthCoordinateX / (float) depthImage.getWidth(), (float) depthCoordinateY / (float) depthImage.getHeight() }; float[] cpuCoordinates = new float[2]; frame.transformCoordinates2d( Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates, Coordinates2d.IMAGE_PIXELS, cpuCoordinates); return new Pair<>((int) cpuCoordinates[0], (int) cpuCoordinates[1]);
Kotlin
val textureCoordinates = floatArrayOf( depthCoordinatesX.toFloat() / depthImage.width.toFloat(), depthCoordinatesY.toFloat() / depthImage.height.toFloat(), ) val cpuCoordinates = FloatArray(2) frame.transformCoordinates2d( Coordinates2d.TEXTURE_NORMALIZED, textureCoordinates, Coordinates2d.IMAGE_PIXELS, cpuCoordinates, ) return cpuCoordinates[0].toInt() to cpuCoordinates[1].toInt()
Test de positionnement de profondeur
Les tests de positionnement permettent aux utilisateurs de placer des objets à un emplacement réel de la scène. Auparavant, les tests de positionnement ne pouvaient être effectués que sur des avions détectés, en limitant les emplacements aux grandes surfaces plates, comme les résultats des Android verts. Les tests de positionnement de profondeur exploitent les informations de profondeur à la fois fluides et brutes pour fournir des résultats de positionnement plus précis, même sur les surfaces non planes et à faible texture. C'est ce qu'illustrent les personnages Android rouges.
<ph type="x-smartling-placeholder">
Pour utiliser des tests de positionnement avec fonctionnalité de profondeur, appelez hitTest()
et recherchez DepthPoints
dans la liste renvoyée.
Java
// Create a hit test using the Depth API. List<HitResult> hitResultList = frame.hitTest(tap); for (HitResult hit : hitResultList) { Trackable trackable = hit.getTrackable(); if (trackable instanceof Plane || trackable instanceof Point || trackable instanceof DepthPoint) { useHitResult(hit); break; } }
Kotlin
// Create a hit test using the Depth API. val hitResult = frame .hitTest(tap) .filter { val trackable = it.trackable trackable is Plane || trackable is Point || trackable is DepthPoint } .firstOrNull() useHitResult(hitResult)
Étapes suivantes
- Améliorez la précision de la détection avec l'API Raw Depth.
- Suivez l'atelier ARCore Depth qui présente différentes façons d'accéder aux données de profondeur.