你們好,本文學習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會被剔除)