From b330ec202929d37d39a10d705cbbce5c3404f600 Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Tue, 14 May 2024 23:58:59 +0900 Subject: [PATCH] reverse-z example --- README.md | 1 + examples/reverse-z.html | 44 ++++++ examples/reverse-z.js | 306 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 examples/reverse-z.html create mode 100644 examples/reverse-z.js diff --git a/README.md b/README.md index c51b3c3..afe66e7 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,7 @@ import { createTextureFromImage } from 'webgpu-utils'; * [cube-map](examples/cube-map.html) * [instancing](examples/instancing.html) * [primitives](examples/primitives.html) +* [reverse-z](examples/reverse-z.html) * [stencil](examples/stencil.html) * [stencil-cube](examples/stencil-cube.html) diff --git a/examples/reverse-z.html b/examples/reverse-z.html new file mode 100644 index 0000000..bec9fc8 --- /dev/null +++ b/examples/reverse-z.html @@ -0,0 +1,44 @@ + + + + webgpu-utils - reverse-z + + + + + + +
+
+
reverse-z perspective projection infinite z
+
+
standard perspective
+
+
+
webgpu-utils - primitives reverse-z
+ + + + \ No newline at end of file diff --git a/examples/reverse-z.js b/examples/reverse-z.js new file mode 100644 index 0000000..f713429 --- /dev/null +++ b/examples/reverse-z.js @@ -0,0 +1,306 @@ +/* global GPUBufferUsage */ +/* global GPUTextureUsage */ +import { mat4, vec3 } from 'https://wgpu-matrix.org/dist/2.x/wgpu-matrix.module.js'; +import * as wgh from '../dist/1.x/webgpu-utils.module.js'; + +async function main() { + const adapter = await navigator.gpu?.requestAdapter(); + const device = await adapter?.requestDevice(); + if (!device) { + fail('need a browser that supports WebGPU'); + return; + } + + const canvas = document.querySelector('canvas'); + const context = canvas.getContext('webgpu'); + const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + context.configure({ + device, + format: presentationFormat, + alphaMode: 'premultiplied', + }); + + const code = ` + struct Uniforms { + world: mat4x4f, + color: vec4f, + }; + + struct SharedUniforms { + viewProjection: mat4x4f, + lightDirection: vec3f, + }; + + @group(0) @binding(0) var uni: Uniforms; + @group(1) @binding(0) var sharedUni: SharedUniforms; + + struct MyVSInput { + @location(0) position: vec4f, + @location(1) normal: vec3f, + @location(2) texcoord: vec2f, + }; + + struct MyVSOutput { + @builtin(position) position: vec4f, + @location(0) normal: vec3f, + @location(1) texcoord: vec2f, + }; + + @vertex + fn myVSMain(v: MyVSInput) -> MyVSOutput { + var vsOut: MyVSOutput; + vsOut.position = sharedUni.viewProjection * uni.world * v.position; + vsOut.normal = (uni.world * vec4f(v.normal, 0.0)).xyz; + vsOut.texcoord = v.texcoord; + return vsOut; + } + + @fragment + fn myFSMain(v: MyVSOutput) -> @location(0) vec4f { + let diffuseColor = uni.color; + let a_normal = normalize(v.normal); + let l = dot(a_normal, sharedUni.lightDirection) * 0.5 + 0.5; + return vec4f(diffuseColor.rgb * l, diffuseColor.a); + } + `; + const defs = wgh.makeShaderDataDefinitions(code); + + const descriptors = wgh.makeBindGroupLayoutDescriptors(defs, {vertex: {}, fragment: {}}); + const bindGroupLayouts = descriptors.map(desc => device.createBindGroupLayout(desc)); + const layout = device.createPipelineLayout({ bindGroupLayouts }); + + const numInstances = 50; + const geometries = [ + wgh.createBuffersAndAttributesFromArrays(device, wgh.primitives.createSphereVertices({subdivisionsAxis: 64, subdivisionsHeight: 32, radius: 0.5 })), + wgh.createBuffersAndAttributesFromArrays(device, wgh.primitives.createCubeVertices()), + ]; + + function r(min, max) { + if (typeof max === 'undefined') { + max = min; + min = 0; + } + return Math.random() * (max - min) + min; + } + + const module = device.createShaderModule({code}); + + const pipelinesAndRenderPassDescriptors = [ + { + depthCompare: 'greater', + depthClearValue: 0, + perspective: (f, a, n) => mat4.perspectiveReverseZ(f, a, n), + clearValue: [ 0.2, 0.2, 0.2, 1.0 ], + loadOp: 'clear', + }, + { + depthCompare: 'less', + depthClearValue: 1, + perspective: mat4.perspective, + loadOp: 'load', + }, + ].map(settings => { + const pipeline = device.createRenderPipeline({ + layout, + vertex: { + module, + entryPoint: 'myVSMain', + buffers: [ + ...geometries[0].bufferLayouts, + ], + }, + fragment: { + module, + entryPoint: 'myFSMain', + targets: [ + {format: presentationFormat}, + ], + }, + primitive: { + topology: 'triangle-list', + cullMode: 'back', + }, + depthStencil: { + depthWriteEnabled: true, + ...settings, + format: 'depth32float', + }, + }); + + const renderPassDescriptor = { + colorAttachments: [ + { + // view: undefined, // Assigned later + ...settings, + storeOp: 'store', + }, + ], + depthStencilAttachment: { + // view: undefined, // Assigned later + ...settings, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }, + }; + + const sharedUniformValues = wgh.makeStructuredView(defs.uniforms.sharedUni); + + const sharedUniformBuffer = device.createBuffer({ + size: sharedUniformValues.arrayBuffer.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + + const sharedBindGroup = device.createBindGroup({ + layout: bindGroupLayouts[1], + entries: [ + { binding: 0, resource: { buffer: sharedUniformBuffer } }, + ], + }); + + return { + pipeline, + renderPassDescriptor, + sharedUniformValues, + sharedUniformBuffer, + sharedBindGroup, + ...settings, + }; + }); + + const objectInfos = []; + for (let i = 0; i < numInstances; ++i) { + const uniformView = wgh.makeStructuredView(defs.uniforms.uni); + const uniformBuffer = device.createBuffer({ + size: uniformView.arrayBuffer.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + uniformView.views.color.set([r(1), r(1), r(1), 1]); + + device.queue.writeBuffer(uniformBuffer, 0, uniformView.arrayBuffer); + + const bindGroup = device.createBindGroup({ + layout: bindGroupLayouts[0], + entries: [ + { binding: 0, resource: { buffer: uniformBuffer } }, + ], + }); + + objectInfos.push({ + uniformView, + uniformBuffer, + bindGroup, + geometry: geometries[(i / 2 | 0) % geometries.length], + }); + } + + let depthTexture; + + function render(time) { + time *= 0.001; + + objectInfos.forEach(({ + uniformBuffer, + uniformView, + }, i) => { + const p = i / 2 | 0; + const world = uniformView.views.world; + const size = 800; + mat4.identity(world); + mat4.translate(world, [0, 0, size * p * 1.1], world); + const s = i % 2 ? size : size - 0.1; + mat4.scale(world, [s, s, s], world); + + device.queue.writeBuffer(uniformBuffer, 0, uniformView.arrayBuffer); + }); + + const encoder = device.createCommandEncoder(); + pipelinesAndRenderPassDescriptors.forEach(({ + pipeline, + renderPassDescriptor, + perspective, + sharedUniformBuffer, + sharedUniformValues, + sharedBindGroup, + }, pNdx) => { + const projection = perspective(90 * Math.PI / 180, canvas.clientWidth / canvas.clientHeight, 0.5, 10000); + const radius = 1000; + const t = time * 0.1; + const eye = [Math.cos(t) * radius, Math.sin(t) * radius, -600]; //[Math.cos(t) * radius, Math.sin(t) * radius, 1000]; + const target = [0, 0, 2000]; + const up = [0, 1, 0]; + + const view = mat4.lookAt(eye, target, up); + mat4.multiply(projection, view, sharedUniformValues.views.viewProjection); + + sharedUniformValues.set({ + lightDirection: vec3.normalize([1, 8, -10]), + }); + + device.queue.writeBuffer(sharedUniformBuffer, 0, sharedUniformValues.arrayBuffer); + + const canvasTexture = context.getCurrentTexture(); + renderPassDescriptor.colorAttachments[0].view = canvasTexture.createView(); + + // If we don't have a depth texture OR if its size is different + // from the canvasTexture then make a new depth texture + if (!depthTexture || + depthTexture.width !== canvasTexture.width || + depthTexture.height !== canvasTexture.height) { + if (depthTexture) { + depthTexture.destroy(); + } + depthTexture = device.createTexture({ + size: [canvasTexture.width, canvasTexture.height], + format: 'depth32float', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + } + renderPassDescriptor.depthStencilAttachment.view = depthTexture.createView(); + + // TODO: Consider a render bundle + const pass = encoder.beginRenderPass(renderPassDescriptor); + const h = canvasTexture.height / 2 | 0; + const y = pNdx * h; + pass.setScissorRect(0, y, canvasTexture.width, h); + pass.setPipeline(pipeline); + pass.setBindGroup(1, sharedBindGroup); + objectInfos.forEach(({ + bindGroup, + geometry, + }) => { + pass.setBindGroup(0, bindGroup); + pass.setVertexBuffer(0, geometry.buffers[0]); + if (geometry.indexBuffer) { + pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat); + pass.drawIndexed(geometry.numElements); + } else { + pass.draw(geometry.numElements); + } + }); + pass.end(); + }); + device.queue.submit([encoder.finish()]); + + requestAnimationFrame(render); + } + requestAnimationFrame(render); + + const observer = new ResizeObserver(entries => { + for (const entry of entries) { + const canvas = entry.target; + const width = entry.contentBoxSize[0].inlineSize * 2; + const height = entry.contentBoxSize[0].blockSize * 2; + canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D)); + canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D)); + } + }); + observer.observe(canvas); +} + +function fail(msg) { + const elem = document.querySelector('#fail'); + elem.style.display = ''; + elem.children[0].textContent = msg; +} + +main();