194 lines
5.6 KiB
JavaScript
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 };
|