render lamp with per-node translation

This commit is contained in:
Zack Buhman 2026-07-25 18:31:21 -05:00
parent 48b3929cd4
commit c60666e7a1
13 changed files with 627 additions and 27 deletions

View File

@ -14,6 +14,24 @@ clang++ \
-o rotate.wasm \
rotate.c
clang++ \
-v \
--target=wasm32 \
-O3 \
-flto \
-nostdlib \
-Wl,--no-entry \
-Wl,--export=mem_size \
-Wl,--export=mem_alloc \
-Wl,--export=node_alloc \
-Wl,--export=node_init_translation \
-Wl,--lto-O3 \
-Wl,--print-map \
-Wl,--import-memory \
-D_XM_NO_INTRINSICS_ \
-I. \
-o memory.wasm \
memory.cpp
# clang \
# -O1 \

25
gltf.js
View File

@ -7,6 +7,29 @@ function jsonChunk(buffer, offset, length)
return object;
}
async function splitGltf(jsonPath, binPath)
{
const jsonResponse = await fetch(jsonPath);
const jsonBuffer = await jsonResponse.arrayBuffer();
const json = jsonChunk(jsonBuffer, 0, jsonBuffer.byteLength);
const binResponse = await fetch(binPath);
const binBuffer = await binResponse.arrayBuffer();
return {
json: json,
bin: [{ buffer: binBuffer, offset: 0, length: binBuffer.byteLength }],
};
}
function transformMatrix()
{
}
function buildTransfomMatrices(json)
{
}
function parseGltfChunks(buffer)
{
const view = new DataView(buffer);
@ -165,4 +188,4 @@ function vertexBufferLayouts(gltf, accessors)
return layouts;
}
export { parseGltfChunks, vertexBufferLayouts, primitiveAccessors };
export { parseGltfChunks, vertexBufferLayouts, primitiveAccessors, splitGltf };

View File

@ -4,11 +4,20 @@ struct Configuration {
};
@group(0) @binding(0) var<uniform> config: Configuration;
@group(0) @binding(1) var<storage> nodeMatrix: array<mat4x4f>;
/*
struct Imm {
nodeIndex: i32,
};
var<immediate> imm: Imm;
*/
struct VertexInput {
@location(0) position: vec3f,
@location(1) normal: vec3f,
@location(2) texture: vec2f,
@builtin(instance_index) instance: u32,
};
struct VertexOutput {
@ -25,7 +34,8 @@ struct FragmentInput {
@vertex
fn vertexMain(input: VertexInput) -> VertexOutput
{
let position = vec4f(input.position, 1);
let position = nodeMatrix[input.instance] * vec4f(input.position, 1);
//let position = vec4f(input.position, 1);
let normal = vec4f(input.normal, 0.0f);
var output: VertexOutput;
output.position = config.matrix * position;

BIN
gltf/Image_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

BIN
gltf/Image_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
gltf/Image_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
gltf/Image_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

BIN
gltf/lamp.bin Normal file

Binary file not shown.

395
gltf/lamp.gltf Normal file
View File

@ -0,0 +1,395 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v5.2.39",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
3
]
}
],
"nodes":[
{
"mesh":0,
"name":"LanternPole_Body",
"translation":[
-3.8231542110443115,
13.016030311584473,
0
]
},
{
"mesh":1,
"name":"LanternPole_Chain",
"translation":[
-9.582000732421875,
21.037872314453125,
0
]
},
{
"mesh":2,
"name":"LanternPole_Lantern",
"translation":[
-9.58200740814209,
18.009151458740234,
0
]
},
{
"children":[
0,
1,
2
],
"name":"Lantern",
"rotation":[
0,
1,
0,
0
]
}
],
"materials":[
{
"emissiveFactor":[
1,
1,
1
],
"emissiveTexture":{
"index":0
},
"name":"LanternPost_Mat",
"normalTexture":{
"index":1
},
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":2
},
"metallicRoughnessTexture":{
"index":3
}
}
}
],
"meshes":[
{
"name":"LanternPole_Body",
"primitives":[
{
"attributes":{
"POSITION":0,
"NORMAL":1,
"TEXCOORD_0":2,
"TANGENT":3
},
"indices":4,
"material":0
}
]
},
{
"name":"LanternPole_Chain",
"primitives":[
{
"attributes":{
"POSITION":5,
"NORMAL":6,
"TEXCOORD_0":7,
"TANGENT":8
},
"indices":9,
"material":0
}
]
},
{
"name":"LanternPole_Lantern",
"primitives":[
{
"attributes":{
"POSITION":10,
"NORMAL":11,
"TEXCOORD_0":12,
"TANGENT":13
},
"indices":14,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
},
{
"sampler":0,
"source":1
},
{
"sampler":0,
"source":2
},
{
"sampler":0,
"source":3
}
],
"images":[
{
"mimeType":"image/png",
"name":"Image_3",
"uri":"Image_3.png"
},
{
"mimeType":"image/png",
"name":"Image_2",
"uri":"Image_2.png"
},
{
"mimeType":"image/png",
"name":"Image_0",
"uri":"Image_0.png"
},
{
"mimeType":"image/png",
"name":"Image_1",
"uri":"Image_1.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":941,
"max":[
7.745599269866943,
12.832109451293945,
2.3157098293304443
],
"min":[
-7.745599269866943,
-12.832109451293945,
-2.3157098293304443
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":941,
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":941,
"type":"VEC2"
},
{
"bufferView":3,
"componentType":5126,
"count":941,
"type":"VEC4"
},
{
"bufferView":4,
"componentType":5123,
"count":2616,
"type":"SCALAR"
},
{
"bufferView":5,
"componentType":5126,
"count":756,
"max":[
0.12920807301998138,
0.6523504257202148,
0.12920807301998138
],
"min":[
-0.12920807301998138,
-0.6523504257202148,
-0.12920807301998138
],
"type":"VEC3"
},
{
"bufferView":6,
"componentType":5126,
"count":756,
"type":"VEC3"
},
{
"bufferView":7,
"componentType":5126,
"count":756,
"type":"VEC2"
},
{
"bufferView":8,
"componentType":5126,
"count":756,
"type":"VEC4"
},
{
"bufferView":9,
"componentType":5123,
"count":3744,
"type":"SCALAR"
},
{
"bufferView":10,
"componentType":5126,
"count":2543,
"max":[
1.034082293510437,
2.5292809009552,
1.034084677696228
],
"min":[
-1.034082293510437,
-2.5292809009552,
-1.034084677696228
],
"type":"VEC3"
},
{
"bufferView":11,
"componentType":5126,
"count":2543,
"type":"VEC3"
},
{
"bufferView":12,
"componentType":5126,
"count":2543,
"type":"VEC2"
},
{
"bufferView":13,
"componentType":5126,
"count":2543,
"type":"VEC4"
},
{
"bufferView":14,
"componentType":5123,
"count":9822,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":11292,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":11292,
"byteOffset":11292,
"target":34962
},
{
"buffer":0,
"byteLength":7528,
"byteOffset":22584,
"target":34962
},
{
"buffer":0,
"byteLength":15056,
"byteOffset":30112,
"target":34962
},
{
"buffer":0,
"byteLength":5232,
"byteOffset":45168,
"target":34963
},
{
"buffer":0,
"byteLength":9072,
"byteOffset":50400,
"target":34962
},
{
"buffer":0,
"byteLength":9072,
"byteOffset":59472,
"target":34962
},
{
"buffer":0,
"byteLength":6048,
"byteOffset":68544,
"target":34962
},
{
"buffer":0,
"byteLength":12096,
"byteOffset":74592,
"target":34962
},
{
"buffer":0,
"byteLength":7488,
"byteOffset":86688,
"target":34963
},
{
"buffer":0,
"byteLength":30516,
"byteOffset":94176,
"target":34962
},
{
"buffer":0,
"byteLength":30516,
"byteOffset":124692,
"target":34962
},
{
"buffer":0,
"byteLength":20344,
"byteOffset":155208,
"target":34962
},
{
"buffer":0,
"byteLength":40688,
"byteOffset":175552,
"target":34962
},
{
"buffer":0,
"byteLength":19644,
"byteOffset":216240,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":235884,
"uri":"lamp.bin"
}
]
}

View File

@ -256,6 +256,13 @@ const rotate = await WebAssembly.instantiateStreaming(fetch("rotate.wasm"), {
env: { memory: memory }
});
const memory2 = new WebAssembly.Memory({ initial: 2 });
const mem = await WebAssembly.instantiateStreaming(fetch("memory.wasm"), {
env: { memory: memory2 }
});
console.log("mem");
document.mem = mem;
const uniformBuffer = device.createBuffer({
label: "grid uniform",
size: uniformArraySize,
@ -432,7 +439,8 @@ function render()
}
//requestAnimationFrame(render);
const gltfRenderer = await loadGltf(device, canvasFormat);
const gltfRenderer = await loadGltf(device, canvasFormat, memory2, mem);
function render2()
{
recreateDepth();

121
index2.js
View File

@ -1,5 +1,40 @@
import { getPath } from "./common.js";
import { parseGltfChunks, vertexBufferLayouts, primitiveAccessors } from "./gltf.js";
import { parseGltfChunks, vertexBufferLayouts, primitiveAccessors, splitGltf } from "./gltf.js";
function loadGltfNode(mem, handle, gltf, nodeIndex)
{
const node = gltf.json.nodes[nodeIndex];
var translation = [0, 0, 0];
if ("translation" in node) {
translation = node.translation;
}
console.log("init", nodeIndex, translation);
mem.instance.exports.node_init_translation(handle, nodeIndex, translation[0], translation[1], translation[2]);
if ("children" in node) {
for (let childIndex in node.children) {
loadGltfNode(mem, handle, gltf, childIndex);
}
}
}
function loadGltfNodes(mem, gltf)
{
const nodesHandle = mem.instance.exports.node_alloc(gltf.json.nodes.length);
for (let nodeIndex of gltf.json.scenes[0].nodes) {
loadGltfNode(mem, nodesHandle, gltf, nodeIndex);
}
return nodesHandle;
}
function printMatrix(m, o)
{
console.log(m[o+0], m[o+1], m[o+2], m[o+3]);
console.log(m[o+4], m[o+5], m[o+6], m[o+7]);
console.log(m[o+8], m[o+9], m[o+10], m[o+11]);
console.log(m[o+12], m[o+13], m[o+14], m[o+15]);
}
class GltfRenderer {
constructor(device, canvasFormat, shaderModule, gltf)
@ -12,9 +47,9 @@ class GltfRenderer {
this.buffers = [];
console.assert(gltf.json.buffers.length === gltf.bin.length);
//console.assert(gltf.json.buffers.length === gltf.bin.length);
for (let i = 0; i < gltf.bin.length; i++) {
console.assert(gltf.json.buffers[i].byteLength == gltf.bin[i].length);
//console.assert(gltf.json.buffers[i].byteLength == gltf.bin[i].length);
const buffer = device.createBuffer({
label: `gltf vertex buffer {i}`,
size: gltf.bin[i].length,
@ -36,6 +71,10 @@ class GltfRenderer {
binding: 0,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "uniform" }
}, {
binding: 1,
visibility: GPUShaderStage.VERTEX,
buffer: { type: "read-only-storage" }
}]
}),
];
@ -98,13 +137,21 @@ class GltfRenderer {
// bind group
//////////////////////////////////////////////////////////////////////
this.uniformBufferSize = (4 * 4 * 4) * 2;
const matrixSize = 4 * 4 * 4;
this.uniformBufferSize = matrixSize * 2;
this.uniformBuffer = device.createBuffer({
label: "gltf uniform 0",
size: this.uniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
this.nodeBufferSize = matrixSize * this.gltf.json.nodes.length;
this.nodeBuffer = device.createBuffer({
label: "gltf node buffer",
size: this.nodeBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
this.bindGroups = [
device.createBindGroup({
label: "cell bind group 0",
@ -112,12 +159,15 @@ class GltfRenderer {
entries: [{
binding: 0,
resource: { buffer: this.uniformBuffer },
}, {
binding: 1,
resource: { buffer: this.nodeBuffer },
}]
}),
];
}
renderPrimitive(renderPass, accessors, indices)
renderPrimitive(renderPass, accessors, indices, nodeIndex)
{
for (let i = 0; i < accessors.length; i++) {
const accessor = accessors[i];
@ -135,7 +185,33 @@ class GltfRenderer {
indicesBufferView.byteLength);
renderPass.setBindGroup(0, this.bindGroups[0]);
renderPass.drawIndexed(indicesAccessor.count);
//console.log("drawIndexed", nodeIndex);
renderPass.drawIndexed(indicesAccessor.count, 1, 0, 0, nodeIndex);
//renderPass.drawIndexed(indicesAccessor.count);
}
renderNode(renderPass, lastKey, nodeIndex)
{
const node = this.gltf.json.nodes[nodeIndex];
if ("mesh" in node) {
const mesh = this.gltf.json.meshes[node.mesh];
for (let j = 0; j < mesh.primitives.length; j++) {
const [key, accessors] = this.meshes[node.mesh][j];
if (key !== lastKey)
renderPass.setPipeline(this.renderPipelines[key]);
lastKey = key;
const primitive = mesh.primitives[j];
this.renderPrimitive(renderPass, accessors, primitive.indices, nodeIndex);
}
}
if ("children" in node) {
for (let childIndex of node.children) {
lastKey = this.renderNode(renderPass, lastKey, childIndex);
}
}
return lastKey;
}
render(device, colorView, depthTexture)
@ -157,17 +233,8 @@ class GltfRenderer {
});
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 primitive = mesh.primitives[j];
this.renderPrimitive(renderPass, accessors, primitive.indices);
}
for (let nodeIndex of this.gltf.json.scenes[0].nodes) {
lastKey = this.renderNode(renderPass, lastKey, nodeIndex);
}
renderPass.end();
@ -177,12 +244,17 @@ class GltfRenderer {
}
}
async function loadGltf(device, canvasFormat)
async function loadGltf(device, canvasFormat, memory, mem)
{
const response = await fetch("monkey.glb");
const buffer = await response.arrayBuffer();
const gltf = parseGltfChunks(buffer);
console.log(gltf);
const gltf = await splitGltf("gltf/lamp.gltf", "gltf/lamp.bin");
const nodesHandle = loadGltfNodes(mem, gltf);
const nodesM = new Float32Array(memory.buffer, nodesHandle);
for (let i = 0; i < gltf.json.nodes.length; i++) {
console.log("[", i, "]");
printMatrix(nodesM, i * 16);
}
const code = await getPath("gltf.wgsl");
const shaderModule = device.createShaderModule({
@ -190,7 +262,10 @@ async function loadGltf(device, canvasFormat)
code: await code,
});
const renderer = new GltfRenderer(device, canvasFormat, shaderModule, gltf);
const renderer = new GltfRenderer(device, canvasFormat, shaderModule, gltf, nodesHandle);
console.log(renderer.nodeBuffer);
device.queue.writeBuffer(renderer.nodeBuffer, 0,
memory.buffer, nodesHandle, (4 * 4 * 4) * gltf.json.nodes.length);
return renderer;
}

71
memory.cpp Normal file
View File

@ -0,0 +1,71 @@
#include <stddef.h>
#include "directxmath/directxmath.h"
//size_t __builtin_wasm_memory_grow(int, size_t);
//size_t __builtin_wasm_memory_size(int);
extern char __heap_base[];
extern char __heap_end[];
static size_t heap_pages = 0;
static size_t alloc_index = 0;
extern "C" {
size_t mem_size();
size_t mem_alloc(size_t length);
size_t node_alloc(int count);
void node_init_translation(size_t handle, int index, float x, float y, float z);
};
static inline size_t memory_heap_base()
{
return (size_t)&__heap_base[0];
}
static inline size_t align(size_t n, size_t m)
{
return (n + (m - 1)) & (~(m - 1));
}
static inline void memory_grow(size_t npages)
{
__builtin_wasm_memory_grow(0, npages);
}
size_t mem_size()
{
return __builtin_wasm_memory_size(0);
}
size_t mem_alloc(size_t length)
{
size_t base = memory_heap_base();
size_t heap_end = (heap_pages << 16);
size_t address = alloc_index;
alloc_index += align(length, 16);
if (heap_end < alloc_index) {
size_t npages = align(alloc_index - heap_end, 65536) >> 16;
memory_grow(npages);
heap_pages += npages;
}
return base + address;
}
size_t node_alloc(int count)
{
return mem_alloc((sizeof (XMFLOAT4X4)) * count);
}
void node_init_translation(size_t handle, int index, float x, float y, float z)
{
XMFLOAT4X4 * nodes = reinterpret_cast<XMFLOAT4X4 *>(handle);
//XMMATRIX XMLoadFloat4x4(&nodes[index]);
XMMATRIX mat = XMMatrixTranslation(x, y, z);
XMStoreFloat4x4(&nodes[index], mat);
}

View File

@ -9,7 +9,7 @@ void rotate(float angle, float aspect, float * buffer)
//XMMATRIX mat2 = XMMatrixTranslation(0, 0, 1);
//XMStoreFloat4x4((XMFLOAT4X4*)buffer, mat * mat2);
float d = 1.9;
float d = 50.9;
XMVECTOR eye = XMVectorSet(d, d / 2, d, 0);
XMVECTOR at = XMVectorSet(0, 0, 0, 0);
XMVECTOR up = XMVectorSet(0, 1, 0, 0);