Tất cả thông tin 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 xuất hiện trên web. Bài viết này 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, 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 gọi là khung. Sự kế tiếp của các khung hình tạo ra ả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 để kết xuất nội dung trong một vòng lặp khung hình trong ứng dụng WebXR. Rất may, nhiều khung cung cấp một lớp trừu tượng trên WebGL và WebGL2. Các khung nà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 không phải là hướng dẫn về 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 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 vật thể trong không gian 3D. Cả trình 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 trình xem ở đâ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 cụ 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'
, 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 phiên có thể khác nhau 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 chưa thể hiện đúng chức năng của trình phát này. 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 đó, nó cung 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 đồ 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 XRWebGLLayer
và WebGLRenderingContext
. Một thuộc tính tương ứng với thiết bị của người xem và thuộc tính còn lại tương ứng với trang web.
WebGLFramebuffer
và XRViewport
được truyền từ lớp trước sang lớp sau.
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, số khung hình/giây có thể dao độ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 cho vòng lặp khung hình như sau:
- Gọi cho
XRSession.requestAnimationFrame()
. Để phản hồi, tác nhân người dùng sẽ gọiXRFrameRequestCallback
do bạn xác định. - Bên trong hàm callback:
- Gọi lại
XRSession.requestAnimationFrame()
. - Nhận tư thế của người xem.
- Truyền ('liên kết')
WebGLFramebuffer
từXRWebGLLayer
đếnWebGLRenderingContext
. - Lặp lại trên từng đối tượng
XRView
, truy xuấtXRViewport
của đối tượng đó từXRWebGLLayer
và truyền đối tượng đó đếnWebGLRenderingContext
. - Vẽ nội dung nào đó vào vùng đệm khung hình.
- 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.
Nhận tư thế của người xem
Điều này chắc chắn là không cần phải nói. Để 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 đã 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 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ế 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 đến 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 mậ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ó nhiều cách để mất dữ liệu theo dõi 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. Nhưng 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
.
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 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ù việc lặp lại một thành phần hiển thị có vẻ như không có ý 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 nội dung có thể quan sát được trên màn hình. Nhưng để vẽ vào thành phần hiển thị đó, tôi cần có toạ độ và kích thước dành riêng cho thiết bị của mình. Cũng như với vùng đệm khung hình, tôi yêu cầu các đối tượng này từ XRWebGLLayer
và chuyển các đối tượ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 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 nào đó 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 trên MDN. Để biết các tính năng nâng cao sắp tới cho chính giao diện, hãy theo dõi các tính năng riêng lẻ trên trang Trạng thái Chrome.
Ảnh của JESHOOTS.COM trên Unsplash