WebGPU入门(一):上下文与渲染管线

834 阅读5分钟

一、WebGPU与Canvas画布

想要使用浏览器获取到GPU设备对象device需要进行以下两个步骤:

  • 通过.requestAdapter方法获取GPU适配器
  • 通过.requestDevice方法获取GPU设备对象

注意:这两个方法都是异步函数,需要使用promise或者await的方法进行获取

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

使用WebGPU进行图像渲染需要使用canvas作为画布,该标签通常用于可视化领域

WebGPU要想在canvas上进行绘制需要通过.getContext进行上下文联系

const canvas = document.getElementById('webgpu');
const context = canvas.getContext('webgpu');

另外这个上下文还需要进行相关的配置,分别需要传入device(设备)和format(颜色格式)

context.configure({
  device, // 传入GPU设备对象
  format // 传入颜色格式
})

其中format如果没有特殊需求可以直接通过gpu的.getPreferredCanvasFormat方法获取

以下为完整的画布配置

if (!navigator.gpu) {
  console.log('No');
}
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
​
// 配置canvas上下文
const canvas = document.getElementById('webgpu');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({
  device, format
})

二、顶点Vertex与渲染管线Pipeline

使用WebGPU渲染一个物体时需要用到顶点Vertex的概念,通过顶点来定义物体的几何形状

例如渲染一个三角形时需要分别定义三角形三个角对应顶点的坐标

2.1WebGPU坐标系

在使用canvas渲染WebGPU图形时,坐标原点通常是这个canvas画布的几何中心,并以该原点为中心垂直向上构建坐标系的y轴,水平向右构建坐标系的x轴,垂直向屏幕内构建坐标系的z轴

image-20230509161740224

在WebGPU中,所有的坐标均采用的是相对值,也就是说x轴的最左边坐标无论何时均为(-1, 0),最右边为(1, 0),同样的y轴最上边为(0, 1),最下边为(0, -1)

在开发中由于涉及到的图形较为复杂,导致顶底较多,因此通常会使用类型化数组的方式来定义顶点数据,例如使用32位浮点数数组来表达三角形的坐标:

const vertexArray = new Float32Array([
  1.0, -1.0, 0.0, // 表示顶点1
  -1.0, -1.0, 0.0, // 表示顶点2
  0.0, 1.0, 0.0 // 表示顶点3
])

image-20230509162923539

2.2顶点缓冲区

开辟缓冲区

完成顶点在画布中对应位置的配置后,需要建立相应的顶点缓冲区

顶点缓冲区:包含顶点数据的内存缓冲区,顶点缓冲区可包含可呈现的任何顶点类型,另外可以处理顶点缓冲区中的顶点以执行转换、照明、生成剪裁标志等操作

通过.createBuffer方法创建一个顶点缓冲区

const vertexBuffer = device.createBuffer();

可以理解为通过该命令可以在计算机的显存(GPU内存)中开辟一片存储空间用于存储vertex数据,后续的操作均在该缓冲区内进行

在通过.createBuffer方法创建缓冲区时需要传入一个对象用于配置该缓冲区

const vertexBuffer = device.createBuffer({
  size: vertexArray.byteLength,
  usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});
  • size:该属性表示缓冲区的字节长度(即缓冲区开辟多大的空间,本例中是vertexArray中长度9 * 4 = 36)
  • usage:该属性用于标注该顶点缓冲区的作用

顶点数据写入缓冲区

device设备对象有一个queue属性具备一个.writeBuffer方法,用于将类型化数组中的数据写入已经开辟好的顶点缓冲区中

该方法需要传入三个参数:

  • 参数一:需要传入的顶点缓冲区
  • 参数二:从类型化数组获取顶点数据的偏移量(单位为字节),如果为0表示从类型化数组的开头开始读数据
  • 参数三:存有顶点数据的类型化数组
const queue = device.queue;
device.queue.writeBuffer(vertexBuffer, 0, vertexArray)

2.3渲染管线

概念

  • 渲染管线:可以理解成一种工厂的流水线,流水线上会提供不同的功能单元完成不同的零部件生产

GPU面对大型渲染环境成千上万的三角面,如果逐一进行单个计算会导致损失率大大提升

类比于汽车生产,在使用流水型进行汽车生产之前,汽车组装只能让一位位工人逐工序完成,效率极低,而引入了流水线概念后每位工人只需要做不停地做同一道工序,所有工序并行进行,极大地提高了工厂的生产效率

同样的,GPU采用了数量众多的计算单元和超长的流水线,但每一个部分只有非常简单的控制逻辑

总的来说渲染流程中需要进行三个大步骤对图像进行渲染,分别是应用阶段、几何阶段、光栅化阶段

image-20230509170915723

  • 应用阶段:CPU将决定递给GPU什么样的数据,例如渲染目标场景中的灯光、场景的模型、摄像机的位置,有时候还会对这些数据进行处理,并且告诉GPU这些数据的渲染状态
  • 几何阶段:从这个阶段开始,进入了流水线。该阶段将把应用阶段发来的数据进行进一步处理,而这个阶段又可以进一步细分为若干个流水线阶段,可以理解为工厂流水线上进行的工序
  • 光栅化阶段:对几何阶段处理后的顶点数据进行图元组装,三角形遍历等操作,并最终生成屏幕图像呈现在Canvas画布中

猴子也能看懂的渲染管线(Render Pipeline)

创建渲染管线

使用.createRenderPipline方法构建一个渲染管线,并传入一个对象用于进行相关配置

const pipline = device.createRenderPipeline({
  // 配置顶点相关参数
  vertex: {
    // 顶点所有缓冲区模块配置,该设置是一个对象数组
    // 需要对所有需要传入的顶点缓冲区进行配置,每个顶点缓冲区是一个单独的对象
    // 本例中只有一个顶点缓冲区,因此只需要传入一个对象
    buffers: [
      {
        arrayStride: 3 * 4,// 一个顶点数据占用的(每间隔多少个数据来读一个顶点,例如一个顶点坐标为(1.0, 0.0, 0.0),下一个顶点为(0.0, 0.0, 0.0)这两个顶点之间隔了3个浮点数,也就是3*浮点数长度4)
        // 顶点缓冲区属性
        attribute: [{
          shaderLocation: 0, // 标记顶点缓冲区存储位置
          format: 'float32x3', // 每个顶点的设置(本例中是32位浮点数,xyz三个轴确认点的坐标,因此为float32x3)
          offset: 0 // 读数据的偏移量
        }]
      }
    ]
  }
})