虛擬實境可說是網路,第 2 部分

影格迴圈完整資訊

Joe Medley
Joe Medley

我最近發布了網路虛擬實境,這篇文章介紹了 WebXR Device API 背後的基本概念。此外,我也提供了要求、進入及結束 XR 工作階段的操作說明。

本文說明影格迴圈,這是由使用者代理程式控管的無限迴圈,會在畫面上重複繪製內容。內容是透過稱為「Frame」的離散區塊來繪製。連續影格會營造出動作的幻覺

本文的論述

WebGL 和 WebGL2 是在 WebXR 應用程式的影格迴圈中算繪內容的唯一方法。幸運的是,許多架構會在 WebGL 和 WebGL2 上提供抽象層。這類架構包括 three.jsbabylonjsPlayCanvas,而 A-FrameReact 360 則是用於與 WebXR 互動。

本文並非 WebGL,也不是架構教學課程。其中會使用 Immersive Web Working Group 的沉浸式 VR 工作階段範例 (示範來源),說明影格迴圈的基本概念。如要深入瞭解 WebGL 或其中一個架構,網際網路提供越來越多文章清單。

選手與遊戲

嘗試瞭解影格迴圈時,我一直找不到細節。有許多物件正在運作,其中有些物件僅以其他物件的參照屬性命名。為協助您保持直立 我來說明「玩家」這個物件我將說明它們如何互動 我稱之為「遊戲」

選手

XRViewerPose

姿勢是指 3D 空間中物體的位置和方向。觀眾和輸入裝置都有一個姿勢,但我們在此要讓觀眾擺脫這個姿勢。檢視器和輸入裝置姿勢都有 transform 屬性,表示其位置為向量,以及相對於來源的四元數。來源是根據呼叫 XRSession.requestReferenceSpace() 時要求的參考空間類型來指定。

參考資料空間需要一點說明。如要進一步瞭解這些做法,請參閱擴增實境。我以本文基礎的範例使用 'local' 參考空間,這表示起點在建立工作階段時位於檢視者的位置,沒有明確定義的底價,因此精確位置可能因平台而異。

XRView

檢視區塊會對應至查看虛擬場景的攝影機。檢視畫面也有 transform 屬性,用來說明其位置做為向量及其方向。上述兩者會以向量/四元數配對的形式提供,並以對等的矩陣形式提供,您可以根據程式碼最適合使用任一表示法。每個檢視畫面都會對應至裝置為了向檢視者呈現圖像的螢幕或部分螢幕。XRView 物件會透過 XRViewerPose 物件的陣列傳回。陣列中的檢視畫面數量會有所不同。在行動裝置上,AR 場景有一個檢視畫面,可能不會蓋住裝置螢幕。頭戴式裝置通常有兩個視角,一個人眼。

XRWebGLLayer

圖層提供點陣圖圖片來源,以及這些圖片在裝置上算繪的方式。這個說明無法確實掌握玩家的功用。我把它想成是裝置和 WebGLRenderingContext 之間的中間人。MDN 使用許多相同的檢視畫面,表示兩者之間會「提供連結」。因此,它可以存取其他玩家。

一般來說,WebGL 物件會儲存用來算繪 2D 和 3D 圖形的狀態資訊。

WebGLFramebuffer

framebuffer 提供圖片資料給 WebGLRenderingContext。從 XRWebGLLayer 擷取後,只需將其傳遞至目前的 WebGLRenderingContext 即可。除了呼叫 bindFramebuffer() (後續會進一步說明) 以外,您永遠無法直接存取這個物件。您只需將其從 XRWebGLLayer 傳遞至 WebGLRenderingContext。

XRViewport

可視區域提供 WebGLFramebuffer 中矩形區域的座標和維度。

WebGLRenderingContext

算繪環境是畫布 (用於繪製的空間) 的程式輔助存取點。方法是同時具備 WebGLFramebuffer 和 XRViewport。

請注意 XRWebGLLayerWebGLRenderingContext 之間的關係。其中一個對應於檢視者的裝置,另一個對應網頁。WebGLFramebufferXRViewport 會從前者傳遞至後者。

XRWebGLLayer 和 WebGLRenderingContext 之間的關係
XRWebGLLayerWebGLRenderingContext 之間的關係

遊戲

現在我們已瞭解玩家是誰,讓我們來看看他們玩的遊戲。此遊戲是從每個影格重新開始。提醒您,影格屬於「影格迴圈」的一部分,而該影格是以底層硬體的速率發生。如果是 VR 應用程式,每秒影格數可能在 60 到 144 之間。Android 版的 AR 執行速度為每秒 30 個影格。程式碼不應假設任何特定影格速率。

影格迴圈的基本程序如下所示:

  1. 呼叫 XRSession.requestAnimationFrame()。做為回應,使用者代理程式會叫用由您定義的 XRFrameRequestCallback
  2. 在回呼函式中:
    1. 再次呼叫 XRSession.requestAnimationFrame()
    2. 請擺好觀眾姿勢。
    3. WebGLFramebufferXRWebGLLayer 傳遞 (「繫結」) 至 WebGLRenderingContext
    4. 對每個 XRView 物件進行疊代,從 XRWebGLLayer 擷取其 XRViewport,並傳遞至 WebGLRenderingContext
    5. 然後繪製內容到 framebuffer。

由於前一篇文章說明瞭步驟 1 和 2a,我會從步驟 2b 開始操作。

取得觀眾姿勢

就算不用說,你也能做到。如要在 AR 或 VR 中繪製任何內容,我必須知道 檢視器的所在位置和所觀看的位置。檢視器的位置和方向由 XRViewerPose 物件提供。我只要在目前的動畫影格上呼叫 XRFrame.getViewerPose(),即可取得檢視器姿勢。把我在設定工作階段時取得的參考空間傳遞給它此物件傳回的值一律會與我在進入目前工作階段時要求的參照空間相對。你可能記得,在要求姿勢時,我必須傳遞目前的參考空間。

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

有一個檢視器姿勢代表使用者的整體位置,即使用智慧型手機的用戶頭部或手機相機。這個姿勢可向應用程式說明檢視器的位置。實際圖像算繪作業會使用 XRView 物件,我稍後會對此說明。

進行後續步驟前,我會測試系統是否傳回檢視器姿勢,以免系統基於隱私權因素追蹤或封鎖姿勢。追蹤是指 XR 裝置能夠判斷其輸入裝置及/或輸入裝置與環境相對的位置。追蹤範圍有幾種方式,且會因追蹤的方法而異。舉例來說,如果將耳機或手機上的相機用於追蹤裝置,可能無法判斷裝置所在位置 (光線不足或完全沒有光線),或相機是否被遮住。

舉例來說,如果耳機顯示安全性對話方塊 (例如權限提示),瀏覽器可能就會在這類事件發生期間停止為應用程式提供行為。但我已經呼叫 XRSession.requestAnimationFrame(),這樣如果系統可以復原,影格迴圈就會繼續。如果沒有,使用者代理程式會結束工作階段,並呼叫 end 事件處理常式。

短暫改道

下一個步驟需要在工作階段設定期間建立的物件。提醒您,我建立了畫布,並指示它建立與 XR 相容的 Web GL 算繪內容,也就是透過呼叫 canvas.getContext() 取得。所有繪圖都是使用 WebGL API、WebGL2 API 或 WebGL 型架構 (例如 Three.js) 進行繪製。這個結構定義已透過 updateRenderState() 及新的 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)
  });

傳遞 (「繫結」) WebGLFramebuffer

XRWebGLLayer 會為專為與 WebXR 提供的 WebGLRenderingContext 提供影格緩衝區,並取代算繪環境的預設 framebuffer。這就是 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
  }
}

疊代每個 XRView 物件

取得姿勢並繫結 framebuffer 後,接著要取得可視區域。XRViewerPose 包含 XRView 介面陣列,每個介面代表一個螢幕或部分顯示內容。這類影格內含的資訊需要資訊,才能轉譯裝置和檢視器正確定位的內容,例如視野、眼部偏移和其他光學屬性。我繪製的是兩隻眼睛,因此有兩個檢視畫面,分別以循環方式繪製,讓每張圖片分開繪製。

實作手機式擴增實境時,我只有一個檢視畫面,但還是會使用迴圈。雖然疊代單一檢視畫面可能毫無意義,但這樣做可讓您針對多種沉浸式體驗設定單一轉譯路徑。這是 WebXR 與其他沉浸系統之間的重要差異。

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
    }
  }
}

將 XRViewport 物件傳遞至 WebGLRenderingContext

XRView 物件是指螢幕上的可觀察內容,但如要繪製檢視畫面,我需要裝置專屬的座標和維度。與 framebuffer 一樣,我向 XRWebGLLayer 要求這些特徵,並將其傳遞至 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

在撰寫本文時,我曾與幾位同事討論 webGLRenContext 物件的命名方式。範例指令碼和大部分 WebXR 程式碼都很容易呼叫這個變數 gl。當我努力瞭解這些範例時,我一直忘記 gl 所指的是什麼。我將其命名為 webGLRenContext,提醒您 學習過程中這是 WebGLRenderingContext 的執行個體。

這是因為使用 gl 可讓方法名稱在 OpenGL ES 2.0 API 中看起來類似,用於以編譯的語言建立 VR。如果您是採用 OpenGL 編寫的 VR 應用程式,這一點顯而易見,但如果您是初次接觸這項技術的使用者,會感到困惑。

繪製內容到 framebuffer

如有極大的挑戰,可以直接使用 WebGL,但不建議這麼做。使用頂端列出的架構會更加簡單。

結論

這不是 WebXR 更新或文章的結尾。您可以在 MDN 找到所有 WebXR 介面和成員的參考資料。如需即將在介面推出的強化項目,請追蹤 Chrome 狀態的個別功能。

JESHOOTS.COMUnsplash 上提供的相片