大家好,本文学习Chrome->webgpu-samplers->twoCubes和instancedCube示例。

这两个示例都与“rotatingCube”示例差不多。建议大家先学习该示例,再学习本文的两个示例

上一篇博文:

WebGPU学习(六):学习“rotatingCube”示例

下一篇博文:

WebGPU学习(八):学习“texturedCube”示例

学习twoCubes.ts

该示例绘制了两个立方体。

与“rotatingCube”示例相比,该示例增加了以下的内容:

  • 一个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=320

  • 创建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。

最终渲染结果

学习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);
...
}

最终渲染结果

参考资料

WebGPU规范

webgpu-samplers Github Repo

WebGPU学习(七):学习“twoCubes”和“instancedCube”示例的更多相关文章

  1. WCF学习之旅—WCF第二个示例(七)

    三.创建客户端应用程序 若要创建客户端应用程序,你将另外添加一个项目,添加对该项目的服务引用,配置数据源,并创建一个用户界面以显示服务中的数据. 在第一个步骤中,你将 Windows 窗体项目添加到解 ...

  2. WCF学习之旅—第三个示例之四(三十)

           上接WCF学习之旅—第三个示例之一(二十七)               WCF学习之旅—第三个示例之二(二十八)              WCF学习之旅—第三个示例之三(二十九)   ...

  3. WCF学习之旅—第三个示例之五(三十一)

       上接WCF学习之旅—第三个示例之一(二十七)               WCF学习之旅—第三个示例之二(二十八)              WCF学习之旅—第三个示例之三(二十九) WCF学习 ...

  4. WCF学习之旅—WCF第二个示例(五)

    二.WCF服务端应用程序 第一步,创建WCF服务应用程序项目 打开Visual Studio 2015,在菜单上点击文件—>新建—>项目—>WCF服务应用程序.在弹出界面的“名称”对 ...

  5. WCF学习之旅—WCF第二个示例(六)

    第五步,创建数据服务 在“解决方案资源管理器”中,使用鼠标左键选中“SCF.WcfService”项目,然后在菜单栏上,依次选择“项目”.“添加新项”. 在“添加新项”对话框中,选择“Web”节点,然 ...

  6. python3.4学习笔记(七) 学习网站博客推荐

    python3.4学习笔记(七) 学习网站博客推荐 深入 Python 3http://sebug.net/paper/books/dive-into-python3/<深入 Python 3& ...

  7. (转)MyBatis框架的学习(七)——MyBatis逆向工程自动生成代码

    http://blog.csdn.net/yerenyuan_pku/article/details/71909325 什么是逆向工程 MyBatis的一个主要的特点就是需要程序员自己编写sql,那么 ...

  8. WCF学习之旅—第三个示例之二(二十八)

    上接WCF学习之旅—第三个示例之一(二十七) 五.在项目BookMgr.Model创建实体类数据 第一步,安装Entity Framework 1)  使用NuGet下载最新版的Entity Fram ...

  9. WCF学习之旅—第三个示例之三(二十九)

    上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) 在上一篇文章中我们创建了实体对象与接口协定,在这一篇文章中我们来学习如何创建WCF的服务端代码.具体步骤见下面. ...

随机推荐

  1. dotnet中文字符工具类

    支持繁体简体互换. using System; using System.Collections.Generic; using System.IO; using System.Linq; using ...

  2. 深入V8引擎-初始化之InitPlatform

    上一篇其实想讲初始化的第二步,但是内容比较无聊,所以换了一个话题,谈了谈v8的命名空间和宏,稍微轻松一下. 在这里还是接着说说初始化过程,毕竟写博客的初衷是对自己努力的记录,不是为了吸粉,这篇没图,对 ...

  3. 关于IDEA中web项目中web.xml配置文件标红的解决办法

    原文链接 https://blog.csdn.net/qq_33451695/article/details/86684127 解决方法前提:web.xml没有实际错误,但依然被web.xml标红 出 ...

  4. 【开发笔记】- Idea启动Gradle报错:Warning:Unable to make the module: reading, related gradle configuration was not found. Please, re-import the Gradle project and try again

    报错信息: Warning:Unable to make the module: reading, related gradle configuration was not found. Please ...

  5. MES助力伊利集团打造智慧工厂

    1.项目背景介绍 在国家政策和事业部.工厂的实际需求双重背景下,2016年7-9月期间,伊利集团信息部门.业务部门,先后与国内外领先的设备和咨询公司进行了智能制造.智慧工厂等话题的沟通交流,并组织实地 ...

  6. python快速生成验证码

    利用python库random,string生成大小写字母和数字的随机验证码 import random import string def generate_code(bit_num): ''' : ...

  7. 使用docker创建mongodb

    1.创建 MongoDB 数据卷 docker volume create mongo_data_yapi 2.启动 MongoDB docker run -d --name mongo-yapi - ...

  8. 快速生成mysql上百万条测试数据

    方案:编写一个存储过程循环添加数据 1. 创建表index_test DROP TABLE IF EXISTS index_test; CREATE TABLE index_test( id ) PR ...

  9. 主进程和服务进程通信调用Acrobat.AcroPDDoc时出现的问题

    场景以及问题 主进程发送命令,服务进程接受到,然后执行转换pdf,调用Acrobat pdfDoc = (Acrobat.CAcroPDDoc)Microsoft.VisualBasic.Intera ...

  10. 用Python玩转微信

    Python玩转微信 大家每天都在用微信,有没有想过用python来控制我们的微信,不多说,直接上干货!  这个是在 itchat上做的封装  http://itchat.readthedocs.io ...