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ünde konumlandırmanıza olanak tanır.

Joe Medley
Joe Medley

WebXR Device API, geçen sonbaharda Chrome 79'da kullanıma sunuldu. O zaman da belirtildiği gibi, Chrome'un API'yi uygulama süreci devam etmektedir. Chrome, ç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 kamera görünümüne yerleştirmenin bir yolu olan WebXR Hit Test API ele alınmaktadır.

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

Immersive AR oturumu örneği

Bu makaledeki kod, Immersive Web Working Group'un Hit Test örneğinde (demo, kaynak) bulunan koda dayanmaktadı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 bulunan mavi bir daire görürsünüz. Nokta, cihazınızdan ortamdaki noktaya doğru çekilen hayali bir çizginin kesişimidir. Cihazı hareket ettirdiğinizde bu simge de hareket eder. Kesişim noktalarını bulurken zemin, masa üstü ve duvar gibi yüzeylere yapışıyormuş gibi görünür. Bunun nedeni, isabet testiyle kesişim noktasının konumu ve yönünün sağlanması ancak yüzeylerin kendileriyle ilgili hiçbir bilgi verilmemesidir.

Bu daireye retikül adı verilir. Retikül, artırılmış gerçeklikte bir nesnenin yerleştirilmesine yardımcı olan geçici bir görüntüdür. Ekrana dokunduğunuzda, ekrana nerede dokunduğunuzdan bağımsız olarak, artı işaretinin konumunda ve yönünde bir ayçiçeği yerleştirilir. Nişangah, cihazınızla birlikte hareket etmeye devam eder.

Bağlamlarına bağlı olarak Lax (Gevşek) veya Strict (Katı) olarak bir duvara işlenmiş bir retikül
Nişangah, bir nesneyi artırılmış gerçekliğe yerleştirmeye yardımcı olan geçici bir görüntüdür.

Retikülü oluşturma

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 çizme yöntemi çerçeveye özeldir. Doğrudan WebGL veya WebGL2 kullanarak çizmiyorsanız çerçeve belgelerinize bakın. Bu nedenle, örnekte retikülün nasıl çizildiği hakkında ayrıntılı bilgi vermeyeceğim. Aşağıda, bu kodun yalnızca bir satırını gösteriyorum. Bunun tek nedeni, sonraki kod örneklerinde reticle değişkenini kullandığımda neyi kastettiğimi anlamanızı sağlamaktır.

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

Oturum isteğinde bulunma

Oturum isteğinde bulunurken aşağıdaki örnekte gösterildiği gibi 'hit-test' değerini requiredFeatures dizisinde istemeniz gerekir.

navigator.xr.requestSession('immersive-ar', {
  requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
  // Do something with the session
});

Oturuma girme

Önceki makalelerde, XR oturumuna girme kodu sunmuştum. Aşağıda, bu kodun bazı eklemeler yapılmış bir sürümünü gösterdim. Öncelikle select etkinlik dinleyicisini ekledim. Kullanıcı ekrana dokunduğunda, artı işaretinin duruşuna göre kamera görünümüne bir çiçek yerleştirilir. Bu etkinlik dinleyicisini 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 çok referans alanı

Vurgulanan kodun XRSession.requestReferenceSpace() işlevini iki kez çağırdığına dikkat edin. Başlangıçta bu durum kafa karıştırıcıydı. İsabet testi kodunun neden bir animasyon karesi istemediğini (kare döngüsünü başlatma) ve kare döngüsünün neden isabet testlerini içermediğini sordum. Kafası karışan kullanıcılar, referans alanlarını yanlış anlamış. 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 donanım kullanarak görüntülediğinizi ve hem kulaklığınızın hem de kontrol cihazınızın olduğunu varsayın. Denetleyiciden uzaklıkları ölçmek için denetleyici 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 ve denetleyici aynı cihazdır. Ancak bir sorunum var. Çizdiğim şey çevreye göre sabit olmalı ancak çizim yaptığım "kontrol cihazı" hareket ediyor.

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

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

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

requestAnimationFrame() geri çağırma işlevi, isabet testiyle ilgili işlemleri gerçekleştirmek için yeni bir kod da alır.

Cihazınızı hareket ettirirken nişangahın da yüzeyleri bulmaya çalışırken cihazınızla birlikte hareket etmesi gerekir. Hareket yanılsaması oluşturmak için her karede retikülü yeniden çizin. Ancak isabet testi başarısız olursa nişangahı 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 herhangi bir şey çizmek için izleyicinin nerede olduğunu ve nereye baktığını bilmem gerekir. Bu nedenle, hitTestSource ve xrViewerPose'nin 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()'ı arıyorum. hitTestSource değerini bağımsız değişken olarak 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 bu değeri kullanırsınız ancak gelişmiş kullanım alanları için bir dizi döndürülür. Örneğin, kameranızın bir masanın üzerindeki bir 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 kutu bu olur. Döndürülen dizinin uzunluğu 0 ise (yani isabet testi döndürülmediyse) devam edin. Bir 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, retikül görüntüsünü isabet testi konumuna dönüştürün (taşıyın), ardından visible özelliğini true olarak ayarlayın. Poz, bir yüzeydeki noktanın pozunu gösterir.

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ıya bakın.)

Bu adımda önemli olan, etiketin nereye yerleştirileceğini bilmektir. Hareketli retikül, sürekli olarak isabet testi kaynağı sağladığından bir nesneyi yerleştirmenin en basit yolu, son isabet testinde retikülün bulunduğu konuma çizmek olacaktır.

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 incelemek veya codelab'i denemektir. Umarım her ikisini de anlamanız için yeterli bilgiyi vermişimdir.

İlgi çekici web API'leri oluşturma sürecimiz henüz tamamlanmadı. İlerleme sağladıkça yeni makaleler yayınlayacağız.

Daniel Frank'in Unsplash'teki fotoğrafı