一、WebGPU API和Canvas画布

228 阅读3分钟

WebGPU提供很多相关的API,通过这些WebGPU API可以控制你的显卡GPU渲染3D场景或计算数据。

GPU设备对象

创建GPU设备对象device非常简单,执行navigator.gpu.requestAdapter()和adapter.requestDevice()两步操作即可完成,两个异步函数,函数前需要加上es6语法的关键字await。

// 浏览器请求GPU适配器,代理对象
const adapter = await navigator.gpu.requestAdapter();
// 获取GPU设备对象,通过GPU设备对象device的WebGPU API可以控制GPU渲染过程
const device = await adapter.requestDevice();

Canvas画布

Canvas画布是一个比较特殊的HTML元素,主要用来实现图形绘制的功能,可以进行2D绘图,可以用来实现WebGL,也可以把WebGPU渲染的图像输出到Canvas画布。

<!-- canvas:用来展示WebGPU渲染的结果 -->
<canvas id="webgpu" width="500" height="500"></canvas>
//配置WebGPU上下文,把id名为webgpu的Canvas元素作为WebGPU的画布
const canvas = document.getElementById('webgpu');//dom 节点
const context = canvas.getContext('webgpu');//webgpu 可以操作的逻辑对象

配置canvas

关联Canvas画布和GPU设备对象device,这样就能把Canvas元素作为WebGPU的画布,用来呈现3D渲染效果。

const canvasFormat = navigator.gpu.getPreferredCanvasFormat();//获取浏览器默认的颜色格式
context.configure({
    device: device,
    format: canvasFormat,//颜色格式
});

配置WebGPU上下文全部代码

<body>
    <!-- canvas:用来展示WebGPU渲染的结果 -->
    <canvas id="webgpu" width="500" height="500"></canvas>
    <script type="module">
        // 1. 初始化WebGPU
        const adapter = await navigator.gpu.requestAdapter();
        // 获取GPU设备对象,通过GPU设备对象device的WebGPU API可以控制GPU渲染过程
        const device = await adapter.requestDevice();

        //配置WebGPU上下文,把id名为webgpu的Canvas元素作为WebGPU的画布
        const canvas = document.getElementById('webgpu');
        const context = canvas.getContext('webgpu');
        const canvasFormat = navigator.gpu.getPreferredCanvasFormat();//获取浏览器默认的
        context.configure({
            device: device,//WebGPU渲染器使用的GPU设备对象
            format: canvasFormat,//WebGPU渲染器使用的颜色格式
        });
    </script>
</body>

创建命令编码器和渲染通道

首先通过GPU设备对象的方法.createCommandEncoder()创建一个命令编码器对象。 通过命令对象的方法.beginRenderPass()可以创建一个渲染通道对象renderPass。

// 创建GPU命令编码器对象
const encoder = device.createCommandEncoder();

// 创建通道,类似绘图的图层
//从之前创建的画布上下文中获取纹理
const pass = encoder.beginRenderPass({
  colorAttachments: [{
     view: context.getCurrentTexture().createView(),
     loadOp: "clear",
     clearValue: { r: 0, g: 0, b: 0.4, a: 1 },
     storeOp: "store",
  }]
});
//结束通道录制工作,相关命令都写入encoder中
pass.end();

const commandBuffer = encoder.finish();
device.queue.submit([encoder.finish()]);

beginRenderPass的参数对象

const pass = commandEncoder.beginRenderPass({
    // 给渲染通道指定颜色缓冲区,配置指定的缓冲区
    colorAttachments:[{
        // 指向用于Canvas画布的纹理视图对象(Canvas对应的颜色缓冲区)
        // 该渲染通道pass输出的像素数据会存储到Canvas画布对应的颜色缓冲区(纹理视图对象)
        view: context.getCurrentTexture().createView(),  
        storeOp: 'store',//像素数据写入颜色缓冲区
        loadOp: 'clear',
        clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, //背景颜色
    }]
});

命令编码器方法.finish()

// 命令编码器.finish()。创建命令缓冲区(生成GPU指令存入缓冲区)
const commandBuffer = commandEncoder.finish();

GPU设备命令队列.queue属性

GPU设备命令队列.queue的功能是用来存放控制GPU运转的指令(命令),简单说就是你命令编码器和渲染通道定义的一系列控制GPU运行的命令方法。 .submit()是GPU设备对象device队列属性.queue的一个提交方法。

// 命令编码器,缓冲区中命令,传入GPU设备对象的命令队列.queue
device.queue.submit([commandBuffer]);

清除canvas全部代码

<body>
    <!-- canvas:用来展示WebGPU渲染的结果 -->
    <canvas id="webgpu" width="500" height="500"></canvas>
    <script type="module">
        // 1. 初始化WebGPU
        const adapter = await navigator.gpu.requestAdapter();
        // 获取GPU设备对象,通过GPU设备对象device的WebGPU API可以控制GPU渲染过程
        const device = await adapter.requestDevice();

        //配置WebGPU上下文,把id名为webgpu的Canvas元素作为WebGPU的画布
        const canvas = document.getElementById('webgpu');
        const context = canvas.getContext('webgpu');
        const canvasFormat = navigator.gpu.getPreferredCanvasFormat();//获取浏览器默认的
        context.configure({
            device: device,//WebGPU渲染器使用的GPU设备对象
            format: canvasFormat,//WebGPU渲染器使用的颜色格式
        });

        // 创建GPU命令编码器对象
        const encoder = device.createCommandEncoder();
        const pass = encoder.beginRenderPass({
        colorAttachments: [{
            view: context.getCurrentTexture().createView(),
            loadOp: "clear",
            clearValue: { r: 0, g: 0, b: 0.4, a: 1 },
            storeOp: "store",
        }]
        });
        pass.end();

        const commandBuffer = encoder.finish();
        device.queue.submit([encoder.finish()]);
    </script>
</body>