WebGPU學習(七):學習「twoCubes」和「instancedCube」示例

你們好,本文學習Chrome->webgpu-samplers->twoCubes和instancedCube示例。html

這兩個示例都與「rotatingCube」示例差很少。建議你們先學習該示例,再學習本文的兩個示例git

上一篇博文:
WebGPU學習(六):學習「rotatingCube」示例github

學習twoCubes.ts

該示例繪製了兩個立方體。web

與「rotatingCube」示例相比,該示例增長了如下的內容:canvas

  • 一個ubo保存兩個立方體的mvp矩陣
  • 每幀更新兩個mvp矩陣數據
  • draw兩次,分別設置對應的uniformBindGroup

下面,咱們打開twoCubes.ts文件,依次來看下新增內容:數組

一個ubo保存兩個立方體的mvp矩陣

  • vertex shader定義uniform block

由於只有一個ubo,因此只有一個uniform block,代碼與rotatingCube示例相同:函數

const vertexShaderGLSL = `#version 450
  layout(set = 0, binding = 0) uniform Uniforms {
    mat4 modelViewProjectionMatrix;
  } uniforms;
  ...
  void main() {
    gl_Position = uniforms.modelViewProjectionMatrix * position;
    ...
  }
  `;
  • 建立uniform buffer

代碼以下:佈局

const matrixSize = 4 * 16; // BYTES_PER_ELEMENT(4) * matrix length(4 * 4 = 16)
  const offset = 256; // uniformBindGroup offset must be 256-byte aligned
  const uniformBufferSize = offset + matrixSize;

  const uniformBuffer = device.createBuffer({
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  });

uniform buffer要保存兩個mvp矩陣的數據,可是它們不能連續存放,它們的起始位置必須爲256的倍數,因此uniform buffer實際的內存佈局爲:
0-63:第一個mvp矩陣
64-255:0(佔位)
256-319:第二個mvp矩陣學習

uniform buffer的size爲256+64=320code

  • 建立uniform bind group

建立兩個uniform bind group,經過指定offset和size,對應到同一個uniform buffer:

const uniformBindGroup1 = device.createBindGroup({
    layout: uniformsBindGroupLayout,
    bindings: [{
      binding: 0,
      resource: {
        buffer: uniformBuffer,
        offset: 0,
        size: matrixSize
      }
    }],
  });

  const uniformBindGroup2 = device.createBindGroup({
    layout: uniformsBindGroupLayout,
    bindings: [{
      binding: 0,
      resource: {
        buffer: uniformBuffer,
        offset: offset,
        size: matrixSize
      }
    }]
  });
  • 建立2個mvp矩陣

代碼以下:

//由於是固定相機,因此只須要計算一次projection矩陣
  const aspect = Math.abs(canvas.width / canvas.height);
  let projectionMatrix = mat4.create();
  mat4.perspective(projectionMatrix, (2 * Math.PI) / 5, aspect, 1, 100.0);
  ...
  
  let modelMatrix1 = mat4.create();
  mat4.translate(modelMatrix1, modelMatrix1, vec3.fromValues(-2, 0, 0));
  let modelMatrix2 = mat4.create();
  mat4.translate(modelMatrix2, modelMatrix2, vec3.fromValues(2, 0, 0));
  //建立兩個mvp矩陣
  let modelViewProjectionMatrix1 = mat4.create();
  let modelViewProjectionMatrix2 = mat4.create();
  //由於是固定相機,因此只須要計算一次view矩陣
  let viewMatrix = mat4.create();
  mat4.translate(viewMatrix, viewMatrix, vec3.fromValues(0, 0, -7));

  let tmpMat41 = mat4.create();
  let tmpMat42 = mat4.create();

每幀更新兩個mvp矩陣數據

相關代碼以下所示:

function updateTransformationMatrix() {
    let now = Date.now() / 1000;

    mat4.rotate(tmpMat41, modelMatrix1, 1, vec3.fromValues(Math.sin(now), Math.cos(now), 0));
    mat4.rotate(tmpMat42, modelMatrix2, 1, vec3.fromValues(Math.cos(now), Math.sin(now), 0));

    mat4.multiply(modelViewProjectionMatrix1, viewMatrix, tmpMat41);
    mat4.multiply(modelViewProjectionMatrix1, projectionMatrix, modelViewProjectionMatrix1);
    mat4.multiply(modelViewProjectionMatrix2, viewMatrix, tmpMat42);
    mat4.multiply(modelViewProjectionMatrix2, projectionMatrix, modelViewProjectionMatrix2);
  }

  return function frame() {
    updateTransformationMatrix();

    ...

    uniformBuffer.setSubData(0, modelViewProjectionMatrix1);
    uniformBuffer.setSubData(offset, modelViewProjectionMatrix2);
    ...
  }

updateTransformationMatrix函數更新兩個mvp矩陣;
調用兩次setSubData,分別將更新後的mvp矩陣數據更新到同一個uniform buffer中。

draw兩次,分別設置對應的uniformBindGroup

代碼以下:

return function frame() {
    ...
    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    ...

    passEncoder.setBindGroup(0, uniformBindGroup1);
    passEncoder.draw(36, 1, 0, 0);

    passEncoder.setBindGroup(0, uniformBindGroup2);
    passEncoder.draw(36, 1, 0, 0);

    passEncoder.endPass();

    ...
  }

第一次draw,繪製第一個cube,設置對應的uniformBindGroup1;
第二次draw,繪製第二個cube,設置對應的uniformBindGroup2。

最終渲染結果

截屏2019-12-22下午4.18.16.png-55kB

學習instancedCube.ts

該示例使用instance技術,經過一次draw,繪製了多個立方體實例。

與「rotatingCube」示例相比,該示例增長了如下的內容:

  • 一個ubo保存全部立方體實例的mvp矩陣
  • 每幀更新全部立方體實例的mvp矩陣數據
  • 指定實例個數,draw一次

下面,咱們打開instancedCube.ts文件,依次來看下新增內容:

一個ubo保存全部立方體實例的mvp矩陣

  • vertex shader定義uniform block

代碼以下:

const vertexShaderGLSL = `#version 450
  //總共16個實例
  #define MAX_NUM_INSTANCES 16
  layout(set = 0, binding = 0) uniform Uniforms {
    //ubo包含mvp矩陣數組,數組長度爲16
    mat4 modelViewProjectionMatrix[MAX_NUM_INSTANCES];
  } uniforms;
  layout(location = 0) in vec4 position;
  layout(location = 1) in vec4 color;
  ...
  void main() {
    //使用gl_InstanceIndex取到當前實例的序號(0-15),經過它獲取對應的mvp矩陣
    gl_Position = uniforms.modelViewProjectionMatrix[gl_InstanceIndex] * position;
    ...
  }`;
  • 建立uniform buffer

代碼以下:

//16個立方體的排列順序是x方向4個、y方向4個
  const xCount = 4;
  const yCount = 4;
  const numInstances = xCount * yCount;
  const matrixFloatCount = 16;
  // BYTES_PER_ELEMENT(4) * matrix length(4 * 4 = 16)
  const matrixSize = 4 * matrixFloatCount;
  const uniformBufferSize = numInstances * matrixSize;

  const uniformBuffer = device.createBuffer({
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  });

這裏與twoCubes不一樣的是,不一樣實例的mvp矩陣的數據是連續存放的,因此uniform buffer的size爲numInstances(16個)* matrixSize。

  • 建立uniform bind group

只建立一個:

const uniformBindGroup = device.createBindGroup({
    layout: uniformsBindGroupLayout,
    bindings: [{
      binding: 0,
      resource: {
        buffer: uniformBuffer,
      }
    }],
  });
  • 準備mvp矩陣數據

代碼以下:

//由於是固定相機,因此只須要計算一次projection矩陣
  const aspect = Math.abs(canvas.width / canvas.height);
  let projectionMatrix = mat4.create();
  mat4.perspective(projectionMatrix, (2 * Math.PI) / 5, aspect, 1, 100.0);
  ...
  
  
  
  let modelMatrices = new Array(numInstances);
  //mvpMatricesData用來依次存放全部立方體實例的mvp矩陣數據
  let mvpMatricesData = new Float32Array(matrixFloatCount * numInstances);

  let step = 4.0;

  let m = 0;
  //準備modelMatrices數據
  for (let x = 0; x < xCount; x++) {
    for (let y = 0; y < yCount; y++) {
      modelMatrices[m] = mat4.create();
      mat4.translate(modelMatrices[m], modelMatrices[m], vec3.fromValues(
        step * (x - xCount / 2 + 0.5),
        step * (y - yCount / 2 + 0.5),
        0
      ));
      m++;
    }
  }

  //由於是固定相機,因此只須要計算一次view矩陣
  let viewMatrix = mat4.create();
  mat4.translate(viewMatrix, viewMatrix, vec3.fromValues(0, 0, -12));

  let tmpMat4 = mat4.create();

每幀更新全部立方體實例的mvp矩陣數據

相關代碼以下所示:

function updateTransformationMatrix() {

    let now = Date.now() / 1000;

    let m = 0, i = 0;
    for (let x = 0; x < xCount; x++) {
      for (let y = 0; y < yCount; y++) {
        mat4.rotate(tmpMat4, modelMatrices[i], 1, vec3.fromValues(Math.sin((x + 0.5) * now), Math.cos((y + 0.5) * now), 0));

        mat4.multiply(tmpMat4, viewMatrix, tmpMat4);
        mat4.multiply(tmpMat4, projectionMatrix, tmpMat4);

        mvpMatricesData.set(tmpMat4, m);

        i++;
        m += matrixFloatCount;
      }
    }
  }

  return function frame() {
    updateTransformationMatrix();
    ...
    uniformBuffer.setSubData(0, mvpMatricesData);
    ...
  }

updateTransformationMatrix函數更新mvpMatricesData;
調用一次setSubData,將更新後的mvpMatricesData設置到uniform buffer中。

指定實例個數,draw一次

代碼以下:

return function frame() {
    ...
    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    ...


    //設置對應的uniformBindGroup
    passEncoder.setBindGroup(0, uniformBindGroup);
    //指定實例個數爲numInstances
    passEncoder.draw(36, numInstances, 0, 0);
    ...
  }

最終渲染結果

截屏2019-12-22下午4.18.23.png-143.2kB

參考資料

WebGPU規範
webgpu-samplers Github Repo

相關文章
相關標籤/搜索