WebGPU圖形程式設計(4):構建一個彩色的正方形<學習引自徐博士教程>

支阿怪?發表於2022-01-21

 本節我們來複原一個彩色的正方形,前提告知,本節的shaders和main的程式碼從結構上有調整,我會更加詳細的描述每行的程式碼意思;

原始碼下載地址:https://github.com/jack1232/webgpu07

一、首先需要你安裝所有的軟體包

安裝完軟體包,確保你的本地生成node_modules資料夾

npm install

二、建立你的index.html檔案

1.<canvas>的"canvas-webgpu"你可以理解為呼叫webgpu內的canvas介面;

2.<script>是呼叫打包好對webgpu操作指令的指令碼檔案bundle.js檔案;

<!DOCTYPE html>
<head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <title>WebGPU Step-by-Step 7</title>
   <meta name="description" content="">
   <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
   <div>  
     <h1>Create Square using GPU Buffer</h1><br>     
     <canvas id="canvas-webgpu" width="640" height="480"></canvas>
   </div>
   <script src="main.bundle.js"></script>  
</body>
</html>

二、建立你的helper.ts檔案

前面說過,程式碼結構上有調整,是因為我們把原來寫在main.ts檔案裡面的一些宣告方法,現在在helper.ts進行了宣告封裝,根據原始碼作者的意思,把一些通用的webgpuAPI呼叫介面宣告出這樣獨立的一個外部方法檔案,我們在main檔案中進行直接呼叫函式方法即可;

我使用了三種顏色區分來說明每個程式碼塊的用法,;

■代表:自定義建立一個CreateGPUBuffer函式,這個函式內包含建立device\data\usageFlage分別來應用webgpuAPI介面來實現buffer緩衝區啟用GPUBuffer;

■代表:在此自定義物件函式,裡面的定義好了大多通用的webgpu方法< device, canvas, format, context>,不需要你在宣告方法,只接呼叫即可

■代表:這是一個檢查函式,判定你的瀏覽器是否支援webgpu;

其中,我有個疑問還未解決:在helper.ts中,建立的CreateGPUBuffer緩衝區物件,其中輸入變數usageFlag的.VERTEX和.COPY_DST代表什麼意思呢?
usageFlag:GPUBufferUsageFlags = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST

程式碼如下:

 1 export const CreateGPUBuffer = (device:GPUDevice, data:Float32Array,   //自定義建立一個CreateGPUBuffer函式,這個函式內包含建立device\data\usageFlage分別來應用webgpuAPI介面來實現
 2     usageFlag:GPUBufferUsageFlags = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST) => {  //device:裝置啟用;data:資料型別;usageFlag:緩衝區資源使用
 3     const buffer = device.createBuffer({  //使用device.createBuffer來產生GPUBuffer
 4         size: data.byteLength,  //size:分配的長度,以位元組為單位;byteLength是js的一個屬性,用來獲取陣列位元組長度
 5         usage: usageFlag, //對應上面usageFlag
 6         mappedAtCreation: true  //true表示這個API定義了GPU緩衝區可以被CPU擁有使用,可以通過typescript讀取或者寫入
 7     });
 8     new Float32Array(buffer.getMappedRange()).set(data); //一但程式處於map狀態,我們的資料便會把data寫入buffer
 9     buffer.unmap();  //unmap是GPU使用的狀態,map是CPU使用的狀態
10     return buffer;   //這裡的buffer是返回unmap狀態的buffer
11 }
12  
13 export const InitGPU = async () => {     //在此自定義物件函式,裡面的定義好了大多通用的webgpu方法< device, canvas, format, context>,不需要你在宣告方法,直接呼叫即可
14     const checkgpu = CheckWebGPU();
15     if(checkgpu.includes('Your current browser does not support WebGPU!')){
16         console.log(checkgpu);
17         throw('Your current browser does not support WebGPU!');
18     }
19     const canvas = document.getElementById('canvas-webgpu') as HTMLCanvasElement;
20     const adapter = await navigator.gpu?.requestAdapter();
21     const device = await adapter?.requestDevice() as GPUDevice;
22     const context = canvas.getContext('webgpu') as unknown as GPUCanvasContext;
23 
24     const devicePixelRatio = window.devicePixelRatio || 1;
25     const size = [
26         canvas.clientWidth * devicePixelRatio,
27         canvas.clientHeight * devicePixelRatio,
28     ];
29     const format = context.getPreferredFormat(adapter!);
30 
31     context.configure({
32         device: device,
33         format: format,
34         size: size
35     });
36     return{ device, canvas, format, context };
37 };
38 
39 export const CheckWebGPU = () => {  //這是一個檢查函式,判定你的瀏覽器是否支援webgpu;
40     let result = 'Great, your current browser supports WebGPU!';
41         if (!navigator.gpu) {
42            result = `Your current browser does not support WebGPU! Make sure you are on a system 
43                      with WebGPU enabled. Currently, SPIR-WebGPU is only supported in  
44                      <a href="https://www.google.com/chrome/canary/">Chrome canary</a>
45                      with the flag "enable-unsafe-webgpu" enabled. See the 
46                      <a href="https://github.com/gpuweb/gpuweb/wiki/Implementation-Status"> 
47                      Implementation Status</a> page for more details.                   
48                     `;
49         } 
50     return result;
51 }

三、開始編寫你的main.ts檔案

需要你注意的是,原先我們把建立頂點和顏色的程式碼部分放到了shaders下實現,現在把建立頂點和顏色的資料放在main下實現,把頂點座標和顏色資料放在緩衝區寫,是因為main.ts是typescript寫成的,有程式碼提示,相比文字的著色器,除錯起來更方便。

程式碼如下:

 1 import { InitGPU, CreateGPUBuffer } from './helper';  //呼叫InitGPU(對GPU的許可權讀取等操作指令的封裝方法介面) CreateGPUBuffer(對GPU緩衝區的設定封裝)
 2 import { Shaders } from './shaders'; //呼叫著色器
 3 
 4 const CreateSquare = async () => {  //宣告一個常量塊,並非同步對映給定範圍
 5     const gpu = await InitGPU();  //第一個範圍,宣告gpu常量,給定範圍是InitGPU介面
 6     const device = gpu.device;  //第二個範圍,宣告device常量,給定範圍是gpu中的.device
 7 
 8     const vertexData = new Float32Array([   //開始建立頂點具體資料,逆時針給定三角形頂點形成一個正方形,並初始化這個變數陣列型別Float32Array
 9        -0.5, -0.5,  // vertex a  
10         0.5, -0.5,  // vertex b
11        -0.5,  0.5,  // vertex d
12        -0.5,  0.5,  // vertex d
13         0.5, -0.5,  // vertex b
14         0.5,  0.5,  // vertex c
15    ]);
16 
17    const colorData = new Float32Array([  //開始給定顏色賦值,同樣按照逆時針方向給定顏色,並初始化陣列型別(這裡的陣列沒有浮點小數,是因為typescript不區分1.0和1的區別;反之著色器程式碼要嚴格區分小數)
18         1, 0, 0,    // vertex a: red
19         0, 1, 0,    // vertex b: green
20         1, 1, 0,    // vertex d: yellow
21         1, 1, 0,    // vertex d: yellow
22         0, 1, 0,    // vertex b: green
23         0, 0, 1     // vertex c: blue
24     ]);
25 
26     const vertexBuffer = CreateGPUBuffer(device, vertexData);  //建立物件,引用helper.ts中的CreateBuffer方法賦予建立的vertexBuffer資料vertexData
27     const colorBuffer = CreateGPUBuffer(device, colorData)  //建立物件,引用helper.ts中的CreateBuffer方法賦予建立的colorBuffer資料colorData
28     
29     const shader = Shaders();     //建立Shader變數引入著色器
30     const pipeline = device.createRenderPipeline({  //產生渲染管線,進行vertex和fragment設定
31         vertex: {         //vertex描述pipeline入口著色器及輸入的緩衝區佈局
32             module: device.createShaderModule({   //建立著色器模組                  
33                 code: shader.vertex   //呼叫著色器程式碼部分的vertex方法
34             }),
35             entryPoint: "main", //入口位置
36             buffers:[     //buffers陣列,具體定義前面建立的頂點和顏色
37                 {            //這是具體描述你前面建立的vertexData的頂點資料
38                    // 描述vertexDara
39                     arrayStride: 8,     //arrayStride是一個api函式,表示該陣列的步幅(以位元組為單位)為什麼是8?每一個頂點有兩個步幅(理解為位元組或座標),2(x,y)x4(四個頂點)=8
40                     attributes: [{      //結構成員,是一個api函式
41                         shaderLocation: 0,  //shaderLocation是一個API,它表示每個屬性由數字location繫結,這裡暫時我還不太理解為什麼定義為0,目前只要記住vertexData的location定義為0
42                         format: "float32x2", //定義頂點格式 ,因為有2個元素,所有float32x2
43                         offset: 0   
44                     }]
45                 },
46                 {
47                     //描述colorData
48                     arrayStride: 12, //為什麼是12?3(3個座標)x4(四個頂點)=12
49                     attributes: [{
50                         shaderLocation: 1, //colorData的location定義為1
51                         format: "float32x3",
52                         offset: 0
53                     }]
54                 }
55             ]
56         },
57         fragment: {  //fragment 描述 pipeline 的片段著色器入口點及其輸出顏色
58             module: device.createShaderModule({                    
59                 code: shader.fragment
60             }),
61             entryPoint: "main",
62             targets: [
63                 {
64                     format: gpu.format as GPUTextureFormat
65                 }
66             ]
67         },
68         primitive:{
69             topology: "triangle-list",
70         }
71     });
72 
73     const commandEncoder = device.createCommandEncoder();
74     const textureView = gpu.context.getCurrentTexture().createView();
75     const renderPass = commandEncoder.beginRenderPass({
76         colorAttachments: [{
77             view: textureView,
78             loadValue: { r: 0.5, g: 0.5, b: 0.8, a: 1.0 }, //background color
79             storeOp: 'store'
80         }]
81     });
82     renderPass.setPipeline(pipeline);  //繫結管線到渲染通道上
83     renderPass.setVertexBuffer(0, vertexBuffer);  //把 vertexBuffer內的頂點資料繫結到渲染通道上,<<這裡的插槽理解還沒有解決>>
84     renderPass.setVertexBuffer(1, colorBuffer);
85     renderPass.draw(6);  //兩個三角形,六個頂點,需要進行draw繪製
86     renderPass.endPass();
87 
88     device.queue.submit([commandEncoder.finish()]);  //結束device的渲染
89 }
90 
91 CreateSquare();  

從第8行開始建立的頂點資料,如下圖進行理解,需要注意頂點和著色全部按照逆時針進行陣列排列

 在main檔案中我有兩個疑問沒有解答,我把我在社群發問的地址公佈出來,以便一些讀者或者社群回覆可以看到:

1.關於setVertexBuffer()插槽的理解(已解決)

 

2.頂點格式下的offset: 怎麼理解?

 

四、接下來就是編寫你的shaders檔案

可以看到著色器程式碼簡化了很多,是因為可以直接使用緩衝區裡的vertexData頂點座標和colorData顏色資料進行聯絡,聯絡方式就是location。

export const Shaders = () => {
    const vertex = `
        struct Output {
            [[builtin(position)]] Position : vec4<f32>;
            [[location(0)]] vColor : vec4<f32>;
        };
        [[stage(vertex)]]
        fn main([[location(0)]] pos: vec4<f32>, [[location(1)]] color: vec4<f32>) -> Output {
            var output: Output;
            output.Position = pos;
            output.vColor = color;
            return output;
        }`;

    const fragment = `
        [[stage(fragment)]]
        fn main([[location(0)]] vColor: vec4<f32>) -> [[location(0)]] vec4<f32> {
            return vColor;
        }`;

    return {
        vertex, 
        fragment
    };
}

五、捆綁檔案,啟用瀏覽器

npm run prod

 學習資料:

嗶哩嗶哩視訊教程:https://www.bilibili.com/video/BV1M64y1r7zL?spm_id_from=333.999.0.0

WebGPU文件:https://www.orillusion.com/zh/webgpu.html#intro

WGSL文件:https://www.orillusion.com/zh/wgsl.html#intro

原始碼地址:https://github.com/jack1232/webgpu07

 

相關文章