Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

a wireframe example #427

Merged
merged 4 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 133 additions & 43 deletions sample/wireframe/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable prettier/prettier */
import { mat4, mat3 } from 'wgpu-matrix';
import { GUI } from 'dat.gui';
import { modelData } from './models';
import { randElement, randColor } from './utils';
import solidColorLitWGSL from './solidColorLit.wgsl';
import wireframeWGSL from './wireframe.wgsl';

Expand All @@ -9,11 +11,11 @@ type TypedArrayView = Float32Array | Uint32Array;
function createBufferWithData(
device: GPUDevice,
data: TypedArrayView,
usage: number
usage: GPUBufferUsageFlags,
) {
const buffer = device.createBuffer({
size: data.byteLength,
usage: usage,
usage,
});
device.queue.writeBuffer(buffer, 0, data);
return buffer;
Expand Down Expand Up @@ -66,25 +68,6 @@ const depthFormat = 'depth24plus';

const models = Object.values(modelData).map(data => createVertexAndIndexBuffer(device, data));

function rand(min?: number, max?: number) {
if (min === undefined) {
max = 1;
min = 0;
} else if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}

function randInt(min: number, max?: number) {
return Math.floor(rand(min, max));
}

function randColor() {
return [rand(), rand(), rand(), 1];
}

const litModule = device.createShaderModule({
code: solidColorLitWGSL,
});
Expand Down Expand Up @@ -141,6 +124,7 @@ const wireframePipeline = device.createRenderPipeline({
},
fragment: {
module: wireframeModule,
entryPoint: 'fs',
targets: [{ format: presentationFormat }],
},
primitive: {
Expand All @@ -153,13 +137,51 @@ const wireframePipeline = device.createRenderPipeline({
},
});

const barycentricCoordinatesBasedWireframePipeline = device.createRenderPipeline({
label: 'barycentric coordinates based wireframe pipeline',
layout: 'auto',
vertex: {
module: wireframeModule,
entryPoint: 'vsIndexedU32BarycentricCoordinateBasedLines',
},
fragment: {
module: wireframeModule,
entryPoint: 'fsBarycentricCoordinateBasedLines',
targets: [
{
format: presentationFormat,
blend: {
color: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
},
alpha: {
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
},
},
},
],
},
primitive: {
topology: 'triangle-list',
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: 'less-equal',
format: depthFormat,
},
});

type ObjectInfo = {
worldViewProjectionMatrixValue: Float32Array;
worldMatrixValue: Float32Array;
uniformValues: Float32Array;
uniformBuffer: GPUBuffer;
lineUniformValues: Float32Array;
lineUniformBuffer: GPUBuffer;
litBindGroup: GPUBindGroup;
wireframeBindGroup: GPUBindGroup;
wireframeBindGroups: GPUBindGroup[];
model: Model;
};

Expand Down Expand Up @@ -187,29 +209,47 @@ for (let i = 0; i < numObjects; ++i) {
const colorValue = uniformValues.subarray(kColorOffset, kColorOffset + 4);
colorValue.set(randColor());

const model = models[randInt(models.length)];
const model = randElement(models);

// Make a bind group for this uniform
const litBindGroup = device.createBindGroup({
layout: litPipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
});

const strideValues = new Uint32Array(1 + 3);
const strideBuffer = device.createBuffer({
size: strideValues.byteLength,
// Note: We're making one lineUniformBuffer per object.
// This is only because stride might be different per object.
// In this sample stride is the same across all objects so
// we could have made just a single shared uniform buffer for
// these settings.
const lineUniformValues = new Float32Array(3 + 1);
const lineUniformValuesAsU32 = new Uint32Array(lineUniformValues.buffer);
const lineUniformBuffer = device.createBuffer({
size: lineUniformValues.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
strideValues[0] = 6;
device.queue.writeBuffer(strideBuffer, 0, strideValues);
lineUniformValuesAsU32[0] = 6; // the array stride for positions for this model.

// We're creating 2 bindGroups, one for each pipeline.
// We could create just one since they are identical. To do
// so we'd have to manually create a bindGroupLayout.
const wireframeBindGroup = device.createBindGroup({
layout: wireframePipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: uniformBuffer } },
{ binding: 1, resource: { buffer: model.vertexBuffer } },
{ binding: 2, resource: { buffer: model.indexBuffer } },
{ binding: 3, resource: { buffer: strideBuffer } },
{ binding: 3, resource: { buffer: lineUniformBuffer } },
],
});

const barycentricCoordinatesBasedWireframeBindGroup = device.createBindGroup({
layout: barycentricCoordinatesBasedWireframePipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: uniformBuffer } },
{ binding: 1, resource: { buffer: model.vertexBuffer } },
{ binding: 2, resource: { buffer: model.indexBuffer } },
{ binding: 3, resource: { buffer: lineUniformBuffer } },
],
});

Expand All @@ -218,8 +258,13 @@ for (let i = 0; i < numObjects; ++i) {
worldMatrixValue,
uniformValues,
uniformBuffer,
lineUniformValues,
lineUniformBuffer,
litBindGroup,
wireframeBindGroup,
wireframeBindGroups: [
wireframeBindGroup,
barycentricCoordinatesBasedWireframeBindGroup,
],
model,
});
}
Expand All @@ -242,7 +287,45 @@ const renderPassDescriptor: GPURenderPassDescriptor = {
},
};

let depthTexture;
const settings = {
barycentricCoordinatesBased: false,
thickness: 2,
alphaThreshold: 0.5,
lines: true,
models: true,
};

const gui = new GUI();
gui.add(settings, 'barycentricCoordinatesBased').onChange(addRemoveGUI);
gui.add(settings, 'lines');
gui.add(settings, 'models');

const guis = [];
function addRemoveGUI() {
if (settings.barycentricCoordinatesBased) {
guis.push(
gui.add(settings, 'thickness', 0.0, 10).onChange(updateThickness),
gui.add(settings, 'alphaThreshold', 0, 1).onChange(updateThickness),
);
} else {
guis.forEach(g => g.remove());
guis.length = 0;
}
}

function updateThickness() {
objectInfos.forEach(({
lineUniformBuffer,
lineUniformValues,
}) => {
lineUniformValues[1] = settings.thickness;
lineUniformValues[2] = settings.alphaThreshold;
device.queue.writeBuffer(lineUniformBuffer, 0, lineUniformValues);
});
}
updateThickness();

let depthTexture: GPUTexture | undefined;

function render(time: number) {
time *= 0.001; // convert to seconds;
Expand Down Expand Up @@ -311,20 +394,27 @@ function render(time: number) {
// Upload our uniform values.
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);

pass.setVertexBuffer(0, vertexBuffer);
pass.setIndexBuffer(indexBuffer, indexFormat);
pass.setBindGroup(0, litBindGroup);
pass.drawIndexed(vertexCount);
if (settings.models) {
pass.setVertexBuffer(0, vertexBuffer);
pass.setIndexBuffer(indexBuffer, indexFormat);
pass.setBindGroup(0, litBindGroup);
pass.drawIndexed(vertexCount);
}
});

objectInfos.forEach(({
wireframeBindGroup,
model: { vertexCount },
}) => {
pass.setPipeline(wireframePipeline);
pass.setBindGroup(0, wireframeBindGroup)
pass.draw(vertexCount * 2);
});
if (settings.lines) {
// Note: If we're using the line-list based pipeline then we need to
// multiply the vertex count by 2 since we need to emit 6 vertices
// for each triangle (3 edges).
const [bindGroupNdx, countMult, pipeline] = settings.barycentricCoordinatesBased
? [1, 1, barycentricCoordinatesBasedWireframePipeline]
: [0, 2, wireframePipeline];
pass.setPipeline(pipeline);
objectInfos.forEach(({ wireframeBindGroups, model: { vertexCount } }) => {
pass.setBindGroup(0, wireframeBindGroups[bindGroupNdx]);
pass.draw(vertexCount * countMult);
});
}

pass.end();

Expand Down
11 changes: 6 additions & 5 deletions sample/wireframe/meta.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export default {
name: 'Wireframe',
description: `
This example demonstrates drawing a wireframe from triangles by using the
vertex buffers as storage buffers and then using \`@builtin(vertex_index)\`
to index the vertex data to generate 3 lines per triangle. It uses
array<f32> in the vertex shader so it can pull out \`vec3f\` positions
at any valid stride.
This example demonstrates drawing a wireframe from triangles in 2 ways.
Both use the vertex and index buffers as storage buffers and the use \`@builtin(vertex_index)\`
to index the vertex data. One method generates 6 vertices per triangle and uses line-list to draw lines.
The other method draws triangles with a fragment shader that uses barycentric coordinates to draw edges.
as detailed [here](https://web.archive.org/web/20130424093557/http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/).

`,
filename: __DIRNAME__,
sources: [
Expand Down
41 changes: 22 additions & 19 deletions sample/wireframe/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,22 +184,25 @@ export const create3DRenderPipeline = (
return device.createRenderPipeline(pipelineDescriptor);
};

export const createTextureFromImage = (
greggman marked this conversation as resolved.
Show resolved Hide resolved
device: GPUDevice,
bitmap: ImageBitmap
) => {
const texture: GPUTexture = device.createTexture({
size: [bitmap.width, bitmap.height, 1],
format: 'rgba8unorm',
usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
});
device.queue.copyExternalImageToTexture(
{ source: bitmap },
{ texture: texture },
[bitmap.width, bitmap.height]
);
return texture;
};
export function rand(min?: number, max?: number) {
if (min === undefined) {
max = 1;
min = 0;
} else if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}

export function randInt(min: number, max?: number) {
return Math.floor(rand(min, max));
}

export function randColor() {
return [rand(), rand(), rand(), 1];
}

export function randElement<T>(arr: T[]): T {
return arr[randInt(arr.length)];
}
Loading
Loading