mirror of
https://github.com/webgpu/webgpufundamentals.git
synced 2026-05-16 08:00:37 -04:00
719 lines
20 KiB
HTML
719 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
|
<title>WebGPU Perspective - perspective - Step 4</title>
|
|
<style>
|
|
@import url(resources/webgpu-lesson.css);
|
|
html, body {
|
|
margin: 0; /* remove the default margin */
|
|
height: 100%; /* make the html,body fill the page */
|
|
}
|
|
canvas {
|
|
display: block; /* make the canvas act like a block */
|
|
width: 100%; /* make the canvas fill its container */
|
|
height: 100%;
|
|
}
|
|
.fill-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.split-horizontal {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: stretch;
|
|
}
|
|
.split-horizontal>* {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
}
|
|
:root {
|
|
--bg-color: #fff;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--bg-color: #000;
|
|
}
|
|
}
|
|
canvas {
|
|
background-color: var(--bg-color);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="split-horizontal fill-container">
|
|
<canvas id="main"></canvas>
|
|
<canvas id="alt"></canvas>
|
|
</div>
|
|
</body>
|
|
<script type="module">
|
|
// see https://webgpufundamentals.org/webgpu/lessons/webgpu-utils.html#wgpu-matrix
|
|
import {mat4} from '../3rdparty/wgpu-matrix.module.js';
|
|
import GUI from '../3rdparty/muigui-0.x.module.js';
|
|
|
|
function createFVertices() {
|
|
const positions = [
|
|
// left column
|
|
-50, 75, 15,
|
|
-20, 75, 15,
|
|
-50, -75, 15,
|
|
-20, -75, 15,
|
|
|
|
// top rung
|
|
-20, 75, 15,
|
|
50, 75, 15,
|
|
-20, 45, 15,
|
|
50, 45, 15,
|
|
|
|
// middle rung
|
|
-20, 15, 15,
|
|
20, 15, 15,
|
|
-20, -15, 15,
|
|
20, -15, 15,
|
|
|
|
// left column back
|
|
-50, 75, -15,
|
|
-20, 75, -15,
|
|
-50, -75, -15,
|
|
-20, -75, -15,
|
|
|
|
// top rung back
|
|
-20, 75, -15,
|
|
50, 75, -15,
|
|
-20, 45, -15,
|
|
50, 45, -15,
|
|
|
|
// middle rung back
|
|
-20, 15, -15,
|
|
20, 15, -15,
|
|
-20, -15, -15,
|
|
20, -15, -15,
|
|
];
|
|
|
|
const indices = [
|
|
0, 2, 1, 2, 3, 1, // left column
|
|
4, 6, 5, 6, 7, 5, // top run
|
|
8, 10, 9, 10, 11, 9, // middle run
|
|
|
|
12, 13, 14, 14, 13, 15, // left column back
|
|
16, 17, 18, 18, 17, 19, // top run back
|
|
20, 21, 22, 22, 21, 23, // middle run back
|
|
|
|
0, 5, 12, 12, 5, 17, // top
|
|
5, 7, 17, 17, 7, 19, // top rung right
|
|
6, 18, 7, 18, 19, 7, // top rung bottom
|
|
6, 8, 18, 18, 8, 20, // between top and middle rung
|
|
8, 9, 20, 20, 9, 21, // middle rung top
|
|
9, 11, 21, 21, 11, 23, // middle rung right
|
|
10, 22, 11, 22, 23, 11, // middle rung bottom
|
|
10, 3, 22, 22, 3, 15, // stem right
|
|
2, 14, 3, 14, 15, 3, // bottom
|
|
0, 12, 2, 12, 14, 2, // left
|
|
];
|
|
|
|
const quadColors = [
|
|
200, 70, 120, // left column front
|
|
200, 70, 120, // top rung front
|
|
200, 70, 120, // middle rung front
|
|
|
|
80, 70, 200, // left column back
|
|
80, 70, 200, // top rung back
|
|
80, 70, 200, // middle rung back
|
|
|
|
70, 200, 210, // top
|
|
160, 160, 220, // top rung right
|
|
90, 130, 110, // top rung bottom
|
|
200, 200, 70, // between top and middle rung
|
|
210, 100, 70, // middle rung top
|
|
210, 160, 70, // middle rung right
|
|
70, 180, 210, // middle rung bottom
|
|
100, 70, 210, // stem right
|
|
76, 210, 100, // bottom
|
|
140, 210, 80, // left
|
|
];
|
|
|
|
const numVertices = indices.length;
|
|
const vertexData = new Float32Array(numVertices * 4); // xyz + color
|
|
const colorData = new Uint8Array(vertexData.buffer);
|
|
|
|
for (let i = 0; i < indices.length; ++i) {
|
|
const positionNdx = indices[i] * 3;
|
|
const position = positions.slice(positionNdx, positionNdx + 3);
|
|
vertexData.set(position, i * 4);
|
|
|
|
const quadNdx = (i / 6 | 0) * 3;
|
|
const color = quadColors.slice(quadNdx, quadNdx + 3);
|
|
colorData.set(color, i * 16 + 12);
|
|
colorData[i * 16 + 15] = 255;
|
|
}
|
|
|
|
return {
|
|
vertexData,
|
|
numVertices,
|
|
};
|
|
}
|
|
|
|
function createCameraFrustumVertices() {
|
|
const positions = [
|
|
-1, -1, 0, // cube vertices
|
|
1, -1, 0,
|
|
-1, 1, 0,
|
|
1, 1, 0,
|
|
-1, -1, 1,
|
|
1, -1, 1,
|
|
-1, 1, 1,
|
|
1, 1, 1,
|
|
];
|
|
const indices = [
|
|
0, 1, 1, 3, 3, 2, 2, 0, // cube indices
|
|
4, 5, 5, 7, 7, 6, 6, 4,
|
|
0, 4, 1, 5, 3, 7, 2, 6,
|
|
];
|
|
return {
|
|
vertexData: new Float32Array(positions),
|
|
indexData: new Uint16Array(indices),
|
|
};
|
|
}
|
|
|
|
// create geometry for a camera
|
|
function createCameraVertices() {
|
|
// first let's add a cube. It goes from 1 to 3
|
|
// because cameras look down -Z so we want
|
|
// the camera to start at Z = 0.
|
|
// We'll put a cone in front of this cube opening
|
|
// toward -Z
|
|
const positions = [
|
|
-1, -1, 1, // cube vertices
|
|
1, -1, 1,
|
|
-1, 1, 1,
|
|
1, 1, 1,
|
|
-1, -1, 3,
|
|
1, -1, 3,
|
|
-1, 1, 3,
|
|
1, 1, 3,
|
|
0, 0, 1, // cone tip
|
|
];
|
|
const indices = [
|
|
0, 1, 1, 3, 3, 2, 2, 0, // cube indices
|
|
4, 5, 5, 7, 7, 6, 6, 4,
|
|
0, 4, 1, 5, 3, 7, 2, 6,
|
|
];
|
|
// add cone segments
|
|
const numSegments = 6;
|
|
const coneBaseIndex = positions.length / 3;
|
|
const coneTipIndex = coneBaseIndex - 1;
|
|
for (let i = 0; i < numSegments; ++i) {
|
|
const u = i / numSegments;
|
|
const angle = u * Math.PI * 2;
|
|
const x = Math.cos(angle);
|
|
const y = Math.sin(angle);
|
|
positions.push(x, y, 0);
|
|
// line from tip to edge
|
|
indices.push(coneTipIndex, coneBaseIndex + i);
|
|
// line from point on edge to next point on edge
|
|
indices.push(coneBaseIndex + i, coneBaseIndex + (i + 1) % numSegments);
|
|
}
|
|
return {
|
|
vertexData: new Float32Array(positions),
|
|
indexData: new Uint16Array(indices),
|
|
};
|
|
}
|
|
|
|
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 degToRad = d => d * Math.PI / 180;
|
|
|
|
const settings = {
|
|
fieldOfView: degToRad(60),
|
|
camera: [-65, 100, 120],
|
|
target: [0, 0, 0],
|
|
near: 1,
|
|
far: 250,
|
|
};
|
|
|
|
const limitOptions = { min: 1, max: 500, minRange: 1, step: 1 };
|
|
|
|
const gui = new GUI();
|
|
GUI.setTheme('float');
|
|
gui.onChange(render);
|
|
gui.add(settings, 'fieldOfView', {min: 1, max: 179, converters: GUI.converters.radToDeg});
|
|
GUI.makeMinMaxPair(gui, settings, 'near', 'far', limitOptions);
|
|
gui.add(settings.camera, '0', -300, 300).name('camera.x');
|
|
gui.add(settings.camera, '1', -300, 300).name('camera.y');
|
|
gui.add(settings.camera, '2', -300, 300).name('camera.z');
|
|
gui.add(settings.target, '0', -300, 300).name('target.x');
|
|
gui.add(settings.target, '1', -300, 300).name('target.y');
|
|
gui.add(settings.target, '2', -300, 300).name('target.z');
|
|
|
|
const mainCanvas = document.querySelector('#main');
|
|
const altCanvas = document.querySelector('#alt');
|
|
const canvases = new Map([
|
|
[
|
|
mainCanvas,
|
|
{
|
|
clearValue: [0.3, 0.3, 0.3, 1.0],
|
|
drawCamera: false,
|
|
settings,
|
|
},
|
|
],
|
|
[
|
|
altCanvas,
|
|
{
|
|
clearValue: [0.5, 0.5, 0.5, 1.0],
|
|
drawCamera: true,
|
|
settings: {
|
|
fieldOfView: degToRad(60),
|
|
camera: [300, 400, 700],
|
|
target: [0, 75, 0],
|
|
near: 1,
|
|
far: 2000,
|
|
},
|
|
},
|
|
],
|
|
]);
|
|
|
|
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
|
|
// Get a WebGPU context from the canvas and configure it
|
|
canvases.forEach((info, canvas) => {
|
|
const context = canvas.getContext('webgpu');
|
|
context.configure({
|
|
device,
|
|
format: presentationFormat,
|
|
});
|
|
info.context = context;
|
|
});
|
|
|
|
const module = device.createShaderModule({
|
|
code: /* wgsl */ `
|
|
struct Uniforms {
|
|
matrix: mat4x4f,
|
|
};
|
|
|
|
struct Vertex {
|
|
@location(0) position: vec4f,
|
|
@location(1) color: vec4f,
|
|
};
|
|
|
|
struct VSOutput {
|
|
@builtin(position) position: vec4f,
|
|
@location(0) color: vec4f,
|
|
};
|
|
|
|
@group(0) @binding(0) var<uniform> uni: Uniforms;
|
|
|
|
@vertex fn vs(vert: Vertex) -> VSOutput {
|
|
var vsOut: VSOutput;
|
|
vsOut.position = uni.matrix * vert.position;
|
|
vsOut.color = vert.color;
|
|
return vsOut;
|
|
}
|
|
|
|
@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {
|
|
return vsOut.color;
|
|
}
|
|
`,
|
|
});
|
|
|
|
const bindGroupLayout = device.createBindGroupLayout({
|
|
entries: [
|
|
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { } },
|
|
],
|
|
});
|
|
|
|
const layout = device.createPipelineLayout({
|
|
bindGroupLayouts: [
|
|
bindGroupLayout,
|
|
],
|
|
});
|
|
|
|
const pipeline = device.createRenderPipeline({
|
|
label: '2 attributes',
|
|
layout,
|
|
vertex: {
|
|
module,
|
|
buffers: [
|
|
{
|
|
arrayStride: (4) * 4, // (3) floats 4 bytes each + one 4 byte color
|
|
attributes: [
|
|
{shaderLocation: 0, offset: 0, format: 'float32x3'}, // position
|
|
{shaderLocation: 1, offset: 12, format: 'unorm8x4'}, // color
|
|
],
|
|
},
|
|
],
|
|
},
|
|
fragment: {
|
|
module,
|
|
targets: [{ format: presentationFormat }],
|
|
},
|
|
primitive: {
|
|
cullMode: 'back',
|
|
},
|
|
depthStencil: {
|
|
depthWriteEnabled: true,
|
|
depthCompare: 'less',
|
|
format: 'depth24plus',
|
|
},
|
|
});
|
|
|
|
const linePipeline = device.createRenderPipeline({
|
|
label: '2 attributes line-list',
|
|
layout,
|
|
vertex: {
|
|
module,
|
|
buffers: [
|
|
{
|
|
arrayStride: (3) * 4, // (3) floats 4 bytes each
|
|
attributes: [
|
|
{shaderLocation: 0, offset: 0, format: 'float32x3'}, // position
|
|
],
|
|
},
|
|
{
|
|
arrayStride: 0, // repeat
|
|
attributes: [
|
|
{shaderLocation: 1, offset: 0, format: 'unorm8x4'}, // color
|
|
],
|
|
},
|
|
],
|
|
},
|
|
fragment: {
|
|
module,
|
|
targets: [{ format: presentationFormat }],
|
|
},
|
|
primitive: {
|
|
topology: 'line-list',
|
|
},
|
|
depthStencil: {
|
|
depthWriteEnabled: true,
|
|
depthCompare: 'less',
|
|
format: 'depth24plus',
|
|
},
|
|
});
|
|
|
|
// matrix
|
|
const uniformBufferSize = (16) * 4;
|
|
const uniformBuffer = device.createBuffer({
|
|
label: 'uniforms',
|
|
size: uniformBufferSize,
|
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
});
|
|
|
|
const uniformValues = new Float32Array(uniformBufferSize / 4);
|
|
|
|
// offsets to the various uniform values in float32 indices
|
|
const kMatrixOffset = 0;
|
|
|
|
const matrixValue = uniformValues.subarray(kMatrixOffset, kMatrixOffset + 16);
|
|
|
|
const { vertexData, numVertices } = createFVertices();
|
|
const vertexBuffer = device.createBuffer({
|
|
label: 'vertex buffer vertices',
|
|
size: vertexData.byteLength,
|
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(vertexBuffer, 0, vertexData);
|
|
|
|
const cameraResources = (() => {
|
|
const { vertexData, indexData } = createCameraVertices();
|
|
const vertexBuffer = device.createBuffer({
|
|
label: 'camera vertex buffer',
|
|
size: vertexData.byteLength,
|
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(vertexBuffer, 0, vertexData);
|
|
|
|
// A color we'll pass in as vertex colors but with arrayStride = 0
|
|
// so it will just use the same value for every vertex.
|
|
const colorBuffer = device.createBuffer({
|
|
label: 'camera vertex buffer',
|
|
size: 4,
|
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(colorBuffer, 0, new Uint8Array([1, 1, 0, 1]));
|
|
|
|
const indexBuffer = device.createBuffer({
|
|
label: 'camera index buffer',
|
|
size: indexData.byteLength,
|
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(indexBuffer, 0, indexData);
|
|
|
|
const uniformBufferSize = (16) * 4;
|
|
const uniformBuffer = device.createBuffer({
|
|
label: 'uniforms',
|
|
size: uniformBufferSize,
|
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
});
|
|
|
|
const uniformValues = new Float32Array(uniformBufferSize / 4);
|
|
const matrixValue = uniformValues.subarray(kMatrixOffset, kMatrixOffset + 16);
|
|
|
|
const bindGroup = device.createBindGroup({
|
|
label: 'bind group for camera',
|
|
layout: bindGroupLayout,
|
|
entries: [
|
|
{ binding: 0, resource: uniformBuffer },
|
|
],
|
|
});
|
|
|
|
|
|
return {
|
|
vertexBuffer,
|
|
colorBuffer,
|
|
indexBuffer,
|
|
indexFormat: 'uint16',
|
|
numElements: indexData.length,
|
|
uniformBuffer,
|
|
uniformValues,
|
|
matrixValue,
|
|
bindGroup,
|
|
};
|
|
})();
|
|
|
|
const frustumResources = (() => {
|
|
const { vertexData, indexData } = createCameraFrustumVertices();
|
|
const vertexBuffer = device.createBuffer({
|
|
label: 'camera vertex buffer',
|
|
size: vertexData.byteLength,
|
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(vertexBuffer, 0, vertexData);
|
|
|
|
// A color we'll pass in as vertex colors but with arrayStride = 0
|
|
// so it will just use the same value for every vertex.
|
|
const colorBuffer = device.createBuffer({
|
|
label: 'camera vertex buffer',
|
|
size: 4,
|
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(colorBuffer, 0, new Uint8Array([1, 1, 0, 1]));
|
|
|
|
const indexBuffer = device.createBuffer({
|
|
label: 'camera index buffer',
|
|
size: indexData.byteLength,
|
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(indexBuffer, 0, indexData);
|
|
|
|
const uniformBufferSize = (16) * 4;
|
|
const uniformBuffer = device.createBuffer({
|
|
label: 'uniforms',
|
|
size: uniformBufferSize,
|
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
});
|
|
|
|
const uniformValues = new Float32Array(uniformBufferSize / 4);
|
|
const matrixValue = uniformValues.subarray(kMatrixOffset, kMatrixOffset + 16);
|
|
|
|
const bindGroup = device.createBindGroup({
|
|
label: 'bind group for camera',
|
|
layout: bindGroupLayout,
|
|
entries: [
|
|
{ binding: 0, resource: uniformBuffer },
|
|
],
|
|
});
|
|
|
|
|
|
return {
|
|
vertexBuffer,
|
|
colorBuffer,
|
|
indexBuffer,
|
|
indexFormat: 'uint16',
|
|
numElements: indexData.length,
|
|
uniformBuffer,
|
|
uniformValues,
|
|
matrixValue,
|
|
bindGroup,
|
|
};
|
|
})();
|
|
|
|
const bindGroup = device.createBindGroup({
|
|
label: 'bind group for object',
|
|
layout: bindGroupLayout,
|
|
entries: [
|
|
{ binding: 0, resource: uniformBuffer },
|
|
],
|
|
});
|
|
|
|
const renderPassDescriptor = {
|
|
label: 'our basic canvas renderPass',
|
|
colorAttachments: [
|
|
{
|
|
// view: <- to be filled out when we render
|
|
loadOp: 'clear',
|
|
storeOp: 'store',
|
|
},
|
|
],
|
|
depthStencilAttachment: {
|
|
// view: <- to be filled out when we render
|
|
depthClearValue: 1.0,
|
|
depthLoadOp: 'clear',
|
|
depthStoreOp: 'store',
|
|
},
|
|
};
|
|
|
|
let depthTexture;
|
|
|
|
function render() {
|
|
canvases.forEach((_, canvas) => renderCanvas(canvas));
|
|
}
|
|
|
|
function renderCanvas(canvas) {
|
|
const canvasInfo = canvases.get(canvas);
|
|
const {
|
|
context,
|
|
clearValue,
|
|
drawCamera,
|
|
settings,
|
|
} = canvasInfo;
|
|
|
|
// Get the current texture from the canvas context and
|
|
// set it as the texture to render to.
|
|
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 when 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: 'depth24plus',
|
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
});
|
|
}
|
|
renderPassDescriptor.depthStencilAttachment.view = depthTexture.createView();
|
|
renderPassDescriptor.colorAttachments[0].clearValue = clearValue;
|
|
|
|
const encoder = device.createCommandEncoder();
|
|
const pass = encoder.beginRenderPass(renderPassDescriptor);
|
|
pass.setPipeline(pipeline);
|
|
pass.setVertexBuffer(0, vertexBuffer);
|
|
|
|
const aspect = canvas.clientWidth / canvas.clientHeight;
|
|
const projection = mat4.perspective(
|
|
settings.fieldOfView,
|
|
aspect,
|
|
settings.near,
|
|
settings.far
|
|
);
|
|
|
|
// Compute a view matrix
|
|
const up = [0, 1, 0];
|
|
const viewMatrix = mat4.lookAt(settings.camera, settings.target, up);
|
|
|
|
mat4.multiply(projection, viewMatrix, matrixValue);
|
|
|
|
canvasInfo.projection = projection;
|
|
canvasInfo.viewMatrix = viewMatrix;
|
|
|
|
// upload the uniform values to the uniform buffer
|
|
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
|
|
|
|
pass.setBindGroup(0, bindGroup);
|
|
pass.draw(numVertices);
|
|
|
|
if (drawCamera) {
|
|
{
|
|
const {
|
|
indexBuffer,
|
|
indexFormat,
|
|
vertexBuffer,
|
|
colorBuffer,
|
|
numElements,
|
|
uniformBuffer,
|
|
uniformValues,
|
|
bindGroup,
|
|
matrixValue,
|
|
} = cameraResources;
|
|
|
|
// use the first's camera's matrix as the matrix to position
|
|
// the camera's representative in the scene
|
|
const {viewMatrix: mainViewMatrix} = canvases.get(mainCanvas);
|
|
const mainCameraMatrix = mat4.inverse(mainViewMatrix);
|
|
mat4.multiply(projection, viewMatrix, matrixValue);
|
|
mat4.multiply(matrixValue, mainCameraMatrix, matrixValue);
|
|
mat4.scale(matrixValue, [20, 20, 20], matrixValue);
|
|
|
|
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
|
|
|
|
pass.setPipeline(linePipeline);
|
|
pass.setVertexBuffer(0, vertexBuffer);
|
|
pass.setVertexBuffer(1, colorBuffer);
|
|
pass.setIndexBuffer(indexBuffer, indexFormat);
|
|
pass.setBindGroup(0, bindGroup);
|
|
pass.drawIndexed(numElements);
|
|
}
|
|
{
|
|
const {
|
|
indexBuffer,
|
|
indexFormat,
|
|
vertexBuffer,
|
|
colorBuffer,
|
|
numElements,
|
|
uniformBuffer,
|
|
uniformValues,
|
|
bindGroup,
|
|
matrixValue,
|
|
} = frustumResources;
|
|
|
|
// use the first's camera's matrix as the matrix to position
|
|
// the camera's representative in the scene
|
|
const {viewMatrix: mainViewMatrix, projection: mainProjection} = canvases.get(mainCanvas);
|
|
const mainCameraMatrix = mat4.inverse(mainViewMatrix);
|
|
mat4.multiply(projection, viewMatrix, matrixValue);
|
|
mat4.multiply(matrixValue, mainCameraMatrix, matrixValue);
|
|
mat4.multiply(matrixValue, mat4.inverse(mainProjection), matrixValue);
|
|
|
|
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
|
|
|
|
pass.setPipeline(linePipeline);
|
|
pass.setVertexBuffer(0, vertexBuffer);
|
|
pass.setVertexBuffer(1, colorBuffer);
|
|
pass.setIndexBuffer(indexBuffer, indexFormat);
|
|
pass.setBindGroup(0, bindGroup);
|
|
pass.drawIndexed(numElements);
|
|
}
|
|
}
|
|
|
|
pass.end();
|
|
|
|
const commandBuffer = encoder.finish();
|
|
device.queue.submit([commandBuffer]);
|
|
}
|
|
|
|
const observer = new ResizeObserver(entries => {
|
|
for (const entry of entries) {
|
|
const canvas = entry.target;
|
|
const width = entry.contentBoxSize[0].inlineSize;
|
|
const height = entry.contentBoxSize[0].blockSize;
|
|
canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D));
|
|
canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D));
|
|
// re-render
|
|
renderCanvas(canvas);
|
|
}
|
|
});
|
|
canvases.forEach((_, canvas) => {
|
|
observer.observe(canvas);
|
|
});
|
|
}
|
|
|
|
function fail(msg) {
|
|
alert(msg);
|
|
}
|
|
|
|
main();
|
|
</script>
|
|
</html>
|