Skip to content

Commit

Permalink
Comments and deletions
Browse files Browse the repository at this point in the history
  • Loading branch information
cmhhelgeson committed Feb 27, 2024
1 parent 9563916 commit 129999a
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 14 deletions.
Binary file removed public/assets/gltf/RiggedSimple.glb
Binary file not shown.
Binary file removed public/assets/gltf/simpleSkin.glb
Binary file not shown.
5 changes: 2 additions & 3 deletions src/sample/skinnedMesh/glbUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { Mat4, Vec3, mat4 } from 'wgpu-matrix';

//NOTE: GLTF code is not generally extensible to all gltf models
// Modified from Will Usher code found at this link https://www.willusher.io/graphics/2023/05/16/0-to-gltf-first-mesh
// Determines the topology of our pipeline

// Associates the mode paramete of a gltf primitive object with the primitive's intended render mode
enum GLTFRenderMode {
POINTS = 0,
LINE = 1,
Expand Down Expand Up @@ -170,7 +171,6 @@ const gltfElementSize = (

// Convert differently depending on if the shader is a vertex or compute shader
const convertGPUVertexFormatToWGSLFormat = (vertexFormat: GPUVertexFormat) => {
console.log(vertexFormat);
switch (vertexFormat) {
case 'float32': {
return 'f32';
Expand Down Expand Up @@ -369,7 +369,6 @@ export class GLTFPrimitive {
}
);
VertexInputShaderString += '}';
console.log(VertexInputShaderString);

const vertexState: GPUVertexState = {
// Shader stage info
Expand Down
2 changes: 1 addition & 1 deletion src/sample/skinnedMesh/gridData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const gridVertices = new Float32Array([
]);

// Representing the indice of four bones that can influence each vertex
export const gridJoints = new Float32Array([
export const gridJoints = new Uint32Array([
0, 0, 0, 0, // Vertex 0 is influenced by bone 0
0, 0, 0, 0, // 1
0, 1, 0, 0, // 2
Expand Down
7 changes: 6 additions & 1 deletion src/sample/skinnedMesh/gridUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { gridVertices, gridIndices, gridJoints, gridWeights } from './gridData';

// Uses constant grid data to create appropriately sized GPU Buffers for our skinned grid
export const createSkinnedGridBuffers = (device: GPUDevice) => {
const createBuffer = (data: Float32Array, type: 'f32' | 'u32') => {
// Utility function that creates GPUBuffers from data
const createBuffer = (
data: Float32Array | Uint32Array,
type: 'f32' | 'u32'
) => {
const buffer = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.VERTEX,
Expand Down
53 changes: 44 additions & 9 deletions src/sample/skinnedMesh/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ enum SkinMode {
OFF,
}

// Copied from toji/gl-matrix
const getRotation = (mat: Mat4): Quat => {
// Initialize our output quaternion
const out = [0, 0, 0, 0];
// Extract the scaling factor from the final matrix transformation
// to normalize our rotation;
const scaling = mat4.getScaling(mat);
const is1 = 1 / scaling[0];
const is2 = 1 / scaling[1];
const is3 = 1 / scaling[2];

// Scale the matrix elements by the scaling factors
const sm11 = mat[0] * is1;
const sm12 = mat[1] * is2;
const sm13 = mat[2] * is3;
Expand All @@ -46,27 +51,34 @@ const getRotation = (mat: Mat4): Quat => {
const sm32 = mat[9] * is2;
const sm33 = mat[10] * is3;

// The trace of a square matrix is the sum of its diagonal entries
// While the matrix trace has many interesting mathematical properties,
// the primary purpose of the trace is to assess the characteristics of the rotation.
const trace = sm11 + sm22 + sm33;
let S = 0;

// If all matrix elements contribute equally to the rotation.
if (trace > 0) {
S = Math.sqrt(trace + 1.0) * 2;
out[3] = 0.25 * S;
out[0] = (sm23 - sm32) / S;
out[1] = (sm31 - sm13) / S;
out[2] = (sm12 - sm21) / S;
// If the rotation is primarily around the x-axis
} else if (sm11 > sm22 && sm11 > sm33) {
S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2;
out[3] = (sm23 - sm32) / S;
out[0] = 0.25 * S;
out[1] = (sm12 + sm21) / S;
out[2] = (sm31 + sm13) / S;
// If rotation is primarily around the y-axis
} else if (sm22 > sm33) {
S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2;
out[3] = (sm31 - sm13) / S;
out[0] = (sm12 + sm21) / S;
out[1] = 0.25 * S;
out[2] = (sm23 + sm32) / S;
// If the rotation is primarily around the z-axis
} else {
S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2;
out[3] = (sm12 - sm21) / S;
Expand Down Expand Up @@ -109,6 +121,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
skinMode: 'ON',
};

// Determine whether we want to render our whale or our skinned grid
gui.add(settings, 'object', ['Whale', 'Skinned Grid']).onChange(() => {
if (settings.object === 'Skinned Grid') {
settings.cameraX = -10;
Expand All @@ -126,6 +139,8 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
}
}
});

// Output the mesh normals, its joints, or the weights that influence the movement of the joints
gui
.add(settings, 'renderMode', ['NORMAL', 'JOINTS', 'WEIGHTS'])
.onChange(() => {
Expand All @@ -135,6 +150,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
new Uint32Array([RenderMode[settings.renderMode]])
);
});
// Determine whether the mesh is static or whether skinning is activated
gui.add(settings, 'skinMode', ['ON', 'OFF']).onChange(() => {
if (settings.object === 'Whale') {
if (settings.skinMode === 'OFF') {
Expand Down Expand Up @@ -207,11 +223,16 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
],
});

// Create whale resources
// Fetch whale resources from the glb file
const whaleScene = await fetch('../assets/gltf/whale.glb')
.then((res) => res.arrayBuffer())
.then((buffer) => convertGLBToJSONAndBinary(buffer, device));

// Builds a render pipeline for our whale mesh
// Since we are building a lightweight gltf parser around a gltf scene with a known
// quantity of meshes, we only build a renderPipeline for the singular mesh present
// within our scene. A more robust gltf parser would loop through all the meshes,
// cache replicated pipelines, and perform other optimizations.
whaleScene.meshes[0].buildRenderPipeline(
device,
gltfWGSL,
Expand All @@ -228,6 +249,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {

// Create skinned grid resources
const skinnedGridVertexBuffers = createSkinnedGridBuffers(device);
// Buffer for our uniforms, joints, and inverse bind matrices
const skinnedGridUniformBufferUsage: GPUBufferDescriptor = {
// 5 4x4 matrices, one for each bone
size: MAT4X4_BYTES * 5,
Expand Down Expand Up @@ -365,8 +387,10 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
const createBoneCollection = (numBones: number): BoneObject => {
// Initial bone transformation
const transforms: Mat4[] = [];
// Bone bind poses
// Bone bind poses, an extra matrix per joint/bone that represents the starting point
// of the bone before any transformations are applied
const bindPoses: Mat4[] = [];
// Create a transform, bind pose, and inverse bind pose for each bone
for (let i = 0; i < numBones; i++) {
transforms.push(mat4.identity());
bindPoses.push(mat4.identity());
Expand All @@ -385,6 +409,8 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
};
};

// Create bones of the skinned grid and write the inverse bind positions to
// the skinned grid's inverse bind matrix array
const gridBoneCollection = createBoneCollection(5);
for (let i = 0; i < gridBoneCollection.bindPosesInv.length; i++) {
device.queue.writeBuffer(
Expand All @@ -394,26 +420,31 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
);
}

const origMatrices = new Map();
// A map that maps a joint index to the original matrix transformation of a bone
const origMatrices = new Map<number, Mat4>();
const animWhaleSkin = (skin: GLTFSkin, angle: number) => {
console.log(skin.joints);
for (let i = 0; i < skin.joints.length; i++) {
// Index into the current joint
const joint = skin.joints[i];
// If our map does
if (!origMatrices.has(joint)) {
origMatrices.set(joint, whaleScene.nodes[joint].source.getMatrix());
}
// Get the original position, rotation, and scale of the current joint
const origMatrix = origMatrices.get(joint);
let m = mat4.create();
// Depending on which bone we are accessing, apply a specific rotation to the bone's original
// transformation to animate it
if (joint === 1 || joint === 0) {
m = mat4.rotateY(origMatrix, -angle);
} else if (joint === 3 || joint === 4) {
m = mat4.rotateX(origMatrix, joint === 3 ? angle : -angle);
} else {
m = mat4.rotateZ(origMatrix, angle);
}
if (joint !== 2) {
whaleScene.nodes[joint].source.position = mat4.getTranslation(m);
}
// Apply the current transformation to the transform values within the relevant nodes
// (these nodes, of course, each being nodes that represent joints/bones)
whaleScene.nodes[joint].source.position = mat4.getTranslation(m);
whaleScene.nodes[joint].source.scale = mat4.getScaling(m);
whaleScene.nodes[joint].source.rotation = getRotation(m);
}
Expand All @@ -434,7 +465,7 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
// Compute Transforms when angle is applied
animSkinnedGrid(gridBoneCollection.transforms, angle);

// Write to camera buffer
// Write to mvp to camera buffer
device.queue.writeBuffer(
cameraBuffer,
0,
Expand Down Expand Up @@ -500,13 +531,17 @@ const init: SampleInit = async ({ canvas, pageState, gui }) => {
}
passEncoder.end();
} else {
// Our skinned grid isn't checking for depth, so we pass it
// a separate render descriptor that does not take in a depth texture
const passEncoder = commandEncoder.beginRenderPass(
skinnedGridRenderPassDescriptor
);
passEncoder.setPipeline(skinnedGridPipeline);
passEncoder.setBindGroup(0, cameraBGCluster.bindGroups[0]);
passEncoder.setBindGroup(1, generalUniformsBGCLuster.bindGroups[0]);
passEncoder.setBindGroup(2, skinnedGridBoneBGCluster.bindGroups[0]);
// Pass in vertex and index buffers generated from our static skinned grid
// data at ./gridData.ts
passEncoder.setVertexBuffer(0, skinnedGridVertexBuffers.positions);
passEncoder.setVertexBuffer(1, skinnedGridVertexBuffers.joints);
passEncoder.setVertexBuffer(2, skinnedGridVertexBuffers.weights);
Expand All @@ -526,7 +561,7 @@ const skinnedMesh: () => JSX.Element = () =>
makeSample({
name: 'Skinned Mesh',
description:
'A demonstration of basic gltf loading and mesh skinning, ported from https://webgl2fundamentals.org/webgl/lessons/webgl-skinning.html. Mesh data, per vertex attributes, and skin inverseBindMatrices are taken from the json parsed from the binary output of the .glb file, with animated joint matrices updated and passed to shaders per frame via uniform buffers.',
'A demonstration of basic gltf loading and mesh skinning, ported from https://webgl2fundamentals.org/webgl/lessons/webgl-skinning.html. Mesh data, per vertex attributes, and skin inverseBindMatrices are taken from the json parsed from the binary output of the .glb file. Animations are generated progrmatically, with animated joint matrices updated and passed to shaders per frame via uniform buffers.',
init,
gui: true,
sources: [
Expand Down

0 comments on commit 129999a

Please sign in to comment.