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

Tất cả thông tin về vòng lặp khung hình

Joe Medley
Joe Medley

Gần đây, tôi đã xuất bản bài viết Thực tế ảo đến với 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 cung cấp hướng dẫn về cách yêu cầu, tham gia và kết thúc phiên XR.

Bài viết này mô tả vòng lặp khung, là 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ẽ lặp lại trên màn hình. Nội dung được vẽ trong các khối riêng biệt được gọi là khung. Sự nối tiếp của các khung hình tạo ảo giác về chuyển động.

Nội dung không đề cập 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. Rất may là nhiều khung cung cấp lớp trừu tượng trên WebGL và WebGL2. Các khung đó 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 hay hướng dẫn về khung. Bài viết này giải thích các khái niệm cơ bản về vòng lặp khung hình bằng cách sử dụng mẫu Phiên VR sống động của Nhóm làm việc về web sống động (bản minh hoạ, nguồn). Nếu bạn muốn tìm hiểu sâu về WebGL hoặc một trong các khung này, Internet cung cấp một danh sách bài viết ngày càng tăng.

Người chơi và trò chơi

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

Người chơi

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ó một tư thế, nhưng đó là tư thế của người xem mà chúng tôi quan tâm ở đây. Cả tư thế người xem và tư thế thiết bị đầu vào đều có thuộc tính transform mô tả vị trí dưới dạng vectơ và hướng dưới dạng 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().

Không gian tham chiếu cần được giải thích một chút. Tôi sẽ trình bày chi tiết về các công nghệ này trong phần Thực tế tăng cường. Mẫu mà tôi đang sử dụng làm cơ sở cho bài viết này sử dụng không gian tham chiếu 'local', có 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õ ràng và vị trí chính xác của điểm gốc có thể thay đổi tuỳ theo nền tảng.

XRView

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

XRWebGLLayer

Các lớp cung cấp nguồn hình ảnh bitmap và nội dung mô tả cách hiển thị các hình ảnh đó trong thiết bị. Nội dung mô tả này không thể hiện chính xác những gì người chơi này làm. Tôi đã coi đó là một trung gian giữa thiết bị và WebGLRenderingContext. MDN cũng có quan điểm tương tự, cho biết rằng nó "cung cấp mối liên kết" giữa hai loại này. Do đó, lớp này cung cấp quyền truy cập cho các 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 đồ hoạ 2D và 3D.

WebGLFramebuffer

Vùng đệm khung hình cung cấp dữ liệu hình ảnh cho WebGLRenderingContext. Sau khi truy xuất từ XRWebGLLayer, bạn chỉ cần truyền giá trị này đến WebGLRenderingContext hiện tại. Ngoài việc gọi bindFramebuffer() (sẽ nói thêm về điều này 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 tham số đó từ XRWebGLLayer đến 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à một điểm truy cập có lập trình cho canvas (không gian mà chúng ta đang vẽ). Để làm được việc này, bạn cần có cả WebGLFramebuffer và XRViewport.

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à nhãn còn lại tương ứng với trang web. WebGLFramebufferXRViewport được truyền từ lớp trước sang lớp 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 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 hình diễn ra với tốc độ phụ thuộc vào phần cứng cơ bản. Đối với các ứng dụng thực tế ảo, khung hình/giây có thể nằm trong khoảng từ 60 đến 144. AR dành 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. Nhận tư thế của người xem.
    3. Truyền ('liên kết') WebGLFramebuffer từ XRWebGLLayer đến WebGLRenderingContext.
    4. Lặp lại trên mỗi đối tượng XRView, truy xuất XRViewport của đối tượng đó từ XRWebGLLayer và truyền đối tượng đó đến WebGLRenderingContext.
    5. Vẽ nội dung nào đó vào vùng đệm khung.

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

Lấy tư thế của người xem

Không cần phải nói nhiều. Để vẽ bất kỳ nội dung nào trong AR hoặc VR, tôi cần biết vị trí của người xem và nơi họ đang nhìn. Vị trí và hướng của người xem do đối tượng XRViewerPose cung cấp. Tôi lấy 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 truyền vào không gian tham chiếu mà tôi đã có được khi thiết lập phiên. Các giá trị do đối tượng này trả về luôn liên quan đến 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ớ, tôi phải truyề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 máy ảnh điện thoại trong trường hợp điện thoại thông minh. Tư thế đó cho ứng dụng của bạn biết vị trí của người xem. Kết xuất hình ảnh thực tế sử dụng các đối tượng XRView mà tôi sẽ tìm hiểu sau.

Trước khi tiếp tục, tôi kiểm tra xem tư thế của người xem có được trả về hay không 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 vệ quyền riêng tư. Theo dõi là khả năng của thiết bị XR để biết vị trí của thiết bị và/hoặc thiết bị đầu vào tương ứng với môi trường. Có một số cách khiến hoạt động theo dõi bị mất và tuỳ thuộc vào phương thức dùng để theo dõi. Ví dụ: nếu máy ảnh trên tai nghe hoặc điện thoại được dùng để theo dõi, thì thiết bị có thể mất khả năng xác định vị trí của thiết bị trong các tình huống có ánh sáng yếu hoặc không có ánh sáng hoặc nếu máy ảnh bị che khuất.

Ví dụ về việc chặn tư thế vì lý do quyền riêng tư: 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), 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 thì vòng lặp khung hình 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.

Một đườ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ớ rằng tôi đã tạo một canvas và hướng dẫn canvas đó tạo một bối cảnh kết xuất Web GL tương thích với XR. Tôi đã nhận được bối cảnh này bằng cách gọi canvas.getContext(). Tất cả các bản vẽ đều được thực hiện bằng API WebGL, API WebGL2 hoặc một khung dựa trên WebGL như Three.js. Ngữ cảnh này được truyề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 ('liên kết') WebGLFramebuffer

XRWebGLLayer cung cấp vùng đệm khung hình cho WebGLRenderingContext được cung cấp riêng để sử dụng với WebXR và thay thế vùng đệm khung hình mặc định của ngữ cảnh kết xuất. Thao tác này được gọi là "liên kết" trong ngôn ngữ 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
  }
}

Lặp lại trên mỗi đối tượng XRView

Sau khi lấy tư thế và liên kết vùng đệm khung hình, đã đến lúc lấy khung nhìn. XRViewerPose chứa một mảng 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. Các tệp này chứa thông tin cần thiết để hiển thị nội dung được định vị chính xác cho thiết bị và người 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ì tôi đang vẽ cho hai mắt, nên tôi có hai chế độ xem, tôi lặp lại và vẽ một hình ảnh riêng cho mỗi chế độ xem.

Khi triển khai công nghệ thực tế tăng cường dựa trên điện thoại, tôi sẽ chỉ có một chế độ xem nhưng vẫn sử dụng vòng lặp. Mặc dù có vẻ như việc lặp lại một thành phần hiển thị là vô nghĩa, nhưng việc này 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à đ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 đề cập đến những nội dung quan sát được trên màn hình. Nhưng để vẽ vào khung hiển thị đó, tôi cần 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 chúng từ XRWebGLLayer và truyền chúng vào 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 quá trình viết bài này, tôi đã tranh luận với một vài đồng nghiệp về việc đặt tên cho đối tượng webGLRenContext. Các tập lệnh mẫu và hầu hết mã WebXR chỉ gọi biến này là gl. Khi cố gắng tìm hiểu các mẫu, tôi liên tục quên mất gl đề cập đến nội dung gì. Tôi đã đặt tên là webGLRenContext để nhắc bạn trong khi 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 tương ứng trong API OpenGL ES 2.0, dùng để tạo VR bằng các ngôn ngữ được biên dịch. Điều này rất rõ ràng nếu bạn đã viết ứng dụng VR bằng OpenGL, nhưng sẽ gây nhầm lẫn nếu bạn hoàn toàn mới sử dụng công nghệ này.

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

Nếu muốn thử sức, bạn có thể trực tiếp sử dụng WebGL, nhưng tôi không khuyến khích bạn làm như vậy. Bạn có thể sử dụng một trong các khung được liệt kê ở đầu một cách đơn giản hơn nhiều.

Kết luận

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

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