webgpu-app-nico/triangleTextured.js
2026-08-14 13:25:45 -05:00

194 lines
5.6 KiB
JavaScript

import { getPathText } from "./common.js"
// Triangle vertices, one vertex per line. Hand-transcribed from a
// .obj file exported from Blender to this form.
//
// I've also written myriad scripts to do this automatically for
// larger models.
//
// Float32Array is a fairly convenient way to convert floating point
// literals to bytes.
const triangleVertices = new Float32Array([
// [position ] [texture ] [normal ]
0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 1.0, 0.0,
1.0, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0,
]);
class TriangleTexturedRenderer {
constructor(device, canvasFormat, wgsl, image)
{
const label = "TriangleRenderer";
//////////////////////////////////////////////////////////////////////
// shader module
//////////////////////////////////////////////////////////////////////
const shaderModule = device.createShaderModule({
label: `${label} shader`,
code: wgsl,
});
//////////////////////////////////////////////////////////////////////
// pipeline
//////////////////////////////////////////////////////////////////////
// "bind group layouts" are a declaration of which `var` bindings
// exist in the shader programs loaded by this pipeline
const bindGroupLayouts = [
device.createBindGroupLayout({
label: "gltf bind group layout 0",
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
sampler: {},
},
{
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
texture: {},
}
]
}),
];
const pipelineLayout = device.createPipelineLayout({
label: `${label} pipeline layout`,
bindGroupLayouts: bindGroupLayouts,
});
// vertexBufferLayouts describes the position of each element for
// each buffer. There is only one vertex buffer used in this demo,
// so there is only one vertex buffer layout in this list.
//
// shaderLocation matches the location numbers defined in
// triangle.wgsl.
//
// "offset" is the starting offset in bytes, 4 bytes per floating
// point number. This matches the arrangement in bytes of the
// triangleVertices Float32Array.
const arrayStride = (4 * 3) + (4 * 2) + (4 * 3);
const vertexBufferLayouts = [
{
arrayStride: arrayStride,
attributes: [{
format: "float32x3",
offset: 0,
shaderLocation: 0,
}, {
format: "float32x2",
offset: 12,
shaderLocation: 1,
}, {
format: "float32x3",
offset: 20,
shaderLocation: 2,
}],
},
];
this.renderPipeline = device.createRenderPipeline({
label: `${label} render pipeline`,
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertexMain",
buffers: vertexBufferLayouts,
},
fragment: {
module: shaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat,
}]
},
primitive: {
topology: "triangle-list",
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: "less",
format: "depth24plus",
},
});
//////////////////////////////////////////////////////////////////////
// samplers and textures
//////////////////////////////////////////////////////////////////////
// default sampler configuration
this.sampler = device.createSampler();
this.texture = device.createTexture({
size: [image.width, image.height],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
});
device.queue.copyExternalImageToTexture(
{ source: image },
{ texture: this.texture },
{ width: image.width, height: image.height },
);
//////////////////////////////////////////////////////////////////////
// bind group
//////////////////////////////////////////////////////////////////////
this.bindGroups = [
device.createBindGroup({
label: "${label} bind group",
layout: bindGroupLayouts[0],
entries: [{
binding: 0,
resource: this.sampler,
}, {
binding: 1,
resource: this.texture,
}],
}),
];
//////////////////////////////////////////////////////////////////////
// buffer
//////////////////////////////////////////////////////////////////////
this.vertexBuffer = device.createBuffer({
label: `${label} vertex buffer`,
size: triangleVertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
// copy the (system RAM) triangleVertices to the (gpu RAM) vertexBuffer.
device.queue.writeBuffer(this.vertexBuffer, 0, triangleVertices);
}
render(renderPass)
{
renderPass.setPipeline(this.renderPipeline);
renderPass.setVertexBuffer(0, this.vertexBuffer);
renderPass.setBindGroup(0, this.bindGroups[0]);
const vertexCount = 3;
renderPass.draw(vertexCount);
}
}
// This "loadTriangleRenderer" function is separate from the
// constructor because Javascript class constructors can't be async
// functions.
async function loadTriangleTexturedRenderer(device, canvasFormat)
{
const triangleWgsl = await getPathText("triangleTextured.wgsl");
const image = new Image();
image.src = "texture.png";
await image.decode();
const renderer = new TriangleTexturedRenderer(device, canvasFormat, triangleWgsl, image);
return renderer;
}
export { loadTriangleTexturedRenderer };