Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a points example
Browse files Browse the repository at this point in the history
greggman committed Mar 9, 2024
1 parent e8e1d1b commit fb55b62
Showing 8 changed files with 438 additions and 0 deletions.
37 changes: 37 additions & 0 deletions sample/points/distance-sized-points.vert.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
struct Vertex {
@location(0) position: vec4f,
};

struct Uniforms {
matrix: mat4x4f,
resolution: vec2f,
size: f32,
};

struct VSOutput {
@builtin(position) position: vec4f,
@location(0) texcoord: vec2f,
};

@group(0) @binding(0) var<uniform> uni: Uniforms;

@vertex fn vs(
vert: Vertex,
@builtin(vertex_index) vNdx: u32,
) -> VSOutput {
let points = array(
vec2f(-1, -1),
vec2f( 1, -1),
vec2f(-1, 1),
vec2f(-1, 1),
vec2f( 1, -1),
vec2f( 1, 1),
);
var vsOut: VSOutput;
let pos = points[vNdx];
let clipPos = uni.matrix * vert.position;
let pointPos = vec4f(pos * uni.size / uni.resolution, 0, 0);
vsOut.position = clipPos + pointPos;
vsOut.texcoord = pos * 0.5 + 0.5;
return vsOut;
}
37 changes: 37 additions & 0 deletions sample/points/fixed-size-points.vert.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
struct Vertex {
@location(0) position: vec4f,
};

struct Uniforms {
matrix: mat4x4f,
resolution: vec2f,
size: f32,
};

struct VSOutput {
@builtin(position) position: vec4f,
@location(0) texcoord: vec2f,
};

@group(0) @binding(0) var<uniform> uni: Uniforms;

@vertex fn vs(
vert: Vertex,
@builtin(vertex_index) vNdx: u32,
) -> VSOutput {
let points = array(
vec2f(-1, -1),
vec2f( 1, -1),
vec2f(-1, 1),
vec2f(-1, 1),
vec2f( 1, -1),
vec2f( 1, 1),
);
var vsOut: VSOutput;
let pos = points[vNdx];
let clipPos = uni.matrix * vert.position;
let pointPos = vec4f(pos * uni.size / uni.resolution * clipPos.w, 0, 0);
vsOut.position = clipPos + pointPos;
vsOut.texcoord = pos * 0.5 + 0.5;
return vsOut;
}
27 changes: 27 additions & 0 deletions sample/points/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>webgpu-samples: points</title>
<style>
html, body {
margin: 0; /* remove default margin */
height: 100%; /* make body fill the browser window */
display: flex;
place-content: center center;
}
canvas {
width: 600px;
height: 600px;
max-width: 100%;
display: block;
}
</style>
<script defer src="main.js" type="module"></script>
<script defer type="module" src="../../js/iframe-helper.js"></script>
</head>
<body>
<canvas></canvas>
</body>
</html>
301 changes: 301 additions & 0 deletions sample/points/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
import { mat4 } from 'wgpu-matrix';
import { GUI } from 'dat.gui';

import distanceSizedPointsVertWGSL from './distance-sized-points.vert.wgsl';
import fixedSizePointsVertWGSL from './fixed-size-points.vert.wgsl';
import orangeFragWGSL from './orange.frag.wgsl';
import texturedFragWGSL from './textured.frag.wgsl';

// See: https://www.google.com/search?q=fibonacci+sphere
function createFibonacciSphereVertices({
numSamples,
radius,
}: {
numSamples: number;
radius: number;
}) {
const vertices = [];
const increment = Math.PI * (3 - Math.sqrt(5));
for (let i = 0; i < numSamples; ++i) {
const offset = 2 / numSamples;
const y = i * offset - 1 + offset / 2;
const r = Math.sqrt(1 - Math.pow(y, 2));
const phi = (i % numSamples) * increment;
const x = Math.cos(phi) * r;
const z = Math.sin(phi) * r;
vertices.push(x * radius, y * radius, z * radius);
}
return new Float32Array(vertices);
}

const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();

// Get a WebGPU context from the canvas and configure it
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const context = canvas.getContext('webgpu');
const devicePixelRatio = window.devicePixelRatio;
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
});

// Create a bind group layout so we can share the bind groups
// with multiple pipelines.
const bindGroupLayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: {},
},
{
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
sampler: {
type: 'filtering',
},
},
{
binding: 2,
visibility: GPUShaderStage.FRAGMENT,
texture: {
sampleType: 'float',
viewDimension: '2d',
},
},
],
});

const pipelineLayout = device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout],
});

// Compile all 4 shaders
const shaderModules = Object.fromEntries(
Object.entries({
orangeFragWGSL,
texturedFragWGSL,
distanceSizedPointsVertWGSL,
fixedSizePointsVertWGSL,
}).map(([key, code]) => [key, device.createShaderModule({ code })])
);

const fragModules = [
shaderModules.orangeFragWGSL,
shaderModules.texturedFragWGSL,
];

const vertModules = [
shaderModules.distanceSizedPointsVertWGSL,
shaderModules.fixedSizePointsVertWGSL,
];

const depthFormat = 'depth24plus';

// make pipelines for each combination
const pipelines = vertModules.map((vertModule) =>
fragModules.map((fragModule) =>
device.createRenderPipeline({
layout: pipelineLayout,
vertex: {
module: vertModule,
buffers: [
{
arrayStride: 3 * 4, // 3 floats, 4 bytes each
stepMode: 'instance',
attributes: [
{ shaderLocation: 0, offset: 0, format: 'float32x3' }, // position
],
},
],
},
fragment: {
module: fragModule,
targets: [
{
format: presentationFormat,
blend: {
color: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
operation: 'add',
},
alpha: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
operation: 'add',
},
},
},
],
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: 'less',
format: depthFormat,
},
})
)
);

const vertexData = createFibonacciSphereVertices({
radius: 1,
numSamples: 1000,
});
const kNumPoints = vertexData.length / 3;

const vertexBuffer = device.createBuffer({
label: 'vertex buffer vertices',
size: vertexData.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(vertexBuffer, 0, vertexData);

const uniformValues = new Float32Array(16 + 2 + 1 + 1);
const uniformBuffer = device.createBuffer({
size: uniformValues.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const kMatrixOffset = 0;
const kResolutionOffset = 16;
const kSizeOffset = 18;
const matrixValue = uniformValues.subarray(kMatrixOffset, kMatrixOffset + 16);
const resolutionValue = uniformValues.subarray(
kResolutionOffset,
kResolutionOffset + 2
);
const sizeValue = uniformValues.subarray(kSizeOffset, kSizeOffset + 1);

// Use canvas 2d to make texture data
const ctx = new OffscreenCanvas(64, 64).getContext('2d');
ctx.font = '60px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('🦋', 32, 32);

const sampler = device.createSampler();
const texture = device.createTexture({
size: [ctx.canvas.width, ctx.canvas.height],
format: 'rgba8unorm',
usage:
GPUTextureUsage.COPY_DST |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
});
device.queue.copyExternalImageToTexture(
{ source: ctx.canvas, flipY: true },
{ texture },
[ctx.canvas.width, ctx.canvas.height]
);

const bindGroup = device.createBindGroup({
layout: bindGroupLayout,
entries: [
{ binding: 0, resource: { buffer: uniformBuffer } },
{ binding: 1, resource: sampler },
{ binding: 2, resource: texture.createView() },
],
});

const renderPassDescriptor: GPURenderPassDescriptor = {
label: 'our basic canvas renderPass',
colorAttachments: [
{
view: undefined, // assigned later
clearValue: [0.3, 0.3, 0.3, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
depthStencilAttachment: {
view: undefined, // to be filled out when we render
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
};

const settings = {
fixedSize: false,
textured: false,
size: 10,
};

const gui = new GUI();
gui.add(settings, 'fixedSize');
gui.add(settings, 'textured');
gui.add(settings, 'size', 0, 80);

let depthTexture;

function render(time: number) {
// Convert to seconds.
time *= 0.001;

// 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: depthFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
}
renderPassDescriptor.depthStencilAttachment.view = depthTexture.createView();

const { size, fixedSize, textured } = settings;

const pipeline = pipelines[fixedSize ? 1 : 0][textured ? 1 : 0];

// Set the size in the uniform values
sizeValue[0] = size;

const fov = (90 * Math.PI) / 180;
const aspect = canvas.clientWidth / canvas.clientHeight;
const projection = mat4.perspective(fov, aspect, 0.1, 50);
const view = mat4.lookAt(
[0, 0, 1.5], // position
[0, 0, 0], // target
[0, 1, 0] // up
);
const viewProjection = mat4.multiply(projection, view);
mat4.rotateY(viewProjection, time, matrixValue);
mat4.rotateX(matrixValue, time * 0.1, matrixValue);

// Update the resolution in the uniform values
resolutionValue.set([canvasTexture.width, canvasTexture.height]);

// Copy the uniform values to the GPU
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);

const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.setVertexBuffer(0, vertexBuffer);
pass.setBindGroup(0, bindGroup);
pass.draw(6, kNumPoints);
pass.end();

const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);

requestAnimationFrame(render);
}

requestAnimationFrame(render);
16 changes: 16 additions & 0 deletions sample/points/meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default {
name: 'Points',
description: `\
This example shows how to render points of various sizes using a quad and instancing.
You can read more details [here](https://webgpufundamentals.org/webgpu/lessons/webgpu-points.html).
`,
filename: __DIRNAME__,
sources: [
{ path: 'main.ts' },
{ path: 'distance-sized-points.vert.wgsl' },
{ path: 'fixed-size-points.vert.wgsl' },
{ path: 'orange.frag.wgsl' },
{ path: 'textured.frag.wgsl' },
],
};
4 changes: 4 additions & 0 deletions sample/points/orange.frag.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

@fragment fn fs() -> @location(0) vec4f {
return vec4f(1, 0.5, 0.2, 1);
}
14 changes: 14 additions & 0 deletions sample/points/textured.frag.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
struct VSOutput {
@location(0) texcoord: vec2f,
};

@group(0) @binding(1) var s: sampler;
@group(0) @binding(2) var t: texture_2d<f32>;

@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {
let color = textureSample(t, s, vsOut.texcoord);
if (color.a < 0.1) {
discard;
}
return color;
}
2 changes: 2 additions & 0 deletions src/samples.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ import instancedCube from '../sample/instancedCube/meta';
import metaballs from '../sample/metaballs/meta';
import normalMap from '../sample/normalMap/meta';
import particles from '../sample/particles/meta';
import points from '../sample/points/meta';
import pristineGrid from '../sample/pristineGrid/meta';
import renderBundles from '../sample/renderBundles/meta';
import resizeCanvas from '../sample/resizeCanvas/meta';
@@ -111,6 +112,7 @@ export const pageCategories: PageCategory[] = [
shadowMapping,
deferredRendering,
particles,
points,
imageBlur,
cornell,
'a-buffer': aBuffer,

0 comments on commit fb55b62

Please sign in to comment.