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

Hit Test API, sanal öğeleri gerçek dünya görünümünde konumlandırmanıza olanak tanır.

Joe Medley
Joe Medley

WebXR Device API, geçen sonbaharda Chrome 79 ile kullanıma sunulmuştu. Daha önce de belirtildiği gibi, Chrome'un API'yi uygulaması devam eden bir çalışmadır. Chrome, çalışmaların bir kısmının tamamlandığını duyurmaktan memnuniyet 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ştirme yöntemi olan WebXR Hit Test API ele alınmaktadır.

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

Immersive AR oturumu örneği

Bu makaledeki kod, Immersive Web Çalışma Grubu'nun isabet testi örneğindeki koda dayanır ancak bu koda tam olarak benzemez (demo, kaynak). 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 alan ile ortamdaki nokta arasındaki hayali bir çizginin kesişim noktasıdır. Cihazı hareket ettirdiğinizde de hareket eder. Kesişim noktalarını bulduğunda zemin, masa üstü ve duvar gibi yüzeylere yapışıyormuş gibi görünür. Bunu yapar çünkü isabet testinin kesişim noktasının konumunu ve yönünü sağlar, ancak yüzeylerin kendisi hakkında bilgi içermez.

Bu daireye kadran denir. Nesneleri artırılmış gerçekliğe yerleştirmenize yardımcı olan geçici bir görüntüdür. Ekrana dokunduğunuzda, ekrana dokunduğunuz yerden bağımsız olarak, ekranın üzerine bir ayçiçeği yerleştirilir. Bu ayçiçeği, ekrandaki nişangahın konumuna ve yönüne yerleştirilir. Nişangah, cihazınızla birlikte hareket etmeye devam eder.

Bağlamı gereği duvarda oluşturulan bir nişangah, Gevşek veya Katı
Nişangah, bir nesneyi artırılmış gerçekliğe yerleştirmenize yardımcı olan geçici bir resimdir.

Nişangahı oluşturma

Tarayıcı veya API tarafından sağlanmadığı için nişangah resmini kendiniz oluşturmanız gerekir. Yükleme ve çizme yöntemi çerçeveye özeldir. Doğrudan WebGL veya WebGL2 kullanarak çizmiyorsanız çerçeve dokümanlarınıza bakın. Bu nedenle, örnekte nişangahın nasıl çizildiği hakkında ayrıntılı bilgi vermeyeceğim. Aşağıda, yalnızca bir satırını gösteriyorum. Bunun nedeni, sonraki kod örneklerinde reticle değişkenini kullandığımda neyi kastetdiğimi anlayabilmenizdir.

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
});

Oturum açma

Önceki makalelerde, XR oturumuna girmek için kullanılacak kodu tanıtmıştım. Aşağıda, bu raporun bazı eklemeler içeren bir versiyonunu görebilirsiniz. Öncelikle select etkinlik dinleyicisini ekledim. Kullanıcı ekrana dokunduğunda, kamera görüntüsüne nişangahın konumuna göre 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() işlevini iki kez çağırdığını fark edin. İlk başta bu durumu karmaşık bulmuştum. İsabet testi kodunun neden animasyon karesi istemediğini (kare döngüsünü başlattığını) ve kare döngüsünün neden isabet testlerini içermediğini sordum. Karışıklığın kaynağı, referans alanlarının yanlış anlaşılmasıydı. Referans alanları, bir kaynak ile dünya arasındaki ilişkileri ifade eder.

Bu kodun ne yaptığını anlamak için bu örneği bağımsız bir kurulum kullanarak görüntülediğinizi ve hem kulaklığınızı hem de kumandanızı kullandığınız varsayın. Denetleyiciye olan mesafeleri ölçmek için denetleyici merkezli bir referans çerçevesi kullanırsınız. Ancak ekrana bir şey çizmek için kullanıcı odaklı koordinatları kullanırsınız.

Bu örnekte izleyici ve kontrol cihazı aynı cihazdır. Ancak bir sorunum var. Çizdiğim şey, çevreye göre sabit olmalıdır ancak çizim yaparken kullandığım "denetleyici" hareket ediyor.

Resim çizmek için local referans alanını kullanıyorum. Bu alan, ortam açısından kararlılık sağlıyor. Bunu aldıktan sonra requestAnimationFrame() işlevini çağırarak kare döngüsünü başlatırım.

İsabet testi için, isabet testi sırasında cihazın duruşuna dayalı viewer referans alanını kullanıyorum. Bir kontrol cihazından bahsettiğimiz için "izleyici" etiketi bu bağlamda biraz kafa karıştırıcı. Denetleyiciyi elektronik bir izleyici olarak düşünürseniz bu durum mantıklı olur. Bunu aldıktan sonra, çizim yaparken kullanacağım isabet testi veri kaynağını oluşturan xrSession.requestHitTestSource() işlevini çağırırım.

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

requestAnimationFrame() geri çağırması, isabet testini gerçekleştirmek için yeni bir kod da alır.

Cihazınızı hareket ettirdiğinizde, yüzey bulmaya çalışırken nişangahın da hareket etmesi gerekir. Hareket illüzyonu oluşturmak için her karede retikülü tekrar çizin. Ancak isabet testi başarısız olursa nişangahı göstermeyin. Bu nedenle, 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() diyorum. hitTestSource bağımsız değişkenini alır ve bir HitTestResult örneği dizisi 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ı bir zemindeki masadaki kutuya doğru tuttuğunuzu düşünün. İsabet testi, dizideki üç yüzeyin tümünü döndürebilir. Çoğu durumda, ilgilendiğim kutu budur. Döndürülen dizinin uzunluğu 0 ise diğer bir deyişle, hiçbir isabet testi döndürülmezse ileriye doğru 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, nişangah resmini isabet testi konumuna dönüştürün (taşıyın) ve ardından visible mülkünü true olarak ayarlayın. Poz, bir yüzeydeki noktanın pozunu 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 bir nesne AR'ye yerleştirilir. Oturuma zaten bir select etkinlik işleyicisi ekledim. (Yukarıdaki bilgilere bakın.)

Bu adımda önemli olan, ürünü nereye yerleştireceğinizi bilmektir. Hareketli retikül size isabet testleri için sabit bir kaynak sağladığından, bir nesneyi yerleştirmenin en basit yolu, nesneyi son isabet 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ç

Bu konuyu anlamanın en iyi yolu örnek kodu adım adım incelemek veya codelab'i denemektir. Umarım size her ikisini de anlamaya yetecek kadar bilgi verebilmişimdir.

Geniş bir yelpazede, etkileyici web API'leri oluşturma işini bitirmedik. İlerleme kaydettikçe burada yeni makaleler yayınlayacağız.

Fotoğraf: Daniel Frank, Unsplash