Thực tế ảo xuất hiện trên web, phần II

Giới thiệu về vòng lặp khung

Joe Medley
Joe Medley

Gần đây, tôi đã xuất bản một bài viết Thực tế ảo lên web, một bài viết giới thiệu các khái niệm cơ bản về API Thiết bị WebXR. Tôi cũng đưa ra hướng dẫn cách yêu cầu, nhập và kết thúc phiên XR.

Bài viết này mô tả vòng lặp khung, một vòng lặp vô hạn do tác nhân người dùng kiểm soát, trong đó nội dung được vẽ liên tục lên màn hình. Nội dung được vẽ trong các khối riêng biệt được gọi là khung. Liên tiếp các khung hình tạo ra ảo giác chuyển động.

Nội dung trong bài viết này

WebGL và WebGL2 là phương tiện duy nhất để hiển thị nội dung trong vòng lặp khung trong Ứng dụng WebXR. May mắn là nhiều khung cung cấp lớp trừu tượng phía trên WebGL và WebGL2. Những khung như vậy bao gồm three.js, babylonjsPlayCanvas, trong khi A-FrameReact 360 được thiết kế để tương tác với WebXR.

Bài viết này không phải là WebGL cũng không phải là hướng dẫn về khung. Tài liệu này giải thích thông tin cơ bản về vòng lặp khung bằng mẫu Phiên thực tế ảo sống động của Immersive Nhóm web (demo, nguồn). Nếu bạn muốn tìm hiểu kỹ hơn về WebGL hoặc một trong các khung, Internet sẽ cung cấp danh sách các bài viết ngày càng tăng.

Cầu thủ và trò chơi

Khi cố gắng hiểu vòng lặp khung, tôi liên tục bị lạc vào các chi tiết. Có rất nhiều đối tượng đang được sử dụng và một số đối tượng chỉ được đặt tên theo các thuộc tính tham chiếu trên các đối tượng khác. Để giúp bạn hiểu rõ hơn, tôi sẽ mô tả các đối tượng mà tôi gọi là "trình phát". Sau đó, tôi sẽ mô tả cách chúng tương tác, mà tôi gọi là "trò chơi".

Cầu thủ

XRViewerPose

Tư thế là vị trí và hướng của một đối tượng nào đó trong không gian 3D. Cả người xem và thiết bị đầu vào đều có tư thế, nhưng chúng ta quan tâm đến tư thế của người xem ở đây. Cả tư thế trình xem và thiết bị đầu vào đều có một thuộc tính transform mô tả vị trí của vectơ đó dưới dạng vectơ và hướng của nó như một quaternion so với gốc. Nguồn gốc được chỉ định dựa trên loại không gian tham chiếu được yêu cầu khi gọi XRSession.requestReferenceSpace().

Cần có chút thời gian để giải thích vì không gian tham chiếu. Tôi sẽ trình bày chi tiết về những chủ đề này trong phần Thực tế tăng cường. Mẫu mà tôi đang dùng làm cơ sở cho bài viết này sử dụng không gian tham chiếu 'local', nghĩa là điểm gốc nằm ở vị trí của người xem tại thời điểm tạo phiên mà không có giá sàn được xác định rõ, và vị trí chính xác của giá trị này có thể thay đổi tuỳ theo nền tảng.

XRView

Một khung hiển thị tương ứng với một camera xem cảnh ảo. Khung hiển thị cũng có một thuộc tính transform mô tả vị trí của vị trí dưới dạng một vectơ và hướng của vectơ đó. Các dữ liệu này được cung cấp cả dưới dạng cặp vectơ/quaternion và ma trận tương đương, bạn có thể dùng một trong hai cách biểu diễn tuỳ thuộc vào mã phù hợp nhất với mã của bạn. Mỗi thành phần hiển thị tương ứng với một màn hình hoặc một phần màn hình mà thiết bị sử dụng để hiển thị hình ảnh cho người xem. Đối tượng XRView được trả về trong một mảng từ đối tượng XRViewerPose. Số lượt xem trong mảng sẽ khác nhau. Trên thiết bị di động, cảnh thực tế tăng cường (AR) có một khung hiển thị có thể che phủ màn hình thiết bị. Tai nghe thường có hai chế độ xem, mỗi chế độ cho một mắt.

XRWebGLLayer

Các lớp cung cấp một nguồn hình ảnh bitmap và nội dung mô tả về cách hiển thị những hình ảnh đó trong thiết bị. Mô tả này không thể hiện được chính xác những gì người chơi này làm. Tôi đã coi đây là phương thức trung gian giữa một thiết bị và WebGLRenderingContext. MDN có cùng chế độ xem, tuyên bố rằng MDN "cung cấp mối liên kết" giữa hai chế độ xem. Do đó, API này cấp quyền truy cập cho những người chơi khác.

Nhìn chung, các đối tượng WebGL lưu trữ thông tin trạng thái để kết xuất đồ họa 2D và 3D.

WebGLFramebuffer

Bộ đệm khung cung cấp dữ liệu hình ảnh cho WebGLRenderingContext. Sau khi truy xuất dữ liệu từ XRWebGLLayer, bạn chỉ cần truyền dữ liệu đó đến WebGLRenderingContext hiện tại. Ngoài việc gọi bindFramebuffer() (sẽ nói thêm về điều đó sau), bạn sẽ không bao giờ truy cập trực tiếp vào đối tượng này. Bạn chỉ cần truyền mã này từ XRWebGLLayer sang WebGLRenderingContext.

XRViewport

Khung nhìn cung cấp toạ độ và kích thước của một vùng hình chữ nhật trong WebGLFramebuffer.

WebGLRenderingContext

Ngữ cảnh kết xuất là điểm truy cập có lập trình cho canvas (không gian chúng ta đang vẽ trên đó). Để làm việc này, bạn cần có cả WebGLFramebuffer và XRViewport.

Hãy lưu ý mối quan hệ giữa XRWebGLLayerWebGLRenderingContext. Một nhãn tương ứng với thiết bị của người xem và một thiết bị tương ứng với trang web. WebGLFramebufferXRViewport được chuyển từ hàm trước sang lệnh sau.

Mối quan hệ giữa XRWebGLLayer và WebGLRenderingContext
Mối quan hệ giữa XRWebGLLayerWebGLRenderingContext

Trò chơi

Giờ chúng ta đã biết những người chơi là ai, hãy xem trò chơi mà họ chơi. Đây là một trò chơi bắt đầu lại với mọi khung hình. Hãy nhớ rằng khung hình là một phần của vòng lặp khung diễn ra ở tốc độ phụ thuộc vào phần cứng cơ bản. Đối với ứng dụng VR, khung hình/giây có thể nằm trong khoảng từ 60 đến 144. AR cho Android chạy ở tốc độ 30 khung hình/giây. Mã của bạn không được giả định bất kỳ tốc độ khung hình cụ thể nào.

Quy trình cơ bản của vòng lặp khung như sau:

  1. Gọi cho XRSession.requestAnimationFrame(). Để phản hồi, tác nhân người dùng sẽ gọi XRFrameRequestCallback do bạn xác định.
  2. Bên trong hàm callback:
    1. Gọi lại cho XRSession.requestAnimationFrame().
    2. Xem tư thế của người xem.
    3. Truyền "liên kết" WebGLFramebuffer từ XRWebGLLayer vào WebGLRenderingContext.
    4. Lặp lại trên từng đối tượng XRView, truy xuất XRViewport của đối tượng đó từ XRWebGLLayer rồi truyền đối tượng đó vào WebGLRenderingContext.
    5. Vẽ nội dung nào đó vào vùng đệm khung.

Vì bước 1 và 2a đã được đề cập trong bài viết trước, nên tôi sẽ bắt đầu ở bước 2b.

Xem tư thế của người xem

Có lẽ mọi thứ đều ổn. Để vẽ bất kỳ thứ gì trong môi trường Thực tế tăng cường hoặc Thực tế ảo, tôi cần biết người xem đang ở đâu và họ đang xem ở đâu. Vị trí và hướng của trình xem do đối tượng XRViewerPose cung cấp. Tôi nhận được tư thế của người xem bằng cách gọi XRFrame.getViewerPose() trên khung ảnh động hiện tại. Tôi chuyển cho nó không gian tham chiếu mà tôi có được khi thiết lập phiên. Các giá trị mà đối tượng này trả về luôn tương ứng với không gian tham chiếu mà tôi đã yêu cầu khi vào phiên hiện tại. Như bạn có thể nhớ lại, tôi phải chuyển không gian tham chiếu hiện tại khi yêu cầu tư thế.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    // Render based on the pose.
  }
}

Có một tư thế người xem thể hiện vị trí tổng thể của người dùng, nghĩa là đầu của người xem hoặc camera trên điện thoại trong trường hợp sử dụng điện thoại thông minh. Tư thế cho ứng dụng biết vị trí của trình xem. Quá trình kết xuất hình ảnh thực tế sử dụng các đối tượng XRView mà tôi sẽ giải quyết sau giây lát.

Trước khi tiếp tục, tôi kiểm tra xem tư thế của người xem có được trả về trong trường hợp hệ thống mất khả năng theo dõi hoặc chặn tư thế đó vì lý do bảo mật hay không. Tính năng theo dõi là khả năng của thiết bị XR biết được vị trí và/hoặc thiết bị đầu vào của thiết bị đó so với môi trường. Hoạt động theo dõi có thể bị mất theo nhiều cách và thay đổi tuỳ thuộc vào phương thức dùng để theo dõi. Ví dụ: nếu bạn dùng máy ảnh trên tai nghe hoặc điện thoại để theo dõi thiết bị, thì thiết bị có thể mất khả năng xác định vị trí trong các tình huống có ánh sáng yếu hay không có ánh sáng, hoặc liệu máy ảnh có bị che phủ hay không.

Một ví dụ về việc chặn tư thế đó vì lý do quyền riêng tư là nếu tai nghe đang hiển thị hộp thoại bảo mật, chẳng hạn như lời nhắc cấp quyền, thì trình duyệt có thể ngừng cung cấp tư thế cho ứng dụng trong khi quá trình này diễn ra. Tuy nhiên, tôi đã gọi XRSession.requestAnimationFrame() để nếu hệ thống có thể khôi phục, vòng lặp khung sẽ tiếp tục. Nếu không, tác nhân người dùng sẽ kết thúc phiên và gọi trình xử lý sự kiện end.

Đường vòng ngắn

Bước tiếp theo yêu cầu các đối tượng được tạo trong quá trình thiết lập phiên. Hãy nhớ lại rằng tôi đã tạo một canvas và hướng dẫn nó tạo ngữ cảnh kết xuất Web GL tương thích với XR mà tôi nhận được bằng cách gọi canvas.getContext(). Toàn bộ quá trình vẽ đều được thực hiện bằng cách sử dụng API WebGL, API WebGL2 hoặc khung dựa trên WebGL như Three.js. Ngữ cảnh này đã được chuyển đến đối tượng phiên thông qua updateRenderState(), cùng với một thực thể mới của XRWebGLLayer.

let canvas = document.createElement('canvas');
// The rendering context must be based on WebGL or WebGL2
let webGLRenContext = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(xrSession, webGLRenContext)
  });

Truyền ("bind") bộ đệm WebGLFrame

XRWebGLLayer cung cấp vùng đệm khung cho WebGLRenderingContext được cung cấp riêng để sử dụng với WebXR và thay thế vùng đệm khung mặc định của ngữ cảnh kết xuất. Đây được gọi là 'binding' (liên kết) trong ngôn ngữ của WebGL.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    // Iterate over the views
  }
}

Thực hiện vòng lặp trên từng đối tượng XRView

Sau khi tạo tư thế và liên kết vùng đệm khung, đã đến lúc tải khung nhìn. XRViewerPose chứa một mảng gồm các giao diện XRView, mỗi giao diện đại diện cho một màn hình hoặc một phần của màn hình. Chúng chứa thông tin cần thiết để hiển thị nội dung được đặt đúng vị trí cho thiết bị và trình xem, chẳng hạn như trường nhìn, độ lệch mắt và các thuộc tính quang học khác. Vì đang vẽ cho hai mắt nên tôi có hai khung hiển thị mà tôi lặp lại và vẽ một hình ảnh riêng cho mỗi khung hiển thị.

Khi triển khai tính năng thực tế tăng cường dựa trên điện thoại, tôi sẽ chỉ có một khung hiển thị nhưng tôi vẫn sử dụng vòng lặp. Mặc dù việc lặp lại qua một khung hiển thị nghe có vẻ vô nghĩa, nhưng làm như vậy sẽ cho phép bạn có một đường dẫn kết xuất duy nhất cho nhiều trải nghiệm sống động. Đây là một điểm khác biệt quan trọng giữa WebXR và các hệ thống sống động khác.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      // Pass viewports to the context
    }
  }
}

Truyền đối tượng XRViewport đến WebGLRenderingContext

Đối tượng XRView là những nội dung có thể quan sát trên màn hình. Nhưng để vẽ được chế độ xem đó, tôi cần có toạ độ và kích thước dành riêng cho thiết bị của mình. Tương tự như với vùng đệm khung, tôi yêu cầu các lớp này từ XRWebGLLayer và truyền các lớp đó đến WebGLRenderingContext.

function onXRFrame(hrTime, xrFrame) {
  let xrSession = xrFrame.session;
  xrSession.requestAnimationFrame(onXRFrame);
  let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
  if (xrViewerPose) {
    let glLayer = xrSession.renderState.baseLayer;
    webGLRenContext.bindFramebuffer(webGLRenContext.FRAMEBUFFER, glLayer.framebuffer);
    for (let xrView of xrViewerPose.views) {
      let viewport = glLayer.getViewport(xrView);
      webGLRenContext.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      // Draw something to the framebuffer
    }
  }
}

WebGLRenContext

Trong bài viết này, tôi đã tranh luận với một vài đối tác về cách đặt tên cho đối tượng webGLRenContext. Các tập lệnh mẫu và hầu hết các mã WebXR đơn giản đều gọi biến gl này. Khi tìm hiểu các mẫu, tôi luôn quên những gì gl đề cập. Tôi đặt tên nó là webGLRenContext để nhắc bạn trong quá trình tìm hiểu rằng đây là một thực thể của WebGLRenderingContext.

Lý do là việc sử dụng gl cho phép tên phương thức giống với tên phương thức trong API OpenGL ES 2.0, dùng để tạo VR trong ngôn ngữ đã biên dịch. Thực tế này hiển nhiên là nếu bạn đã viết ứng dụng thực tế ảo bằng OpenGL, nhưng sẽ gây khó hiểu nếu bạn hoàn toàn mới với công nghệ này.

Vẽ nội dung vào vùng đệm khung

Nếu cảm thấy thực sự tham vọng, bạn có thể sử dụng trực tiếp WebGL, nhưng tôi không khuyên bạn điều đó. Sẽ đơn giản hơn nhiều nếu bạn sử dụng một trong các khung liệt kê ở trên cùng.

Kết luận

Đây chưa phải là phần cuối của các bản cập nhật hoặc bài viết về WebXR. Bạn có thể tìm thấy tài liệu tham khảo về tất cả các giao diện và thành phần của WebXR tại MDN. Đối với các tính năng nâng cao sắp tới cho chính các giao diện, hãy làm theo các tính năng riêng lẻ trên Trạng thái Chrome.

Ảnh chụp của JESHOOTS.COM trên Unsplash