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

API Kiểm tra lượt truy cập cho phép bạn định vị các mục ảo trong chế độ xem thực tế.

Joe Medley
Joe Medley

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

Bài viết này đề cập đến API Kiểm thử lượt truy cập WebXR, một phương tiện để đặt các đối tượng ảo trong khung hiển thị 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. 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 viết 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 tra lượt truy cập của Nhóm hoạt động web sống động (demo, 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ó dấu chấm ở giữa. Dấu chấm là điểm giao cắt 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. Nó di chuyển khi bạn di chuyển thiết bị. Khi tìm thấy các điểm giao nhau, công cụ này có vẻ như bám vào các bề mặt như sàn, mặt bàn và tường. Phương thức này thực hiện vì hoạt động kiểm thử lượt truy cập cung cấp vị trí và hướng của điểm giao nhau, nhưng không cung cấp thông tin về các bề mặt.

Vòng tròn này được gọi là ô tô (reticle), là một hình ảnh tạm thời hỗ trợ đặ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 ở vị trí ô ô và hướng của điểm ô, bất kể bạn nhấn vào màn hình ở đâu. Kìm tiếp tục di chuyển cùng với thiết bị của bạn.

Nét chữ hiển thị trên tường, lax hoặc Nghiêm ngặt tuỳ thuộc vào bối cảnh
Kẻ ô là một hình ảnh tạm thời hỗ trợ đặt một đối tượng trong chế độ thực tế tăng cường.

Tạo ô từ

Bạn phải tự tạo hình ảnh kẻ ô vì hình ảnh này không do trình duyệt hoặc API cung cấp. Phương thức tải và vẽ tuỳ thuộc vào khung cụ thể. 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 khung của bạn. Vì lý do này, tôi sẽ không đi sâu vào cách vẽ ô kẻ ô trong mẫu. Dưới đây, tôi chỉ trình bày một dòng vì một lý do duy nhất: để trong các 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 phiên

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

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

Tham gia 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 đã trình bày một phiên bản của tính năng này bên dưới, kèm theo 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 máy ảnh dựa trên tư thế của ô kẻ ô. 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 sẽ gọi XRSession.requestReferenceSpace() hai lần. Ban đầu tôi thấy vấn đề này khá khó hiểu. Tôi đã hỏi tại sao mã kiểm tra lượt truy cập 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 có vẻ như không liên quan đến kiểm tra lượt truy cập. Nguồn gốc của sự nhầm lẫn này là do hiểu nhầm về không gian tham chiếu. Không gian tham chiếu thể hiện mối quan hệ giữa nguồn gốc và thế giới.

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

Trong ví dụ này, người xem và tay điều khiển là cùng một thiết bị. Nhưng tôi gặp vấn đề. Nội dung tôi vẽ phải ổn định đối với môi trường, nhưng "bộ điều khiển" tôi đang vẽ đang di chuyển.

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

Đối với thử nghiệm lần 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 thử nghiệm lượt truy cập. Nhãn 'trình xem' có phần khó hiểu trong ngữ cảnh này vì tôi đang nói về bộ điều khiển. Nếu bạn coi bộ điều khiển như một thiết bị xem điện tử thì điều này cũng hợp lý. Sau khi nhận được giá trị này, tôi sẽ 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

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

Khi bạn di chuyển thiết bị, ô tô cần di chuyển theo thiết bị khi cố gắng tìm các bề mặt. Để tạo ảo giác về chuyển động, hãy vẽ lại kẻ ô trong mọi khung hình. Nhưng không hiển thị từ khóa nếu kiểm tra lượt truy cập không thành công. Vì vậy, đối vớ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 môi trường thực tế tăng cường, tôi cần biết vị trí và vị trí của người xem. 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
}

Giờ tôi sẽ gọi getHitTestResults(). Phương thức này lấy hitTestSource làm đối số và trả về một mảng gồm các thực thể HitTestResult. Thử nghiệm lần truy cập có thể tìm thấy nhiều nền tảng. Giá trị đầu tiên trong mảng là giá trị gần với camera nhất. Bạn sẽ sử dụng lớp này trong hầu hết trường hợp, nhưng hệ thống sẽ trả về một mảng 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 hướng vào một chiếc hộp trên bàn trên sàn. Có thể phép kiểm thử lần truy cập sẽ trả về cả 3 nền tảng trong mảng. Trong hầu hết trường hợp, đó sẽ là chiếc hộp mà tôi quan tâm. Nếu độ dài của mảng được trả về là 0, nói cách khác là nếu không có lượt kiểm thử lượt truy cập 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 tra lượt truy cập. Quy trình cơ bản là vậy. Lấy một tư thế từ kết quả thử nghiệm lượt truy cập, biến đổi (di chuyển) hình ảnh ô tô sang vị trí thử nghiệm lượt truy cập, sau đó đặt thuộc tính visible của nó thành true. Tư thế này đại diện cho tư thế của một điểm trên 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 một đối tượng

Một đối tượng sẽ đượ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 một trình xử lý sự kiện select vào phiên. (Xem bên trên.)

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

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à sử dụng mã mẫu hoặc dùng thử lớp học lập trình. Tôi hy vọng rằng tôi đã cung cấp đủ thông tin để bạn hiểu rõ cả hai khía cạnh này.

Chúng tôi đã không xây dựng xong các API web sống động, chứ không chỉ bằng một quá trình dài. Chúng tôi sẽ xuất bản các bài viết mới tại đây trong quá trình triển khai.

Ảnh chụp của Daniel Frank trên Unsplash