本系列介紹 WebGPU 與一般電腦圖形學。
首先讓我們看看我們要建造什麼,
生命遊戲
3D 渲染
3D 渲染,但有燈光
渲染 3D 模型
除了JS基礎知識外,不需要任何基礎知識。
教學已經在我的 github 上完成,附有原始碼。
WebGPU 是一個相對較新的 GPU API。儘管名為 WebGPU,但它實際上可以被視為 Vulkan、DirectX 12、Metal、OpenGL 和 WebGL 之上的一層。它被設計為低階 API,旨在用於高效能應用程序,例如遊戲和模擬。
在本章中,我們將在螢幕上繪製一些東西。第一部分將參考 Google Codelabs 教學。我們將在螢幕上創建一個生活遊戲。
我們將在啟用 typescript 的 vite 中建立一個空的普通 JS 專案。然後清除所有多餘的程式碼,只留下main.ts。
const main = async () => { console.log('Hello, world!') } main()
在實際編碼之前,請檢查您的瀏覽器是否啟用了 WebGPU。您可以在 WebGPU Samples 上查看它。
Chrome 現在預設為啟用狀態。在 Safari 上,您應該前往開發者設定、標記設定並啟用 WebGPU。
我們還需要為 WebGPU 啟用這些類型,安裝 @webgpu/types,並在 tsc 編譯器選項中加入 "types": ["@webgpu/types"]。
此外,我們替換了
WebGPU 有許多樣板程式碼,如下圖所示。
首先我們需要存取 GPU。在WebGPU中,是透過適配器的概念來完成的,適配器是GPU和瀏覽器之間的橋樑。
const adapter = await navigator.gpu.requestAdapter();
然後我們需要向適配器請求一個設備。
const device = await adapter.requestDevice(); console.log(device);
我們在畫布上繪製三角形。我們需要取得canvas元素並配置它。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
這裡,我們使用 getContext 來取得畫布的相關資訊。透過指定 webgpu,我們將獲得一個負責使用 WebGPU 進行渲染的上下文。
CanvasFormat其實就是顏色模式,例如srgb。我們通常只使用首選格式。
最後,我們使用設備和格式來配置上下文。
在深入研究工程細節之前,我們首先必須了解 GPU 如何處理渲染。
GPU 渲染管道是 GPU 渲染影像所採取的一系列步驟。
在 GPU 上執行的應用程式稱為著色器。著色器是運行在GPU上的程式。著色器有一種特殊的程式語言,我們稍後會討論。
渲染管有以下步驟,
根據圖元(GPU 可以渲染的最小單位)的不同,管道可能有不同的步驟。通常,我們使用三角形,它通知 GPU 將每 3 組頂點視為三角形。
Render Pass 是完整 GPU 渲染的一個步驟。建立渲染頻道後,GPU 將開始渲染場景,完成後反之亦然。
要建立渲染通道,我們需要建立一個編碼器,負責將渲染通道編譯為 GPU 程式碼。
const main = async () => { console.log('Hello, world!') } main()
然後我們建立一個渲染通道。
const adapter = await navigator.gpu.requestAdapter();
在這裡,我們建立一個帶有顏色附件的渲染通道。附件是 GPU 中的一個概念,表示將要渲染的影像。一張圖像可能有很多個方面需要 GPU 處理,每個方面都是一個附件。
這裡我們只有一個配件,就是顏色配件。視圖是 GPU 將在其上渲染的面板,這裡我們將其設定為畫布的紋理。
loadOp是GPU在渲染通道之前執行的操作,clear表示GPU將首先清除最後一幀之前的所有數據,storeOp是GPU在渲染通道之後執行的操作,store表示GPU將把數據儲存到紋理中。
loadOp可以是load,它保留最後一幀的數據,也可以是clear,它清除最後一幀的數據。 storeOp可以是store,資料儲存到紋理,也可以是discard,丟棄資料。
現在,只需呼叫 pass.end() 即可結束渲染通道。現在,該命令已保存在 GPU 的命令緩衝區中。
要取得編譯後的指令,請使用以下程式碼,
const device = await adapter.requestDevice(); console.log(device);
最後,將指令提交到 GPU 的渲染佇列。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
現在,您應該會看到一個醜陋的黑色畫布。
根據我們對 3D 的刻板印象,我們期望空白空間是藍色的。我們可以透過設定透明顏色來做到這一點。
const encoder = device.createCommandEncoder();
現在,我們將在畫布上繪製一個三角形。我們將使用著色器來做到這一點。著色器語言將是 wgsl,WebGPU 著色語言。
現在,假設我們要繪製一個具有以下座標的三角形,
const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: "clear", storeOp: "store", }] });
正如我們之前所說,要完成渲染管道,我們需要一個頂點著色器和一個片段著色器。
使用以下程式碼建立著色器模組。
const commandBuffer = encoder.finish();
這裡的label只是一個名稱,用來除錯。 code 是實際的著色器代碼。
頂點著色器是一個接受任意參數並傳回頂點位置的函數。然而,與我們的預期相反,頂點著色器會傳回一個四維向量,而不是一個三維向量。第四個維度是w維度,用於透視劃分。我們稍後再討論。
現在,您可以簡單地將四維向量 (x, y, z, w) 視為三維向量 (x / w, y / w, z / w)。
但是,還有一個問題-如何將資料傳遞給著色器,以及如何從著色器中取出資料。
為了將資料傳遞給著色器,我們使用 vertexBuffer,一個包含頂點資料的緩衝區。我們可以使用以下程式碼建立一個緩衝區,
const main = async () => { console.log('Hello, world!') } main()
這裡我們建立了一個緩衝區,大小是24位元組,6個浮點數,這是頂點的大小。
usage是緩衝區的使用情況,對於頂點資料來說就是VERTEX。 GPUBufferUsage.COPY_DST 表示該緩衝區可作為複製目標。對於所有由CPU寫入資料的緩衝區,我們需要設定這個標誌。
這裡的map是指將buffer映射到CPU,也就是說CPU可以對buffer進行讀寫操作。 unmap的意思是取消緩衝區的映射,這表示CPU不能再讀寫緩衝區,因此內容可供GPU使用。
現在,我們可以將資料寫入緩衝區。
const adapter = await navigator.gpu.requestAdapter();
這裡,我們將緩衝區映射到CPU,並將資料寫入緩衝區。然後我們取消映射緩衝區。
vertexBuffer.getMappedRange() 將會傳回對應到 CPU 的緩衝區範圍。我們可以用它來將資料寫入緩衝區。
但是,這些只是原始數據,GPU 不知道如何解釋它們。我們需要定義緩衝區的佈局。
const device = await adapter.requestDevice(); console.log(device);
在這裡,arrayStride是GPU在尋找下一個輸入時需要在緩衝區中向前跳過的位元組數。例如,如果 arrayStride 為 8,GPU 將跳過 8 個位元組來取得下一個輸入。
由於這裡我們使用float32x2,步幅是8個位元組,每個float 4個位元組,每個頂點2個float。
現在我們可以寫頂點著色器了。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
這裡,@vertex 表示這是一個頂點著色器。 @location(0) 表示屬性的位置,如前面定義的那樣,為 0。請注意,在著色器語言中,您正在處理緩衝區的佈局,因此每當您傳遞一個值時,您需要傳遞一個結構體,其欄位已定義@location,或僅傳遞一個帶有@location的值。
vec2f 是二維浮點向量,vec4f 是四維浮點向量。由於頂點著色器需要傳回 vec4f 位置,因此我們需要使用 @builtin(position) 對其進行註解。
片段著色器,類似地,是取得插值頂點輸出並輸出附件(在本例中為顏色)的東西。插值意味著雖然只有頂點上的某些像素具有確定的值,但對於每隔一個像素,這些值都會被插值,可以是線性的、平均的或其他方式。 fragment的顏色是一個四維向量,即fragment的顏色,分別是紅、綠、藍、alpha。
請注意,顏色的範圍是0到1,而不是0到255。此外,片段著色器定義的是每個頂點的顏色,而不是三角形的顏色。三角形的顏色由頂點的顏色透過內插法決定。
由於我們目前不想控製片段的顏色,所以我們可以簡單地回傳一個常數顏色。
const main = async () => { console.log('Hello, world!') } main()
然後我們透過取代頂點和片段著色器來定義自訂渲染管道。
const adapter = await navigator.gpu.requestAdapter();
注意,在片段著色器中,我們需要指定目標的格式,也就是畫布的格式。
在渲染過程結束之前,我們先加入繪製呼叫。
const device = await adapter.requestDevice(); console.log(device);
這裡,在setVertexBuffer中,第一個參數是緩衝區的索引,在管道定義欄位buffers中,第二個參數是緩衝區本身。
呼叫draw時,參數是要繪製的頂點數。由於我們有 3 個頂點,因此我們繪製 3 個。
現在,您應該在畫布上看到一個黃色三角形。
現在我們稍微調整一下程式碼 - 因為我們想要建立一個生活遊戲,所以我們需要繪製正方形而不是三角形。
正方形其實是兩個三角形,所以我們要畫6個頂點。這裡的改動很簡單,不需要詳細解釋。
const canvas = document.getElementById('app') as HTMLCanvasElement; const context = canvas.getContext("webgpu")!; const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: device, format: canvasFormat, });
現在,您應該在畫布上看到一個黃色方塊。
我們沒有討論GPU的座標系。嗯,這相當簡單。 GPU實際的座標係是右手座標系,即x軸指向右側,y軸指向上方,z軸指向螢幕外。
座標系的範圍是-1到1。原點位於螢幕中心。 z軸從0到1,0是近平面,1是遠平面。然而,z 軸代表深度。當你做3D渲染時,你不能只使用z軸來決定物體的位置,你需要使用透視劃分。這稱為 NDC,標準化設備座標。
例如,要在螢幕左上角畫一個正方形,頂點為(-1, 1), (-1, 0), (0, 1), (0, 0) ,儘管你需要使用兩個三角形來繪製它。
以上是網路上的三角形 抓取一些東西的詳細內容。更多資訊請關注PHP中文網其他相關文章!