Interfejs Depth API pomaga aparatowi urządzenia zrozumieć rozmiar i kształt rzeczywistych obiektów. Aparat używa aparatu do tworzenia obrazów głębi w mapach, dzięki czemu do aplikacji dodawane są warstwy realizmu AR. Dzięki takim informacjom możesz wyświetlać obiekty wirtualne przed rzeczywistymi obiektami lub za nimi, a tym samym tworzyć realistyczne i wciągające efekty.
Informacje o głębokości są obliczane na podstawie ruchu i mogą być połączone z informacjami z czujnika głębokości sprzętu, np. z czujnikiem czasu lotu (ToF), jeśli to możliwe. Urządzenie nie potrzebuje czujnika ToF, aby obsługiwać interfejs Depth API.
Wymagania wstępne
Zanim przejdziesz dalej, zapoznaj się z podstawowymi pojęciami dotyczącymi AR i dowiedz się, jak skonfigurować sesję ARCore.
Ogranicz dostęp do urządzeń z funkcją głębi
Jeśli Twoja aplikacja wymaga obsługi interfejsu Deth API, ponieważ główna część interfejsu AR opiera się na głębokości lub nie ma żadnego zastępczego rozwiązania dla części aplikacji, które korzystają z tej funkcji, możesz ograniczyć rozpowszechnianie swojej aplikacji w Sklepie Google Play do urządzeń obsługujących ten interfejs, dodając do polecenia AndroidManifest.xml
taki wiersz:
<uses-feature android:name="com.google.ar.core.depth" />
Włącz głębię
W nowej sesji ARCore sprawdź, czy urządzenie obsługuje Głębokość. Nie wszystkie urządzenia zgodne z technologią ARCore obsługują interfejs Depth API ze względu na ograniczenia dotyczące zasilania. Aby zaoszczędzić zasoby, głębokość jest domyślnie wyłączona w ARCore. Włącz tryb głębi, aby aplikacja korzystała z interfejsu Depth API.
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)
Uzyskiwanie obrazów głębi
Wywołaj Frame.acquireDepthImage16Bits()
, aby zobaczyć obraz głębi w bieżącej klatce.
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. }
Zwrócony obraz zawiera bufor nieprzetworzonego obrazu, który może zostać przekazany do shadera fragmentów w celu użycia ich na GPU. Ten język jest ustawiony na OPENGL_NORMALIZED_DEVICE_COORDINATES
i można go zmienić na TEXTURE_NORMALIZED
, dzwoniąc pod numer Frame.transformCoordinates2d()
. Gdy obraz głębi jest dostępny w cieniu obiektu, pomiary te są dostępne bezpośrednio do obsługi przesłaniania.
Wartości głębi
Biorąc pod uwagę punkt A
na zaobserwowanej rzeczywistej geometrii i punkt 2D a
, reprezentujący ten sam punkt na obrazie głębi, wartość podana przez interfejs Głębokość interfejsu a
na poziomie równym długości CA
jest przewidywana na osi podmiotu zabezpieczeń.
Może to być także określane jako współrzędna A
względem punktu początkowego aparatu C
. W przypadku korzystania z interfejsu Depth API ważne jest, aby pamiętać, że wartości głębi nie są długością promienia CA
, ale jego projekcją.
Użyj głębi w cieniach
Analizuj informacje o głębokości bieżącej ramki
Aby uzyskać informacje o głębokości bieżącej pozycji ekranu, użyj funkcji pomocniczych DepthGetMillimeters()
i DepthGetVisibility()
w cienisku fragmentu. Następnie używasz tych informacji do selektywnego zasłaniania fragmentów wyrenderowanego obiektu.
// 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;
}
Zamknij obiekty wirtualne
Zasłaniaj obiekty wirtualne w treści shadera fragmentów. Zaktualizuj kanał alfa obiektu według jego głębokości. Spowoduje to wyrenderowanie przesłanianego obiektu.
// 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);
Mogą się one wyświetlać w ramach renderowania z dwoma kartami lub z poszczególnych obiektów. Skuteczność każdego z nich zależy od złożoności sceny i innych kwestii związanych z aplikacją.
Wyświetlenie obiektu z pominięciem obiektu
W przypadku każdego obiektu, renderowanie z przekazem dalej, określa przesłanianie każdego piksela obiektu w cieniu materiałowym. Jeśli piksele są niewidoczne, są przycinane, zazwyczaj przez mieszanie w wersji alfa, co powoduje symulowanie przesłaniania na urządzeniu użytkownika.
Renderowanie dwupasmowe
W przypadku renderowania dwustronnego pierwsza karta przekazuje całą zawartość wirtualną do bufora pośredniego. Druga karta łączy scenę wirtualną w tle na podstawie różnicy między głębią obrazu a rzeczywistością wirtualną. Ta metoda nie wymaga żadnej dodatkowej pracy polegającej na cieniowaniu obiektu i generuje zwykle bardziej jednolite wyniki niż metoda przekazywania.
Wyodrębnij odległość od obrazu głębi
Aby używać interfejsu Depth API do celów innych niż zasłanianie obiektów wirtualnych lub wizualizowanie danych o głębi, wydobywaj informacje z obrazu głębi.
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 buffer.getShort(byteIndex); }
Kotlin
/** Obtain the depth in millimeters for [depthImage] at coordinates ([x], [y]). */ fun getMillimetersDepth(depthImage: Image, x: Int, y: Int): Int { // 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.toInt() }
Konwersja współrzędnych między zdjęciami z aparatu i zdjęciami głębi
Obrazy uzyskane przy użyciu getCameraImage()
mogą mieć inne współczynniki proporcji niż obrazy głębi.
W tym przypadku obraz głębi jest przyciętym obrazem z kamery, ale nie wszystkie piksele na zdjęciu w aparacie mają odpowiednią szacunkową głębokość.
Aby uzyskać współrzędne obrazu głębi w obrazie procesora:
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()
Aby uzyskać współrzędne obrazu procesora dla współrzędnych obrazu głębi:
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()
Badanie głębi
Testy tego typu pozwalają użytkownikom umieszczać obiekty w rzeczywistym miejscu na scenie. Wcześniej testy zbiorowe można przeprowadzać tylko na wykrytych samolotach, ograniczając ich lokalizacje do dużych, płaskich powierzchni, takich jak wyniki wyświetlane przez zielone urządzenia z Androidem. Testy trafień dotyczą zarówno gładkich, jak i nieprzetworzonych informacji o głębi – w ten sposób uzyskasz dokładniejsze wyniki działań, nawet w przypadku powierzchni o nieregularnej powierzchni i niskiej teksturze. Jest to zaznaczone na czerwono na Androidzie.
Aby korzystać z testów działań z włączoną głębokością, wywołaj funkcję hitTest()
i sprawdź, czy na liście powrotnej znajduje się wartość DepthPoints
.
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)
Co dalej?
- Zwiększ dokładność wykrywania za pomocą interfejsu Raw Depth API.
- Zajrzyj do ARCore Depth Lab, które pokazuje różne sposoby uzyskiwania dostępu do danych o głębokości.