Hit Test API, sanal öğeleri gerçek dünyada konumlandırmanıza olanak tanır.
WebXR Device API geçen sonbaharda Chrome 79'da kullanıma sunuldu. O zaman da belirtildiği gibi, Chrome'un API'yi uygulaması devam eden bir çalışmadır. Chrome, bu çalışmanın bir kısmının tamamlandığını duyurmaktan mutluluk duyuyor. Chrome 81'de iki yeni özellik kullanıma sunuldu:
Bu makalede, sanal nesneleri gerçek dünyadaki kamera görünümüne yerleştirme yöntemi olan WebXR Hit Test API ele alınmaktadır.
Bu makalede artırılmış gerçeklik oturumu oluşturmayı ve bir kare döngüsü çalıştırmayı bildiğinizi varsayıyorum. Bu kavramlara aşina değilseniz bu dizinin önceki makalelerini okumalısınız.
- Sanal gerçeklik artık internette
- Sanal gerçeklik web'e geliyor, 2. bölüm
- Web AR: Nasıl kullanıldığını zaten biliyor olabilirsiniz
Etkileyici AR oturumu örneği
Bu makaledeki kod, Immersive Web Working Group'un İsabet Testi örneğindeki (demo, source) bulunan koda dayanır ancak aynı değildir. Bu örnek, sanal ayçiçeklerini gerçek dünyadaki yüzeylere yerleştirmenize olanak tanır.
Uygulamayı ilk açtığınızda, ortasında nokta olan mavi bir daire görürsünüz. Nokta, cihazınızdan ortamdaki noktaya kadar hayali bir çizgi arasındaki kesişim noktasıdır. Siz cihazı hareket ettirdikçe hareket eder. Kesişme noktaları bulduğunda zemin, masa üstü ve duvar gibi yüzeylere yapıştığı anlaşılıyor. Bunu yapar çünkü isabet testi, kesişim noktasının konumunu ve yönünü sağlar, ancak yüzeylerle ilgili hiçbir şey yoktur.
Bu daireye retikül adı verilir. Bu daire, bir nesnenin artırılmış gerçeklikteki yerini belirlemeye yardımcı olan geçici bir görüntüdür. Ekrana dokunduğunuzda, ekrana nereden dokunduğunuzdan bağımsız olarak, retikül konumuna ve retikül noktasının yönüne yüzeye bir ayçiçeği yerleştirilir. Direksiyon, cihazınızla birlikte hareket etmeye devam eder.
Retikülü oluştur
Tarayıcı veya API tarafından sağlanmadığı için retikül görüntüsünü kendiniz oluşturmanız gerekir. Yükleme ve çizim yöntemi çerçeveye özeldir.
Doğrudan WebGL veya WebGL2 kullanarak çizmiyorsanız çerçeve dokümanlarınıza bakın. Bu nedenle, örnekte retikülün nasıl
nasıl çizildiğine değinmeyeceğim. Aşağıda bunun bir satırını yalnızca bir nedenden dolayı gösteriyorum. Böylece sonraki kod
örneklerinde, reticle
değişkenini kullandığımda neyi kastettiğimi anlayabilirsiniz.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
Oturum iste
Oturum isteğinde bulunurken, aşağıda gösterildiği gibi requiredFeatures
dizisinde 'hit-test'
isteğinde bulunmanız gerekir.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
Oturuma girme
Önceki makalelerde, XR oturumuna girmek için kullanılacak kod sunmuştuk. Bunun bir versiyonunu bazı eklemeler
yaparak aşağıda görebilirsiniz. İlk olarak select
etkinlik işleyicisini ekledim. Kullanıcı ekrana dokunduğunda, retikinin pozuna göre kamera görünümüne bir çiçek yerleştirilir. Etkinlik işleyiciyi daha sonra açıklayacağım.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
xrSession.addEventListener('select', onSelect);
let canvas = document.createElement('canvas');
gl = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
xrSession.requestReferenceSpace('viewer').then((refSpace) => {
xrViewerSpace = refSpace;
xrSession.requestHitTestSource({ space: xrViewerSpace })
.then((hitTestSource) => {
xrHitTestSource = hitTestSource;
});
});
xrSession.requestReferenceSpace('local').then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
Birden fazla referans alanı
Vurgulanan kodun XRSession.requestReferenceSpace()
öğesini iki kez çağırdığına dikkat edin. İlk başta bu kafa karıştırıcı buldum. İsabet test kodunun neden animasyon karesi istemediğini (kare döngüsünü başlatır) ve kare döngüsünün neden isabet testleri içermediğini sordum. Karışıklığın kaynağı referans alanlarının
yanlış anlaşılmasıydı. Referans alanları, köken ile dünya arasındaki
ilişkileri ifade eder.
Bu kodun ne işe yaradığını anlamak için bu örneği bağımsız bir düzenek kullanarak görüntülüyor ve hem mikrofonlu kulaklık hem de kumandanız varmış gibi düşünün. Kumandayla arasındaki mesafeleri ölçmek için kumanda merkezli bir referans çerçevesi kullanırsınız. Ancak ekrana bir şey çizmek için kullanıcı merkezli koordinatlar kullanırsınız.
Bu örnekte izleyici ile kumanda aynı cihazdır. Ama bir sorunum var. Çizdiğim şey ortama göre sabit olmalı ama çiziminde kullandığım 'denetleyici' hareket ediyor.
Resim çizimi için ortam açısından kararlılık sağlayan local
referans alanını kullanıyorum. Bunu aldıktan sonra, requestAnimationFrame()
çağrısı yaparak
kare döngüsünü başlatıyorum.
İsabet testi için cihazın isabet testi sırasındaki pozisyonuna dayanan viewer
referans alanını kullanıyorum. "Görüntüleyici" etiketi bu bağlamda biraz
kafa karıştırıcıdır, çünkü denetleyiciden bahsediyorum. Kumandayı elektronik bir
izleyici olarak düşünün. Bunu aldıktan sonra, çizim sırasında kullanacağım isabet testi verilerinin kaynağını
oluşturan xrSession.requestHitTestSource()
adını veriyorum.
Kare döngüsü çalıştırma
requestAnimationFrame()
geri çağırma işlevi, isabet testini gerçekleştirmek için yeni kod da alır.
Cihazınızı hareket ettirdikçe retikülün yüzeyleri bulmaya çalışırken onunla birlikte hareket etmesi gerekir. Hareket illüzyonu yaratmak için retikülü her karede yeniden çizin.
Ancak, isabet testi başarısız olursa retiküleyi göstermeyin. Bu nedenle, daha önce oluşturduğum retikül için visible
özelliğini false
olarak ayarladım.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Artırılmış gerçeklikte bir şey çizmek için görüntüleyenin nerede olduğunu ve
nereye baktığını bilmem gerekir. Bu nedenle, hitTestSource
ve xrViewerPose
öğelerinin hâlâ geçerli olup olmadığını test ediyorum.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Şimdi getHitTestResults()
numaralı telefonu arıyorum. hitTestSource
öğesini bağımsız değişken olarak alır ve HitTestResult
örneklerden oluşan bir dizi döndürür. İsabet testi birden fazla
yüzeyi bulabilir. Dizideki ilk, kameraya en yakın olandır.
Çoğu zaman bunu kullanacaksınız ancak gelişmiş kullanım alanları için bir dizi döndürülür. Örneğin, kameranızın zemindeki masada duran bir kutuya doğru
durduğunu düşünün. İsabet testinin dizideki üç yüzeyi de döndürmesi mümkündür. Çoğu durumda, önemsediğim kutu bu olacak. Döndürülen dizinin uzunluğu 0 ise yani isabet testi döndürülmezse çalışmaya devam edin. Sonraki karede tekrar deneyin.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
Son olarak, isabet testi sonuçlarını işlemem gerekiyor. Temel süreç budur. İsabet testi sonucundan bir poz alın, retikül resmini isabet testi konumuna dönüştürün (taşıyın), ardından visible
özelliğini true olarak ayarlayın. Poz, yüzey üzerindeki bir noktanın pozisyonunu temsil eder.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.matrix = pose.transform.matrix;
reticle.visible = true;
}
}
// Draw to the screen
}
Nesne yerleştirme
Kullanıcı ekrana dokunduğunda artırılmış gerçeklik (AR) moduna yerleştiriliyor. Oturuma zaten bir select
etkinlik işleyici ekledim. (Yukarı bakın.)
Bu adımda önemli olan, etiketin nereye yerleştirileceğini bilmektir. Hareketli retikül size sabit bir isabet testi kaynağı sağladığından, bir nesneyi yerleştirmenin en kolay yolu, son vuruş testinde retikülün bulunduğu yere çizmektir.
function onSelect(event) {
if (reticle.visible) {
// The reticle should already be positioned at the latest hit point,
// so we can just use its matrix to save an unnecessary call to
// event.frame.getHitTestResults.
addARObjectAt(reticle.matrix);
}
}
Sonuç
Bunu belirlemenin en iyi yolu örnek kodu görmek veya codelab'i denemektir. Umarım size her ikisini de anlamak için yeterli deneyim sunabilmişimdir.
Tam kapsamlı web API'leri oluşturma işimiz yakın zamanda değil. İlerledikçe burada yeni makaleler yayınlayacağız.
Fotoğraf: Daniel Frank'in Unsplash'teki fotoğrafı