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的服务端代码.具体步骤见下面. ...
随机推荐
- HTML模版大全网
HTML模版大全网,里面有一部分是后台管理的网站模版,HTML全都有.大家有需要的话,可以自行去下载. http://www.htmlmbdq.com
- vertx 异步编程指南 step7-保护和控制访问
保护和控制访问与Vert.x很容易.在本节中,我们将: 从HTTP转移到HTTPS,以及 使用基于组的权限将用户身份验证添加到Web应用程序,以及 使用JSON Web令牌(JWT)控制对Web AP ...
- 基于第三方vuejs库组件做适配性个性开发
相信大家在使用vuejs时候会用到很多的第三方库,能够找到适合自己的库并且加以使用可以大大加快进度,减少bug.但是很多时候会出现这样一个尴尬的境地: 基线的第三方组件并不能很好地满足我们自己地需求, ...
- 纯C语言实现线性表
#include <stdio.h> #include <stdlib.h> #define MAXSIZE 100 typedef int ElemType; typedef ...
- ES6 对象的拓展(三)
一.对象中的属性及方法1.属性属性简写:当对象属性名与属性值变量相同可以简写eg: let [name,age]=['nzc','18']; let obj = { name:name, age:ag ...
- mybatis中集成sharing-jdbc采坑
1. mybatis中集成sharing-jdbc采坑 1.1. 错误信息 Caused by: org.apache.ibatis.binding.BindingException: Invalid ...
- vue通过Blob实现下载文件
需求是这样的...... 具体实现,前端拿到后端返回回来的数据,然后通过Blob实现下载,文件内容样式啥的都是后端写的 script代码: 这里的data就是后端返回回来的数据,此方法兼容IE dow ...
- Zabbix监控多个JVM进程
一.场景说明: 我们这边的环境用的是微服务,每个程序都是有单独的进程及单独的端口号,但用jps查询出来的结果有些还会有重名的情况,所以某些脚本不太适用本场景: 二.需求说明: 需使用Zabbix- ...
- 枚举ENUM的tostring() valueof()name()和values()用法
从jdk5出现了枚举类后,定义一些字典值可以使用枚举类型; 枚举常用的方法是values():对枚举中的常量值进行遍历; valueof(String name) :根据名称获取枚举类中定义的常量值; ...
- system.exit(int status)中status值不同时的区别
status为0时为正常退出程序,也就是结束当前正在运行中的java虚拟机. status为非0的其他整数(包括负数,一般是1或者-1),表示非正常退出当前程序. 可以明确的是,无论status是什么 ...