1. Hinweis
ARCore ist eine Plattform zum Erstellen von Augmented-Reality-Apps (AR) auf Mobilgeräten. Durch die Nutzung verschiedener APIs ermöglicht ARCore dem Gerät eines Nutzers, Informationen über seine Umgebung zu erfassen und zu empfangen sowie mit diesen Informationen zu interagieren.
In diesem Codelab erfahren Sie, wie Sie eine einfache AR-fähige App mit der ARCore Depth API erstellen.
Vorbereitung
Dieses Codelab wurde für Entwickler mit Kenntnissen grundlegender AR-Konzepte geschrieben.
Inhalt
Sie erstellen eine App, die das Tiefenbild für jeden Frame verwendet, um die Geometrie der Szene zu visualisieren und platzierte virtuelle Assets zu verdecken. Sie führen die folgenden Schritte durch:
- Depth API-Unterstützung auf dem Smartphone wird geprüft
- Tiefenbild für jeden Frame abrufen
- Tiefeninformationen auf verschiedene Arten visualisieren (siehe Animation oben)
- Nutzung von Tiefe, um Apps mit Verdeckung realistischer zu gestalten
- Den Umgang mit Smartphones, die die Depth API nicht unterstützen, lernen
Voraussetzungen
Hardware Requirements
- Ein unterstütztes ARCore-Gerät, das über ein USB-Kabel mit dem Entwicklungscomputer 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 für Android verfügbar.
- USB-Debugging für dieses Gerät aktivieren
Softwareanforderungen
- ARCore SDK 1.31.0 oder höher
- Ein Entwicklungscomputer 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 von einer Tiefenkarte bereitgestellten Informationen können Sie virtuelle Objekte genau vor oder hinter realen Objekten anzeigen lassen. Dies ermöglicht immersive und realistische Nutzererfahrungen.
Die ARCore Depth API bietet Zugriff auf Tiefenbilder, die mit jedem Frame der ARCore-Sitzung übereinstimmen. Jedes Pixel stellt eine Entfernungsmessung von der Kamera zur Umgebung bereit, wodurch Ihre AR-App noch realistischer wird.
Eine wichtige Funktion der Depth API ist die Verdeckung, also die Fähigkeit, digitale Objekte genau in Bezug auf reale Objekte zu erscheinen. Dadurch fühlen sich Objekte an, als befänden sie sich 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 Ihrem Computer. Ihr Gerät muss USB-Debugging zulassen.
- Öffnen Sie ein Terminal und führen Sie
adb devices
wie unten gezeigt aus:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
<DEVICE_SERIAL_NUMBER>
ist ein für Ihr Gerät eindeutiger String. Vergewissere dich, dass du genau ein Gerät siehst, bevor du fortfährst.
Code herunterladen und installieren
- 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 bestehendes 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 Projektbereich oben links in Android Studio nicht bereits im Projektbereich angezeigt wird, klicken Sie im Drop-down-Menü auf Projects (Projekte).
Das Ergebnis sollte wie folgt aussehen:
Dieses Projekt enthält die folgenden Module:
|
Sie werden im Modul part0_work
arbeiten. Es gibt auch vollständige Lösungen für jeden Teil des Codelabs. Jedes Modul ist eine programmierbare App.
4. Start-App ausführen
- Klicken Sie auf Ausführen > Ausführen... > "part0_work". Im 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 deinem Gerät aus.
- Die App fordert Kameraberechtigungen an. Tippen Sie zum Fortfahren auf Zulassen.
Verwendung der App
|
Ihre App ist im Moment sehr simpel und weiß nicht viel über die reale Szenengeometrie.
Wenn Sie zum Beispiel eine Android-Figur hinter einem Stuhl platzieren, erscheint das Rendering im Vordergrund, da die App nicht weiß, dass sich der Stuhl dort befindet, und das Android-Gerät verborgen sein sollte.
Um dieses Problem zu beheben, verwenden wir die Depth API, um die immersive und realistische Darstellung dieser App zu verbessern.
5. Prüfen, ob die Depth API unterstützt wird (Teil 1)
Die ARCore Depth API kann nur auf einem Teil der unterstützten Geräte ausgeführt werden. Bevor Sie Funktionen mithilfe dieser Tiefenbilder in eine App integrieren, müssen Sie zuerst prüfen, ob die App auf einem unterstützten Gerät ausgeführt wird.
Fügen Sie DepthCodelabActivity
ein neues privates Mitglied hinzu. Dieses dient als Flag, mit dem gespeichert wird, ob das aktuelle Gerät Tiefe unterstützt:
private boolean isDepthSupported;
Dieses Flag kann aus der Funktion onResume()
ausgefüllt werden, in der eine neue Sitzung erstellt wird.
Suchen Sie den vorhandenen Code:
// Creates the ARCore session.
session = new Session(/* context= */ this);
Ändern Sie den Code wie folgt:
// 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 Session ist jetzt entsprechend konfiguriert und deine App weiß, ob sie tiefenbasierte Funktionen verwenden kann.
Sie sollten dem Nutzer auch mitteilen, ob für diese Sitzung Tiefe verwendet wird oder nicht.
Füge eine weitere Nachricht zur Snackbar hinzu. Es 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 Tiefendaten unterstützt, wird die soeben 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)
Die Depth API erfasst 3D-Beobachtungen der Geräteumgebung und gibt ein Tiefenbild mit diesen Daten an Ihre App zurück. Jedes Pixel im Tiefenbild repräsentiert eine Entfernung von der Gerätekamera zu seiner realen Umgebung.
Jetzt verwenden Sie diese Tiefenbilder, um das Rendering und die Visualisierung in der App zu verbessern. Der erste Schritt besteht darin, das Tiefenbild für jeden Frame abzurufen und diese Textur zur Verwendung durch die GPU zu binden.
Fügen Sie Ihrem Projekt zuerst eine neue Klasse hinzu.DepthTextureHandler
ist dafür zuständig, das Tiefenbild eines bestimmten ARCore-Frames abzurufen.
Diese Datei hinzufügen:
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 Member-Variable hinzu:
private final DepthTextureHandler depthTexture = new DepthTextureHandler();
Aktualisieren Sie als Nächstes die Methode onSurfaceCreated()
, 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 Methode update()
auf, die Sie oben für den letzten aus session
abgerufenen Frame erstellt haben.
Da für diese App Tiefensupport optional ist, solltest du diesen Anruf nur verwenden, wenn du Tiefe verwendest.
// Add this just after "frame" is created inside onDrawFrame().
if (isDepthSupported) {
depthTexture.update(frame);
}
Jetzt haben Sie ein Tiefenbild, das mit jedem Frame aktualisiert wird. Sie kann jetzt von Ihren Shadern verwendet werden.
Am Verhalten der App hat sich jedoch noch nichts geändert. Jetzt verwenden Sie das Tiefenbild, um Ihre App zu verbessern.
7. Tiefenbild rendern (Teil 3)
Jetzt haben Sie ein Tiefenbild zum Spielen und möchten wissen, wie es aussieht. In diesem Abschnitt fügen Sie der App eine Schaltfläche hinzu, mit der Sie die Tiefe für jeden Frame rendern können.
Neue Shader hinzufügen
Es gibt viele Möglichkeiten, ein Tiefenbild zu betrachten. Die folgenden Shader ermöglichen eine einfache Farbzuordnungsvisualisierung.
Neuen .vert-Shader hinzufügenIn Android Studio:
|
Fügen Sie der neuen Datei den folgenden Code hinzu:
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 Schritte oben, um den Fragment-Shader im selben Verzeichnis zu erstellen, und nennen Sie ihn background_show_depth_map.frag
.
Fügen Sie dieser 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 in src/main/java/com/google/ar/core/codelab/common/rendering/BackgroundRenderer.java
zu verwenden.
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 Mitgliedervariablen hinzu, da darin zwei Shader ausgeführt werden:
// 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 hinzu, um diese Felder auszufüllen:
// 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 auf 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
Da Sie nun die Möglichkeit haben, die Tiefenkarte zu rendern, verwenden Sie sie. Füge eine Schaltfläche hinzu, die das Rendering ein- und ausschaltet.
Fügen Sie oben in der Datei DepthCodelabActivity
einen Import für die zu verwendende Schaltfläche hinzu:
import android.widget.Button;
Aktualisieren Sie die Klasse, um ein boolesches Mitglied hinzuzufügen, das angibt, ob das Tiefenrendering aktiviert ist (standardmäßig deaktiviert):
private boolean showDepthMap = false;
Fügen Sie als Nächstes die Schaltfläche, über die 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 diese Strings zu res/values/strings.xml
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 am unteren Rand des App-Layouts 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"/>
Über die Schaltfläche wird jetzt der Wert des booleschen showDepthMap
-Werts festgelegt. 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());
Jetzt können Sie das Tiefenbild jedes Frames sehen, indem Sie die Schaltfläche oben rechts auf dem Bildschirm drücken.
Ohne Depth API-Unterstützung ausführen | Mit Depth API-Unterstützung ausführen |
[Optional] Raffinierte Tiefenanimation
In der App wird derzeit die Tiefenkarte direkt angezeigt. Rote Pixel stellen Bereiche dar, die nahe beieinander liegen. Blaue Pixel stellen weit entfernte Bereiche dar.
Es gibt viele Möglichkeiten, detaillierte Informationen zu vermitteln. In diesem Abschnitt ändern Sie die Puls-Tiefe des Shaders in regelmäßigen Abständen. Dazu ändern Sie den Shader so, dass die Tiefe nur innerhalb von Bändern dargestellt wird, die sich wiederholt von der Kamera wegbewegen.
Fügen Sie diese Variablen oben in background_show_depth_map.frag
hinzu:
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);
Als Nächstes aktualisieren Sie BackgroundRenderer.java
, um diese Shader-Parameter zu verwalten. Fügen Sie oben in 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 Methode createDepthShaders()
Folgendes hinzu, um diese Parameter mit dem Shader-Programm abzugleichen:
depthRangeToRenderMmParam = GLES20.glGetUniformLocation(depthProgram, "u_DepthRangeToRenderMm");
- Schließlich können Sie diesen Zeitraum in der Methode
drawDepth()
über einen längeren Zeitraum steuern. Fügen Sie den folgenden Code hinzu, mit dem dieser Bereich jedes Mal erhöht wird, wenn ein Frame gezeichnet wird:
// 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);
Jetzt wird die Tiefe als animierter Puls visualisiert, der durch deine Szene fließt.
Du kannst die hier angegebenen Werte ändern, um den Puls langsamer, schneller, breiter, schmaler usw. zu machen. Du kannst auch ganz neue Möglichkeiten ausprobieren, den Shader zu ändern, um Tiefeninformationen anzuzeigen!
8. Depth API für Verdeckungen verwenden (Teil 4)
Jetzt bearbeiten Sie die Objektverdeckung in Ihrer App.
Verdeckung bezieht sich darauf, was passiert, wenn das virtuelle Objekt nicht vollständig gerendert werden kann, weil sich zwischen dem virtuellen Objekt und der Kamera reale Objekte befinden. Das Verwalten von Verdeckungen ist für eine immersive AR-Erfahrung unerlässlich.
Das korrekte Rendern virtueller Objekte in Echtzeit erhöht die Realisierung und Glaubwürdigkeit der erweiterten Szene. Weitere Beispiele finden Sie in unserem Video zum Zusammenführen von Realitäten mit der Depth API.
In diesem Abschnitt aktualisieren Sie Ihre App so, dass virtuelle Objekte nur dann einbezogen werden, wenn Tiefe verfügbar ist.
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 eine Verdeckungsfunktion hinzufügen.
Es ist wichtig, beide Versionen der Objekt-Shader zu behalten, damit Ihre App während der Laufzeit entscheiden kann, ob sie Tiefe unterstützen soll.
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 oberhalb von main()
ein:
varying vec3 v_ScreenSpacePosition;
Legen Sie diese Variable am Ende von main()
fest:
v_ScreenSpacePosition = gl_Position.xyz / gl_Position.w;
Aktualisieren Sie occlusion_object.frag
, indem Sie diese Variablen über main()
oben in der Datei einfü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 dem Shader die folgenden Hilfsfunktionen über
main()
hinzu, um den Umgang 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;
}
Aktualisiere jetzt main()
in occlusion_object.frag
, um die Tiefenerkennung zu verbessern und Verdeckungen anzuwenden. 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);
Da Sie nun über eine neue Version Ihrer Objekt-Shader verfügen, können Sie den Renderer-Code ändern.
Verdeckung des Rendering-Objekts
Erstellen Sie als Nächstes eine Kopie des Kurses „ObjectRenderer
“, die Sie unter „src/main/java/com/google/ar/core/codelab/common/rendering/ObjectRenderer.java
“ finden.
ObjectRenderer
-Klasse auswählen- Rechtsklick > Text
- Wählen Sie den Ordner rendering aus.
- Rechtsklick > Einfügen
- Kurs in „
OcclusionObjectRenderer
“ umbenennen
Der neue, umbenannte Kurs sollte jetzt im selben Ordner angezeigt werden:
Öffnen Sie die neu erstellte OcclusionObjectRenderer.java
und ändern Sie die Shader-Pfade am Anfang 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 Member-Variablen zusammen mit den anderen oben in der Klasse hinzu. Die Variablen passen die Schärfe des Verdeckungsrahmens an.
// 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 Mitgliedervariablen mit Standardwerten am Anfang 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");
- Aktualisieren Sie die Methode
draw()
, damit diese Werte jedes Mal aktualisiert werden, wenn sie gezeichnet werden:
// 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üge in draw()
die folgenden Zeilen hinzu, um den Mischmodus beim Rendern zu aktivieren und so Transparenz auf virtuelle Objekte anzuwenden, wenn sie verdeckt sind:
// 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
OcclusionObjectRenderer
die 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
Da du jetzt ein neues OcclusionObjectRenderer
hast, kannst du es zu DepthCodelabActivity
hinzufügen und festlegen, wann und wie das Verdeckungs-Rendering 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 kannst du festlegen, wann dieses Gerät (
occludedVirtualObject
) verwendet wird – je nachdem, ob das aktuelle Gerät die Depth API unterstützt. Fügen Sie diese Zeilen in die MethodeonSurfaceCreated
ein, in dervirtualObject
konfiguriert ist:
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 Instanz occludedVirtualObject
erstellt, aber nicht verwendet. Auf Telefonen mit Tiefe werden beide Versionen initialisiert, und es wird eine Laufzeitentscheidung getroffen, welche Version des Renderers beim Zeichnen verwendet werden soll.
Suchen Sie in der Methode onDrawFrame()
den 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);
}
Stellen Sie schließlich sicher, dass das Tiefenbild dem Ausgabe-Rendering korrekt zugeordnet ist. Da das Tiefenbild eine andere Auflösung und möglicherweise ein anderes Seitenverhältnis als Ihr Bildschirm hat, können die Texturkoordinaten zwischen sich selbst und dem Kamerabild unterschiedlich sein.
- Fügen Sie am Ende der Datei die Hilfsmethode
getTextureTransformMatrix()
hinzu. Diese Methode gibt eine Transformationsmatrix zurück, die bei Anwendung dafür sorgt, dass die UVs des Bildschirmbereichs korrekt mit den quadratischen Texturkoordinaten übereinstimmen, die zum Rendern des Kamerafeeds verwendet werden. Dabei wird auch die Geräteausrichtung 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;
}
getTextureTransformMatrix()
erfordert den folgenden Import am Anfang der Datei:
import com.google.ar.core.Coordinates2d;
Sie möchten die Transformation zwischen diesen Texturkoordinaten immer dann 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 neu berechnet werden muss, nachdem der Frame und die Kamera erstellt wurden:
// 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 umgesetzt haben, können Sie die App jetzt mit virtueller Objektverdeckung ausführen.
Deine App sollte jetzt auf allen Smartphones ordnungsgemäß ausgeführt werden und automatisch die Tiefenverdeckung verwenden, wenn dies unterstützt wird.
App mit Depth API-Unterstützung ausführen | App ohne Depth API-Unterstützung ausführen |
9. [Optional] Verdeckungsqualität verbessern
Die oben implementierte Methode zur tiefenbasierten Verdeckung bietet eine Verdeckung mit scharfen Grenzen. Je weiter sich die Kamera vom Objekt wegbewegt, desto ungenauer werden die Tiefenmessungen, was zu visuellen Artefakten führen kann.
Wir können dieses Problem mindern, indem wir den Verdeckungstest zusätzlich weichzeichnen. Dadurch werden versteckte virtuelle Objekte weicher dargestellt.
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 etwas über main()
im Shader hinzu, die eine Kernel-Weichzeichnung auf das Sampling für die Verdeckung anwendet:
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 nutzen zu können.
OcclusionObjectRenderer.java
Fügen Sie am Anfang der Klasse die folgenden Membervariablen hinzu:
private int occlusionBlurUniform;
private final float occlusionsBlur = 0.01f;
Fügen Sie in der Methode createOnGlThread
Folgendes hinzu:
// Add alongside the other calls to GLES20.glGetUniformLocation.
occlusionBlurUniform = GLES20.glGetUniformLocation(program, "u_OcclusionBlurAmount");
Fügen Sie in der Methode draw
Folgendes hinzu:
// Add alongside the other calls to GLES20.glUniform1f.
GLES20.glUniform1f(occlusionBlurUniform, occlusionsBlur);
Visueller VergleichDie Verdeckungsgrenze sollte jetzt mit diesen Änderungen gleichmäßiger sein. |
10. Build-Run-Test
App erstellen und ausführen
- Schließen Sie ein Android-Gerät über USB an.
- Wählen Sie Datei > Erstellen und ausführen.
- Speichern unter: ARCodeLab.apk.
- Warten Sie, bis die App erstellt und auf Ihrem Gerät bereitgestellt wurde.
Wenn Sie zum ersten Mal versuchen, die App auf Ihrem Gerät bereitzustellen:
- Du musst 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. Erlaube 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 in Ihrem Raum bewegen und langsam einen Bereich scannen. Versuchen Sie, mindestens 10 Sekunden an Daten zu erfassen und den Bereich aus verschiedenen Richtungen zu scannen, 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 dem Entwicklungscomputer. Wenn Sie die Entwicklung unter Windows verwenden, müssen Sie möglicherweise den entsprechenden USB-Treiber für Ihr Gerät installieren.
- Führen Sie die folgenden Schritte aus, um USB-Debugging im Fenster Entwickleroptionen zu aktivieren:
- Ö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 suchen und zu aktivieren.
Weitere Informationen zu diesem Vorgang finden Sie auf der Google-Website für Android-Entwickler.
Build-Fehler im Zusammenhang mit Lizenzen
Wenn ein Build-Fehler im Zusammenhang mit Lizenzen auftritt (Die folgenden Android SDK-Pakete konnten nicht installiert werden, da einige Lizenzen nicht akzeptiert wurden), 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.