美文网首页
WebGPU入门

WebGPU入门

作者: 强某某 | 来源:发表于2023-05-11 13:57 被阅读0次

    检测浏览器是否支持webgpu

    chrome 113版本以上已经完全支持

    //包含该属性就证明支持
    console.log(navigator.gpu);
    

    渲染流程

    1.png

    常见api

        device.createRenderPipeline();//创建渲染管线
        device.createComputePipeline();//创建计算管线
        device.createShaderModule();//创建着色器模块
        device.createCommandEncoder();//创建命令对象(绘制和计算命令)
        device.createBuffer();//创建缓冲区
    

    通过顶点缓冲区创建顶点数据

    • usage的属性值可以是下面两种
    • GPUBufferUsage.VERTEXT : 表示用于该缓冲区是顶点缓冲区,就算存储顶点数据的缓冲区
    • GPUBufferUsage.COPY_DST : 表示该缓冲区可以写入顶点数据,作为复制顶点数据的目的地
    //渲染一个物体,需要先通过顶点坐标来定义该物体的几何形状
        //通过顶点缓冲区创建顶点数据
        const vertexBuffer = device.createBuffer({
            size: vertextArray.byteLength,//根据字节长度,此时是4*9=36字节
            //缓冲区用途:作为顶点缓冲区|可以写入顶点数据
            usage: GPUBufferUsage.VERTEXT | GPUBufferUsage.COPY_DST
        });
    

    webgpu的着色器语言是wgsl

    wgsl基础类型
           bool  布尔
          u32   无符号整数
          i32   有符号整数
          f32   32位浮点数
          f16   16位浮点数
          
          
          wgsl可以用var声明变量
          var a:u32;
          a=2;
          var b:f16=1.0;
          
          wgsl类型推断
          var c=20;
          
          两个变量进行运算,需要保持一样的数据类型
          
          
          函数
          fn add(x:f32,y:32)->f32{
           return x+y;
          }
          
          if语句
          var a:f32=2.0;
          if(a>10.0){
          }
          
          for语句
          var n:u32=10;
          for(var i:u32=0;i<n;i++){
           s+=0.05;
          }
          
          
          向量表示颜色
          四维向量有四个分量,可以用来表示颜色的RGBA
          var color:vec4<f32>=vec4<f32>(1.0,0.0,0.0,1.0);//红色不透明
          
          向量表示位置
          三维向量vec3<f32>表示具有三个分量,可以用来表示xyz坐标
          var pos:vec3<f32>;
          pos=vec3<f32>(1.0,2.0,3.0);
          等价于vec4<f32>(1.0,2.0,3.0,1.0);
          三维向量转四维向量
          var pos2=vec4<f32>(pos,1.0)
          二维向量转四维向量
          var pos2:vec2<f32>(1.0,2.0);
          var pos4:vec4<f32>(pos2,3.0,4.0);
          
          
          
          结构体
          struct pointLight{
           color:vec3<f32>,
           intensity:f32
          }
          
          var light1:pointLight;
          light1.color=vec3<f32>(1.0,0.0,0.0);
         light1.intensity=0.6;
    
    

    颜色缓冲区

    通过WebGPU渲染管线各个功能处理后,会得到图形的片元数据,或者说像素数据,这些像素数据,会存储到显卡内存颜色缓冲区中。
    你可以类比顶点缓冲区和理解颜色缓冲区,顶点缓冲区的功能是存储顶点数据,颜色缓冲区 的功能是存储渲染管线输出的像素数据。
    颜色缓冲区和顶点缓冲区类似,可以创建,不过有一个比较特殊,就是canvas画布对应一个默认的颜色缓冲区,可以直接使用。
    如果你希望webgpu绘制的图形,呈现在canvas画布上,就要把绘制的结果输出到canvas画布对应的颜色缓冲区中。
    重点:其实存储就是片元着色器处理生成的一个个片元,只不过这个缓冲区不需要创建,有默认的
    如果不想让颜色显示出来,则可以手动创建一个颜色缓冲区,把片元着色器生成的片元数据存储在自己创建的颜色缓冲区中

    2.png

    webgpu坐标系

    坐标原点在canvas画布的中间位置,x轴向右,y轴向上,z轴垂直画布向屏幕内;x和y的范围是[-1,1],z是[0,1]

    对于webgpu坐标系,图形学中称为标准化设备坐标系(简称NDC),因为坐标范围是-1~10~1的相对值,所以把NDC称为归一化设备坐标系也行。

    webgpu默认(注意是默认)的渲染规律是,渲染的xyz如果超出了范围(x和y的范围是[-1,1],z是[0,1]),会被裁剪

    那么默认情况下,WebGPU会如何渲染上面顶点坐标定义的三角形?

    为了更好理解,假设在WebGPU的3D空间中,存在一束平行光线,沿着z轴照射到XOY平面(实际上就是x0y)上,这时候3D空间中的三角形会在XOY平面上产生投影,就像生活中,人在太阳光 下,会地面上产生投影。
    这时候,z轴上的任何顶点,投影后,其实都在坐标原点,这样上面一个等边三角形,三个点投影后,就是两个点在x和y轴, z轴上的点投影到坐标原点,这样三个点连接起来,渲染的投影结果就是一个直角三角形。

    矩阵补充

    3.png 4.png

    上图中坐标平移的实现;内部的一些过程可以说明,为什么有齐次坐标而这些变换(平移旋转缩放)又为什么用矩阵计算

    旋转矩阵

    绕x轴旋转.png 绕y轴旋转.png 绕z轴旋转.png

    模型矩阵

    在图形学中经常会提到模型矩阵的概念,其实模型矩阵就是平移/缩放/旋转矩阵的统称;或者说是平移/缩放/旋转矩阵相乘得到的复合矩阵

    几何变换顺序对结果的影响?

    假设一个顶点原始坐标(2,0,0)。
    先平移2、后缩放10:如果先沿着x轴平移2,变为(4,0,0),再x轴方向缩放10倍,最终坐标是(40,0,0)。
    先缩放10、后平移2:如果先x轴方向缩放10倍,变为(20,0,0),再沿着x轴平移2,最终坐标 是(22,0,0)。
    可以发现上面同样的平移和缩放,顺序不同,变换后的顶点坐标也不相同。

    以矩阵乘法不符合一般乘法的(交换律)

    5.png 6.png 7.png

    模型矩阵:先计算所有几何变化对应的矩阵的乘积,得到一个模型矩阵,再对顶点坐标(转换为齐次坐标)进行变换。


    8.png

    一般不推荐每次变换都和顶点坐标进行计算;但是这种计算的话,矩阵和齐次坐标的计算靠近顺序参考图9

    9.png

    单位矩阵

    10.png
    11.png

    计算开源库gl-matrix

    webgpu计算理论上不需要自己编写算法,可以借助于开源库 gl-matrix,提供了矩阵,向量,四元数等等与图形学相关的数学计算函数

    12.png

    [补充视频]https://www.bilibili.com/video/BV11M41137UH?p=12&vd_source=1fe868f5f3b4b37f6561498b82c13364

    基础案例

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    
    <body>
        <canvas id="webgpu" width="500" height="500"></canvas>
    </body>
    
    </html>
    <script type="module">
        import { vertex, fragment } from "./shader.js";
        //浏览器请求gpu适配器
        const adapter = await navigator.gpu.requestAdapter();
        //获取gpu设备对象,通过gpu设备对象device的webgpu api可以控制gpu渲染过程
        const device = await adapter.requestDevice();
        const canvas = document.getElementById('webgpu');
        const ctx = canvas.getContext('webgpu');
        const format = navigator.gpu.getPreferredCanvasFormat();//获取浏览器默认的颜色格式
        ctx.configure({
            device,  //webgpu渲染器使用的gpu设备对象
            format
        });
    
        //类数组
        const vertextArray = new Float32Array([
            0.0, 0.0, 0.0,//顶点1坐标
            1.0, 0.0, 0.0,
            0.0, 1.0, 0.0
        ])
    
        //渲染一个物体,需要先通过顶点坐标来定义该物体的几何形状
        //通过顶点缓冲区创建顶点数据
        const vertexBuffer = device.createBuffer({
            size: vertextArray.byteLength,//根据字节长度,此时是4*9=36字节
            //缓冲区用途:作为顶点缓冲区|可以写入顶点数据
            usage: GPUBufferUsage.VERTEXT | GPUBufferUsage.COPY_DST
        });
        /**
         * usage的属性值可以是下面两种
         * GPUBufferUsage.VERTEXT   :  表示用于该缓冲区是顶点缓冲区,就算存储顶点数据的缓冲区
         * GPUBufferUsage.COPY_DST  :  表示该缓冲区可以写入顶点数据,作为复制顶点数据的目的地
         **/
    
    
        //顶点数据写入顶点缓冲区:0 表示从vertextArray的数据开头开始读取数据
        device.queue.writeBuffer(vertexBuffer, 0, vertextArray)
    
        //创建渲染管线
        //渲染管线类似于工厂流水线,流水线上不同的功能单元,完成不同的零部件生产,渲染管线上面提供用于3D渲染的不同功能单元
        //根据需要可以创建多个渲染管线
        const pipeline = device.createRenderPipeline({
            layout: 'auto',//默认这么写即可
            vertex: {
                //顶点着色器
                buffer: [//顶点所有的缓冲区模块设置
                    {//其中一个顶点缓冲区的设置
                        arrayStride: 3 * 4,//一个顶点的字节长度
                        attributes: [{//顶点缓冲区属性
                            shaderLocation: 0,//GPU显存上顶点缓冲区标记存储位置,例如,当前buffer内部可以是多个缓冲区,此处是标记
                            format: "float32x3",//表示一个顶点包含3个浮点数
                            offset: 0,//表示arrayStride每组顶点数据间隔字节数
                        }]
    
                    }
                ],
                module: device.createShaderModule({ code: vertex }),//顶点着色器代码
                entryPoint: "main",//指定着色器代码的入口函数
            },
            fragment: {
                //片元着色器
                module: device.createShaderModule({ code: fragment }),
                entryPoint: "main",
                targets: [
                    {
                        format,//和webgpu上下文配置的颜色格式保持一致
                    }
                ]
            },
            //这个和图元装配相关,指定绘制的形式
            primitive: {
                //绘制三角形,线条,点
                topology: "triangle-list",//三角形绘制顶点数据,如果顶点数超过三个,则三个为一组画一个三角形
    
                //line-strip :多个定点依次连线(不闭合)
                //point-list : 每个顶点坐标对应位置渲染一个小点
            }
        });
    
        //创建命令编码器对象:commandEncoder可以控制渲染管线pipeline渲染输出像素数据
        const commandEncoder = device.createCommandEncoder();
        //创建渲染通道对象
        //之前的webgpu  api默认不会执行,还需要配置GPU命令编码器对象commandEncoder实现
        //beginRenderPass中参数很多,例如colorAttachments(颜色附件),depthStencilAttachment(深度/模板附件)
        //colorAttachments属性就和颜色缓冲区有关,属性值是数组,数组中元素是对象,每个对象和一个颜色缓冲区相关,每个对象具有view,loadOp,storeOp,clearValue等属性
        //需要把渲染管线的像素数据存储到多个颜色缓冲区时候才需要colorAttachments设置多个元素对象,一般设置一个即可
        const renderPass = commandEncoder.beginRenderPass({
            colorAttachments:[
                {
                    //指向用于canvas画布的纹理视图对象(canvas对应的颜色缓冲区)
                    //该渲染通道renderPass输出的像素数据会存储到canvas画布对应的颜色缓冲区中
                    view:ctx.getCurrentTexture().createView(),
                    storeOp:'store',//像素数据写入颜色缓冲区
                    loadOp:'clear',
                    clearValue:{r:0.5,g:0.5,b:0.5,a:1.0},//背景色
    
                }
            ]
        });
        //设置该渲染通道对应的渲染管线
        //可以根据需要创建多个渲染通道,每个渲染通道都可以控制自己对应的渲染管线输出对象
        renderPass.setPipeline(pipeline);
        //顶点缓冲区数据和渲染管线shaderLocation:0 bioassay存储位置关联起来
        renderPass.setVertexBuffer(0,vertexBuffer);
        //绘制顶点数据
        renderPass.draw(3);//3是因为上面只有三个点
        renderPass.end();//结束命令
        //darw,createRenderPipeline等webgpu的api不能直接在GPU上面运行,需要转换为GPU指令(命令),才能控制GPU运转
        //命令编码器.finish()创建命令缓冲区(生成GPU指令存入GPU命令缓冲区)
        const commandBuffer=commandEncoder.finish();//返回一个命令缓冲区对象,同时编码api为GPU指令存入缓冲区
        //命令编码器缓冲区中命令传入GPU设置对象的命令队列queue
        device.queue.submit([commandBuffer]);//之所以是数组,因为命令缓冲区可能是多组
    </script>
    

    引入的wgsl代码文件,此处使用js导出字符串形式

    //@vertex表示字符串里面的代码是顶点着色器代码,在GPU渲染管线的顶点着色器单元上执行
    //fn声明一个函数,命令为main,作为顶点着色器代码的入口函数
    
    //location是wgsl语言的一个关键字,通用用来指定顶点缓冲区相关的顶点数据,使用location时候需要加上@前缀,@location()
    //小括号里面设置参数
    //@location(0) 表示GPU显存中标记为0(shaderLocation)的顶点缓冲区中的顶点数据
    
    //wgsl顶点着色器代码中,很多时候用四维向量vec4表示顶点的位置坐标,vec4第四个分量一般默认1.0
    const vertex =/*wgsl*/`
    @vertex
    fn main(@location(0) pos:vec3<f32>)->@builtin(position) vec4<f32>{
        var pos2=vec4<f32>(pos,1.0);//pos转齐次坐标
        pos2.x-=0.2;//偏移所有顶点的x坐标,而不只是一个点,区别去js的语法
        //return返回之后,渲染管线的下一个环节就可以使用了
        return pos2;
    }
    `
    
    //内置关键字
    //position是wgsl语言的一个内置变量,表示顶点数据
    //builtin,表示和其配合使用的是内置变量(例如position)
    //使用时候前面要加@ 例如:@builtin(position);一般用于返回值类型,表示通知下一个环节,返回值是顶点位置坐标数据
    
    
    //通常片元着色器输出的片元像素数据,会存储在显卡内存上,location(0)含义简单理解为输出的片元数据存储到显卡内存上
    //并把存储位置标记为0(存储到0,然后供后续使用),用于渲染管线的后续操作和处理;也就是说此处的location(0)和顶点着色器中的location(0)不相干
    const fragment=/*wgsl*/`
    @fragment
    fn main()->location(0) vec4<f32>{
        return vec4<f32>(1.0,0.0,0.0,1.0);//片元设置为红色
    }
    `;
    export {vertex,fragment}
    

    为什么webgpu使用矩阵?

    首先,3D的旋转平移缩放操作可以通过矩阵的乘法实现计算,不需要特殊的处理;齐次,gpu可以进行矩阵的并行计算,虽然单个计算速度小于cpu,但是总体速度更快。

    比较 GPU 和 CPU ,就是比较它们两者如何处理任务。CPU 使用几个核心处理单元去优化串行顺序任务,而 GPU 的大规模并行架构拥有数以千计的更小、更高效的处理单元,用于处理多个并行小任务。

    CPU 拥有复杂的系统指令,能够进行复杂的任务操作和调度,两者是互补关系,而不能相互代替。

    GPU 是大规模并行架构,处理并行任务毫无疑问是非常快的,深度学习需要高效的矩阵操作和大量的卷积操作, GPU 的并行架构再适合不过。简单来说,确实如此,但是为什么 GPU 进行矩阵操作和卷积操作会比 CPU 要快呢?

    真正原因是 GPU具有如下特性 :
    (1) 高带宽
    (2) 高速的缓存性能
    (3) 并行单元多

    在执行多任务时, CPU 需要等待带宽,而 GPU 能够优化带宽。

    举个简单的例子,我们可以把 CPU 看作跑车, GPU 是大卡车,任务就是要把一堆货物从北京搬运到广州。 CPU(跑车〉可以快速地把数据(货物〉从内存读入 RAM 中,然而 GPU (大卡车〉装货的速度就好慢了。不过后面才是重点, CPU (跑车)把这堆数据(货物)从北京搬运到广州|需要来回操作很多次,也就是往返京广线很多次,而 GPU (大卡车)只需要一 次就可以完成搬运(一次可以装载大量数据进入内存)。换言之, CPU 擅长操作小的内存块,而 GPU 则擅长操作大的内存块 。 CPU 集群大概可以达到 50GB/s 的带宽总量,而等量的 GPU 集群可以达到 750GB/s 的带宽量。

    如果让一辆大卡车去装载很多堆货物,就要等待很长的时间了,因为要等待大卡车从北京运到广州,然后再回来装货物。设想一下,我们现在拥有了跑车车队和卡车车队(线程并行〉,运载一堆货物(非常大块的内存数据需要读入缓存,如大型矩阵)。我们会等待第一辆卡车,但是后面就不需要等待的时间了,因为在广州会有一队伍的大卡车正在排队输送货物(数据),这时处理器就可以直接从缓存中读取数据了。在线性并行的情况下, GPU 可以提供高带宽,从而隐藏延迟时间。这也就是GPU 比 CPU 更适合处理深度学习的原因。

    相关文章

      网友评论

          本文标题:WebGPU入门

          本文链接:https://www.haomeiwen.com/subject/aoessdtx.html