WebGPU入门(二):着色器、图元装配与光栅化

425 阅读4分钟

一、顶点着色器

顶点着色器是渲染管线中的一个处理单元,该单元的作用就是对顶点坐标的x,y,z进行平移、旋转、缩放等操作的计算

写顶点着色器的功能需要使用WSGL着色器语言

由于WSGL语言既能写顶点着色器又能写片元着色器,因此需要使用 **@vertex**进行功能标注

@vertex
fn main(@location(0) pos: vec3<f32>) -> vec4<f32> {
  // 在WGSL语言中通常会使用四维向量来表示顶点位置坐标,第四个向量默认值为1.0
  var pos2 = vec4<f32>(pos, 1.0);
  // 偏移所有顶点的x坐标
  pos2.x -= 0.2;
  // 返回顶点数据,进入渲染管线的下一个环节使用
  return pos2;
}

WGSL代码中有一个location关键字,用于指定顶点缓冲区相关的顶点数据,使用该关键字同样需要加上@前缀,并在括号中设置参数,该参数表示GPU显存中标记为0的顶点缓冲区中顶点数据

值得注意的是,在WGSL语言中如果需要处理所有的顶点数据,不需要进行for循环对所有的点进行遍历,而是直接对点进行处理即可,因为WGSL设置的是一个处理单元,会将所有符合条件的点以流水线的形式传入其中进行处理,如下所示

image-20230509183115825

顶点着色器处理完成后需要将顶点传至渲染管线的下一个环节继续进行处理,此时就需要使用到WGSL的内置变量position@builtin关键字,position变量用于表明顶点数据,@builtin用于表明某个变量为内置变量,即:需要使用position就一定要使用@builtin(position)的形式

综上,需要使用WGSL对顶点着色器进行配置,需要通过location传入未处理的顶点缓冲区相关的顶点数据,然后经过一系列计算处理后通过@builtin(position)传出处理后的顶点数据

@vertex
fn main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> {
  ...(处理过程)
  return vec4<f32>(pos, 1.0);
}

完成着色器代码后,需要使用.createShaderModule方法将着色器代码转化为GPI着色器代码块对象,并将其配置在渲染管线pipeline中同时指定WGSL语言构建的着色器入口函数,本案例中为main函数

const vertext = /* wgsl */`
@vertex
fn main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> {
  return vec4<f32>(pos, 1.0);
}
`
// 以字符串的形式引入javascript中
const pipline = device.createRenderPipeline({
  vertex: { 
    module: device.createShaderModule({ code: vertex }),
    entryPoint: 'main',
    buffers: [...(缓冲区配置)],
  },
});

二、图元装配

经过顶点着色器后的顶点数据进入图元装配环节,即:根据顶点数据生成对应的几何图形

通过primitive.topology属性可以设置WebGPU如何绘制顶点数据,例如:

const pipeline = device.createRenderPipeline({
  vertext: {...},
  primitive: {
    // 绘制三角形
     
    // 或者绘制依次连线
    topology: "line-strip",
    // 每个顶点对应渲染的一个小点
    topology: "point-list"
  }
})

三、光栅化与片元着色器

光栅化:光栅化就是将顶点数据转化为片元的过程,具有将图转化为一个个栅格组成的图象的作用,特点是每个元素对应帧缓冲区中的一像素,片元中的每一个元素对应帧缓冲区的一个像素。光栅化的过程就是一种将几何图元转化为二维图像的过程

光栅化时WebGPU内部的操作,不需要进行代码编写

片元:图元经过光栅化阶段后,被分割成一个个像素大小的基本单位。片元包含了比RGBA更多的信息,比如可能有深度值,法线,纹理坐标等等信息。 片元需要在通过一些测试后才会最终成为像素

片元着色器与顶点着色器类似,都属于渲染管线上的功能单元,同样需要使用WGSL着色器代码,

片元着色器需要使用 @fragment进行标注

  • 通常通过片元着色器输出的片元数据会存储在显卡的内存上,需要使用location(0)进行返回值标注,可以理解为将输出片元数据存储在显卡内存上,并将存储位置标记为0,方便渲染管线后续的操作和处理
@fragment
fn main() -> @location(0) vec4<f32> {
  return vec4<f32>(1.0, 0.0, 0.0, 1.0); // 红色片元
}

把配置好的片元着色器代码块对象device.createShaderModule({ code: fragment })作为渲染管线参数fragment.module属性的值,将其嵌入渲染管线,并配置entryPoint设置入口函数

import { fragment } from './shader.js'
const pipeline = device.createRenderPipeline({
  //片元相关配置
  fragment: {
    module: device.createShaderModule({ code: fragment }),
    entryPoint: "main"
  },
});
  • 通过配置fragmemt.targets的format属性可以配置获取浏览器默认的颜色格式

最终渲染管线相关配置代码为:

const pipline = device.createRenderPipeline({
  layout: 'auto',
  vertex: {
    module: device.createShaderModule({ code: vertex }),
    entryPoint: 'main',
    buffers: [
      {
        arrayStride: 3 * 4,
        attribute: [
          {
            shaderLocation: 0,
            format: 'float32x3',
            offset: 0,
          },
        ],
      },
    ],
  },
  fragment: {
    module: device.createShaderModule({ code: fragment }),
    entryPoint: 'main',
    targets: [{
      format: format
    }]
  },
  // 光栅化
  primitive: {
    topology: 'triangle-list',
  },
});