WebGPU图形编程(4):构建一个彩色的正方形<学习引自徐博士教程>

 本节我们来复原一个彩色的正方形,前提告知,本节的shaders和main的代码从结构上有调整,我会更加详细的描述每行的代码意思;

源代码下载地址:https://github.com/jack1232/webgpu07

一、首先需要你安装所有的软件包

安装完软件包,确保你的本地生成node_modules文件夹

npm install

二、代码部分

2.1、创建你的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>

2.2、创建你的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

已解决:GPUBufferUsage.VERTEX 指的是你定义的缓冲区可以成为绘制操作中的顶点缓冲区。
GPUBufferUsage.COPY_DST指的是当你复制缓冲区的时候, 你定义的缓冲区可以用于目标缓冲区(destination buffer), 也就是允许将数据复制到你定义的缓冲区。
GPUBufferUsage 还可以包括其他选项。例如:UNIFORM, STORAGE, INDEX等等。>

代码如下:

 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 }

2.3、开始编写你的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", //描述了用户提供的GPUShaderModule中的入口点,该入口点控制pipeline的可编程阶段之一
36             buffers:[     //buffers数组,具体定义前面创建的顶点和颜色属性
37                 {            //这是具体描述你前面创建的vertexData的顶点数据
38                    // 描述vertexData
39                     arrayStride: 8,     //arrayStride是一个api函数,表示该数组的步幅(以字节为单位)为什么是8?一个顶点包含两个数字,32位机一个数字有4个字节,2(数字)x4(四个字节)=8
40                     attributes: [{      //声明属性结构成员,这是一个api函数
41                         shaderLocation: 0,  //shaderLocation地址属性,和由shaders中的location[]对应,是管线和着色器连接的具体地址识别,当然也可以理解位index(索引)
42                         format: "float32x2", //定义顶点格式属性 ,因为有2个元素,所以float32x2
43                         offset: 0   //偏移量属性,如果是单一buffer,怎可能color、nomal、nv是整数值,这里是因为vertexbuffer和colorbuffer是分开创建,所以都是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 //调用gpu.format定义片段纹理格式(这里比较抽象:我理解为要做渲染,需要调用gpu进行bit识别分配<你创建的物体颜色是按照什么bit渲染生成>进行渲染)
65                 }
66             ]
67         },
68         primitive:{  //所有的三维实体都有三角面组成<这是因为三点确定一个平面,连接三个点的线构成一个三角形,这个三角形必然在一个平面内~由这个三角形的一个边和另外一个点组成的和这个三角形相邻的图形也是个三角形,必然也在一个平面内
                       //所以在这里规定了你如果要创建三维实体或是大于三个顶点的平面,你都必须使用原始状态的primitive函数进行triangle-list创建>
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行开始创建的顶点数据,如下图进行理解,需要注意顶点和着色全部按照逆时针进行数组排列

2.4 、在main文件中我有两个疑问没有解答,我把我在社区发问的地址公布出来,以便一些读者或者社区回复可以看到:

1.关于setVertexBuffer()插槽的理解(已解决)

 

2.顶点格式下的offset: 怎么理解?(已解决)

2.5、接下来就是编写你的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

 

posted @ 2022-01-21 17:43  支阿怪🔥  阅读(762)  评论(0编辑  收藏  举报