1. Hinweis
ARCore ist eine Plattform zum Erstellen von Augmented Reality-Apps (AR-Apps) auf Mobilgeräten. Mit verschiedenen APIs ermöglicht ARCore es dem Gerät eines Nutzers, seine Umgebung zu erfassen und Informationen dazu zu erhalten und mit diesen Informationen zu interagieren.
In diesem Codelab erfahren Sie, wie Sie eine einfache AR-fähige App erstellen, die die ARCore Depth API verwendet.
Vorbereitung
Dieses Codelab wurde für Entwickler mit Kenntnissen der grundlegenden AR-Konzepte geschrieben.
Umfang

Sie erstellen eine App, die das Tiefenbild für jeden Frame verwendet, um die Geometrie der Szene zu visualisieren und Okklusionen auf platzierten virtuellen Assets auszuführen. Sie durchlaufen die folgenden Schritte:
- Prüfen, ob die Depth API auf dem Smartphone unterstützt wird
- Tiefenbild für jeden Frame abrufen
- Tiefeninformationen auf verschiedene Arten visualisieren (siehe Animation oben)
- Realismus von Apps mit Okklusion durch Verwendung von Tiefe erhöhen
- Informationen zum ordnungsgemäßen Umgang mit Smartphones, die die Depth API nicht unterstützen
Voraussetzungen
Hardware Requirements
- Ein unterstütztes ARCore-Gerät, das über ein USB-Kabel mit Ihrem Entwicklercomputer verbunden ist. Dieses Gerät muss auch die Depth API unterstützen. Hier finden Sie eine Liste der unterstützten Geräte. Die Depth API ist nur auf Android-Geräten verfügbar.
- Aktivieren Sie das USB-Debugging für dieses Gerät.
Softwareanforderungen
- ARCore SDK 1.31.0 oder höher.
- Ein Entwicklercomputer mit Android Studio (Version 3.0 oder höher).
2. ARCore und die Depth API
Die Depth API verwendet die RGB-Kamera eines unterstützten Geräts, um Tiefenkarten (auch Tiefenbilder genannt) zu erstellen. Mithilfe der Informationen einer Tiefenkarte können Sie virtuelle Objekte präzise vor oder hinter realen Objekten darstellen und so immersive und realistische Nutzererlebnisse schaffen.
Die ARCore Depth API bietet Zugriff auf Tiefenbilder, die jedem Frame der ARCore-Sitzung entsprechen. Jeder Pixel liefert eine Entfernungsmessung von der Kamera zur Umgebung, was die Realitätstreue Ihrer AR-App verbessert.
Eine wichtige Funktion der Depth API ist die Verdeckung. Damit können digitale Objekte in Bezug auf reale Objekte korrekt dargestellt werden. Dadurch fühlen sich Objekte so an, als wären sie tatsächlich in der Umgebung des Nutzers.
In diesem Codelab erfahren Sie, wie Sie eine einfache AR-fähige App erstellen, die mithilfe von Tiefenbildern virtuelle Objekte hinter realen Oberflächen verdeckt und die erkannte Geometrie des Raums visualisiert.
3. Einrichten
Entwicklungscomputer einrichten
- Verbinden Sie Ihr ARCore-Gerät über das USB-Kabel mit dem Computer. Achten Sie darauf, dass Ihr Gerät USB-Debugging zulässt.
- Öffnen Sie ein Terminal und führen Sie
adb devicesaus, wie unten gezeigt:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
Die <DEVICE_SERIAL_NUMBER> ist ein String, der für Ihr Gerät eindeutig ist. Achten Sie darauf, dass genau ein Gerät angezeigt wird, bevor Sie fortfahren.
Laden Sie Code herunter und installieren Sie es.
- Sie können das Repository entweder klonen:
git clone https://github.com/googlecodelabs/arcore-depth
Oder laden Sie eine ZIP-Datei herunter und extrahieren Sie sie:
- Starten Sie Android Studio und klicken Sie auf Open an existing Android Studio project (Vorhandenes Android Studio-Projekt öffnen).
- Suchen Sie das Verzeichnis, in dem Sie die oben heruntergeladene ZIP-Datei extrahiert haben, und öffnen Sie das Verzeichnis
depth_codelab_io2020.
Dies ist ein einzelnes Gradle-Projekt mit mehreren Modulen. Wenn der Bereich „Projekt“ oben links in Android Studio noch nicht angezeigt wird, klicken Sie im Drop-down-Menü auf Projekte.
Das Ergebnis sollte so aussehen:
| Dieses Projekt enthält die folgenden Module:
|
Sie arbeiten im Modul part0_work. Außerdem gibt es vollständige Lösungen für jeden Teil des Codelabs. Jedes Modul ist eine App, die erstellt werden kann.
4. Start-App ausführen
- Klicken Sie auf Ausführen > Ausführen… > ‘part0_work'. Im angezeigten Dialogfeld Bereitstellungsziel auswählen sollte Ihr Gerät unter Verbundene Geräte aufgeführt sein.
- Wählen Sie Ihr Gerät aus und klicken Sie auf OK. Android Studio erstellt die erste App und führt sie auf Ihrem Gerät aus.
- Die App fordert die Berechtigung für die Kamera an. Tippe auf Zulassen, um fortzufahren.

| App verwenden
|
Derzeit ist Ihre App sehr einfach und weiß nicht viel über die Geometrie der realen Umgebung.
Wenn Sie beispielsweise eine Android-Figur hinter einen Stuhl stellen, wird sie im Rendering vor dem Stuhl angezeigt, da die App nicht weiß, dass der Stuhl da ist und die Android-Figur verdecken sollte.

Um dieses Problem zu beheben, werden wir die Depth API verwenden, um die Immersion und den Realismus in dieser App zu verbessern.
5. Prüfen, ob die Depth API unterstützt wird (Teil 1)
Die ARCore Depth API wird nur auf einer Teilmenge der unterstützten Geräte ausgeführt. Bevor Sie Funktionen in eine App einbinden, die diese Tiefenbilder verwendet, müssen Sie zuerst dafür sorgen, dass die App auf einem unterstützten Gerät ausgeführt wird.
Fügen Sie DepthCodelabActivity ein neues privates Mitglied hinzu, das als Flag dient und speichert, ob das aktuelle Gerät Tiefe unterstützt:
private boolean isDepthSupported;
Wir können dieses Flag in der Funktion onResume() festlegen, in der eine neue Sitzung erstellt wird.
So finden Sie den vorhandenen Code:
// Creates the ARCore session.
session = new Session(/* context= */ this);
Aktualisieren Sie den Code auf:
// Creates the ARCore session.
session = new Session(/* context= */ this);
Config config = session.getConfig();
isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC);
if (isDepthSupported) {
config.setDepthMode(Config.DepthMode.AUTOMATIC);
} else {
config.setDepthMode(Config.DepthMode.DISABLED);
}
session.configure(config);
Die AR-Sitzung ist jetzt richtig konfiguriert und Ihre App weiß, ob sie tiefenbasierte Funktionen verwenden kann.
Sie sollten den Nutzer auch darüber informieren, ob für diese Sitzung die Tiefe verwendet wird.
Fügen Sie der Snackbar eine weitere Nachricht hinzu. Sie wird unten auf dem Bildschirm angezeigt:
// Add this line at the top of the file, with the other messages.
private static final String DEPTH_NOT_AVAILABLE_MESSAGE = "[Depth not supported on this device]";
In onDrawFrame() können Sie diese Nachricht nach Bedarf präsentieren:
// Add this if-statement above messageSnackbarHelper.showMessage(this, messageToShow).
if (!isDepthSupported) {
messageToShow += "\n" + DEPTH_NOT_AVAILABLE_MESSAGE;
}
Wenn Ihre App auf einem Gerät ausgeführt wird, das keine Tiefeninformationen unterstützt, wird die gerade hinzugefügte Meldung unten angezeigt:

Als Nächstes aktualisieren Sie die App, um die Depth API aufzurufen und Tiefenbilder für jeden Frame abzurufen.
6. Tiefenbilder abrufen (Teil 2)
Mit der Depth API werden 3D-Beobachtungen der Umgebung des Geräts erfasst und ein Tiefenbild mit diesen Daten an Ihre App zurückgegeben. Jedes Pixel im Tiefenbild stellt eine Entfernungsmessung von der Gerätekamera zur realen Umgebung dar.
Jetzt verwenden Sie diese Tiefenbilder, um das Rendern und die Visualisierung in der App zu verbessern. Der erste Schritt besteht darin, das Tiefenbild für jeden Frame abzurufen und die Textur für die Verwendung durch die GPU zu binden.
Fügen Sie zuerst eine neue Klasse zu Ihrem Projekt hinzu.DepthTextureHandler ist für das Abrufen des Tiefenbilds für einen bestimmten ARCore-Frame verantwortlich.
Fügen Sie diese Datei hinzu:

src/main/java/com/google/ar/core/codelab/depth/DepthTextureHandler.java
package com.google.ar.core.codelab.depth;
import static android.opengl.GLES20.GL_CLAMP_TO_EDGE;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_S;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_T;
import static android.opengl.GLES20.GL_UNSIGNED_BYTE;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glGenTextures;
import static android.opengl.GLES20.glTexImage2D;
import static android.opengl.GLES20.glTexParameteri;
import static android.opengl.GLES30.GL_LINEAR;
import static android.opengl.GLES30.GL_RG;
import static android.opengl.GLES30.GL_RG8;
import android.media.Image;
import com.google.ar.core.Frame;
import com.google.ar.core.exceptions.NotYetAvailableException;
/** Handle RG8 GPU texture containing a DEPTH16 depth image. */
public final class DepthTextureHandler {
private int depthTextureId = -1;
private int depthTextureWidth = -1;
private int depthTextureHeight = -1;
/**
* Creates and initializes the depth texture. This method needs to be called on a
* thread with a EGL context attached.
*/
public void createOnGlThread() {
int[] textureId = new int[1];
glGenTextures(1, textureId, 0);
depthTextureId = textureId[0];
glBindTexture(GL_TEXTURE_2D, depthTextureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
/**
* Updates the depth texture with the content from acquireDepthImage16Bits().
* This method needs to be called on a thread with an EGL context attached.
*/
public void update(final Frame frame) {
try {
Image depthImage = frame.acquireDepthImage16Bits();
depthTextureWidth = depthImage.getWidth();
depthTextureHeight = depthImage.getHeight();
glBindTexture(GL_TEXTURE_2D, depthTextureId);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RG8,
depthTextureWidth,
depthTextureHeight,
0,
GL_RG,
GL_UNSIGNED_BYTE,
depthImage.getPlanes()[0].getBuffer());
depthImage.close();
} catch (NotYetAvailableException e) {
// This normally means that depth data is not available yet.
}
}
public int getDepthTexture() {
return depthTextureId;
}
public int getDepthWidth() {
return depthTextureWidth;
}
public int getDepthHeight() {
return depthTextureHeight;
}
}
Fügen Sie nun eine Instanz dieser Klasse zu DepthCodelabActivity hinzu, damit Sie für jeden Frame eine leicht zugängliche Kopie des Tiefenbilds haben.
Fügen Sie in DepthCodelabActivity.java eine Instanz unserer neuen Klasse als private Mitgliedsvariable hinzu:
private final DepthTextureHandler depthTexture = new DepthTextureHandler();
Aktualisieren Sie als Nächstes die onSurfaceCreated()-Methode, um diese Textur zu initialisieren, damit sie von unseren GPU-Shadern verwendet werden kann:
// Put this at the top of the "try" block in onSurfaceCreated().
depthTexture.createOnGlThread();
Schließlich möchten Sie diese Textur in jedem Frame mit dem neuesten Tiefenbild füllen. Dazu rufen Sie die oben erstellte Methode update() für den neuesten Frame auf, der von session abgerufen wurde.
Da die Unterstützung von Tiefe für diese App optional ist, sollten Sie diesen Aufruf nur verwenden, wenn Sie Tiefe verwenden.
// Add this just after "frame" is created inside onDrawFrame().
if (isDepthSupported) {
depthTexture.update(frame);
}
Sie haben jetzt ein Tiefenbild, das mit jedem Frame aktualisiert wird. Sie kann von Ihren Shadern verwendet werden.
Am Verhalten der App hat sich jedoch noch nichts geändert. Jetzt können Sie das Tiefenbild verwenden, um Ihre App zu verbessern.
7. Tiefenbild rendern (Teil 3)
Jetzt haben Sie ein Tiefenbild, mit dem Sie experimentieren können. Sehen wir uns an, wie es aussieht. In diesem Abschnitt fügen Sie der App eine Schaltfläche hinzu, mit der die Tiefe für jeden Frame gerendert wird.
Neue Shader hinzufügen
Es gibt viele Möglichkeiten, ein Tiefenbild anzusehen. Die folgenden Shader bieten eine einfache Visualisierung der Farbzuordnung.
| Neuen .vert-Shader hinzufügenIn Android Studio:
|
Fügen Sie in die neue Datei den folgenden Code ein:
src/main/assets/shaders/background_show_depth_map.vert
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
v_TexCoord = a_TexCoord;
gl_Position = a_Position;
}
Wiederholen Sie die obigen Schritte, um den Fragment-Shader im selben Verzeichnis zu erstellen und ihn background_show_depth_map.frag zu nennen.
Fügen Sie der neuen Datei den folgenden Code hinzu:
src/main/assets/shaders/background_show_depth_map.frag
precision mediump float;
uniform sampler2D u_Depth;
varying vec2 v_TexCoord;
const highp float kMaxDepth = 20000.0; // In millimeters.
float GetDepthMillimeters(vec4 depth_pixel_value) {
return 255.0 * (depth_pixel_value.r + depth_pixel_value.g * 256.0);
}
// Returns an interpolated color in a 6 degree polynomial interpolation.
vec3 GetPolynomialColor(in float x,
in vec4 kRedVec4, in vec4 kGreenVec4, in vec4 kBlueVec4,
in vec2 kRedVec2, in vec2 kGreenVec2, in vec2 kBlueVec2) {
// Moves the color space a little bit to avoid pure red.
// Removes this line for more contrast.
x = clamp(x * 0.9 + 0.03, 0.0, 1.0);
vec4 v4 = vec4(1.0, x, x * x, x * x * x);
vec2 v2 = v4.zw * v4.z;
return vec3(
dot(v4, kRedVec4) + dot(v2, kRedVec2),
dot(v4, kGreenVec4) + dot(v2, kGreenVec2),
dot(v4, kBlueVec4) + dot(v2, kBlueVec2)
);
}
// Returns a smooth Percept colormap based upon the Turbo colormap.
vec3 PerceptColormap(in float x) {
const vec4 kRedVec4 = vec4(0.55305649, 3.00913185, -5.46192616, -11.11819092);
const vec4 kGreenVec4 = vec4(0.16207513, 0.17712472, 15.24091500, -36.50657960);
const vec4 kBlueVec4 = vec4(-0.05195877, 5.18000081, -30.94853351, 81.96403246);
const vec2 kRedVec2 = vec2(27.81927491, -14.87899417);
const vec2 kGreenVec2 = vec2(25.95549545, -5.02738237);
const vec2 kBlueVec2 = vec2(-86.53476570, 30.23299484);
const float kInvalidDepthThreshold = 0.01;
return step(kInvalidDepthThreshold, x) *
GetPolynomialColor(x, kRedVec4, kGreenVec4, kBlueVec4,
kRedVec2, kGreenVec2, kBlueVec2);
}
void main() {
vec4 packed_depth = texture2D(u_Depth, v_TexCoord.xy);
highp float depth_mm = GetDepthMillimeters(packed_depth);
highp float normalized_depth = depth_mm / kMaxDepth;
vec4 depth_color = vec4(PerceptColormap(normalized_depth), 1.0);
gl_FragColor = depth_color;
}
Aktualisieren Sie als Nächstes die Klasse BackgroundRenderer, um diese neuen Shader zu verwenden, die sich in src/main/java/com/google/ar/core/codelab/common/rendering/BackgroundRenderer.java befinden.
Fügen Sie die Dateipfade zu den Shadern oben in der Klasse hinzu:
// Add these under the other shader names at the top of the class.
private static final String DEPTH_VERTEX_SHADER_NAME = "shaders/background_show_depth_map.vert";
private static final String DEPTH_FRAGMENT_SHADER_NAME = "shaders/background_show_depth_map.frag";
Fügen Sie der Klasse BackgroundRenderer weitere Mitgliedsvariablen hinzu, da sie zwei Shader ausführt:
// Add to the top of file with the rest of the member variables.
private int depthProgram;
private int depthTextureParam;
private int depthTextureId = -1;
private int depthQuadPositionParam;
private int depthQuadTexCoordParam;
Fügen Sie eine neue Methode zum Ausfüllen dieser Felder hinzu:
// Add this method below createOnGlThread().
public void createDepthShaders(Context context, int depthTextureId) throws IOException {
int vertexShader =
ShaderUtil.loadGLShader(
TAG, context, GLES20.GL_VERTEX_SHADER, DEPTH_VERTEX_SHADER_NAME);
int fragmentShader =
ShaderUtil.loadGLShader(
TAG, context, GLES20.GL_FRAGMENT_SHADER, DEPTH_FRAGMENT_SHADER_NAME);
depthProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(depthProgram, vertexShader);
GLES20.glAttachShader(depthProgram, fragmentShader);
GLES20.glLinkProgram(depthProgram);
GLES20.glUseProgram(depthProgram);
ShaderUtil.checkGLError(TAG, "Program creation");
depthTextureParam = GLES20.glGetUniformLocation(depthProgram, "u_Depth");
ShaderUtil.checkGLError(TAG, "Program parameters");
depthQuadPositionParam = GLES20.glGetAttribLocation(depthProgram, "a_Position");
depthQuadTexCoordParam = GLES20.glGetAttribLocation(depthProgram, "a_TexCoord");
this.depthTextureId = depthTextureId;
}
Fügen Sie diese Methode hinzu, die zum Zeichnen mit diesen Shadern in jedem Frame verwendet wird:
// Put this at the bottom of the file.
public void drawDepth(@NonNull Frame frame) {
if (frame.hasDisplayGeometryChanged()) {
frame.transformCoordinates2d(
Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
quadCoords,
Coordinates2d.TEXTURE_NORMALIZED,
quadTexCoords);
}
if (frame.getTimestamp() == 0 || depthTextureId == -1) {
return;
}
// Ensure position is rewound before use.
quadTexCoords.position(0);
// No need to test or write depth, the screen quad has arbitrary depth, and is expected
// to be drawn first.
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glDepthMask(false);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, depthTextureId);
GLES20.glUseProgram(depthProgram);
GLES20.glUniform1i(depthTextureParam, 0);
// Set the vertex positions and texture coordinates.
GLES20.glVertexAttribPointer(
depthQuadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadCoords);
GLES20.glVertexAttribPointer(
depthQuadTexCoordParam, TEXCOORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadTexCoords);
// Draws the quad.
GLES20.glEnableVertexAttribArray(depthQuadPositionParam);
GLES20.glEnableVertexAttribArray(depthQuadTexCoordParam);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glDisableVertexAttribArray(depthQuadPositionParam);
GLES20.glDisableVertexAttribArray(depthQuadTexCoordParam);
// Restore the depth state for further drawing.
GLES20.glDepthMask(true);
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
ShaderUtil.checkGLError(TAG, "BackgroundRendererDraw");
}
Ein/Aus-Schaltfläche hinzufügen
Jetzt, da Sie die Tiefenkarte rendern können, sollten Sie das auch tun. Fügen Sie eine Schaltfläche hinzu, mit der diese Darstellung aktiviert und deaktiviert werden kann.
Fügen Sie oben in der Datei DepthCodelabActivity einen Import für die Schaltfläche hinzu:
import android.widget.Button;
Aktualisieren Sie die Klasse, um ein boolesches Mitglied hinzuzufügen, das angibt, ob das Rendern von Tiefe aktiviert ist (standardmäßig ist es deaktiviert):
private boolean showDepthMap = false;
Fügen Sie als Nächstes die Schaltfläche, mit der der boolesche Wert showDepthMap gesteuert wird, am Ende der Methode onCreate() hinzu:
final Button toggleDepthButton = (Button) findViewById(R.id.toggle_depth_button);
toggleDepthButton.setOnClickListener(
view -> {
if (isDepthSupported) {
showDepthMap = !showDepthMap;
toggleDepthButton.setText(showDepthMap ? R.string.hide_depth : R.string.show_depth);
} else {
showDepthMap = false;
toggleDepthButton.setText(R.string.depth_not_available);
}
});
Fügen Sie res/values/strings.xml die folgenden Strings hinzu:
<string translatable="false" name="show_depth">Show Depth</string>
<string translatable="false" name="hide_depth">Hide Depth</string>
<string translatable="false" name="depth_not_available">Depth Not Available</string>
Fügen Sie diese Schaltfläche unten im App-Layout in res/layout/activity_main.xml hinzu:
<Button
android:id="@+id/toggle_depth_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:gravity="center"
android:text="@string/show_depth"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"/>
Die Schaltfläche steuert jetzt den Wert des booleschen Werts showDepthMap. Mit diesem Flag können Sie steuern, ob die Tiefenkarte gerendert wird.
Fügen Sie in der Methode onDrawFrame() in DepthCodelabActivity Folgendes hinzu:
// Add this snippet just under backgroundRenderer.draw(frame);
if (showDepthMap) {
backgroundRenderer.drawDepth(frame);
}
Übergeben Sie die Tiefentextur an backgroundRenderer, indem Sie die folgende Zeile in onSurfaceCreated() hinzufügen:
// Add to onSurfaceCreated() after backgroundRenderer.createonGlThread(/*context=*/ this);
backgroundRenderer.createDepthShaders(/*context=*/ this, depthTexture.getDepthTexture());
Wenn Sie oben rechts auf dem Bildschirm auf die Schaltfläche tippen, können Sie sich das Tiefenbild jedes Frames ansehen.
|
| ||
Ausführung ohne Unterstützung der Depth API | Mit Depth API-Unterstützung ausführen | ||
[Optional] Animation mit Tiefeneffekt
Die App zeigt derzeit direkt die Tiefenkarte an. Rote Pixel stehen für Bereiche, die nah beieinander liegen. Blaue Pixel stehen für Bereiche, die weit entfernt sind.
|
|
Es gibt viele Möglichkeiten, Tiefeninformationen zu vermitteln. In diesem Abschnitt ändern Sie den Shader so, dass die Tiefe regelmäßig pulsiert. Dazu modifizieren Sie den Shader so, dass die Tiefe nur in Bändern angezeigt wird, die sich wiederholt von der Kamera entfernen.
Fügen Sie diese Variablen zuerst oben in background_show_depth_map.frag ein:
uniform float u_DepthRangeToRenderMm;
const float kDepthWidthToRenderMm = 350.0;
- Verwenden Sie diese Werte dann, um zu filtern, welche Pixel mit Tiefenwerten in der
main()-Funktion des Shaders abgedeckt werden sollen:
// Add this line at the end of main().
gl_FragColor.a = clamp(1.0 - abs((depth_mm - u_DepthRangeToRenderMm) / kDepthWidthToRenderMm), 0.0, 1.0);
Aktualisieren Sie dann BackgroundRenderer.java, um diese Shader-Parameter beizubehalten. Fügen Sie der Klasse die folgenden Felder hinzu:
private static final float MAX_DEPTH_RANGE_TO_RENDER_MM = 20000.0f;
private float depthRangeToRenderMm = 0.0f;
private int depthRangeToRenderMmParam;
Fügen Sie in der createDepthShaders()-Methode Folgendes hinzu, um diese Parameter mit dem Shader-Programm abzugleichen:
depthRangeToRenderMmParam = GLES20.glGetUniformLocation(depthProgram, "u_DepthRangeToRenderMm");
- Schließlich können Sie diesen Bereich im Zeitverlauf mit der
drawDepth()-Methode steuern. Fügen Sie den folgenden Code hinzu, um diesen Bereich bei jedem Zeichnen eines Frames zu erhöhen:
// Enables alpha blending.
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// Updates range each time draw() is called.
depthRangeToRenderMm += 50.0f;
if (depthRangeToRenderMm > MAX_DEPTH_RANGE_TO_RENDER_MM) {
depthRangeToRenderMm = 0.0f;
}
// Passes latest value to the shader.
GLES20.glUniform1f(depthRangeToRenderMmParam, depthRangeToRenderMm);
Die Tiefe wird jetzt als animierter Puls visualisiert, der durch die Szene fließt.

Sie können die hier angegebenen Werte ändern, um den Puls langsamer, schneller, breiter oder schmaler zu machen. Sie können auch ganz neue Möglichkeiten ausprobieren, den Shader zu ändern, um die Tiefeninformationen darzustellen.
8. Depth API für Verdeckung verwenden (Teil 4)
Als Nächstes kümmern Sie sich um die Verdeckung von Objekten in Ihrer App.
Okklusion bezieht sich darauf, was passiert, wenn das virtuelle Objekt nicht vollständig gerendert werden kann, weil sich reale Objekte zwischen dem virtuellen Objekt und der Kamera befinden. Die Verwaltung von Verdeckungen ist entscheidend für immersive AR-Erlebnisse.
Wenn virtuelle Objekte in Echtzeit richtig gerendert werden, wird die Augmented-Reality-Szene realistischer und glaubwürdiger. Weitere Beispiele finden Sie in unserem Video zum Verschmelzen von Realitäten mit der Depth API.
In diesem Abschnitt aktualisieren Sie Ihre App so, dass virtuelle Objekte nur dann eingefügt werden, wenn die Tiefeninformationen verfügbar sind.
Neue Objekt-Shader hinzufügen
Wie in den vorherigen Abschnitten fügen Sie neue Shader hinzu, um Tiefeninformationen zu unterstützen. Dieses Mal können Sie die vorhandenen Objekt-Shader kopieren und Okklusionsfunktionen hinzufügen.
Es ist wichtig, beide Versionen der Objekt-Shader beizubehalten, damit Ihre App zur Laufzeit entscheiden kann, ob sie Tiefe unterstützt.
Erstellen Sie im Verzeichnis src/main/assets/shaders Kopien der Shader-Dateien object.vert und object.frag.
|
|
Fügen Sie in occlusion_object.vert die folgende Variable über main() ein:
varying vec3 v_ScreenSpacePosition;
Legen Sie diese Variable unten in main() fest:
v_ScreenSpacePosition = gl_Position.xyz / gl_Position.w;
Aktualisieren Sie occlusion_object.frag, indem Sie diese Variablen oben in der Datei über main() hinzufügen:
varying vec3 v_ScreenSpacePosition;
uniform sampler2D u_Depth;
uniform mat3 u_UvTransform;
uniform float u_DepthTolerancePerMm;
uniform float u_OcclusionAlpha;
uniform float u_DepthAspectRatio;
- Fügen Sie diese Hilfsfunktionen über
main()in den Shader ein, um die Arbeit mit Tiefeninformationen zu erleichtern:
float GetDepthMillimeters(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(u_Depth, depth_uv).xyz;
return dot(packedDepthAndVisibility.xy, vec2(255.0, 256.0 * 255.0));
}
// Returns linear interpolation position of value between min and max bounds.
// E.g., InverseLerp(1100, 1000, 2000) returns 0.1.
float InverseLerp(in float value, in float min_bound, in float max_bound) {
return clamp((value - min_bound) / (max_bound - min_bound), 0.0, 1.0);
}
// Returns a value between 0.0 (not visible) and 1.0 (completely visible)
// Which represents how visible or occluded is the pixel in relation to the
// depth map.
float GetVisibility(in vec2 depth_uv, in float asset_depth_mm) {
float depth_mm = GetDepthMillimeters(depth_uv);
// Instead of a hard z-buffer test, allow the asset to fade into the
// background along a 2 * u_DepthTolerancePerMm * asset_depth_mm
// range centered on the background depth.
float visibility_occlusion = clamp(0.5 * (depth_mm - asset_depth_mm) /
(u_DepthTolerancePerMm * asset_depth_mm) + 0.5, 0.0, 1.0);
// Depth close to zero is most likely invalid, do not use it for occlusions.
float visibility_depth_near = 1.0 - InverseLerp(
depth_mm, /*min_depth_mm=*/150.0, /*max_depth_mm=*/200.0);
// Same for very high depth values.
float visibility_depth_far = InverseLerp(
depth_mm, /*min_depth_mm=*/17500.0, /*max_depth_mm=*/20000.0);
float visibility =
max(max(visibility_occlusion, u_OcclusionAlpha),
max(visibility_depth_near, visibility_depth_far));
return visibility;
}
Aktualisieren Sie nun main() in occlusion_object.frag, damit die Tiefe berücksichtigt und Verdeckung angewendet wird. Fügen Sie am Ende der Datei die folgenden Zeilen hinzu:
const float kMToMm = 1000.0;
float asset_depth_mm = v_ViewPosition.z * kMToMm * -1.;
vec2 depth_uvs = (u_UvTransform * vec3(v_ScreenSpacePosition.xy, 1)).xy;
gl_FragColor.a *= GetVisibility(depth_uvs, asset_depth_mm);
Nachdem Sie eine neue Version Ihrer Objekt-Shader haben, können Sie den Renderer-Code ändern.
Objektverdeckung rendern
Erstellen Sie als Nächstes eine Kopie der ObjectRenderer-Klasse in src/main/java/com/google/ar/core/codelab/common/rendering/ObjectRenderer.java.
- Wählen Sie die
ObjectRenderer-Klasse aus. - Klicken Sie mit der rechten Maustaste > Kopieren.
- Wählen Sie den Ordner rendering aus.
- Klicken Sie mit der rechten Maustaste > Einfügen.

- Benennen Sie die Klasse in
OcclusionObjectRendererum.

Die neue, umbenannte Klasse sollte jetzt im selben Ordner angezeigt werden:

Öffnen Sie die neu erstellte Datei OcclusionObjectRenderer.java und ändern Sie die Shader-Pfade oben in der Datei:
private static final String VERTEX_SHADER_NAME = "shaders/occlusion_object.vert";
private static final String FRAGMENT_SHADER_NAME = "shaders/occlusion_object.frag";
- Fügen Sie diese tiefenbezogenen Mitgliedsvariablen oben in der Klasse hinzu. Mit den Variablen wird die Schärfe der Okklusionsgrenze angepasst.
// Shader location: depth texture
private int depthTextureUniform;
// Shader location: transform to depth uvs
private int depthUvTransformUniform;
// Shader location: depth tolerance property
private int depthToleranceUniform;
// Shader location: maximum transparency for the occluded part.
private int occlusionAlphaUniform;
private int depthAspectRatioUniform;
private float[] uvTransform = null;
private int depthTextureId;
Erstellen Sie diese Membervariablen mit Standardwerten oben in der Klasse:
// These values will be changed each frame based on the distance to the object.
private float depthAspectRatio = 0.0f;
private final float depthTolerancePerMm = 0.015f;
private final float occlusionsAlpha = 0.0f;
Initialisieren Sie die einheitlichen Parameter für den Shader in der Methode createOnGlThread():
// Occlusions Uniforms. Add these lines before the first call to ShaderUtil.checkGLError
// inside the createOnGlThread() method.
depthTextureUniform = GLES20.glGetUniformLocation(program, "u_Depth");
depthUvTransformUniform = GLES20.glGetUniformLocation(program, "u_UvTransform");
depthToleranceUniform = GLES20.glGetUniformLocation(program, "u_DepthTolerancePerMm");
occlusionAlphaUniform = GLES20.glGetUniformLocation(program, "u_OcclusionAlpha");
depthAspectRatioUniform = GLES20.glGetUniformLocation(program, "u_DepthAspectRatio");
- Achten Sie darauf, dass diese Werte jedes Mal aktualisiert werden, wenn sie gezeichnet werden. Aktualisieren Sie dazu die
draw()-Methode:
// Add after other GLES20.glUniform calls inside draw().
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, depthTextureId);
GLES20.glUniform1i(depthTextureUniform, 1);
GLES20.glUniformMatrix3fv(depthUvTransformUniform, 1, false, uvTransform, 0);
GLES20.glUniform1f(depthToleranceUniform, depthTolerancePerMm);
GLES20.glUniform1f(occlusionAlphaUniform, occlusionsAlpha);
GLES20.glUniform1f(depthAspectRatioUniform, depthAspectRatio);
Fügen Sie die folgenden Zeilen in draw() ein, um den Mischmodus beim Rendern zu aktivieren, damit Transparenz auf virtuelle Objekte angewendet werden kann, wenn sie verdeckt werden:
// Add these lines just below the code-block labeled "Enable vertex arrays"
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// Add these lines just above the code-block labeled "Disable vertex arrays"
GLES20.glDisable(GLES20.GL_BLEND);
GLES20.glDepthMask(true);
- Fügen Sie die folgenden Methoden hinzu, damit Aufrufer von
OcclusionObjectRendererdie Tiefeninformationen angeben können:
// Add these methods at the bottom of the OcclusionObjectRenderer class.
public void setUvTransformMatrix(float[] transform) {
uvTransform = transform;
}
public void setDepthTexture(int textureId, int width, int height) {
depthTextureId = textureId;
depthAspectRatio = (float) width / (float) height;
}
Objektverdeckung steuern
Nachdem Sie eine neue OcclusionObjectRenderer haben, können Sie sie Ihrem DepthCodelabActivity hinzufügen und auswählen, wann und wie das Okklusionsrendering verwendet werden soll.
Aktivieren Sie diese Logik, indem Sie der Aktivität eine Instanz von OcclusionObjectRenderer hinzufügen, sodass sowohl ObjectRenderer als auch OcclusionObjectRenderer Mitglieder von DepthCodelabActivity sind:
// Add this include at the top of the file.
import com.google.ar.core.codelab.common.rendering.OcclusionObjectRenderer;
// Add this member just below the existing "virtualObject", so both are present.
private final OcclusionObjectRenderer occludedVirtualObject = new OcclusionObjectRenderer();
- Als Nächstes können Sie festlegen, wann dieses
occludedVirtualObjectverwendet wird, je nachdem, ob das aktuelle Gerät die Depth API unterstützt. Fügen Sie diese Zeilen in deronSurfaceCreated-Methode ein, unterhalb der Konfiguration vonvirtualObject:
if (isDepthSupported) {
occludedVirtualObject.createOnGlThread(/*context=*/ this, "models/andy.obj", "models/andy.png");
occludedVirtualObject.setDepthTexture(
depthTexture.getDepthTexture(),
depthTexture.getDepthWidth(),
depthTexture.getDepthHeight());
occludedVirtualObject.setMaterialProperties(0.0f, 2.0f, 0.5f, 6.0f);
}
Auf Geräten, auf denen die Tiefe nicht unterstützt wird, wird die occludedVirtualObject-Instanz erstellt, aber nicht verwendet. Auf Smartphones mit Tiefeninformationen werden beide Versionen initialisiert und zur Laufzeit wird entschieden, welche Version des Renderers zum Zeichnen verwendet wird.
Suchen Sie in der onDrawFrame()-Methode nach dem vorhandenen Code:
virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);
Ersetzen Sie diesen Code durch Folgendes:
if (isDepthSupported) {
occludedVirtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
occludedVirtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);
} else {
virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);
}
Achten Sie schließlich darauf, dass das Tiefenbild korrekt auf das gerenderte Ergebnis abgebildet wird. Da das Tiefenbild eine andere Auflösung und möglicherweise ein anderes Seitenverhältnis als Ihr Bildschirm hat, können sich die Texturkoordinaten zwischen dem Tiefenbild und dem Kamerabild unterscheiden.
- Fügen Sie die Hilfsmethode
getTextureTransformMatrix()am Ende der Datei hinzu. Diese Methode gibt eine Transformationsmatrix zurück, die, wenn sie angewendet wird, dafür sorgt, dass die UV-Koordinaten des Bildschirmraums korrekt mit den Quad-Texturkoordinaten übereinstimmen, die zum Rendern des Kamerabilds verwendet werden. Dabei wird auch die Ausrichtung des Geräts berücksichtigt.
private static float[] getTextureTransformMatrix(Frame frame) {
float[] frameTransform = new float[6];
float[] uvTransform = new float[9];
// XY pairs of coordinates in NDC space that constitute the origin and points along the two
// principal axes.
float[] ndcBasis = {0, 0, 1, 0, 0, 1};
// Temporarily store the transformed points into outputTransform.
frame.transformCoordinates2d(
Coordinates2d.OPENGL_NORMALIZED_DEVICE_COORDINATES,
ndcBasis,
Coordinates2d.TEXTURE_NORMALIZED,
frameTransform);
// Convert the transformed points into an affine transform and transpose it.
float ndcOriginX = frameTransform[0];
float ndcOriginY = frameTransform[1];
uvTransform[0] = frameTransform[2] - ndcOriginX;
uvTransform[1] = frameTransform[3] - ndcOriginY;
uvTransform[2] = 0;
uvTransform[3] = frameTransform[4] - ndcOriginX;
uvTransform[4] = frameTransform[5] - ndcOriginY;
uvTransform[5] = 0;
uvTransform[6] = ndcOriginX;
uvTransform[7] = ndcOriginY;
uvTransform[8] = 1;
return uvTransform;
}
Für getTextureTransformMatrix() ist der folgende Import am Anfang der Datei erforderlich:
import com.google.ar.core.Coordinates2d;
Sie möchten die Transformation zwischen diesen Texturkoordinaten berechnen, wenn sich die Bildschirmtextur ändert (z. B. wenn sich der Bildschirm dreht). Diese Funktion ist eingeschränkt.
Fügen Sie oben in der Datei das folgende Flag hinzu:
// Add this member at the top of the file.
private boolean calculateUVTransform = true;
- Prüfen Sie in
onDrawFrame(), ob die gespeicherte Transformation nach dem Erstellen des Frames und der Kamera neu berechnet werden muss:
// Add these lines inside onDrawFrame() after frame.getCamera().
if (frame.hasDisplayGeometryChanged() || calculateUVTransform) {
calculateUVTransform = false;
float[] transform = getTextureTransformMatrix(frame);
occludedVirtualObject.setUvTransformMatrix(transform);
}
Nachdem Sie diese Änderungen vorgenommen haben, können Sie die App jetzt mit der Verdeckung virtueller Objekte ausführen.
Ihre App sollte jetzt auf allen Smartphones einwandfrei funktionieren und automatisch die Tiefeninformationen für die Verdeckung verwenden, wenn sie unterstützt werden.
|
| ||
App mit Unterstützung der Depth API ausführen | App ohne Unterstützung der Depth API ausführen | ||
9. [Optional] Qualität der Verdeckung verbessern
Die oben implementierte Methode für die tiefenbasierte Verdeckung bietet eine Verdeckung mit scharfen Grenzen. Wenn sich die Kamera weiter vom Objekt entfernt, können die Tiefenmessungen ungenauer werden, was zu visuellen Artefakten führen kann.
Wir können dieses Problem beheben, indem wir dem Okklusionstest zusätzliches Unkenntlichmachen hinzufügen, wodurch die Kante verborgener virtueller Objekte weicher wird.
occlusion_object.frag
Fügen Sie oben in occlusion_object.frag die folgende einheitliche Variable hinzu:
uniform float u_OcclusionBlurAmount;
Fügen Sie diese Hilfsfunktion direkt über main() in den Shader ein. Sie wendet eine Kernel-Unschärfe auf das Occlusion-Sampling an:
float GetBlurredVisibilityAroundUV(in vec2 uv, in float asset_depth_mm) {
// Kernel used:
// 0 4 7 4 0
// 4 16 26 16 4
// 7 26 41 26 7
// 4 16 26 16 4
// 0 4 7 4 0
const float kKernelTotalWeights = 269.0;
float sum = 0.0;
vec2 blurriness = vec2(u_OcclusionBlurAmount,
u_OcclusionBlurAmount * u_DepthAspectRatio);
float current = 0.0;
current += GetVisibility(uv + vec2(-1.0, -2.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+1.0, -2.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(-1.0, +2.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+1.0, +2.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(-2.0, +1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+2.0, +1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(-2.0, -1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+2.0, -1.0) * blurriness, asset_depth_mm);
sum += current * 4.0;
current = 0.0;
current += GetVisibility(uv + vec2(-2.0, -0.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+2.0, +0.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+0.0, +2.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(-0.0, -2.0) * blurriness, asset_depth_mm);
sum += current * 7.0;
current = 0.0;
current += GetVisibility(uv + vec2(-1.0, -1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+1.0, -1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(-1.0, +1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+1.0, +1.0) * blurriness, asset_depth_mm);
sum += current * 16.0;
current = 0.0;
current += GetVisibility(uv + vec2(+0.0, +1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(-0.0, -1.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(-1.0, -0.0) * blurriness, asset_depth_mm);
current += GetVisibility(uv + vec2(+1.0, +0.0) * blurriness, asset_depth_mm);
sum += current * 26.0;
sum += GetVisibility(uv , asset_depth_mm) * 41.0;
return sum / kKernelTotalWeights;
}
Ersetzen Sie diese vorhandene Zeile in main():
gl_FragColor.a *= GetVisibility(depth_uvs, asset_depth_mm);
durch diese Zeile:
gl_FragColor.a *= GetBlurredVisibilityAroundUV(depth_uvs, asset_depth_mm);
Aktualisieren Sie den Renderer, um diese neue Shader-Funktion zu nutzen.
OcclusionObjectRenderer.java
Fügen Sie oben in der Klasse die folgenden Mitgliedsvariablen hinzu:
private int occlusionBlurUniform;
private final float occlusionsBlur = 0.01f;
Fügen Sie Folgendes in die Methode createOnGlThread ein:
// Add alongside the other calls to GLES20.glGetUniformLocation.
occlusionBlurUniform = GLES20.glGetUniformLocation(program, "u_OcclusionBlurAmount");
Fügen Sie Folgendes in die Methode draw ein:
// Add alongside the other calls to GLES20.glUniform1f.
GLES20.glUniform1f(occlusionBlurUniform, occlusionsBlur);
| Visueller VergleichDie Okklusionsgrenze sollte durch diese Änderungen jetzt glatter sein. |
10. Build-Run-Test
App erstellen und ausführen
- Schließen Sie ein Android-Gerät über USB an.
- Wählen Sie File > Build and Run aus.
- Speichern unter: ARCodeLab.apk.
- Warten Sie, bis die App erstellt und auf Ihrem Gerät bereitgestellt wurde.
Wenn Sie die App zum ersten Mal auf Ihrem Gerät bereitstellen:
- Sie müssen das USB-Debugging auf dem Gerät zulassen. Wähle „OK“ aus, um fortzufahren.
- Sie werden gefragt, ob die App die Berechtigung hat, die Gerätekamera zu verwenden. Gewähren Sie den Zugriff, um die AR-Funktionen weiterhin nutzen zu können.
App testen
Wenn Sie Ihre App ausführen, können Sie ihr grundlegendes Verhalten testen, indem Sie Ihr Gerät halten, sich im Raum bewegen und einen Bereich langsam scannen. Erfassen Sie mindestens 10 Sekunden lang Daten und scannen Sie den Bereich aus verschiedenen Richtungen, bevor Sie mit dem nächsten Schritt fortfahren.
Fehlerbehebung
Android-Gerät für die Entwicklung einrichten
- Verbinden Sie Ihr Gerät über ein USB-Kabel mit Ihrem Entwicklercomputer. Wenn Sie unter Windows entwickeln, müssen Sie möglicherweise den entsprechenden USB-Treiber für Ihr Gerät installieren.
- So aktivieren Sie USB-Debugging im Fenster Entwickleroptionen:
- Öffnen Sie die Einstellungen.
- Wenn auf Ihrem Gerät Android 8.0 oder höher installiert ist, wählen Sie System aus. Fahren Sie andernfalls mit dem nächsten Schritt fort.
- Scrollen Sie nach unten und wählen Sie Über das Telefon aus.
- Scrollen Sie nach unten und tippen Sie siebenmal auf Build-Nummer.
- Kehren Sie zum vorherigen Bildschirm zurück, scrollen Sie nach unten und tippen Sie auf Entwickleroptionen.
- Scrollen Sie im Fenster Entwickleroptionen nach unten, um USB-Debugging zu finden und zu aktivieren.
Fehler beim Erstellen im Zusammenhang mit Lizenzen

Wenn beim Erstellen ein Fehler im Zusammenhang mit Lizenzen auftritt (Failed to install the following Android SDK packages as some licences have not been accepted), können Sie die folgenden Befehle verwenden, um diese Lizenzen zu prüfen und zu akzeptieren:
cd <path to Android SDK>
tools/bin/sdkmanager --licenses
11. Glückwunsch
Herzlichen Glückwunsch! Sie haben Ihre erste tiefenbasierte Augmented Reality-App mit der ARCore Depth API von Google erstellt und ausgeführt.












