372 lines
10 KiB
JavaScript
372 lines
10 KiB
JavaScript
const cubeResponse = await fetch("obj/cube.bin");
|
|
const cubeObj = await cubeResponse.arrayBuffer();
|
|
|
|
const cubeView = new DataView(cubeObj);
|
|
const indexCount = cubeView.getUint32(0, true);
|
|
const indexBufferOffset = cubeView.getUint32(4, true);
|
|
const indexBufferSize = cubeView.getUint32(8, true);
|
|
const vertexBufferOffset = cubeView.getUint32(12, true);
|
|
const vertexBufferSize = cubeView.getUint32(16, true);
|
|
//console.log(`indexBufferOffset ${indexBufferOffset} indexBufferSize ${indexBufferSize}`);
|
|
//console.log(`vertexBufferOffset ${vertexBufferOffset} vertexBufferSize ${vertexBufferSize}`);
|
|
const cubeIndexCount = indexBufferSize / 2;
|
|
|
|
const cubeVerticesStride = 36;
|
|
|
|
const indexVertexBuffer = device.createBuffer({
|
|
label: "cell vertices",
|
|
size: indexBufferSize + vertexBufferSize,
|
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(indexVertexBuffer, 0, cubeObj, indexBufferOffset, indexBufferSize);
|
|
device.queue.writeBuffer(indexVertexBuffer, indexBufferSize, cubeObj, vertexBufferOffset, vertexBufferSize);
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// instance buffer
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
async function loadVoxModel(path)
|
|
{
|
|
const voxResponse = await fetch(path);
|
|
const vox = await voxResponse.arrayBuffer();
|
|
const voxView = new DataView(vox);
|
|
const sizeX = voxView.getUint32(0, true);
|
|
const sizeY = voxView.getUint32(4, true);
|
|
const sizeZ = voxView.getUint32(8, true);
|
|
const voxelCount = voxView.getUint32(12, true);
|
|
|
|
const instanceBuffer = device.createBuffer({
|
|
label: `instanceBuffer %{}`,
|
|
size: voxelCount * 4,
|
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(instanceBuffer, 0, vox, 16, voxelCount * 4);
|
|
|
|
const modelConfigurationBuffer = device.createBuffer({
|
|
label: "modelConfigurationBuffer",
|
|
size: 4 * 4 + 256 * 4 * 4,
|
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
});
|
|
const paletteOffset = 16 + voxelCount * 4;
|
|
|
|
const sizeArray = new Float32Array([sizeX, sizeY, sizeZ, 0]);
|
|
device.queue.writeBuffer(modelConfigurationBuffer, 0, sizeArray);
|
|
device.queue.writeBuffer(modelConfigurationBuffer, 4 * 4, vox, paletteOffset, 256 * 4 * 4);
|
|
|
|
return {
|
|
instanceBuffer: instanceBuffer,
|
|
modelConfigurationBuffer: modelConfigurationBuffer,
|
|
voxelCount: voxelCount,
|
|
path: path,
|
|
}
|
|
}
|
|
|
|
const modelPaths = [
|
|
"vox/monu1.bin",
|
|
"vox/monu7.bin",
|
|
"vox/monu9.bin",
|
|
"vox/monu16.bin",
|
|
"vox/chr_fox.bin",
|
|
"vox/ff3.bin",
|
|
"vox/dragon.bin",
|
|
];
|
|
|
|
const models = {}
|
|
for (const path of modelPaths) {
|
|
models[path] = loadVoxModel(path);
|
|
}
|
|
for (const path of modelPaths) {
|
|
models[path] = await models[path];
|
|
}
|
|
var currentModel = modelPaths[0];
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// vertex layout
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const vertexBufferLayouts = [
|
|
{
|
|
arrayStride: cubeVerticesStride,
|
|
attributes: [{
|
|
format: "float32x3",
|
|
offset: 0,
|
|
shaderLocation: 0,
|
|
}, {
|
|
format: "float32x2",
|
|
offset: 12,
|
|
shaderLocation: 1,
|
|
}, {
|
|
format: "float16x4",
|
|
offset: 20,
|
|
shaderLocation: 2,
|
|
}, {
|
|
format: "float16x4",
|
|
offset: 28,
|
|
shaderLocation: 3,
|
|
}],
|
|
stepMode: "vertex",
|
|
},
|
|
{
|
|
arrayStride: 4,
|
|
attributes: [{
|
|
format: "uint8x4",
|
|
offset: 0,
|
|
shaderLocation: 4,
|
|
}],
|
|
stepMode: "instance",
|
|
},
|
|
];
|
|
|
|
const indexWgsl = getPath("index.wgsl");
|
|
const computeWgsl = getPath("compute.wgsl");
|
|
|
|
const cellShaderModule = device.createShaderModule({
|
|
label: "cell shader",
|
|
code: await indexWgsl,
|
|
});
|
|
|
|
const bindGroupLayouts = [
|
|
device.createBindGroupLayout({
|
|
label: "bind group layout 0",
|
|
entries: [{
|
|
binding: 0,
|
|
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
|
|
buffer: { type: "uniform" }
|
|
}, {
|
|
binding: 1,
|
|
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
|
|
buffer: { type: "read-only-storage" }
|
|
}, {
|
|
binding: 2,
|
|
visibility: GPUShaderStage.COMPUTE,
|
|
buffer: { type: "storage" }
|
|
}]
|
|
}),
|
|
device.createBindGroupLayout({
|
|
label: "bind group layout 1",
|
|
entries: [{
|
|
binding: 0,
|
|
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
buffer: { type: "uniform" }
|
|
}]
|
|
}),
|
|
];
|
|
|
|
const pipelineLayout = device.createPipelineLayout({
|
|
label: "pipeline layout",
|
|
bindGroupLayouts: bindGroupLayouts,
|
|
});
|
|
|
|
const cellPipeline = device.createRenderPipeline({
|
|
label: "cell pipeline",
|
|
layout: pipelineLayout,
|
|
vertex: {
|
|
module: cellShaderModule,
|
|
entryPoint: "vertexMain",
|
|
buffers: vertexBufferLayouts,
|
|
},
|
|
fragment: {
|
|
module: cellShaderModule,
|
|
entryPoint: "fragmentMain",
|
|
targets: [{
|
|
format: canvasFormat,
|
|
}]
|
|
},
|
|
primitive: {
|
|
topology: 'triangle-list',
|
|
},
|
|
depthStencil: {
|
|
depthWriteEnabled: true,
|
|
depthCompare: 'less',
|
|
format: 'depth24plus',
|
|
},
|
|
});
|
|
|
|
const computeShaderModule = device.createShaderModule({
|
|
label: "compute shader",
|
|
code: await computeWgsl,
|
|
});
|
|
|
|
const computePipelineLayout = device.createPipelineLayout({
|
|
label: "compute pipeline layout",
|
|
bindGroupLayouts: [ bindGroupLayouts[0] ],
|
|
});
|
|
|
|
const computePipeline = device.createComputePipeline({
|
|
label: "compute pipeline",
|
|
layout: computePipelineLayout,
|
|
compute: {
|
|
module: computeShaderModule,
|
|
entryPoint: "computeMain",
|
|
},
|
|
});
|
|
|
|
const gridSize = 32;
|
|
const workGroupSize = 16;
|
|
|
|
//const uniformArray2 = new Float32Array([gridSize, gridSize, 1, 1]);
|
|
const memory = new WebAssembly.Memory({ initial: 1 });
|
|
const uniformArray = new Float32Array(memory.buffer);
|
|
uniformArray[0] = 1;
|
|
uniformArray[1] = 0;
|
|
uniformArray[2] = 0;
|
|
uniformArray[3] = 0;
|
|
const uniformArraySize = 4 * 4 + (4 * 4 * 4) * 2;
|
|
const rotate = await WebAssembly.instantiateStreaming(fetch("rotate.wasm"), {
|
|
env: { memory: memory }
|
|
});
|
|
|
|
const uniformBuffer = device.createBuffer({
|
|
label: "grid uniform",
|
|
size: uniformArraySize,
|
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
});
|
|
device.queue.writeBuffer(uniformBuffer, 0, uniformArray, 0, uniformArraySize / 4);
|
|
|
|
const cellStateArray = new Uint32Array(gridSize * gridSize);
|
|
|
|
const cellStateStorage = [
|
|
device.createBuffer({
|
|
label: "cell state 0",
|
|
size: cellStateArray.byteLength,
|
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
}),
|
|
device.createBuffer({
|
|
label: "cell state 1",
|
|
size: cellStateArray.byteLength,
|
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
}),
|
|
];
|
|
|
|
for (let i = 0; i < cellStateArray.length; i += 1) {
|
|
cellStateArray[i] = Math.random() > 0.6 ? 1 : 0;
|
|
}
|
|
device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray);
|
|
device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray);
|
|
|
|
const bindGroups = [
|
|
device.createBindGroup({
|
|
label: "cell bind group 0",
|
|
layout: bindGroupLayouts[0],
|
|
entries: [{
|
|
binding: 0,
|
|
resource: { buffer: uniformBuffer },
|
|
}, {
|
|
binding: 1,
|
|
resource: { buffer: cellStateStorage[0] },
|
|
}, {
|
|
binding: 2,
|
|
resource: { buffer: cellStateStorage[1] },
|
|
}],
|
|
}),
|
|
device.createBindGroup({
|
|
label: "cell bind group 1",
|
|
layout: bindGroupLayouts[0],
|
|
entries: [{
|
|
binding: 0,
|
|
resource: { buffer: uniformBuffer },
|
|
}, {
|
|
binding: 1,
|
|
resource: { buffer: cellStateStorage[1] },
|
|
}, {
|
|
binding: 2,
|
|
resource: { buffer: cellStateStorage[0] },
|
|
}],
|
|
})
|
|
];
|
|
|
|
const modelBindGroups = {};
|
|
for (const path of modelPaths) {
|
|
const bindGroup = device.createBindGroup({
|
|
label: "palette bind group",
|
|
layout: bindGroupLayouts[1],
|
|
entries: [{
|
|
binding: 0,
|
|
resource: { buffer: models[path].modelConfigurationBuffer },
|
|
}]
|
|
});
|
|
modelBindGroups[path] = bindGroup;
|
|
}
|
|
|
|
let tick = 0;
|
|
let which = 0;
|
|
|
|
function render()
|
|
{
|
|
recreateDepth();
|
|
|
|
const encoder = device.createCommandEncoder();
|
|
|
|
const computePass = encoder.beginComputePass();
|
|
computePass.setPipeline(computePipeline);
|
|
computePass.setBindGroup(0, bindGroups[which]);
|
|
const workgroupCount = Math.ceil((gridSize * gridSize) / workGroupSize);
|
|
computePass.dispatchWorkgroups(workgroupCount);
|
|
computePass.end();
|
|
|
|
const aspect = canvas.width / canvas.height;
|
|
rotate.instance.exports.rotate(tick * 0.01, aspect, 4 * 4);
|
|
|
|
tick += 1;
|
|
if ((tick % 5) == 0) {
|
|
which ^= 1;
|
|
}
|
|
|
|
if (lightingSelect.value === "diffuse") {
|
|
uniformArray[0] = 1;
|
|
} else {
|
|
uniformArray[0] = 0;
|
|
}
|
|
device.queue.writeBuffer(uniformBuffer, 0, uniformArray, 0, uniformArraySize / 4);
|
|
|
|
const renderPass = encoder.beginRenderPass({
|
|
colorAttachments: [{
|
|
view: context.getCurrentTexture().createView(),
|
|
loadOp: "clear",
|
|
clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 },
|
|
storeOp: "store",
|
|
}],
|
|
depthStencilAttachment: {
|
|
view: depthTexture.createView(),
|
|
depthClearValue: 1.0,
|
|
depthLoadOp: 'clear',
|
|
depthStoreOp: 'store',
|
|
},
|
|
});
|
|
|
|
currentModel = modelSelect.value;
|
|
|
|
renderPass.setPipeline(cellPipeline);
|
|
const vertexOffset = indexBufferSize;
|
|
renderPass.setVertexBuffer(0, indexVertexBuffer, vertexOffset, vertexBufferSize);
|
|
renderPass.setVertexBuffer(1, models[currentModel].instanceBuffer);
|
|
renderPass.setIndexBuffer(indexVertexBuffer, "uint16", 0, indexBufferSize);
|
|
renderPass.setBindGroup(0, bindGroups[which]);
|
|
renderPass.setBindGroup(1, modelBindGroups[currentModel]);
|
|
//renderPass.draw(cubeVerticesCount, gridSize * gridSize);
|
|
//renderPass.drawIndexed(cubeIndexCount, gridSize * gridSize);
|
|
//renderPass.drawIndexed(cubeIndexCount);
|
|
renderPass.drawIndexed(cubeIndexCount, models[currentModel].voxelCount);
|
|
|
|
renderPass.end();
|
|
|
|
const commandBuffer = encoder.finish();
|
|
device.queue.submit([commandBuffer]);
|
|
|
|
requestAnimationFrame(render)
|
|
}
|
|
//requestAnimationFrame(render);
|
|
|
|
|
|
const modelSelect = document.getElementById("model-select");
|
|
for (const path of modelPaths) {
|
|
const option = document.createElement("option");
|
|
option.value = path;
|
|
option.innerHTML = path;
|
|
modelSelect.appendChild(option);
|
|
}
|
|
modelSelect.value = currentModel;
|
|
|
|
const lightingSelect = document.getElementById("lighting-select");
|
|
lightingSelect.value = "unlit";
|