Mọi điều cần biết về vòng lặp khung hình
Gần đây, tôi đã xuất bản bài viết Thực tế ảo đến với web, giới thiệu các khái niệm cơ bản đằng sau WebXR Device API. Tôi cũng cung cấp hướng dẫn về 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 hình, 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 đi lặp lại trên màn hình. Nội dung được vẽ trong các khối rời rạc gọi là khung hình. Sự kế tiếp của các khung hình tạo ra ảo ảnh về chuyển động.
Nội dung bài viết này không đề cập
WebGL và WebGL2 là những cách duy nhất để kết xuất nội dung trong vòng lặp khung hình trong Ứng dụng WebXR. May mắn là nhiều khung cung cấp một lớp trừu tượng trên WebGL và WebGL2. Các khung như vậy bao gồm three.js, babylonjs, và PlayCanvas, trong khi A-Frame và React 360 được thiết kế để tương tác với WebXR.
Bài viết này giải thích những điều cơ bản về vòng lặp khung hình, sử dụng mẫu Phiên VR nhập vai của Nhóm làm việc về web nhập vai (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, thì có một danh sách ngày càng tăng các tài nguyên trực tuyến.
Các đối tượng và trò chơi
Khi cố gắng hiểu 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 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õ, tôi sẽ mô tả các đối tượng mà tôi gọi là "đối tượng". Sau đó, tôi sẽ mô tả cách chúng tương tác, mà tôi gọi là "trò chơi".
Các đối tượng
XRViewerPose
Tư thế là vị trí và hướng của một đối tượng trong không gian 3D. Cả người xem và thiết bị đầu vào đều có tư thế, nhưng tư thế của người xem là điều chúng ta quan tâm ở đây. Cả tư thế của người xem và thiết bị đầu vào đều có thuộc tính transform mô tả vị trí của tư thế đó dưới dạng vectơ và hướng của tư thế đó dưới dạng quaternion so với gốc. 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 một 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ề không gian tham chiếu trong bài viết 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à 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ó sàn được xác định rõ ràng và vị trí chính xác của nó có thể thay đổi theo nền tảng.
XRView
Khung hiển thị tương ứng với một camera xem cảnh ảo. Khung hiển thị cũng có thuộc tính transform mô tả vị trí của khung hiển thị dưới dạng vectơ và hướng của khung hiển thị.
Các thuộc tính này được cung cấp dưới dạng cặp vectơ/quaternion và dưới dạng ma trận tương đương. Bạn có thể sử dụng bất kỳ cách biểu diễn nào tuỳ thuộc vào cách phù hợp nhất với mã của bạn. Mỗi khung 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 để trình bày hình ảnh cho người xem. XRView đối tượng được trả về trong một mảng từ
đối tượng XRViewerPose. Số lượng khung hiển thị trong mảng sẽ khác nhau. Trên thiết bị di động, cảnh AR có một khung hiển thị có thể bao phủ hoặc không bao phủ màn hình thiết bị.
Tai nghe thường có 2 khung hiển thị, một cho mỗi mắt.
XRWebGLLayer
Các lớp cung cấp nguồn hình ảnh bitmap và mô tả cách kết xuất những hình ảnh đó trong thiết bị. Phần mô tả này không hoàn toàn nắm bắt được những gì người chơi này thực hiện. Tôi đã nghĩ đến việc coi đối tượng này là trung gian giữa thiết bị và WebGLRenderingContext. MDN có quan điểm tương tự, nêu rõ rằng đối tượng này "cung cấp một liên kết" giữa hai đối tượng. Do đó, đối tượng này cung cấp quyền truy cập vào các đối tượng khác.
Nói 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
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 sẽ truyền dữ liệu đó đến WebGLRenderingContext hiện tại. Ngoài việc gọi bindFramebuffer() (thông tin chi tiết sẽ được trình bà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 đối tượng này 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à điểm truy cập theo chương trình cho một canvas (không gian mà chúng ta đang vẽ). Để thực hiện việc này, ngữ cảnh kết xuất cần cả WebGLFramebuffer và XRViewport.
Lưu ý mối quan hệ giữa XRWebGLLayer và WebGLRenderingContext. Một đối tượng tương ứng với thiết bị của người xem và đối tượng còn lại tương ứng với trang web.
WebGLFramebuffer và XRViewport được truyền từ đối tượng trước đến đối tượng sau.
XRWebGLLayer và WebGLRenderingContext
Trò chơi
Bây giờ chúng ta đã biết các đối tượng là ai, hãy xem trò chơi mà chúng 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 các khung hình là một phần của một vòng lặp khung hình diễn ra với tốc độ tuỳ thuộc vào phần cứng cơ bản. Đối với các ứng dụng VR, số khung hình trên giây có thể 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 cho vòng lặp khung hình có dạng như sau:
- Gọi
XRSession.requestAnimationFrame(). Để phản hồi, tác nhân người dùng sẽ gọiXRFrameRequestCallbackdo bạn xác định. - Bên trong hàm callback:
- Gọi lại
XRSession.requestAnimationFrame(). - Lấy tư thế của người xem.
- Truyền ("liên kết")
WebGLFramebuffertừXRWebGLLayerđếnWebGLRenderingContext. - Lặp lại từng đối tượng
XRView, truy xuấtXRViewportcủa đối tượng đó từXRWebGLLayervà truyền đối tượng đó đếnWebGLRenderingContext. - Vẽ một đối tượng vào bộ đệm khung.
- Gọi lại
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
Có lẽ không cần phải nói. Để vẽ bất kỳ đối tượng nào trong AR hoặc VR, tôi cần biết vị trí của người xem và vị trí mà họ đang nhìn. Vị trí và
hướng của người xem được cung cấp bởi đối tượng XRViewerPose. Tôi lấy tư thế của người xem bằng cách gọi XRFrame.getViewerPose() trên khung hình động hiện tại. Tôi truyền không gian tham chiếu mà tôi thu đượ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 nhập 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ế của người xem đại diện cho vị trí tổng thể của người dùng, có nghĩa là đầu của người xem hoặc camera của điện thoại.
Tư thế này cho ứng dụng biết vị trí của người 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ẽ đề cập sau.
Trước khi chuyển sang bước tiếp theo, 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 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à thiết bị đầu vào của thiết bị so với môi trường. Khả năng theo dõi có thể bị mất theo nhiều cách và tuỳ thuộc vào phương thức được sử dụng để theo dõi. Ví dụ: nếu camera 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 camera bị che.
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 điều này đang xảy ra. Nhưng 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 đoạn đườ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 ngữ cảnh kết xuất Web GL tương thích với XR mà tôi thu được bằng cách gọi canvas.getContext(). Tất cả quá trình vẽ đều được thực hiện bằng WebGL API, WebGL2 API hoặc khung dựa trên WebGL, chẳng hạn như Three.js. Ngữ cảnh này được truyền đến đối tượng phiên bằng updateRenderState(), ngoà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 bộ đệm khung cho WebGLRenderingContext được cung cấp riêng để sử dụng với WebXR và thay thế bộ đệm khung mặc định của ngữ cảnh kết xuất. Trong ngôn ngữ WebGL, thao tác này được gọi là "liên kết".
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 từng đối tượng XRView
Sau khi lấy tư thế và liên kết bộ đệm khung, đã đế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 giao diện 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 2 mắt, nên tôi có 2 khung hiển thị mà tôi lặp lại và vẽ một hình ảnh riêng biệt cho mỗi khung hiển thị.
Khi triển khai cho 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 vẫn sử dụng vòng lặp. Mặc dù việc lặp lại một khung hiển thị có vẻ 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 một loạt trải nghiệm nhập vai. Đây là điểm khác biệt quan trọng giữa WebXR và các hệ thống nhập vai 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 gì có thể 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. Giống như bộ đệm khung, tôi yêu cầu các toạ độ và kích thước đó từ XRWebGLLayer và truyền chúng đế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 quá trình viết, 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 đều gọi biến này là gl. Khi cố gắng hiểu các mẫu, tôi liên tục quên mất gl đề cập đến đối tượng nào. Tôi đã gọi đối tượng này là webGLRenContext để nhắc bạn trong khi học rằng đây là một thực thể của WebGLRenderingContext.
Lý do là vì việc sử dụng gl cho phép tên phương thức trông giống như các đối tượng tương ứng trong OpenGL ES 2.0 API, được dùng để tạo VR trong các ngôn ngữ đã biên dịch. Sự thật này là 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 làm quen với công nghệ này.
Vẽ một đối tượng vào bộ đệ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 khích điều đó. Việc sử dụng một trong các khung được liệt kê ở trên cùng sẽ đơ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 WebXR. Bạn có thể tìm thấy tài liệu tham khảo cho tất cả cá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 chính các giao diện, hãy theo dõi từng tính năng trên Trang trạng thái Chrome.