Đị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 hiểu rõ 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ã được tìm thấy trong Mẫu kiểm tra lượt truy cập của Nhóm làm việc trên web nhập vai (bản minh hoạ, 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à điểm giao cắt giữa một đường thẳng tưởng tượng từ thiết bị của bạn đến một đ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, 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. Điều này xảy ra vì tính năng kiểm thử lượt nhấn cung cấp vị trí và hướng của điểm giao nhau, nhưng không cung cấp thông tin nào về các bề mặt.

Vòng tròn này được gọi là khung ngắm, là một hình ảnh tạm thời giúp đặt đối tượng trong 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 tại vị trí và hướng của điểm ngắm, bất kể bạn nhấn vào vị trí nào trên màn hình. Mặt kẻ ô tiếp tục di chuyển cùng với thiết bị của bạn.

Một đường ngắm được kết xuất trên tường, Tự do hoặc Nghiêm ngặt tuỳ thuộc vào ngữ 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 đường ngắm 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 vào chi tiết về cách vẽ mặt kẻ ô trong mẫu. Dưới đây, tôi chỉ cho thấy một dòng của mã đó vì một lý do duy nhất: để trong các mẫu mã sau này, bạn sẽ biết tôi đang đề cập đến điều gì khi 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à tay đ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) hơi khó hiểu trong ngữ cảnh này vì tôi đang nói về một tay đ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ị, mặt kẻ ô cần di chuyển theo khi thiết bị cố gắng tìm các bề mặt. Để tạo ảo giác chuyển động, hãy vẽ lại đường ngắm trong mỗi khung hình. Tuy nhiên, đừng hiển thị tâm ngắm nếu kiểm thử lượt nhấn 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 môi trường AR, 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
}

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. Điểm đầu tiên trong mảng là điểm gần máy ảnh nhất. Trong hầu hết trường hợp bạn sẽ sử dụng thuộc tính 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 một chiếc bàn trên sàn nhà. Có thể phép kiểm thử lượt truy cập sẽ trả về cả 3 thành phần 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 AR 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 ở bước này là phải biết đặt mã ở đâu. Vì ô di chuyển cung cấp cho bạn một nguồn không đổi để 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 tại vị trí của ô ở lần kiểm thử lượt truy cập gần 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à thực hiện qua mã mẫu hoặc thử tham khảo 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