天天看點

Web 端的下一代三維圖形

<b>本文講的是Web 端的下一代三維圖形,</b>

<b></b>

讓我們來看看我們成立這個社群群組的前因後果,以及這個新組與現有 Web 圖形 API(如 WebGL)的關系。

随着時間的推移,Web 應用程式和内容漸趨豐富和複雜,并開始觸及平台的瓶頸。以遊戲為例,其性能和視覺品質至關重要。在浏覽器中開發遊戲的需求是有的,但大多數遊戲使用的是 GPU 提供的 3D 圖形 API。Mozilla 和 Opera 公布了一些從 “canvas” 元素中暴露出 3D 渲染上下文的實驗,其結果非常具有吸引力,是以社群決定一起将大家都可以實作的内容進行标準化。

在 WebGL 發展的同時,GPU 技術也在發展進步,而且已經建立了新的軟體 API,能夠更好地反映現代 GPU 的設計特性。這些新 API 的抽象級别比較低,并且由于其降低了開銷,通常來說比 OpenGL 的性能更好。該領域的主要技術平台有微軟的 Direct3D 12、蘋果的 Metal 和 Khronos Group 的 Vulkan。雖然這些技術的設計理念都是相似的,但可惜的是沒有一項技術是跨平台可用的。

那麼這對 Web 意味着什麼呢?從充分利用 GPU 的角度來講,這些新技術無疑是未來的發展方向。Web 平台想要成功必須定義一種允許多個系統上實作的通用标準,而現在已經有幾個在架構上稍有差别的圖形 API 了。要開發一款可以加速圖形和計算的現代化底層技術,必須設計一個可以在多種系統(包括上面提到的那些系統)上實作的 API。随着圖形技術的蓬勃發展,繼續遵循像 OpenGL 這樣的某個特定 API 标準顯然是不可行的。

相反,我們需要評估和設計一個新的 Web 标準:它能夠提供一組核心功能,以及一個支援多種系統圖形技術和平台的 API,此外還要保障 Web 所要求的保密性和安全性。

再者,我們還需要考慮如何在圖形處理之外使用 GPU,以及新标準如何與其他 Web 技術協同工作。該标準應該暴露現代 GPU 的通用計算功能。其設計架構應符合 Web 的既定模式以便開發和使用。它需要能夠與其他重要的新興 Web 标準(如 WebAssembly 和 WebVR)協同工作。最重要的是,這個标準的制定應該是一個開放的過程,允許行業專家和更廣泛的網絡社群參與。

下文将詳細闡述我們的實驗,我們将它稱為 “WebGPU”。

不出意料,WebGPU 的接口是通過 “canvas” 元素來通路的。

WebGPU 比 WebGL 要更加面向對象化,事實上這也是性能提升的緣由之一。WebGPU 允許你建立和存儲表示狀态的對象和可以處理一組指令的對象,而無需在每次繪制操作之前設定狀态。這樣,我們可以在狀态建立時就執行一些驗證工作,進而減少繪圖時的工作量。

WebGPU 上下文暴露了圖形指令和并行計算指令。假設需要繪制一些圖形,這需要用到圖形管道。圖形管道中最重要的元素是着色器(shaders),它們是在 GPU 上運作用以處理幾何資料并為每個像素的繪制提供顔色的程式。着色器通常用專門用于圖形的程式設計語言進行編寫。

決定 Web API 使用何種着色語言是件有趣的事情,因為有很多因素需要考慮。我們需要一種功能強大的語言,要求程式設計盡量簡單、能序列化為可高效傳輸的格式,并要求可以由浏覽器進行驗證以確定着色器的安全性。業内有部分人傾向于使用可以從許多源格式生成的着色器表示,這有點類似于彙編語言。同時,在“檢視源代碼”方面 Web 可謂發展迅速,對人而言代碼的可讀性還是很重要的。我們期望關于着色語言的讨論成為标準化過程中最有趣的部分之一,我們也十分願意聽取社群的意見。

我們使用 <code>gpu</code> 對象從源代碼加載并編譯着色器,生成一個 <code>WebGPULibrary</code>。着色器代碼本身并不重要 —— 其實就是一個非常簡單的頂點(vertex)和片段(fragment)的組合。一個<code>WebGPULibrary</code> 可以容納多個着色器函數,是以我們通過函數名稱取出将要在管道中用到的相應函數。

現在我們就可以建立管道了。

傳入所需描述資訊(包括使用的頂點、片段着色器以及圖像格式)即可從上下文中得到一個新的 <code>WebGPURenderPipelineState</code> 對象。

繪圖操作要求使用緩沖區向渲染管道提供資料,例如幾何坐标、顔色、法向量等等,而<code>WebGPUBuffer</code> 則是容納這些資料的對象。

此例中,我們有一個 <code>Float32Array</code>,它包含了需要在幾何圖形中繪制的每個頂點的資料。我們從 <code>Float32Array</code> 建立一個 <code>WebGPUBuffer</code>,該緩沖區會在之後的繪圖操作中用到。

諸如此類的頂點資料很少發生變化,但也有些資料是幾乎每次繪制時都會發生變化的。像這種不變的資料被稱為 uniforms。表示相機位置的目前變換矩陣即是 uniform 的一個很常見的例子。<code>WebGPUBuffer</code> 也可用于 uniform,但此處我們希望在建立之後将其寫入緩沖區。

這樣做的好處之一是 JavaScript 開發人員可以将 ArrayBufferView 封裝在帶有自定義 getter 和 setter 的類或代理對象(Proxy object)中,這樣外部接口看起來像典型的 JavasScript 對象一樣。然後,包裝器對象會更新緩沖區正在使用的底層數組中的相應部分。

在通知 WebGPU 上下文繪圖之前還需要設定一些狀态,這包括渲染的目标位置(最終将在<code>canvas</code> 中顯示的 <code>WebGPUTexture</code>)以及紋理(texture)初始化和使用情況的描述資訊。這些狀态存儲在 <code>WebGPURenderPassDescriptor</code> 中。

首先,我們向 WebGPU 上下文請求一個表示下一可繪幀的對象,此對象最終會被複制到 canvas 元素中去。完成繪圖代碼後,我們要通知 WebGPU 以便其顯示繪圖結果并準備下一個可繪幀。

從初始化 <code>WebGPURenderPassDescriptor</code> 的代碼中可以看出,我們不會在繪圖操作正在進行的時候從紋理中讀取資訊(因為 <code>loadAction</code> 的值是 <code>clear</code>),而是在繪圖操作完成之後才使用該紋理(因為 <code>storeAction</code> 的值是 <code>store</code>),此外代碼還指定了紋理的填充顔色。

接下來,我們建立用于儲存實際繪制操作的對象。一個 <code>WebGPUCommandQueue</code> 有一組<code>WebGPUCommandBuffers</code>。我們使用 <code>WebGPUCommandEncoder</code> 将操作推送到 <code>WebGPUCommandBuffer</code>中去。

至此,我們已經設定好了一個渲染管道,其中包含若幹着色器、一個用于儲存幾何資訊的緩沖區、一個用于儲存繪制操作的隊列以及一個可以送出到該隊列的編碼器。現在隻需将實際繪圖指令推入編碼器即可。

像大多數 3D 圖形的示例代碼一樣,繪制一個簡單的形狀看起來要寫很多代碼,但其實并非如此。這些現代 API 有一個優點 —— 其大部分代碼都是在建立可以重用以繪制其他内容的對象。例如,一般渲染上下文隻需要一個 <code>WebGPUCommandQueue</code> 執行個體,又者可以為不同的着色器提前建立多個 <code>WebGPURenderPipelineState</code> 對象。此外,浏覽器還可以在前期進行很多驗證工作,進而減少繪圖操作過程中的開銷。

希望本文可以讓你對 WebGPU 提案有一個大緻了解。盡管由 W3C 社群群組最終确定的 API 可能同此提案有很大不同,但我們相信很多一般的設計原則都是通用的。

<b>原文釋出時間為:2017年8月03日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>

繼續閱讀