support for multiple {buffers, meshes, primitives, vertex layouts}

This commit is contained in:
Zack Buhman 2026-07-21 13:12:36 -05:00
parent 2dfd749180
commit f781e5f134
2 changed files with 116 additions and 73 deletions

32
gltf.js
View File

@ -17,7 +17,9 @@ function parseGltfChunks(buffer)
console.assert(magic === 0x46546C67); console.assert(magic === 0x46546C67);
console.assert(version === 2); console.assert(version === 2);
const gltf = {}; const gltf = {
bin: [],
};
var offset = 12; var offset = 12;
while ((length - offset) > 0) { while ((length - offset) > 0) {
@ -34,8 +36,7 @@ function parseGltfChunks(buffer)
gltf.json = object; gltf.json = object;
} }
if (chunkType === 0x004E4942) { if (chunkType === 0x004E4942) {
console.assert(!("bin" in gltf)); gltf.bin.push({buffer: buffer, offset: offset, length: chunkLength});
gltf.bin = {buffer: buffer, offset: offset, length: chunkLength};
} }
offset += chunkLength; offset += chunkLength;
@ -127,24 +128,29 @@ function vertexFormat(componentType, type)
} }
} }
function primitiveVertexBufferLayouts(gltf, primitive) function primitiveAccessors(primitive)
{ {
const accessors = [ return [
gltf.json.accessors[primitive.attributes["POSITION"]], primitive.attributes["POSITION"],
gltf.json.accessors[primitive.attributes["NORMAL"]], primitive.attributes["NORMAL"],
gltf.json.accessors[primitive.attributes["TEXCOORD_0"]], primitive.attributes["TEXCOORD_0"],
primitive.attributes["TANGENT"],
]; ];
}
function vertexBufferLayouts(gltf, accessors)
{
const layouts = []; const layouts = [];
for (let i = 0; i < accessors.length; i++) { for (let i = 0; i < accessors.length; i++) {
const componentType = accessors[i].componentType; const accessor = gltf.json.accessors[accessors[i]];
const type = accessors[i].type; const componentType = accessor.componentType;
const type = accessor.type;
const bufferView = gltf.json.bufferViews[accessors[i].bufferView]; const bufferView = gltf.json.bufferViews[accessor.bufferView];
const arrayStride = "byteStride" in bufferView ? bufferView.byteStride : attributeStride(componentType, type); const arrayStride = "byteStride" in bufferView ? bufferView.byteStride : attributeStride(componentType, type);
const offset = "byteOffset" in accessors[i] ? accessors[i].byteOffset : 0; const offset = "byteOffset" in accessor ? accessor.byteOffset : 0;
const layout = { const layout = {
arrayStride: arrayStride, arrayStride: arrayStride,
attributes: [{ attributes: [{
@ -159,4 +165,4 @@ function primitiveVertexBufferLayouts(gltf, primitive)
return layouts; return layouts;
} }
export { parseGltfChunks, primitiveVertexBufferLayouts }; export { parseGltfChunks, vertexBufferLayouts, primitiveAccessors };

157
index2.js
View File

@ -1,23 +1,29 @@
import { getPath } from "./common.js"; import { getPath } from "./common.js";
import { parseGltfChunks, primitiveVertexBufferLayouts } from "./gltf.js"; import { parseGltfChunks, vertexBufferLayouts, primitiveAccessors } from "./gltf.js";
class GltfRenderer { class GltfRenderer {
constructor(device, canvasFormat, shaderModule, gltf, primitive, bufferLayouts) constructor(device, canvasFormat, shaderModule, gltf)
{ {
this.gltf = gltf; this.gltf = gltf;
this.primitive = primitive;
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// buffer // buffer
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
console.assert(gltf.json.buffers[0].byteLength == gltf.bin.length); this.buffers = [];
this.vertexBuffer = device.createBuffer({
label: "gltf vertex buffer 0", console.assert(gltf.json.buffers.length === gltf.bin.length);
size: gltf.bin.length, for (let i = 0; i < gltf.bin.length; i++) {
usage: GPUBufferUsage.INDEX | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, console.assert(gltf.json.buffers[i].byteLength == gltf.bin[i].length);
}); const buffer = device.createBuffer({
device.queue.writeBuffer(this.vertexBuffer, 0, gltf.bin.buffer, gltf.bin.offset, gltf.bin.length); label: `gltf vertex buffer {i}`,
size: gltf.bin[i].length,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(buffer, 0,
gltf.bin[i].buffer, gltf.bin[i].offset, gltf.bin[i].length);
this.buffers.push(buffer);
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// pipeline // pipeline
@ -39,30 +45,54 @@ class GltfRenderer {
bindGroupLayouts: bindGroupLayouts, bindGroupLayouts: bindGroupLayouts,
}); });
this.renderPipeline = device.createRenderPipeline({ // by vertex buffer layout
label: "gltf pipeline", const renderPipelineKeys = {};
layout: pipelineLayout, this.renderPipelines = {};
vertex: {
module: shaderModule, this.meshes = [];
entryPoint: "vertexMain", for (let i = 0; i < gltf.json.meshes.length; i++) {
buffers: bufferLayouts, const mesh = gltf.json.meshes[i];
}, const meshPrimitives = [];
fragment: {
module: shaderModule, for (let j = 0; j < mesh.primitives.length; j++) {
entryPoint: "fragmentMain", const accessors = primitiveAccessors(mesh.primitives[j]);
targets: [{ const key = accessors.join(";");
format: canvasFormat, meshPrimitives.push([key, accessors]);
}] renderPipelineKeys[key] = accessors;
}, }
primitive: { this.meshes.push(meshPrimitives);
topology: 'triangle-list', }
},
depthStencil: { for (let [key, accessors] of Object.entries(renderPipelineKeys)) {
depthWriteEnabled: true, const bufferLayouts = vertexBufferLayouts(gltf, accessors);
depthCompare: 'less',
format: 'depth24plus', const renderPipeline = device.createRenderPipeline({
}, label: "gltf pipeline",
}); layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertexMain",
buffers: bufferLayouts,
},
fragment: {
module: shaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat,
}]
},
primitive: {
topology: 'triangle-list',
},
depthStencil: {
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth24plus',
},
});
this.renderPipelines[key] = renderPipeline;
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// bind group // bind group
@ -87,6 +117,27 @@ class GltfRenderer {
]; ];
} }
renderPrimitive(renderPass, accessors, indices)
{
for (let i = 0; i < accessors.length; i++) {
const accessor = accessors[i];
const bufferViewIndex = this.gltf.json.accessors[accessor].bufferView;
const bufferView = this.gltf.json.bufferViews[bufferViewIndex];
renderPass.setVertexBuffer(i, this.buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength);
}
const indicesAccessor = this.gltf.json.accessors[indices];
const indicesBufferView = this.gltf.json.bufferViews[indicesAccessor.bufferView];
renderPass.setIndexBuffer(this.buffers[indicesBufferView.buffer],
"uint16",
indicesBufferView.byteOffset,
indicesBufferView.byteLength);
renderPass.setBindGroup(0, this.bindGroups[0]);
renderPass.drawIndexed(indicesAccessor.count);
}
render(device, colorView, depthTexture) render(device, colorView, depthTexture)
{ {
const encoder = device.createCommandEncoder(); const encoder = device.createCommandEncoder();
@ -105,27 +156,20 @@ class GltfRenderer {
}, },
}); });
renderPass.setPipeline(this.renderPipeline); var lastKey = undefined;
for (let i = 0; i < this.gltf.json.meshes.length; i++) {
const mesh = this.gltf.json.meshes[i];
for (let j = 0; j < mesh.primitives.length; j++) {
const [key, accessors] = this.meshes[i][j];
if (key !== lastKey)
renderPass.setPipeline(this.renderPipelines[key]);
lastKey = key;
const attributes = ["POSITION", "NORMAL", "TEXCOORD_0"]; const primitive = mesh.primitives[j];
for (let i = 0; i < attributes.length; i++) { this.renderPrimitive(renderPass, accessors, primitive.indices);
const attributeIndex = this.primitive.attributes[attributes[i]]; }
const bufferViewIndex = this.gltf.json.accessors[attributeIndex].bufferView;
const bufferView = this.gltf.json.bufferViews[bufferViewIndex];
renderPass.setVertexBuffer(i, this.vertexBuffer, bufferView.byteOffset, bufferView.byteLength);
} }
const indicesAccessor = this.gltf.json.accessors[this.primitive.indices];
const indicesBufferView = this.gltf.json.bufferViews[indicesAccessor.bufferView];
renderPass.setIndexBuffer(this.vertexBuffer,
"uint16",
indicesBufferView.byteOffset,
indicesBufferView.byteLength);
renderPass.setBindGroup(0, this.bindGroups[0]);
renderPass.drawIndexed(indicesAccessor.count);
renderPass.end(); renderPass.end();
const commandBuffer = encoder.finish(); const commandBuffer = encoder.finish();
@ -140,20 +184,13 @@ async function loadGltf(device, canvasFormat)
const gltf = parseGltfChunks(buffer); const gltf = parseGltfChunks(buffer);
console.log(gltf); console.log(gltf);
const primitive = gltf.json.meshes[0].primitives[0];
//console.log(primitive.indices);
//console.log(primitive.attributes);
const bufferLayouts = primitiveVertexBufferLayouts(gltf, primitive);
console.log(bufferLayouts);
const code = await getPath("gltf.wgsl"); const code = await getPath("gltf.wgsl");
const shaderModule = device.createShaderModule({ const shaderModule = device.createShaderModule({
label: "gltf module", label: "gltf module",
code: await code, code: await code,
}); });
const renderer = new GltfRenderer(device, canvasFormat, shaderModule, gltf, primitive, bufferLayouts); const renderer = new GltfRenderer(device, canvasFormat, shaderModule, gltf);
return renderer; return renderer;
} }