WebGPU[4] 紋理三角形

秋意正寒發表於2021-04-05

程式碼見:https://github.com/onsummer/my-dev-notes/tree/master/webgpu-Notes/04-texture-triangle
原創,釋出日 2021年4月5日,@秋意正寒。若程式碼失效請留言,或自行到官網根據最新 API 修改。

image

資料預覽

NDC 座標和 紋理座標(原點、朝向、值域)基礎不再補充。

0.1 VBO

const vbodata = new Float32Array([
  -1.0, -1.0, 0.0, 1.0,
  0.0, 1.0, 0.5, 0.0,
  1.0, -1.0, 1.0, 1.0
])

0.2 貼圖

一張 256 x 256 的 png 貼圖,jpg 和 webp 沒測試。

image

1 紋理與取樣器

使用取樣器在紋理貼圖上,通過紋理座標取頂點的顏色值,這是一個很常規的操作。

1.1 建立取樣器、紋理

/* create sampler */
const sampler = device.createSampler({
  minFilter: "linear",
  magFilter: "linear"
})

/* --- create img source --- */
const img = document.createElement('img')
img.src = 'texture.png'
// 用 await 代表這個操作必須在 async 函式中,或者在 html 中提前做好 img 標籤並載入紋理貼圖
await img.decode()
const imageBitmap = await createImageBitmap(img)

/* create texture */
const texture = device.createTexture({
  size: [img.width, img.height], // 256, 256 -> 2d的紋理
  format: "rgba8unorm",
  usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST
})

1.2 將紋理貼圖資料 填入 紋理物件

我在文件中看到有兩個方法,一個是 GPUQueue.prototype.writeToTexture(),另一個是下面這個 GPUQueue.prototype.copyImageBitmapToTexture()

device.queue.copyImageBitmapToTexture(
  { imageBitmap: imageBitmap },
  { texture: texture },
  [img.width, img.height, 1]
)

至此,取樣器、紋理物件都準備好了,紋理資料也寫入紋理物件中了。

2 將紋理物件、取樣器物件傳進流水線(Pipeline)

要向渲染通道傳遞紋理和取樣器,必須建立一個 “Pipeline佈局” 物件。
這個佈局物件要對紋理物件、取樣器物件進行繫結。在 WebGPU 中,將諸如 uniform變數、取樣器、紋理物件 等資源統一打組,這個組叫 GPUBindGroup
它們的關係圖大概是這樣的:

image

上程式碼:

/* 建立繫結組的佈局物件 */
const bindGroupLayout = device.createBindGroupLayout({
  entries: [
    {
      /* use for sampler */
      binding: 0,
      visibility: GPUShaderStage.FRAGMENT,
      sampler: {
        type: 'filtering',
      },
    },
    {
      /* use for texture view */
      binding: 1,
      visibility: GPUShaderStage.FRAGMENT,
      texture: {
        sampleType: 'float'
      }
    }
  ]
})
/* 建立 Pipeline 的佈局物件 */
const pipelineLayout = device.createPipelineLayout({
  bindGroupLayouts: [bindGroupLayout],
})
/* 建立 pipeline 時,傳遞 Pipeline 的佈局物件 */
const pipeline = device.createRenderPipeline({
  layout: pipelineLayout, // <- 傳遞佈局物件
  // ... 其他
})
/* 建立繫結組:GPUBindGroup,一組資源 */
const uniformBindGroup = device.createBindGroup({
  layout: pipeline.getBindGroupLayout(0), // <- 指定繫結組的佈局物件
  entries: [
    {
      binding: 0,
      resource: sampler, // <- 傳入取樣器物件
    },
    {
      binding: 1,
      resource: texture.createView() // <- 傳入紋理物件的檢視
    }
  ]
})

3 修改著色器

光將取樣器、紋理物件傳入流水線還是不夠的,在頂點著色器階段、片元著色器階段,仍需要把顏色給弄到。

3.1 頂點著色器

[[builtin(position)]] var<out> out_position: vec4<f32>;
[[location(0)]] var<out> out_st: vec2<f32>; // <- 輸出紋理座標到下一階段,即片元著色器

[[location(0)]] var<in> in_position_2d: vec2<f32>;
[[location(1)]] var<in> in_st: vec2<f32>; // <- 從 GPUBuffer 中獲取的紋理座標

[[stage(vertex)]]
// 注意這個主函式被改為 vertex_main,在建立流水線時,要改 entryPoint 屬性為 'frag_main'
fn vertex_main() -> void {
  out_position = vec4<f32>(in_position_2d, 0.0, 1.0);
  out_uv = in_st;
  return;
}

3.2 片元著色器

// 從繫結組i裡取繫結的資源j的語法是 [[binding(j), group(i)]]
[[binding(0), group(0)]] var mySampler: sampler; // <- 取樣器物件
[[binding(1), group(0)]] var myTexture: texture_2d<f32>; // <- 紋理物件

[[location(0)]] var<out> outColor: vec4<f32>;

[[location(0)]] var<in> in_st: vec2<f32>; // 從頂點著色器裡傳遞進來的紋理座標

[[stage(fragment)]]
// 注意這個主函式被改為 frag_main,在建立流水線時,要改 entryPoint 屬性為 'frag_main'
fn frag_main() -> void {
  outColor = textureSample(myTexture, mySampler, in_st); // 使用 textureSample 內建函式獲取對應紋理座標的顏色
  return;
}

4 渲染通道編碼器傳入資源

即把繫結組傳入物件傳入即可。

passEncoder.setBindGroup(0, uniformBindGroup)

踩坑點

不要設定流水線中 primitive 屬性的 cullMode 屬性值為 "back",否則會背面剔除。

const pipeline = device.createRenderPipeline({
  // ...
  primitive: {
    topology: 'triangle-list',
    cullMode: 'back', // <- 如果設為 back 三角形就不見了
  }
  // ...
})

相關文章