1. Başlamadan önce
ARCore, mobil cihazlarda Artırılmış Gerçeklik (AR) uygulamaları oluşturmaya yönelik bir platformdur. Farklı API'ler kullanan ARCore, kullanıcı cihazının ortamıyla ilgili bilgileri gözlemleyip almasına ve bu bilgilerle etkileşim kurmasına olanak tanır.
Bu codelab'de, ARCore Depth API'yi kullanan, AR özellikli basit bir uygulama geliştirmeyi öğreneceksiniz.
Ön koşullar
Bu codelab, temel AR kavramlarını bilen geliştiriciler için hazırlanmıştır.
Oluşturacaklarınız
Sahnenin geometrisini görselleştirmek ve yerleştirilen sanal öğeleri kapatmak için her karede derinlik resmini kullanan bir uygulama oluşturacaksınız. Aşağıdaki adımların üzerinden geçeceksiniz:
- Telefonda Depth API desteği kontrol ediliyor
- Her kare için derinlik görüntüsü alınıyor
- Derinlik bilgilerini çeşitli şekillerde görselleştirme (yukarıdaki animasyona bakın)
- Örtme özelliğiyle uygulamaların gerçekçiliğini artırmak için derinliği kullanma
- Depth API'yi desteklemeyen telefonları sorunsuz bir şekilde kullanmayı öğrenme
Gerekenler
Donanım Gereksinimleri
- Geliştirme makinenize USB kablosuyla bağlanmış, desteklenen bir ARCore cihazı. Bu cihaz, Depth API'yi de desteklemelidir. Lütfen desteklenen cihazların listesini inceleyin. Depth API yalnızca Android'de kullanılabilir.
- Bu cihaz için USB üzerinden hata ayıklamayı etkinleştirin.
Yazılım Gereksinimleri
- ARCore SDK 1.31.0 veya sonraki sürümler.
- Android Studio (v3.0 veya sonraki sürümler) yüklü bir geliştirme makinesi.
2. ARCore ve Depth API
Depth API, derinlik haritaları (derinlik görüntüleri de denir) oluşturmak için desteklenen bir cihazın RGB kamerasını kullanır. Derinlik haritasının sağladığı bilgileri kullanarak sanal nesnelerin gerçek dünyadaki nesnelerin önünde veya arkasında doğru şekilde görünmesini sağlayabilir, sürükleyici ve gerçekçi kullanıcı deneyimleri sağlayabilirsiniz.
ARCore Depth API, ARCore'un Oturumu tarafından sağlanan her kareyle eşleşen derinlik görüntülerine erişim sağlar. Her piksel, kamera ile çevre arasındaki mesafenin ölçülmesini sağlayarak AR uygulamanız için gelişmiş gerçekçilik sağlar.
Depth API'nin temel özelliklerinden biri engelleme: Dijital nesnelerin gerçek dünyadaki nesnelere göre doğru şekilde görünebilmesidir. Böylece nesneler, kullanıcının bulunduğu ortamdaymış gibi hisseder.
Bu codelab'de, gerçek yüzeylerin arkasına sanal nesneleri yerleştirmek ve alanın algılanan geometrisini görselleştirmek için derinlik görüntüleri kullanan basit bir AR özellikli uygulama geliştirme sürecinde size yol gösterilecektir.
3. Hazırlanın
Geliştirme makinesini kurma
- ARCore cihazınızı USB kablosuyla bilgisayarınıza bağlayın. Cihazınızın USB üzerinden hata ayıklamaya izin verdiğinden emin olun.
- Bir terminal açın ve aşağıda gösterildiği gibi
adb devices
komutunu çalıştırın:
adb devices List of devices attached <DEVICE_SERIAL_NUMBER> device
<DEVICE_SERIAL_NUMBER>
, cihazınıza özel bir dizedir. Devam etmeden önce tam olarak bir cihaz gördüğünüzden emin olun.
Kod'u indirip yükleyin
- Depoyu klonlayabilirsiniz:
git clone https://github.com/googlecodelabs/arcore-depth
Veya bir ZIP dosyası indirip dosyayı çıkarın:
- Android Studio'yu başlatın ve Mevcut bir Android Studio projesini aç'ı tıklayın.
- Yukarıda indirilen ZIP dosyasını çıkardığınız dizini bulun ve
depth_codelab_io2020
dizinini açın.
Bu, birden fazla modül içeren tek bir Gradle projesidir. Android Studio'nun sol üst tarafındaki Proje bölmesi Proje bölmesinde görünmüyorsa açılır menüden Projeler'i tıklayın.
Sonuç şu şekilde görünmelidir:
Bu proje aşağıdaki modülleri içermektedir:
|
part0_work
modülünde çalışacaksınız. Codelab'in her bölümü için eksiksiz çözümler de mevcuttur. Her modül, derlenebilir bir uygulamadır.
4. Başlangıç uygulamasını çalıştırma
- Çalıştır > Çalıştır... > "part0_work". Görüntülenen Dağıtım Hedefi Seçin iletişim kutusunda, cihazınız Bağlı Cihazlar altında listelenmelidir.
- Cihazınızı seçin ve Tamam'ı tıklayın. Android Studio ilk uygulamayı oluşturur ve cihazınızda çalıştırır.
- Uygulama, kamera izinleri ister. Devam etmek için İzin ver'e dokunun.
Uygulama nasıl kullanılır?
|
Uygulamanız şu anda çok basit ve gerçek dünyadaki sahne geometrisi hakkında fazla bilgiye sahip değil.
Mesela bir Android figürünü bir sandalyenin arkasına yerleştirirseniz uygulama sandalyenin orada olduğunu bilmediği ve Android'i gizlemesi gerektiğini bilmediği için oluşturma işlemi önde duruyormuş gibi görünür.
Bu sorunu düzeltmek için Depth API'yi kullanarak uygulamadaki gerçekçiliği ve gerçekçiliği artıracağız.
5. Depth API'nin desteklenip desteklenmediğini kontrol etme (1. Bölüm)
ARCore Depth API, desteklenen cihazların yalnızca bir alt kümesinde çalışır. Bu derinlikli görüntüleri kullanarak işlevi bir uygulamaya entegre etmeden önce, uygulamanın desteklenen bir cihazda çalıştığından emin olmanız gerekir.
DepthCodelabActivity
için, mevcut cihazın derinliği destekleyip desteklemediğini depolayan yeni bir gizli üye ekleyin:
private boolean isDepthSupported;
Bu işareti, yeni bir Oturum'un oluşturulduğu onResume()
işlevinin içinden doldurabiliriz.
Mevcut kodu bulun:
// Creates the ARCore session.
session = new Session(/* context= */ this);
Kodu şu şekilde güncelleyin:
// 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);
AR Oturumu artık uygun şekilde yapılandırıldı ve uygulamanız derinliğe dayalı özellikleri kullanıp kullanamayacağını bilir.
Ayrıca, kullanıcıya bu oturum için derinliğin kullanılıp kullanılmadığını da bildirmeniz gerekir.
Snackbar'a bir mesaj daha ekleyin. Bu simge ekranın alt kısmında görünür:
// 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]";
onDrawFrame()
içinde bu mesajı gerektiğinde gösterebilirsiniz:
// Add this if-statement above messageSnackbarHelper.showMessage(this, messageToShow).
if (!isDepthSupported) {
messageToShow += "\n" + DEPTH_NOT_AVAILABLE_MESSAGE;
}
Uygulamanız derinliği desteklemeyen bir cihazda çalışıyorsa az önce eklediğiniz mesaj en altta görünür:
Ardından, Depth API'yi çağırmak ve her kare için derinlik görüntülerini almak için uygulamayı güncellemeniz gerekir.
6. Derinlik görüntülerini alma (2. Bölüm)
Depth API, cihaz ortamının 3D gözlemlerini yakalar ve bu verilerin yer aldığı derinlik görüntüsünü uygulamanıza gönderir. Derinlik resmindeki her piksel, cihaz kamerası ile gerçek dünya ortamına arasındaki mesafeyi temsil eder.
Artık bu derinlikli resimleri, uygulamada oluşturmayı ve görselleştirmeyi iyileştirmek için kullanacaksınız. İlk adım, her kare için derinlik resmini almak ve GPU tarafından kullanılacak dokuyu bağlamaktır.
Öncelikle projenize yeni bir sınıf ekleyin.DepthTextureHandler
, belirli bir ARCore çerçevesi için derinlik görüntüsü almaktan sorumludur.
Şu dosyayı ekleyin:
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;
}
}
Şimdi bu sınıfın bir örneğini DepthCodelabActivity
sınıfına ekleyerek her kare için derinlik resminin kolayca erişilebilen bir kopyasını elde edersiniz.
DepthCodelabActivity.java
aracında, yeni sınıfınızın bir örneğini gizli üye değişkeni olarak ekleyin:
private final DepthTextureHandler depthTexture = new DepthTextureHandler();
Ardından, onSurfaceCreated()
yöntemini güncelleyerek bu dokuyu GPU gölgelendiricilerimiz tarafından kullanılabilmesi için güncelleyin:
// Put this at the top of the "try" block in onSurfaceCreated().
depthTexture.createOnGlThread();
Son olarak, bu dokuyu her karede en son derinlik görüntüsüyle doldurmak istersiniz. Bunu, session
kaynağından alınan en son karede yukarıda oluşturduğunuz update()
yöntemini çağırarak yapabilirsiniz.
Derinlik desteği bu uygulama için isteğe bağlı olduğundan, bu çağrıyı yalnızca derinlik kullanıyorsanız kullanın.
// Add this just after "frame" is created inside onDrawFrame().
if (isDepthSupported) {
depthTexture.update(frame);
}
Artık her karede güncellenen bir derinlik resminiz var. Artık gölgelendiricileriniz tarafından kullanılmaya hazır.
Ancak uygulamanın davranışında henüz herhangi bir değişiklik yapılmadı. Şimdi derinlik resmini, uygulamanızı iyileştirmek için kullanacaksınız.
7. Derinlik görüntüsünü oluşturma (3. Bölüm)
Artık üzerinde deneyebileceğiniz bir derinlik resminiz var, nasıl göründüğüne bakmak istersiniz. Bu bölümde, her karenin derinliğini oluşturmak için uygulamaya bir düğme ekleyeceksiniz.
Yeni gölgelendirici ekleme
Derinlikli resmi görüntülemenin birçok yolu vardır. Aşağıdaki gölgelendiriciler, basit bir renk eşleme görselleştirmesi sağlar.
Yeni bir .vert gölgelendirici ekleyinAndroid Studio'da:
|
Yeni dosyaya aşağıdaki kodu ekleyin:
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;
}
Parça gölgelendiriciyi aynı dizinde oluşturmak ve background_show_depth_map.frag
olarak adlandırmak için yukarıdaki adımları tekrarlayın.
Aşağıdaki kodu bu yeni dosyaya ekleyin:
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;
}
Sonra, src/main/java/com/google/ar/core/codelab/common/rendering/BackgroundRenderer.java
konumunda bulunan bu yeni gölgelendiricileri kullanmak için BackgroundRenderer
sınıfını güncelleyin.
Dosya yollarını sınıfın üst kısmındaki gölgelendiricilere ekleyin:
// 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";
İki gölgelendirici çalıştıracağı için BackgroundRenderer
sınıfına daha fazla üye değişkeni ekleyin:
// 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;
Bu alanları doldurmak için yeni bir yöntem ekleyin:
// 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;
}
Her karede bu gölgelendiricilerle çizim yapmak için kullanılan şu yöntemi ekleyin:
// 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");
}
Açma/kapatma düğmesi ekleme
Artık derinlik haritasını oluşturma özelliğine sahip olduğunuza göre bu haritayı kullanabilirsiniz. Bu oluşturmayı açıp kapatan bir düğme ekleyin.
DepthCodelabActivity
dosyasının en üstüne, düğmenin kullanacağı bir içe aktarma işlemi ekleyin:
import android.widget.Button;
Sınıfı, derinlik işlemenin açılıp açılmadığını gösteren bir boole üyesi ekleyerek güncelleyin: (varsayılan olarak kapalıdır):
private boolean showDepthMap = false;
Sonra, showDepthMap
boole değerini kontrol eden düğmeyi onCreate()
yönteminin sonuna ekleyin:
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);
}
});
Şu dizeleri res/values/strings.xml
öğesine ekleyin:
<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>
Bu düğmeyi res/layout/activity_main.xml
uygulamasındaki uygulama düzeninin alt kısmına ekleyin:
<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"/>
Düğme artık showDepthMap
boole değerini kontrol ediyor. Derinlik haritasının oluşturulup oluşturulmayacağını kontrol etmek için bu işareti kullanın.
DepthCodelabActivity
tablosundaki onDrawFrame()
yöntemine geri dönüp şunları ekleyin:
// Add this snippet just under backgroundRenderer.draw(frame);
if (showDepthMap) {
backgroundRenderer.drawDepth(frame);
}
Derinlik dokusunu backgroundRenderer
içine onSurfaceCreated()
içine aşağıdaki satırı ekleyerek iletin:
// Add to onSurfaceCreated() after backgroundRenderer.createonGlThread(/*context=*/ this);
backgroundRenderer.createDepthShaders(/*context=*/ this, depthTexture.getDepthTexture());
Artık ekranın sağ üst kısmındaki düğmeye basarak her bir karenin derinlik görüntüsünü görebilirsiniz.
Depth API desteği olmadan çalışma | Depth API desteğiyle çalışma |
[İsteğe bağlı] Süslü derinlik animasyonu
Uygulama şu anda derinlik haritasını doğrudan gösteriyor. Kırmızı pikseller yakın alanları temsil eder. Mavi pikseller uzak alanları temsil eder.
Derinlikli bilgiyi aktarmanın birçok yolu vardır. Bu bölümde gölgelendiriciyi, yalnızca sürekli olarak kameradan uzaklaşan şeritlerin içindeki derinliği gösterecek şekilde değiştirerek gölgelendiriciyi düzenli aralıklarla nabızlama derinliği sağlayacak şekilde değiştireceksiniz.
Aşağıdaki değişkenleri background_show_depth_map.frag
öğesinin üstüne ekleyerek başlayın:
uniform float u_DepthRangeToRenderMm;
const float kDepthWidthToRenderMm = 350.0;
- Daha sonra, gölgelendiricinin
main()
işlevinde derinlik değerleriyle kaplanacak pikselleri filtrelemek için bu değerleri kullanın:
// Add this line at the end of main().
gl_FragColor.a = clamp(1.0 - abs((depth_mm - u_DepthRangeToRenderMm) / kDepthWidthToRenderMm), 0.0, 1.0);
Sonra, bu gölgelendirici parametrelerini korumak için BackgroundRenderer.java
öğesini güncelleyin. Sınıfın en üstüne aşağıdaki alanları ekleyin:
private static final float MAX_DEPTH_RANGE_TO_RENDER_MM = 20000.0f;
private float depthRangeToRenderMm = 0.0f;
private int depthRangeToRenderMmParam;
Bu parametreleri gölgelendirici programıyla eşleştirmek için createDepthShaders()
yönteminin içine aşağıdakileri ekleyin:
depthRangeToRenderMmParam = GLES20.glGetUniformLocation(depthProgram, "u_DepthRangeToRenderMm");
- Son olarak,
drawDepth()
yöntemi ile bu aralığı zaman içinde kontrol edebilirsiniz. Aşağıdaki kodu ekleyin. Bu kod, her kare çizildiğinde bu aralığı artırır:
// 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);
Derinlik artık sahnenizde akan animasyonlu bir nabız şeklinde görselleştirilir.
Burada sağlanan değerleri değiştirerek nabzı daha yavaş, hızlı, geniş, dar vb. değiştirebilirsiniz. Derinlik bilgisini göstermek için gölgelendiriciyi değiştirmenin yepyeni yollarını da deneyebilirsiniz!
8. Örtme için Depth API'yi kullanma (4. Bölüm)
Şimdi uygulamanızda nesne kapatma işlemini halledeceksiniz.
Örtme, sanal nesne ile kamera arasında gerçek nesneler olduğu için sanal nesnenin tam olarak oluşturulamadığı durumlarda meydana gelen olayları ifade eder. AR deneyimlerinin etkileyici olması için kapama yönetimi çok önemlidir.
Sanal nesnelerin gerçek zamanlı olarak doğru şekilde oluşturulması, artırılmış sahnenin gerçekçiliğini ve inandırıcılığını artırır. Daha fazla örnek için lütfen Depth API ile gerçeklikleri harmanlama konulu videomuzu izleyin.
Bu bölümde, uygulamanızı yalnızca derinlik mevcutsa sanal nesneler içerecek şekilde güncelleyeceksiniz.
Yeni nesne gölgelendiricileri ekleme
Önceki bölümlerde olduğu gibi, derinlik bilgisini desteklemek için yeni gölgelendiriciler ekleyeceksiniz. Bu kez, mevcut nesne gölgelendiricilerini kopyalayabilir ve kapama işlevi ekleyebilirsiniz.
Uygulamanızın çalışma zamanında derinliği destekleyip desteklemediğine karar verebilmesi için nesne gölgelendiricilerinin her iki sürümünü de tutmanız önemlidir.
src/main/assets/shaders
dizininde object.vert
ve object.frag
gölgelendirici dosyalarının kopyalarını oluşturun.
|
occlusion_object.vert
içinde, aşağıdaki değişkeni main()
değerinin üstüne ekleyin:
varying vec3 v_ScreenSpacePosition;
Bu değişkeni main()
öğesinin altına ayarlayın:
v_ScreenSpacePosition = gl_Position.xyz / gl_Position.w;
Aşağıdaki değişkenleri dosyanın en üstünde main()
öğesinin üstüne ekleyerek occlusion_object.frag
dosyasını güncelleyin:
varying vec3 v_ScreenSpacePosition;
uniform sampler2D u_Depth;
uniform mat3 u_UvTransform;
uniform float u_DepthTolerancePerMm;
uniform float u_OcclusionAlpha;
uniform float u_DepthAspectRatio;
- Derinlikli bilgilerle ilgilenmeyi kolaylaştırmak için gölgelendiriciye
main()
üzerindeki şu yardımcı işlevleri ekleyin:
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;
}
Şimdi occlusion_object.frag
uygulamasındaki main()
uygulamasını derinliği öğrenecek ve kapamayı uygulayacak şekilde güncelleyin. Dosyanın altına şu satırları ekleyin:
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);
Artık nesne gölgelendiricilerinizin yeni bir sürümüne sahip olduğunuza göre, oluşturucu kodunu değiştirebilirsiniz.
Oluşturma nesne kapama
Sonraki ObjectRenderer
dersinin, src/main/java/com/google/ar/core/codelab/common/rendering/ObjectRenderer.java
klasöründe bulunan bir kopyasını oluştur.
ObjectRenderer
sınıfını seç- Sağ tıklayın > Kopyala
- rendering klasörünü seçin
- Sağ tıklayın > Yapıştır
- Sınıfın adını
OcclusionObjectRenderer
olarak değiştirin
Yeniden adlandırılan yeni sınıf, artık aynı klasörde görünecektir:
Yeni oluşturulan OcclusionObjectRenderer.java
dosyasını açın ve dosyanın en üstündeki gölgelendirici yollarını değiştirin:
private static final String VERTEX_SHADER_NAME = "shaders/occlusion_object.vert";
private static final String FRAGMENT_SHADER_NAME = "shaders/occlusion_object.frag";
- Derinlikle ilgili bu üye değişkenlerini sınıfın üst tarafındaki diğer değişkenlerle birlikte ekleyin. Değişkenler, kapama kenarlığının keskinliğini ayarlar.
// 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;
Aşağıdaki üye değişkenlerini sınıfın en üstünde varsayılan değerlerle oluşturun:
// 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;
createOnGlThread()
yönteminde gölgelendirici için tek tip parametreleri başlatın:
// 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");
draw()
yöntemini güncelleyerek bu değerlerin her çizildiğinde güncellendiğinden emin olun:
// 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);
Oluşturma sırasında karıştırma modunu etkinleştirmek için draw()
içine aşağıdaki satırları ekleyin. Böylece, şeffaflık kaplandıkları zaman sanal nesnelere uygulanabilir:
// 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);
OcclusionObjectRenderer
uygulamasını arayanların derinlik bilgisi sağlayabilmesi için aşağıdaki yöntemleri ekleyin:
// 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;
}
Nesne kapamayı kontrol etme
Artık yeni bir OcclusionObjectRenderer
cihazınız olduğuna göre bunu DepthCodelabActivity
cihazınıza ekleyebilir ve kapama oluşturma işleminin ne zaman ve nasıl kullanılacağını seçebilirsiniz.
ObjectRenderer
ve OcclusionObjectRenderer
öğelerinin DepthCodelabActivity
üyesi olması için etkinliğe bir OcclusionObjectRenderer
örneği ekleyerek bu mantığı etkinleştirin:
// 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();
- Mevcut cihazın Depth API'yi destekleyip desteklemediğine bağlı olarak bu
occludedVirtualObject
cihazının ne zaman kullanılacağını kontrol edebilirsiniz. Bu satırları,virtualObject
yönteminin yapılandırıldığıonSurfaceCreated
yönteminin içine ekleyin:
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);
}
Derinliğin desteklenmediği cihazlarda occludedVirtualObject
örneği oluşturulur ancak kullanılmaz. Derinlikli telefonlarda her iki sürüm de başlatılır ve çizim sırasında oluşturucunun hangi sürümünün kullanılacağına çalışma zamanı kararı verilir.
onDrawFrame()
yönteminin içinde mevcut kodu bulun:
virtualObject.updateModelMatrix(anchorMatrix, scaleFactor);
virtualObject.draw(viewmtx, projmtx, colorCorrectionRgba, OBJECT_COLOR);
Bu kodu aşağıdaki kodla değiştirin:
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);
}
Son olarak, derinlik resminin çıktı oluşturma işlemiyle doğru şekilde eşlendiğinden emin olun. Derinlik resmi, ekranınızdan farklı bir çözünürlükte ve potansiyel olarak ekranınızdan farklı bir en boy oranına sahip olduğundan, doku koordinatlarının kendisi ile kamera görüntüsü arasında farklı olabilir.
- Dosyanın en altına
getTextureTransformMatrix()
yardımcı yöntemini ekleyin. Bu yöntem, uygulandığında ekran alanı UV'lerinin kamera feed'ini oluşturmak için kullanılan dörtlü doku koordinatlarıyla doğru bir şekilde eşleşmesini sağlayan bir dönüşüm matrisi döndürür. Ayrıca cihaz yönü de dikkate alınır.
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()
, dosyanın en üstünde şu içe aktarma işlemini gerektiriyor:
import com.google.ar.core.Coordinates2d;
Ekran dokusu değiştiğinde (ekranın dönmesi gibi) bu doku koordinatları arasındaki dönüşümü hesaplamak istiyorsunuz. Bu işleve erişim kısıtlanmıştır.
Dosyanın en üstüne şu işareti ekleyin:
// Add this member at the top of the file.
private boolean calculateUVTransform = true;
onDrawFrame()
içinde, kare ve kamera oluşturulduktan sonra depolanan dönüşümün yeniden hesaplanması gerekip gerekmediğini kontrol edin:
// Add these lines inside onDrawFrame() after frame.getCamera().
if (frame.hasDisplayGeometryChanged() || calculateUVTransform) {
calculateUVTransform = false;
float[] transform = getTextureTransformMatrix(frame);
occludedVirtualObject.setUvTransformMatrix(transform);
}
Bu değişiklikler yapıldıktan sonra uygulamayı artık sanal nesne engelleme özelliğiyle çalıştırabilirsiniz.
Uygulamanız artık tüm telefonlarda sorunsuz bir şekilde çalışmalı ve desteklendiğinde otomatik olarak kapamak için derinliği kullanmalıdır.
Uygulamayı Depth API desteğiyle çalıştırma | Uygulamayı Depth API desteği olmadan çalıştırma |
9. [İsteğe bağlı] Kapatma kalitesini iyileştirme
Yukarıda uygulanan derinliğe dayalı kapama yöntemi, keskin sınırlarla kapama sağlar. Kamera nesneden uzaklaştıkça derinlik ölçümlerindeki doğruluk oranı düşebilir ve bu da görsel kusurlara yol açabilir.
Gizli sanal nesneler için daha düzgün bir kenar sağlayan kapama testine ekstra bulanıklaştırma ekleyerek bu sorunu azaltabiliriz.
occlusion_object.frag
occlusion_object.frag
öğesinin üst kısmına aşağıdaki tek tip değişkeni ekleyin:
uniform float u_OcclusionBlurAmount;
Gölgelendiricide main()
öğesinin hemen üzerinde yer alan şu yardımcı işlevi ekleyin. Bu işlev, kapama örneklemesine çekirdek bulanıklığı uygular:
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;
}
main()
dilinde mevcut olan bu satırı değiştirin:
gl_FragColor.a *= GetVisibility(depth_uvs, asset_depth_mm);
şu satırla birlikte:
gl_FragColor.a *= GetBlurredVisibilityAroundUV(depth_uvs, asset_depth_mm);
Bu yeni gölgelendirici işlevinden yararlanmak için oluşturucuyu güncelleyin.
OcclusionObjectRenderer.java
Aşağıdaki üye değişkenlerini sınıfın üst kısmına ekleyin:
private int occlusionBlurUniform;
private final float occlusionsBlur = 0.01f;
createOnGlThread
yönteminin içine aşağıdakileri ekleyin:
// Add alongside the other calls to GLES20.glGetUniformLocation.
occlusionBlurUniform = GLES20.glGetUniformLocation(program, "u_OcclusionBlurAmount");
draw
yönteminin içine aşağıdakileri ekleyin:
// Add alongside the other calls to GLES20.glUniform1f.
GLES20.glUniform1f(occlusionBlurUniform, occlusionsBlur);
Görsel KarşılaştırmaBu değişikliklerle, kapama sınırı artık daha yumuşak olacaktır. |
10. Build-Run-Test
Uygulamanızı derleme ve çalıştırma
- USB üzerinden bir Android cihaz takın.
- Dosya > Derleme ve Çalıştırma.
- Farklı Kaydet: ARCodeLab.apk.
- Uygulamanın derlenip cihazınıza dağıtılmasını bekleyin.
Uygulamayı cihazınıza ilk kez dağıtmayı denediğinizde:
- Cihazda USB üzerinden hata ayıklamaya izin vermeniz gerekir. Devam etmek için Tamam'ı seçin.
- Uygulamanın, cihaz kamerasını kullanma izni olup olmadığı sorulur. AR işlevini kullanmaya devam etmek için erişime izin verin.
Uygulamanızı test etme
Uygulamanızı çalıştırdığınızda cihazınızı basılı tutarak, alanınızda gezinerek ve bir alanı yavaşça tarayarak uygulamanın temel davranışını test edebilirsiniz. Sonraki adıma geçmeden önce en az 10 saniyelik veri toplamaya çalışın ve alanı çeşitli yönlerden tarayın.
Sorun giderme
Android cihazınızı geliştirme için ayarlama
- Bir USB kablosuyla cihazınızı geliştirme makinenize bağlayın. Windows kullanarak geliştiriyorsanız cihazınıza uygun USB sürücüsünü yüklemeniz gerekebilir.
- Geliştirici seçenekleri penceresinde USB üzerinden hata ayıklama'yı etkinleştirmek için aşağıdaki adımları uygulayın:
- Ayarlar uygulamasını açın.
- Cihazınızda Android 8.0 veya sonraki bir sürüm kullanılıyorsa Sistem'i seçin. Aksi takdirde, sonraki adıma geçin.
- Alta doğru ilerleyin ve Telefon hakkında'yı seçin.
- En alta ilerleyin ve Derleme numarası'na 7 kez dokunun.
- Önceki ekrana dönün, en alta gidin ve Geliştirici seçenekleri'ne dokunun.
- Geliştirici seçenekleri penceresini aşağı kaydırarak USB üzerinden hata ayıklama'yı bulup etkinleştirin.
Bu işlemle ilgili daha ayrıntılı bilgileri Google'ın Android geliştirici web sitesinde bulabilirsiniz.
Lisanslarla ilgili derleme hataları
Lisanslarla ilgili bir derleme hatasıyla karşılaşırsanız (Bazı lisanslar kabul edilmediğinden aşağıdaki Android SDK paketleri yüklenemedi) bu lisansları incelemek ve kabul etmek için aşağıdaki komutları kullanabilirsiniz:
cd
<path to Android SDK>
tools/bin/sdkmanager --licenses
11. Tebrikler
Tebrikler, Google'ın ARCore Depth API'sini kullanarak ilk derinlik tabanlı Artırılmış Gerçeklik uygulamanızı başarıyla derleyip çalıştırdınız.