你们好,本文学习Chrome->webgpu-samplers->rotatingCube示例。html
上一篇博文:
WebGPU学习(五): 现代图形API技术要点和WebGPU支持状况调研git
下一篇博文:
WebGPU学习(七):学习“twoCubes”和“instancedCube”示例github
咱们已经学习了“绘制三角形”的示例,与它相比,本示例增长了如下的内容:web
下面,咱们打开rotatingCube.ts文件,依次来看下新增内容:canvas
在WebGL 1中,咱们经过uniform1i,uniform4fv等函数传递每一个gameObject对应的uniform变量(如diffuseMap, diffuse color, model matrix等)到shader中。
其中不少相同的值是不须要被传递的,举例以下:
若是gameObject1和gameObject3使用同一个shader1,它们的diffuse color相同,那么只须要传递其中的一个diffuse color,而在WebGL 1中咱们通常把这两个diffuse color都传递了,形成了重复的开销。数组
WebGPU使用uniform buffer object来传递uniform变量。uniform buffer是一个全局的buffer,咱们只须要设置一次值,而后在每次draw以前,设置使用的数据范围(经过offset, size来设置),从而复用相同的数据。若是uniform值有变化,则只须要修改uniform buffer对应的数据。app
在WebGPU中,咱们能够把全部gameObject的model矩阵设为一个ubo,全部相机的view和projection矩阵设为一个ubo,每一种material(如phong material,pbr material等)的数据(如diffuse color,specular color等)设为一个ubo,每一种light(如direction light、point light等)的数据(如light color、light position等)设为一个ubo,这样能够有效减小uniform变量的传输开销。less
另外,咱们须要注意ubo的内存布局:
默认的布局为std140,咱们能够粗略地理解为,它约定了每一列都有4个元素。
咱们来举例说明:
下面的ubo对应的uniform block,定义布局为std140:ide
layout (std140) uniform ExampleBlock { float value; vec3 vector; mat4 matrix; float values[3]; bool boolean; int integer; };
它在内存中的实际布局为:函数
layout (std140) uniform ExampleBlock { // base alignment // aligned offset float value; // 4 // 0 vec3 vector; // 16 // 16 (must be multiple of 16 so 4->16) mat4 matrix; // 16 // 32 (column 0) // 16 // 48 (column 1) // 16 // 64 (column 2) // 16 // 80 (column 3) float values[3]; // 16 // 96 (values[0]) // 16 // 112 (values[1]) // 16 // 128 (values[2]) bool boolean; // 4 // 144 int integer; // 4 // 148 };
也就是说,这个ubo的第一个元素为value,第2-4个元素为0(为了对齐);
第5-7个元素为vector的x、y、z的值,第8个元素为0;
第9-24个元素为matrix的值(列优先);
第25-27个元素为values数组的值,第28个元素为0;
第29个元素为boolean转为float的值,第30-32个元素为0;
第33个元素为integer转为float的值,第34-36个元素为0。
代码以下:
const vertexShaderGLSL = `#version 450 layout(set = 0, binding = 0) uniform Uniforms { mat4 modelViewProjectionMatrix; } uniforms; ... void main() { gl_Position = uniforms.modelViewProjectionMatrix * position; fragColor = color; } `;
布局为默认的std140,指定了set和binding,包含一个mvp矩阵
代码以下:
const uniformsBindGroupLayout = device.createBindGroupLayout({ bindings: [{ binding: 0, visibility: 1, type: "uniform-buffer" }] });
visibility为GPUShaderStage.VERTEX(等于1),指定type为“uniform-buffer”
代码以下:
const uniformBufferSize = 4 * 16; // BYTES_PER_ELEMENT(4) * matrix length(4 * 4 = 16) const uniformBuffer = device.createBuffer({ size: uniformBufferSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, });
代码以下:
const uniformBindGroup = device.createBindGroup({ layout: uniformsBindGroupLayout, bindings: [{ binding: 0, resource: { buffer: uniformBuffer, }, }], });
代码以下:
//由于是固定相机,因此只须要计算一次projection矩阵 const aspect = Math.abs(canvas.width / canvas.height); let projectionMatrix = mat4.create(); mat4.perspective(projectionMatrix, (2 * Math.PI) / 5, aspect, 1, 100.0); ... //计算mvp矩阵 function getTransformationMatrix() { let viewMatrix = mat4.create(); mat4.translate(viewMatrix, viewMatrix, vec3.fromValues(0, 0, -5)); let now = Date.now() / 1000; mat4.rotate(viewMatrix, viewMatrix, 1, vec3.fromValues(Math.sin(now), Math.cos(now), 0)); let modelViewProjectionMatrix = mat4.create(); mat4.multiply(modelViewProjectionMatrix, projectionMatrix, viewMatrix); return modelViewProjectionMatrix; } ... return function frame() { uniformBuffer.setSubData(0, getTransformationMatrix()); ... }
代码以下:
return function frame() { ... passEncoder.setBindGroup(0, uniformBindGroup); passEncoder.draw(36, 1, 0, 0); ... }
本示例使用setSubData来更新uniform buffer:
return function frame() { uniformBuffer.setSubData(0, getTransformationMatrix()); ... }
咱们在WebGPU学习(五): 现代图形API技术要点和WebGPU支持状况调研->Approaching zero driver overhead->persistent map buffer中,提到了WebGPU目前有两种方法实现“CPU把数据传输到GPU“,即更新GPUBuffer的值:
1.调用GPUBuffer->setSubData方法
2.使用persistent map buffer技术
咱们看下如何在本示例中使用第2种方法:
function setBufferDataByPersistentMapBuffer(device, commandEncoder, uniformBufferSize, uniformBuffer, mvpMatricesData) { const [srcBuffer, arrayBuffer] = device.createBufferMapped({ size: uniformBufferSize, usage: GPUBufferUsage.COPY_SRC }); new Float32Array(arrayBuffer).set(mvpMatricesData); srcBuffer.unmap(); commandEncoder.copyBufferToBuffer(srcBuffer, 0, uniformBuffer, 0, uniformBufferSize); const commandBuffer = commandEncoder.finish(); const queue = device.defaultQueue; queue.submit([commandBuffer]); srcBuffer.destroy(); } return function frame() { //uniformBuffer.setSubData(0, getTransformationMatrix()); ... const commandEncoder = device.createCommandEncoder({}); setBufferDataByPersistentMapBuffer(device, commandEncoder, uniformBufferSize, uniformBuffer, getTransformationMatrix()); ... }
为了验证性能,我作了benchmark测试,建立一个ubo,包含160000个mat4,进行js profile:
使用setSubData(调用setBufferDataBySetSubData函数):
setSubData占91.54%
使用persistent map buffer(调用setBufferDataByPersistentMapBuffer函数):
createBufferMapped和setBufferDataByPersistentMapBuffer占72.72+18.06=90.78%
能够看到两个的性能差很少。但考虑到persistent map buffer从实现原理上要更快(cpu和gpu共用一个buffer,不须要copy),所以应该优先使用该方法。
另外,WebGPU社区如今还在讨论如何优化更新buffer数据(若有人提出增长GPUUploadBuffer pass),所以咱们还须要继续关注该方面的进展。
Advanced-GLSL->Uniform buffer objects
代码以下:
const vertexShaderGLSL = `#version 450 ... layout(location = 0) in vec4 position; layout(location = 1) in vec4 color; layout(location = 0) out vec4 fragColor; void main() { gl_Position = uniforms.modelViewProjectionMatrix * position; fragColor = color; } const fragmentShaderGLSL = `#version 450 layout(location = 0) in vec4 fragColor; layout(location = 0) out vec4 outColor; void main() { outColor = fragColor; } `;
这里设置color为fragColor(out,至关于WebGL 1的varying变量),而后在fragment shader中接收fragColor,将其设置为outColor,从而将fragment的color设置为对应顶点的color
代码以下:
cube.ts: //每一个顶点包含position,color,uv数据 export const cubeVertexArray = new Float32Array([ // float4 position, float4 color, float2 uv, 1, -1, 1, 1, 1, 0, 1, 1, 1, 1, -1, -1, 1, 1, 0, 0, 1, 1, 0, 1, -1, -1, -1, 1, 0, 0, 0, 1, 0, 0, 1, -1, -1, 1, 1, 0, 0, 1, 1, 0, 1, -1, 1, 1, 1, 0, 1, 1, 1, 1, -1, -1, -1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 0, 1, 1, 0, 1, 1, -1, -1, 1, 1, 0, 0, 1, 0, 0, 1, 1, -1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 0, 0, 1, 0, 0, -1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, -1, 1, 1, 1, 0, 1, 0, 0, -1, 1, -1, 1, 0, 1, 0, 1, 1, 0, -1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 0, 1, 0, 0, -1, -1, 1, 1, 0, 0, 1, 1, 1, 1, -1, 1, 1, 1, 0, 1, 1, 1, 0, 1, -1, 1, -1, 1, 0, 1, 0, 1, 0, 0, -1, -1, -1, 1, 0, 0, 0, 1, 1, 0, -1, -1, 1, 1, 0, 0, 1, 1, 1, 1, -1, 1, -1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 0, 1, 1, 1, 0, 1, -1, -1, 1, 1, 0, 0, 1, 1, 0, 0, -1, -1, 1, 1, 0, 0, 1, 1, 0, 0, 1, -1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 0, 0, 1, 1, 1, -1, -1, -1, 1, 0, 0, 0, 1, 0, 1, -1, 1, -1, 1, 0, 1, 0, 1, 0, 0, 1, 1, -1, 1, 1, 1, 0, 1, 1, 0, 1, -1, -1, 1, 1, 0, 0, 1, 1, 1, -1, 1, -1, 1, 0, 1, 0, 1, 0, 0, ]);
rotatingCube.ts: const verticesBuffer = device.createBuffer({ size: cubeVertexArray.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST }); verticesBuffer.setSubData(0, cubeVertexArray);
由于只须要设置一次顶点数据,因此这里可使用setSubData来设置,对性能影响不大
代码以下:
cube.ts: export const cubeVertexSize = 4 * 10; // Byte size of one cube vertex. export const cubePositionOffset = 0; export const cubeColorOffset = 4 * 4; // Byte offset of cube vertex color attribute.
rotatingCube.ts: const pipeline = device.createRenderPipeline({ ... vertexState: { vertexBuffers: [{ arrayStride: cubeVertexSize, attributes: [{ // position shaderLocation: 0, offset: cubePositionOffset, format: "float4" }, { // color shaderLocation: 1, offset: cubeColorOffset, format: "float4" }] }], }, ... });
代码以下:
return function frame() { ... const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); ... passEncoder.draw(36, 1, 0, 0); passEncoder.endPass(); ... }
相关代码为:
const pipeline = device.createRenderPipeline({ ... rasterizationState: { cullMode: 'back', }, ... });
相关的定义为:
enum GPUFrontFace { "ccw", "cw" }; enum GPUCullMode { "none", "front", "back" }; ... dictionary GPURasterizationStateDescriptor { GPUFrontFace frontFace = "ccw"; GPUCullMode cullMode = "none"; ... };
其中ccw表示逆时针,cw表示顺时针。
由于本示例设置了cullMode为back,没有设置frontFace(frontFace为默认的ccw),因此WebGPU会把逆时针方向设为外侧,把全部背面的三角形(顶点链接方向为内侧,即顺时针方向的三角形)剔除掉
[WebGL入门]六,顶点和多边形
Investigation: Rasterization State
如今分析相关代码,并忽略与模版测试相关的代码:
代码以下:
const pipeline = device.createRenderPipeline({ ... depthStencilState: { //开启深度测试 depthWriteEnabled: true, //设置比较函数为less,后面会继续说明 depthCompare: "less", //设置depth为24bit format: "depth24plus-stencil8", }, ... });
代码以下:
const depthTexture = device.createTexture({ size: { width: canvas.width, height: canvas.height, depth: 1 }, format: "depth24plus-stencil8", usage: GPUTextureUsage.OUTPUT_ATTACHMENT }); const renderPassDescriptor: GPURenderPassDescriptor = { ... depthStencilAttachment: { attachment: depthTexture.createView(), depthLoadValue: 1.0, depthStoreOp: "store", ... } };
其中,depthStencilAttachment的定义为:
dictionary GPURenderPassDepthStencilAttachmentDescriptor { required GPUTextureView attachment; required (GPULoadOp or float) depthLoadValue; required GPUStoreOp depthStoreOp; ... };
depthLoadValue和depthStoreOp与WebGPU学习(二): 学习“绘制一个三角形”示例->分析render pass->colorAttachment的loadOp和StoreOp相似,咱们直接分析本示例的相关代码:
const pipeline = device.createRenderPipeline({ ... depthStencilState: { ... depthCompare: "less", ... }, ... }); ... const renderPassDescriptor: GPURenderPassDescriptor = { ... depthStencilAttachment: { ... depthLoadValue: 1.0, depthStoreOp: "store", ... } };
在深度测试时,gpu会将fragment的z值(范围为[0.0-1.0])与这里设置的depthLoadValue值(这里为1.0)比较。其中比较的函数使用depthCompare定义的函数(这里为less,意思是全部z值大于等于1.0的fragment会被剔除)