WebGPU学习(七):学习“twoCubes”和“instancedCube”示例
大家好,本文学习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”示例的更多相关文章
- WCF学习之旅—WCF第二个示例(七)
三.创建客户端应用程序 若要创建客户端应用程序,你将另外添加一个项目,添加对该项目的服务引用,配置数据源,并创建一个用户界面以显示服务中的数据. 在第一个步骤中,你将 Windows 窗体项目添加到解 ...
- WCF学习之旅—第三个示例之四(三十)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) WCF学习之旅—第三个示例之三(二十九) ...
- WCF学习之旅—第三个示例之五(三十一)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) WCF学习之旅—第三个示例之三(二十九) WCF学习 ...
- WCF学习之旅—WCF第二个示例(五)
二.WCF服务端应用程序 第一步,创建WCF服务应用程序项目 打开Visual Studio 2015,在菜单上点击文件—>新建—>项目—>WCF服务应用程序.在弹出界面的“名称”对 ...
- WCF学习之旅—WCF第二个示例(六)
第五步,创建数据服务 在“解决方案资源管理器”中,使用鼠标左键选中“SCF.WcfService”项目,然后在菜单栏上,依次选择“项目”.“添加新项”. 在“添加新项”对话框中,选择“Web”节点,然 ...
- python3.4学习笔记(七) 学习网站博客推荐
python3.4学习笔记(七) 学习网站博客推荐 深入 Python 3http://sebug.net/paper/books/dive-into-python3/<深入 Python 3& ...
- (转)MyBatis框架的学习(七)——MyBatis逆向工程自动生成代码
http://blog.csdn.net/yerenyuan_pku/article/details/71909325 什么是逆向工程 MyBatis的一个主要的特点就是需要程序员自己编写sql,那么 ...
- WCF学习之旅—第三个示例之二(二十八)
上接WCF学习之旅—第三个示例之一(二十七) 五.在项目BookMgr.Model创建实体类数据 第一步,安装Entity Framework 1) 使用NuGet下载最新版的Entity Fram ...
- WCF学习之旅—第三个示例之三(二十九)
上接WCF学习之旅—第三个示例之一(二十七) WCF学习之旅—第三个示例之二(二十八) 在上一篇文章中我们创建了实体对象与接口协定,在这一篇文章中我们来学习如何创建WCF的服务端代码.具体步骤见下面. ...
随机推荐
- Blend 触发器
原文:Blend 触发器 介绍用定义触发器来控制视频 的 开始 暂停 继续 停止 触发器设置 效果
- C#中的System.Type和System.RuntimeType之间的区别
string str = string.Empty; Type strType = str.GetType(); Type strTypeType = strType.GetType(); strTy ...
- 活动任务出现bug
之前做的一个活动任务发现一个bug,是以前和离职同事一起对逻辑的时候没有考虑到的,配置活动的时候应该要先查询下,如果这个产品线上在这段时间已经配置了并且上线了,则不能在做活动处理了,否则就和前面的活动 ...
- android studio学习---怎么创建一个新的module并且再次运行起来(在当前的project里面)
选择File->new module出现的界面,选择android application选择下一步,就出现了和刚刚一样的流程了,一步步创建完成即可. 我们看到多了个secondAndroid的 ...
- 掌握 Async/Await
摘要: 还不用Async/Await就OUT了.. 原文:掌握 Async/Await 作者:Jartto Fundebug经授权转载,版权归原作者所有. 前端工程师肯定都经历过 JS 回调链狱的痛苦 ...
- Robot Framework常用关键字
虽然通过RIDE提供"填表"一样的写测试用例的方式.但它却支持强大的关键字功能,以及可以开发关键字的扩展能力. Comment 注释功能,也可以使用python中的"#& ...
- OAuth 2.0 的四种授权模式
RFC 6749 OAuth 2.0 的标准是 RFC 6749 文件.该文件先解释了 OAuth 是什么. OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者.......资源 ...
- centos6升级gcc版本
初始版本: 下面是CentOS6.6将gcc升级至4.8.2的过程: 安装仓库: #安装仓库 wget http://people.centos.org/tru/devtools-2/devtools ...
- CentOS6安装部署配置Nginx
操作系统: CentOS 6.9 脚本语言: shell https://github.com/iscongyang/Practical/blob/master/shell-scripts/insta ...
- 一文教你如何使用miniconda
安装python 之前安装python包,导致了python里面的包不兼容,用管理工具卸载也下载不掉,重新安装也安装不上,没有办法只能卸掉python重装. 安装Anaconda Anaconda指的 ...