大家好,本文学习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. 深入V8引擎-初始化之InitPlatform

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

  2. asp.net webapi 随笔

    第一次写博客,文笔有限,记录下学习的过程 话不多说,直接开干 首先用vs2017建立一个空网站项目,然后只勾选api 项目建立后,如下结构 其中WebApiConfig类配置了路由相关信息 publi ...

  3. git 命令从入门到放弃

    o(︶︿︶)o  由于项目使用 git 作为版本控制工具,自己可以进行一些常用操作但是有时候还是会忘掉,导致每次遇到 git 命令的使用问题时就要再查一遍,效率就立马降下来了,所以今天就来一个从头到尾 ...

  4. Power BI连接Oracle的注意事项

    开始 Power BI 连接Oracle需要安装对应位数的ODAC,这个过程中有几个点要注意. ODAC 12c.x 版本(32.64),在安装时要将GAC的勾搭上.否则打开Power BI时会提示找 ...

  5. Markdown温故知新(1):Markdown面面观

    1.什么是 Markdown? 2.有哪些人在用 Markdown? 3.用 Markdown 的优势是什么? 4.Markdown 的语法标准简介 5.怎么用 Markdown? 6.如何选择 Ma ...

  6. datatable转layui表格【偏原理】

    如题这个类负责把datatable转换为layui表格可以显示的内容.适合配合表格url字段的webapi服务端,为其返回响应字符串.代码如下:using System;using System.We ...

  7. 【翻译】Tusdotnet中文文档(3)自定义功能和相关技术

    自定义功能和相关技术 本篇按照如下结构翻译 自定义功能 自定义数据仓库 相关技术 架构和总体概念 自定义数据仓库 tusdotnet附带一个存储库TusDiskStore,它将文件保存在磁盘上的一个目 ...

  8. 【译】使用WebDriver采样器将JMeter与Selenium集成

    原为地址:https://dev.to/raghwendrasonu/jmeter-integration-with-selenium-using-webdriver-sampler-176k 第一步 ...

  9. js 实现watch监听数据变化

    1.js /** * @desc 属性改变监听,属性被set时出发watch的方法,类似vue的watch * @author Jason * @study https://www.jianshu.c ...

  10. vue-quill-editor富文本编辑器 中文翻译组件,编辑与展示

    vue项目中用到了富文本编辑器,网上找了一些,觉得vue-quill-editor最好用, ui简洁,功能也好配,够用了,文档不好读,有些小细节需要自己注意,我懒得分析,就封装成了组件 大家用的时候直 ...