Định vị đối tượng ảo trong chế độ xem thực tế

API Kiểm thử nhấn cho phép bạn định vị các mục ảo trong chế độ xem thực tế.

Joe Medley
Joe Medley

API Thiết bị WebXR được phát hành vào mùa thu năm ngoái trong Chrome 79. Như đã nêu, việc triển khai API của Chrome đang trong quá trình hoàn thiện. Chrome rất vui mừng được thông báo rằng một số công việc đã hoàn tất. Trong Chrome 81, hai tính năng mới đã có:

Bài viết này trình bày về API kiểm thử lượt nhấn WebXR, một phương thức đặt các đối tượng ảo trong chế độ xem máy ảnh thực tế.

Trong bài viết này, tôi giả định rằng bạn đã biết cách tạo phiên thực tế tăng cường và biết cách chạy vòng lặp khung hình. Nếu chưa quen với các khái niệm này, bạn nên đọc các bài viết trước đó trong loạt bài này.

Mẫu phiên thực tế tăng cường sống động

Mã trong bài viết này dựa trên, nhưng không giống với mã trong mẫu Kiểm thử lượt nhấn của Nhóm làm việc về web sống động (mẫu, nguồn). Ví dụ này cho phép bạn đặt hoa hướng dương ảo trên các bề mặt trong thế giới thực.

Khi mở ứng dụng lần đầu tiên, bạn sẽ thấy một vòng tròn màu xanh dương có một dấu chấm ở giữa. Dấu chấm là giao điểm giữa một đường tưởng tượng từ thiết bị của bạn đến điểm trong môi trường. Thiết bị di chuyển khi bạn di chuyển thiết bị. Khi tìm thấy các điểm giao nhau, nó sẽ xuất hiện để chụp nhanh các bề mặt như sàn nhà, mặt bàn và tường. Thực hiện điều này là do kiểm thử nhấn cung cấp vị trí và hướng của điểm giao cắt, nhưng không cung cấp bản thân các giao diện.

Vòng tròn này được gọi là hình kẻ ô, là một hình ảnh tạm thời hỗ trợ việc đặt một đối tượng trong chế độ thực tế tăng cường. Nếu bạn nhấn vào màn hình, một hoa hướng dương sẽ được đặt trên bề mặt theo vị trí ô và hướng của điểm kẻ ô, bất kể bạn đã nhấn vào màn hình ở đâu. Dấu ngắm sẽ tiếp tục di chuyển cùng với thiết bị của bạn.

Lưỡi kẻ ô được kết xuất trên tường, dẹt hoặc Nghiêm ngặt tuỳ theo bối cảnh
Dòng kẻ mục tiêu là một hình ảnh tạm thời giúp đặt đối tượng trong thực tế tăng cường.

Tạo đường ngắm

Bạn phải tự tạo hình ảnh mặt kẻ ô vì trình duyệt hoặc API không cung cấp hình ảnh này. Phương thức tải và vẽ nó là dành riêng cho khung. Nếu bạn không vẽ trực tiếp bằng WebGL hoặc WebGL2, hãy tham khảo tài liệu về khung của bạn. Vì lý do này, tôi sẽ không đi sâu vào chi tiết về cách vẽ đường ngắm trong mẫu. Dưới đây, tôi trình bày một dòng chỉ vì một lý do: để trong các đoạn mã mẫu sau này, bạn sẽ biết tôi đang đề cập đến điều gì khi tôi sử dụng biến reticle.

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

Yêu cầu một phiên

Khi yêu cầu một phiên, bạn phải yêu cầu 'hit-test' trong آرایه requiredFeatures như minh hoạ dưới đây.

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

Bắt đầu một phiên

Trong các bài viết trước, tôi đã trình bày mã để tham gia phiên XR. Tôi đã hiển thị một phiên bản của mã này ở bên dưới với một số nội dung bổ sung. Trước tiên, tôi đã thêm trình nghe sự kiện select. Khi người dùng nhấn vào màn hình, một bông hoa sẽ được đặt trong chế độ xem camera dựa trên tư thế của đường ngắm. Tôi sẽ mô tả trình nghe sự kiện đó sau.

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

Nhiều không gian tham chiếu

Lưu ý rằng mã được làm nổi bật gọi XRSession.requestReferenceSpace() hai lần. Ban đầu, tôi thấy điều này khá khó hiểu. Tôi đã hỏi tại sao mã kiểm thử lượt nhấn không yêu cầu khung ảnh động (bắt đầu vòng lặp khung) và tại sao vòng lặp khung hình dường như không liên quan đến kiểm thử lượt nhấn. Nguyên nhân gây nhầm lẫn là do hiểu sai về không gian tham chiếu. Không gian tham chiếu thể hiện mối quan hệ giữa một nguồn gốc và thế giới.

Để hiểu chức năng của mã này, hãy giả sử bạn đang xem mẫu này bằng một thiết bị độc lập và bạn có cả tai nghe và tay điều khiển. Để đo khoảng cách từ tay điều khiển, bạn sẽ sử dụng khung tham chiếu tập trung vào tay điều khiển. Tuy nhiên, để vẽ một nội dung nào đó lên màn hình, bạn sẽ sử dụng toạ độ tập trung vào người dùng.

Trong mẫu này, trình xem và trình điều khiển là cùng một thiết bị. Nhưng tôi có một vấn đề. Những gì tôi vẽ phải ổn định liên quan đến môi trường, nhưng "bộ điều khiển" mà tôi đang vẽ lại đang di chuyển.

Để vẽ hình ảnh, tôi sử dụng không gian tham chiếu local, giúp tôi có được sự ổn định về môi trường. Sau khi nhận được thông tin này, tôi bắt đầu vòng lặp khung hình bằng cách gọi requestAnimationFrame().

Để kiểm thử lượt truy cập, tôi sử dụng không gian tham chiếu viewer, dựa trên tư thế của thiết bị tại thời điểm kiểm thử lượt truy cập. Nhãn "viewer" (người xem) có phần gây nhầm lẫn trong ngữ cảnh này vì tôi đang nói về một trình điều khiển. Điều này sẽ hợp lý nếu bạn coi tay điều khiển là một trình xem điện tử. Sau khi nhận được mã này, tôi gọi xrSession.requestHitTestSource() để tạo nguồn dữ liệu thử nghiệm lượt truy cập mà tôi sẽ sử dụng khi vẽ.

Chạy vòng lặp khung hình

Lệnh gọi lại requestAnimationFrame() cũng nhận được mã mới để xử lý kiểm thử lượt nhấn.

Khi bạn di chuyển thiết bị, đường ngắm cần di chuyển cùng thiết bị khi thiết bị cố gắng tìm các bề mặt. Để tạo ảo giác về sự chuyển động, hãy vẽ lại mặt kẻ ô trong mọi khung hình. Tuy nhiên, không hiển thị mặt kẻ ô nếu kiểm thử lượt truy cập không thành công. Vì vậy, đối với đường ngắm mà tôi đã tạo trước đó, tôi đặt thuộc tính visible thành false.

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
}

Để vẽ bất kỳ nội dung nào trong AR, tôi cần biết người xem đang ở đâu và họ đang nhìn vào đâu. Vì vậy, tôi kiểm tra để đảm bảo rằng hitTestSourcexrViewerPose vẫn hợp lệ.

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
}

Bây giờ, tôi gọi getHitTestResults(). Phương thức này lấy hitTestSource làm đối số và trả về một mảng các thực thể HitTestResult. Kiểm thử lượt nhấn có thể tìm thấy nhiều bề mặt. Mục đầu tiên trong mảng là mục gần máy ảnh nhất. Trong hầu hết các trường hợp, bạn sẽ sử dụng phương thức này, nhưng một mảng sẽ được trả về cho các trường hợp sử dụng nâng cao. Ví dụ: hãy tưởng tượng máy ảnh của bạn đang hướng vào một chiếc hộp trên bàn trên sàn. Có thể kiểm thử lượt nhấn sẽ trả về cả ba nền tảng trong mảng. Trong hầu hết các trường hợp, đó sẽ là hộp mà tôi quan tâm. Nếu độ dài của آرایه được trả về là 0, tức là nếu không có kết quả kiểm thử nhấn nào được trả về, hãy tiếp tục. Hãy thử lại trong khung hình tiếp theo.

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
}

Cuối cùng, tôi cần xử lý kết quả kiểm thử lượt nhấn. Đây là quy trình cơ bản. Lấy tư thế từ kết quả kiểm thử lượt nhấn, biến đổi (di chuyển) hình ảnh đường ngắm đến vị trí kiểm thử lượt nhấn, sau đó đặt thuộc tính visible thành true. Tư thế thể hiện tư thế của một điểm trên một bề mặt.

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
}

Đặt đối tượng

Một đối tượng được đặt trong môi trường thực tế tăng cường khi người dùng nhấn vào màn hình. Tôi đã thêm trình xử lý sự kiện select vào phiên. (Xem ở trên.)

Điều quan trọng trong bước này là biết vị trí đặt nút. Vì tâm ngắm di chuyển cung cấp cho bạn một nguồn kiểm thử lượt nhấn liên tục, nên cách đơn giản nhất để đặt một đối tượng là vẽ đối tượng đó tại vị trí của tâm ngắm trong lần kiểm thử lượt nhấn gần đây nhất.

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

Kết luận

Cách tốt nhất để xử lý vấn đề này là tìm hiểu mã mẫu hoặc thử lớp học lập trình. Tôi hy vọng rằng mình đã cung cấp cho bạn đủ thông tin cơ bản để hiểu rõ cả hai.

Chúng tôi vẫn chưa hoàn tất việc xây dựng API web sống động, chưa tính đến việc xây dựng API web sống động. Chúng tôi sẽ xuất bản các bài viết mới tại đây khi tiến hành.

Ảnh của Daniel Frank trên Unsplash