Skip to content

Commit

Permalink
address feedback and add barycentric coordinate based lines
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Jun 17, 2024
1 parent be43431 commit e6d7fc3
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 103 deletions.
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 = (
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

0 comments on commit e6d7fc3

Please sign in to comment.