Sanal nesneleri gerçek dünya görünümlerinde konumlandırma

İsabet Testi API'si, sanal öğeleri gerçek dünya görünümüne yerleştirmenize olanak tanır.

Joe Medley
Joe Medley

WebXR Device API, geçen sonbaharda Chrome 79 ile kullanıma sunuldu. O zaman da belirtildiği gibi, Chrome'un API'yi uygulaması devam eden bir çalışmadır. Chrome, bu çalışmaları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 bir kamera görünümüne yerleştirmenin bir yolu olan WebXR İsabet Testi API'si ele alınmaktadır.

Bu makalede, artırılmış gerçeklik oturumu oluşturmayı ve kare döngüsü çalıştırmayı bildiğinizi varsayıyorum. Bu kavramlara aşina değilseniz bu serideki önceki makaleleri okumanız gerekir.

Sürükleyici AR oturumu örneği

Bu makaledeki kod, Immersive Web Working Group'un isabet testi örneğinde (demo, kaynak) bulunan koda dayanır ancak bu kodla aynı değildir. Bu örnek, gerçek dünyadaki yüzeylere sanal ayçiçekleri 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 uzanan hayali bir çizginin kesişim noktasıdır. Cihazı hareket ettirdiğinizde bu da hareket eder. Kesişme noktaları buldukça zemin, masa üstü ve duvar gibi yüzeylere tutunuyormuş gibi görünür. Bunun nedeni, isabet testinin kesişim noktasının konumunu ve yönünü sağlaması ancak yüzeylerin kendileri hakkında hiçbir bilgi vermemesidir.

Bu daireye retikül denir. Retikül, artırılmış gerçeklikte bir nesneyi yerleştirmeye yardımcı olan geçici bir görüntüdür. Ekrana dokunduğunuzda, ekrana dokunduğunuz yerden bağımsız olarak, nişangah konumundaki yüzeye ve nişangah noktasının yönüne bir ayçiçeği yerleştirilir. Retikül, cihazınızla birlikte hareket etmeye devam eder.

Bağlamlarına bağlı olarak duvarda, Lax veya Strict olarak oluşturulmuş bir nişangah
Retikül, artırılmış gerçeklikte nesne yerleştirmeye yardımcı olan geçici bir resimdir.

Nişangah oluşturma

Nişangah resmi tarayıcı veya API tarafından sağlanmadığından bu resmi kendiniz oluşturmanız gerekir. Yükleme ve çizme yöntemi, çerçeveye özeldir. Doğrudan WebGL veya WebGL2 kullanarak çizim yapmıyorsanız çerçeve dokümanlarınıza bakın. Bu nedenle, örnekte nişangahın nasıl çizildiğine dair ayrıntılara girmeyeceğim. Aşağıda, yalnızca bir satırını gösteriyorum. Bunun tek nedeni, sonraki kod örneklerinde reticle değişkenini kullandığımda neyi kastettiğimi anlamanızdır.

let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});

Oturum isteğinde bulunma

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 kod sunmuştum. Aşağıda bunun bazı eklemeler yapılmış bir versiyonunu gösterdim. Öncelikle select etkinlik işleyicisini ekledim. Kullanıcı ekrana dokunduğunda, imlecin konumuna göre kamera görünümüne bir çiçek yerleştirilir. Bu 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() yöntemini iki kez çağırdığına dikkat edin. İlk başta bu bana kafa karıştırıcı gelmişti. İsabet testi kodunun neden animasyon karesi istemediğini (kare döngüsünü başlatma) ve kare döngüsünün neden isabet testleri içermediğini sordum. Bu kafa karışıklığının nedeni, referans uzayların yanlış anlaşılmasıydı. Referans uzaylar, başlangıç noktası ile dünya arasındaki ilişkileri ifade eder.

Bu kodun ne yaptığını anlamak için bu örneği bağımsız bir donanım kullanarak görüntülediğinizi ve hem başlığınız hem de kumandanız olduğunu varsayın. Kumandadan olan 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, görüntüleyici ve kumanda aynı cihazdır. Ancak bir sorunum var. Çizdiğim şeyin ortama göre sabit olması gerekiyor ancak çizim yaptığım "kumanda" hareket ediyor.

Resim çizimi için local referans alanını kullanıyorum. Bu alan, ortam açısından bana istikrar sağlıyor. Bunu aldıktan sonra requestAnimationFrame() yöntemini çağırarak kare döngüsünü başlatıyorum.

İsabet testi için, isabet testi sırasında cihazın duruşunu temel alan viewer referans alanını kullanıyorum. "Görüntüleyici" etiketi bu bağlamda biraz kafa karıştırıcı çünkü bir denetleyiciden bahsediyorum. Kumandayı elektronik bir vizör olarak düşünürseniz bu mantıklı gelecektir. Bunu aldıktan sonra xrSession.requestHitTestSource() yöntemini çağırıyorum. Bu yöntem, çizim yaparken kullanacağım isabet testi verilerinin kaynağını oluşturur.

Kare döngüsü çalıştırma

requestAnimationFrame() geri çağırma işlevi, isabet testini işlemek için yeni kod da alır.

Cihazınızı hareket ettirdiğinizde, yüzeyleri bulmaya çalışırken imlecin de cihazla birlikte hareket etmesi gerekir. Hareket algısı oluşturmak için her karede nişangahı yeniden çizin. Ancak isabet testi başarısız olursa nişangahı göstermeyin. Daha önce oluşturduğum nişangahın 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 izleyicinin nerede olduğunu ve nereye baktığını bilmem gerekiyor. Bu nedenle, hitTestSource ve xrViewerPose değerlerinin 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() adlı kişiyi arıyorum. Bağımsız değişken olarak hitTestSource alır ve HitTestResult örneklerinden oluşan bir dizi döndürür. İsabet testi birden fazla yüzey bulabilir. Dizideki ilk öğe, kameraya en yakın olan öğedir. Çoğu zaman bunu kullanırsınız ancak gelişmiş kullanım alanları için bir dizi döndürülür. Örneğin, kameranızın zemindeki bir masanın üzerinde duran kutuya doğru tutulduğunu düşünün. İsabet testinin, dizideki üç yüzeyi de döndürmesi mümkündür. Çoğu durumda, ilgilendiğim şey kutu olacaktır. Döndürülen dizinin uzunluğu 0 ise yani isabet testi döndürülmezse 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ç şu şekildedir. İsabet testi sonucundan bir poz alın, nişangah resmini isabet testi konumuna dönüştürün (taşıyın) ve ardından visible özelliğini doğru olarak ayarlayın. Duruş, bir yüzeydeki noktanın duruşunu 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 AR'ye bir nesne yerleştirilir. Oturuma bir select etkinlik işleyici ekledim. (Yukarıya bakın.)

Bu adımda önemli olan, bu öğeyi nereye yerleştireceğinizi bilmektir. Hareketli imleç size sürekli bir isabet testi kaynağı sağladığından, bir nesneyi yerleştirmenin en basit yolu, son isabet testinde imlecin bulunduğu konuma ç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 anlamanın en iyi yolu, örnek kod üzerinden ilerlemek veya codelab'i denemektir. Umarım her ikisini de anlamanız için yeterli bilgi verebilmişimdir.

Sürükleyici web API'leri oluşturma çalışmalarımız henüz tamamlanmadı. İlerleme kaydettikçe burada yeni makaleler yayınlayacağız.