diff --git a/public/AntiqueCamera.bin b/public/AntiqueCamera.bin new file mode 100644 index 0000000..362c59a Binary files /dev/null and b/public/AntiqueCamera.bin differ diff --git a/public/AntiqueCamera.gltf b/public/AntiqueCamera.gltf new file mode 100644 index 0000000..37a23ca --- /dev/null +++ b/public/AntiqueCamera.gltf @@ -0,0 +1,338 @@ +{ + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5123, + "count" : 18360, + "max" : [ + 6509 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 6510, + "max" : [ + 2.6307950019836426, + 9.506563186645508, + 2.852458953857422 + ], + "min" : [ + -3.324300527572632, + -0.0014147758483886719, + -1.7042126655578613 + ], + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 6510, + "max" : [ + 1, + 1, + 1 + ], + "min" : [ + -1, + -1, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 3, + "componentType" : 5126, + "count" : 6510, + "max" : [ + 0.9990595579147339, + 0.9990596017451026 + ], + "min" : [ + 0.0009403982548974454, + 0.003113090991973877 + ], + "type" : "VEC2" + }, + { + "bufferView" : 4, + "componentType" : 5123, + "count" : 41838, + "max" : [ + 14667 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 5, + "componentType" : 5126, + "count" : 14668, + "max" : [ + 1.8916611671447754, + 12.645012855529785, + 1.2003434896469116 + ], + "min" : [ + -1.8916611671447754, + 9.395228385925293, + -1.200343370437622 + ], + "type" : "VEC3" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 14668, + "max" : [ + 1, + 1, + 1 + ], + "min" : [ + -1, + -1, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 7, + "componentType" : 5126, + "count" : 14668, + "max" : [ + 0.9987892508506775, + 0.9987891529453918 + ], + "min" : [ + 0.0012108470546081662, + 0.008463442325592041 + ], + "type" : "VEC2" + } + ], + "asset" : { + "generator" : "Khronos Blender glTF 2.0 I/O", + "version" : "2.0" + }, + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 36720, + "byteOffset" : 0, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 78120, + "byteOffset" : 36720, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 78120, + "byteOffset" : 114840, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 52080, + "byteOffset" : 192960, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 83676, + "byteOffset" : 245040, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 176016, + "byteOffset" : 328716, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 176016, + "byteOffset" : 504732, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 117344, + "byteOffset" : 680748, + "target" : 34962 + } + ], + "buffers" : [ + { + "byteLength" : 798092, + "uri" : "AntiqueCamera.bin" + } + ], + "extensions" : {}, + "images" : [ + { + "name" : "camera_camera_Normal", + "uri" : "camera_camera_Normal.png" + }, + { + "name" : "camera_camera_BaseColor", + "uri" : "camera_camera_BaseColor.png" + }, + { + "name" : "camera_tripod_BaseColor", + "uri" : "camera_tripod_BaseColor.png" + }, + { + "name" : "camera_tripod_Normal", + "uri" : "camera_tripod_Normal.png" + }, + { + "name" : "camera_camera_Roughness", + "uri" : "camera_camera_Roughness.png" + }, + { + "name" : "camera_tripod_Roughness", + "uri" : "camera_tripod_Roughness.png" + } + ], + "materials" : [ + { + "name" : "camera", + "normalTexture" : { + "index" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1 + }, + "metallicRoughnessTexture" : { + "index" : 4 + } + } + }, + { + "name" : "tripod", + "normalTexture" : { + "index" : 3 + }, + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 2 + }, + "metallicRoughnessTexture" : { + "index" : 5 + } + } + } + ], + "meshes" : [ + { + "name" : "tripod_uv", + "primitives" : [ + { + "attributes" : { + "NORMAL" : 2, + "POSITION" : 1, + "TEXCOORD_0" : 3 + }, + "indices" : 0, + "material" : 1 + } + ] + }, + { + "name" : "camera_uv", + "primitives" : [ + { + "attributes" : { + "NORMAL" : 6, + "POSITION" : 5, + "TEXCOORD_0" : 7 + }, + "indices" : 4, + "material" : 0 + } + ] + } + ], + "nodes" : [ + { + "mesh" : 1, + "name" : "camera", + "rotation" : [ + 0, + -0.7071068286895752, + 0, + 0.7071067690849304 + ], + "scale" : [ + 0.5700311064720154, + 0.5700311064720154, + 0.5700311064720154 + ] + }, + { + "mesh" : 0, + "name" : "tripod", + "rotation" : [ + 0, + -0.7071068286895752, + 0, + 0.7071067690849304 + ], + "scale" : [ + 0.5700311064720154, + 0.5700311064720154, + 0.5700311064720154 + ] + } + ], + "samplers" : [ + {} + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0, + 1 + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 1 + }, + { + "sampler" : 0, + "source" : 2 + }, + { + "sampler" : 0, + "source" : 3 + }, + { + "sampler" : 0, + "source" : 4 + }, + { + "sampler" : 0, + "source" : 5 + } + ] +} diff --git a/public/Cube.bin b/public/Cube.bin new file mode 100644 index 0000000..7dae4b0 Binary files /dev/null and b/public/Cube.bin differ diff --git a/public/Cube.gltf b/public/Cube.gltf new file mode 100644 index 0000000..bc873da --- /dev/null +++ b/public/Cube.gltf @@ -0,0 +1,193 @@ +{ + "accessors" : [ + { + "bufferView" : 0, + "byteOffset" : 0, + "componentType" : 5123, + "count" : 36, + "max" : [ + 35 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 1, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000001 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + { + "bufferView" : 2, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC3" + }, + { + "bufferView" : 3, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + -0.000000, + -0.000000, + 1.000000 + ], + "min" : [ + 0.000000, + -0.000000, + -1.000000, + -1.000000 + ], + "type" : "VEC4" + }, + { + "bufferView" : 4, + "byteOffset" : 0, + "componentType" : 5126, + "count" : 36, + "max" : [ + 1.000000, + 1.000000 + ], + "min" : [ + -1.000000, + -1.000000 + ], + "type" : "VEC2" + } + ], + "asset" : { + "generator" : "VKTS glTF 2.0 exporter", + "version" : "2.0" + }, + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 72, + "byteOffset" : 0, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 72, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 432, + "byteOffset" : 504, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 576, + "byteOffset" : 936, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 1512, + "target" : 34962 + } + ], + "buffers" : [ + { + "byteLength" : 1800, + "uri" : "Cube.bin" + } + ], + "images" : [ + { + "uri" : "Cube_BaseColor.png" + }, + { + "uri" : "Cube_MetallicRoughness.png" + } + ], + "materials" : [ + { + "name" : "Cube", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 0 + }, + "metallicRoughnessTexture" : { + "index" : 1 + } + } + } + ], + "meshes" : [ + { + "name" : "Cube", + "primitives" : [ + { + "attributes" : { + "NORMAL" : 2, + "POSITION" : 1, + "TANGENT" : 3, + "TEXCOORD_0" : 4 + }, + "indices" : 0, + "material" : 0, + "mode" : 4 + } + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Cube" + } + ], + "samplers" : [ + {} + ], + "scene" : 0, + "scenes" : [ + { + "nodes" : [ + 0 + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 1 + } + ] +} diff --git a/public/camera_camera_BaseColor.png b/public/camera_camera_BaseColor.png new file mode 100644 index 0000000..4621bbe Binary files /dev/null and b/public/camera_camera_BaseColor.png differ diff --git a/public/camera_camera_Normal.png b/public/camera_camera_Normal.png new file mode 100644 index 0000000..f88008a Binary files /dev/null and b/public/camera_camera_Normal.png differ diff --git a/public/camera_camera_Roughness.png b/public/camera_camera_Roughness.png new file mode 100644 index 0000000..346b0e1 Binary files /dev/null and b/public/camera_camera_Roughness.png differ diff --git a/public/camera_tripod_BaseColor.png b/public/camera_tripod_BaseColor.png new file mode 100644 index 0000000..1047ace Binary files /dev/null and b/public/camera_tripod_BaseColor.png differ diff --git a/public/camera_tripod_Normal.png b/public/camera_tripod_Normal.png new file mode 100644 index 0000000..f5200b0 Binary files /dev/null and b/public/camera_tripod_Normal.png differ diff --git a/public/camera_tripod_Roughness.png b/public/camera_tripod_Roughness.png new file mode 100644 index 0000000..080ee18 Binary files /dev/null and b/public/camera_tripod_Roughness.png differ diff --git a/src/App.ts b/src/App.ts index eea4f03..24688dd 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,3 +1,4 @@ +import GltfLoader from "./loaders/GltfLoader"; import Renderer from "./renderer/Renderer"; import Scene from "./renderer/Scene"; @@ -16,9 +17,16 @@ export default class App { try { await this.renderer.init(); + const res = await GltfLoader.load( + this.renderer.device, + "AntiqueCamera.gltf" + ); + + console.log(res); + this.update(0); } catch (e) { - alert("WebGPU is not supported in this browser: " + e); + alert("WebGPU failed to execute: " + e); } } diff --git a/src/loaders/GltfLoader.ts b/src/loaders/GltfLoader.ts new file mode 100644 index 0000000..8430ad9 --- /dev/null +++ b/src/loaders/GltfLoader.ts @@ -0,0 +1,318 @@ +import { mat4, vec3 } from "gl-matrix"; +import { BufferView, GlTf, MeshPrimitive, Node } from "../types/Gltf"; +import { + gpuFormatForAccessor, + gpuPrimitiveTopologyForMode, +} from "../utils/gltf"; + +const ShaderLocations = { + POSITION: 0, + NORMAL: 1, +}; + +import * as glm from "gl-matrix"; + +// @ts-ignore +window.glm = glm; + +let shaderModule: GPUShaderModule; + +/** + * Singleton class for loading glTF files. + * + * resource used: https://toji.github.io/webgpu-gltf-case-study/ + */ +class GltfLoader { + private static instance: GltfLoader; + + private device!: GPUDevice; + + private nodeBindGroupLayout!: GPUBindGroupLayout; + + private constructor() {} + + public static getInstance(): GltfLoader { + if (!GltfLoader.instance) GltfLoader.instance = new GltfLoader(); + + return GltfLoader.instance; + } + + public async load(device: GPUDevice, url: string) { + if (!this.device) { + this.device = device; + + this.nodeBindGroupLayout = this.device.createBindGroupLayout({ + label: "Node Bind Group Layout", + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: {}, + }, + ], + }); + } + + const gltf = await this.loadGltfJson(url); + const buffers = await this.loadBuffers(gltf); + const nodeGpuData = new Map(); + const primitiveGpuData = new Map(); + + gltf.meshes?.forEach((mesh) => { + mesh.primitives.forEach((primitive) => { + const gpuData = this.setupPrimitive(gltf, primitive, device, buffers); + + primitiveGpuData.set(primitive, gpuData); + }); + + return primitiveGpuData; + }); + + gltf.nodes?.forEach((node) => { + if ("mesh" in node) { + const bindGroup = this.setupMeshNodeBindGroup(node)!; + + nodeGpuData.set(node, { bindGroup }); + } + }); + + console.warn("nodeGpuData", nodeGpuData); + console.warn("primitiveGpuData", primitiveGpuData); + + return gltf; + } + + private async loadGltfJson(url: string): Promise { + const response = await fetch(url); + + return response.json(); + } + + private loadBuffers(gltf: GlTf) { + return Promise.all( + gltf.buffers!.map(async (buffer) => { + const response = await fetch(buffer.uri!); + const arrayBuffer = await response.arrayBuffer(); + + return arrayBuffer; + }) + ); + } + + private getGpuBufferView(buffers: ArrayBuffer[], bufferView: BufferView) { + const buffer = buffers[bufferView.buffer]; + + const gpuBuffer = this.device.createBuffer({ + label: "Mesh Vertex Buffer", + size: Math.ceil(bufferView.byteLength / 4) * 4, + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, + mappedAtCreation: true, + }); + + const array = new Uint8Array(gpuBuffer.getMappedRange()); + + array.set( + new Uint8Array(buffer, bufferView.byteOffset, bufferView.byteLength) + ); + + gpuBuffer.unmap(); + + return gpuBuffer; + } + + getNodeWorldTransform(node: Node) { + if (node.matrix) return new Float32Array(node.matrix); + + const worldMatrix = mat4.create(); + + mat4.fromRotationTranslationScale( + worldMatrix, + node.rotation, + (node.translation as vec3) || vec3.create(), + node.scale as vec3 + ); + + return worldMatrix as Float32Array; + } + + setupMeshNodeBindGroup(node: Node) { + if (node.mesh === undefined) return; + + const nodeGpuBuffer = this.device.createBuffer({ + label: "Node Buffer", + size: 16 * Float32Array.BYTES_PER_ELEMENT, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + + this.device.queue.writeBuffer( + nodeGpuBuffer, + 0, + this.getNodeWorldTransform(node) + ); + + // bind group + const nodeBindGroup = this.device.createBindGroup({ + label: "Node Bind Group", + layout: this.nodeBindGroupLayout, + entries: [ + { + binding: 0, + resource: { + buffer: nodeGpuBuffer, + }, + }, + ], + }); + + return nodeBindGroup; + } + + setupPrimitive( + gltf: GlTf, + primitive: MeshPrimitive, + device: GPUDevice, + buffers: ArrayBuffer[] + ) { + const bufferLayout: GPUVertexBufferLayout[] = []; + const gpuBuffers = []; + let drawCount = 0; + + // Loop through every attribute in the primitive and build a description of the vertex + // layout, which is needed to create the render pipeline. + for (const [attribName, accessorIndex] of Object.entries( + primitive.attributes + )) { + const accessor = gltf.accessors![accessorIndex]; + const bufferView = gltf.bufferViews![accessor.bufferView!]; + + // Get the shader location for this attribute. If it doesn't have one skip over the + // attribute because we don't need it for rendering (yet). + const shaderLocation = + ShaderLocations[attribName as keyof typeof ShaderLocations]; + + if (shaderLocation === undefined) { + continue; + } + + // Create a new vertex buffer entry for the render pipeline that describes this + // attribute. Implicitly assumes that one buffer will be bound per attribute, even if + // the attribute data is interleaved. + bufferLayout.push({ + arrayStride: bufferView.byteStride! || 12, + attributes: [ + { + shaderLocation, + format: gpuFormatForAccessor(accessor), + offset: accessor.byteOffset || 0, + }, + ] as any, + }); + + // Since we're skipping some attributes, we need to track the WebGPU buffers that are + // used here so that we can bind them in the correct order at draw time. + gpuBuffers.push(this.getGpuBufferView(buffers, bufferView)); + + // All attributes should have the same count, which will be the draw count for + // non-indexed geometry. + drawCount = accessor.count; + } + + // Create a render pipeline that is compatible with the vertex buffer layout for this primitive. + const pipeline = device.createRenderPipeline({ + label: "GLTF Mesh Render Pipeline", + vertex: { + module: getShaderModule(this.device), + entryPoint: "vertexMain", + buffers: bufferLayout, + }, + primitive: { + topology: gpuPrimitiveTopologyForMode(primitive.mode), + }, + layout: "auto", + }); + + return { + pipeline, + buffers: gpuBuffers, + drawCount, + }; + } +} + +function getShaderModule(device: GPUDevice) { + // Cache the shader module, since all the pipelines use the same one. + if (!shaderModule) { + // The shader source used here is intentionally minimal. It just displays the geometry + // as white with a very simplistic directional lighting based only on vertex normals + // (just to show the shape of the mesh a bit better.) + const code = ` + // These are being managed in the demo base code. + struct Camera { + projection : mat4x4f, + view : mat4x4f, + }; + @group(0) @binding(0) var camera : Camera; + + // This comes from the bind groups being created in setupMeshNode in the next section. + @group(1) @binding(0) var model : mat4x4f; + + // These locations correspond with the values in the ShaderLocations struct in our JS and, by + // extension, the buffer attributes in the pipeline vertex state. + struct VertexInput { + @location(${ShaderLocations.POSITION}) position : vec3f, + @location(${ShaderLocations.NORMAL}) normal : vec3f, + }; + + struct VertexOutput { + // Always need to at least output something to the position builtin. + @builtin(position) position : vec4f, + + // The other locations can be anything you want, as long as it's consistent between the + // vertex and fragment shaders. Since we're defining both in the same module and using the + // same structure for the input and output, we get that alignment for free! + @location(0) normal : vec3f, + }; + + @vertex + fn vertexMain(input : VertexInput) -> VertexOutput { + // Determines the values that will be sent to the fragment shader. + var output : VertexOutput; + + // Transform the vertex position by the model/view/projection matrices. + output.position = camera.projection * camera.view * model * vec4f(input.position, 1); + + // Transform the normal by the model and view matrices. Normally you'd just do model matrix, + // but adding the view matrix in this case is a hack to always keep the normals pointing + // towards the light, so that we can clearly see the geometry even as we rotate it. + output.normal = normalize((camera.view * model * vec4f(input.normal, 0)).xyz); + + return output; + } + + // Some hardcoded lighting constants. + const lightDir = vec3f(0.25, 0.5, 1); + const lightColor = vec3f(1); + const ambientColor = vec3f(0.1); + + @fragment + fn fragmentMain(input : VertexOutput) -> @location(0) vec4f { + // An extremely simple directional lighting model, just to give our model some shape. + let N = normalize(input.normal); + let L = normalize(lightDir); + let NDotL = max(dot(N, L), 0.0); + + // Surface color will just be the light color, so everything will appear white/grey. + let surfaceColor = ambientColor + NDotL; + + // No transparency at this point. + return vec4f(surfaceColor, 1); + } + `; + + shaderModule = device.createShaderModule({ code }); + } + + return shaderModule; +} + +export default GltfLoader.getInstance(); diff --git a/src/meshes/ObjMesh.ts b/src/meshes/ObjMesh.ts index 7dcba91..f36b6b1 100644 --- a/src/meshes/ObjMesh.ts +++ b/src/meshes/ObjMesh.ts @@ -1,6 +1,13 @@ import ObjLoader, { ObjModel } from "../loaders/ObjLoader"; -export class ObjMesh { +interface Mesh { + buffer: GPUBuffer; + bufferLayout: GPUVertexBufferLayout; + + model: ObjModel; +} + +export class ObjMesh implements Mesh { buffer!: GPUBuffer; bufferLayout!: GPUVertexBufferLayout; diff --git a/src/types/Gltf.d.ts b/src/types/Gltf.d.ts new file mode 100644 index 0000000..11edf64 --- /dev/null +++ b/src/types/Gltf.d.ts @@ -0,0 +1,684 @@ +export type GlTfId = number; +/** + * An object pointing to a buffer view containing the indices of deviating accessor values. The number of indices is equal to `accessor.sparse.count`. Indices **MUST** strictly increase. + */ +export interface AccessorSparseIndices { + /** + * The index of the buffer view with sparse indices. The referenced buffer view **MUST NOT** have its `target` or `byteStride` properties defined. The buffer view and the optional `byteOffset` **MUST** be aligned to the `componentType` byte length. + */ + bufferView: GlTfId; + /** + * The offset relative to the start of the buffer view in bytes. + */ + byteOffset?: number; + /** + * The indices data type. + */ + componentType: number | number | number | number; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * An object pointing to a buffer view containing the deviating accessor values. The number of elements is equal to `accessor.sparse.count` times number of components. The elements have the same component type as the base accessor. The elements are tightly packed. Data **MUST** be aligned following the same rules as the base accessor. + */ +export interface AccessorSparseValues { + /** + * The index of the bufferView with sparse values. The referenced buffer view **MUST NOT** have its `target` or `byteStride` properties defined. + */ + bufferView: GlTfId; + /** + * The offset relative to the start of the bufferView in bytes. + */ + byteOffset?: number; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * Sparse storage of accessor values that deviate from their initialization value. + */ +export interface AccessorSparse { + /** + * Number of deviating accessor values stored in the sparse array. + */ + count: number; + /** + * An object pointing to a buffer view containing the indices of deviating accessor values. The number of indices is equal to `count`. Indices **MUST** strictly increase. + */ + indices: AccessorSparseIndices; + /** + * An object pointing to a buffer view containing the deviating accessor values. + */ + values: AccessorSparseValues; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A typed view into a buffer view that contains raw binary data. + */ +export interface Accessor { + /** + * The index of the bufferView. + */ + bufferView?: GlTfId; + /** + * The offset relative to the start of the buffer view in bytes. + */ + byteOffset?: number; + /** + * The datatype of the accessor's components. + */ + componentType: number | number | number | number | number | number | number; + /** + * Specifies whether integer data values are normalized before usage. + */ + normalized?: boolean; + /** + * The number of elements referenced by this accessor. + */ + count: number; + /** + * Specifies if the accessor's elements are scalars, vectors, or matrices. + */ + type: any | any | any | any | any | any | any | string; + /** + * Maximum value of each component in this accessor. + */ + max?: number[]; + /** + * Minimum value of each component in this accessor. + */ + min?: number[]; + /** + * Sparse storage of elements that deviate from their initialization value. + */ + sparse?: AccessorSparse; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * The descriptor of the animated property. + */ +export interface AnimationChannelTarget { + /** + * The index of the node to animate. When undefined, the animated object **MAY** be defined by an extension. + */ + node?: GlTfId; + /** + * The name of the node's TRS property to animate, or the `"weights"` of the Morph Targets it instantiates. For the `"translation"` property, the values that are provided by the sampler are the translation along the X, Y, and Z axes. For the `"rotation"` property, the values are a quaternion in the order (x, y, z, w), where w is the scalar. For the `"scale"` property, the values are the scaling factors along the X, Y, and Z axes. + */ + path: any | any | any | any | string; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * An animation channel combines an animation sampler with a target property being animated. + */ +export interface AnimationChannel { + /** + * The index of a sampler in this animation used to compute the value for the target. + */ + sampler: GlTfId; + /** + * The descriptor of the animated property. + */ + target: AnimationChannelTarget; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * An animation sampler combines timestamps with a sequence of output values and defines an interpolation algorithm. + */ +export interface AnimationSampler { + /** + * The index of an accessor containing keyframe timestamps. + */ + input: GlTfId; + /** + * Interpolation algorithm. + */ + interpolation?: any | any | any | string; + /** + * The index of an accessor, containing keyframe output values. + */ + output: GlTfId; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A keyframe animation. + */ +export interface Animation { + /** + * An array of animation channels. An animation channel combines an animation sampler with a target property being animated. Different channels of the same animation **MUST NOT** have the same targets. + */ + channels: AnimationChannel[]; + /** + * An array of animation samplers. An animation sampler combines timestamps with a sequence of output values and defines an interpolation algorithm. + */ + samplers: AnimationSampler[]; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * Metadata about the glTF asset. + */ +export interface Asset { + /** + * A copyright message suitable for display to credit the content creator. + */ + copyright?: string; + /** + * Tool that generated this glTF model. Useful for debugging. + */ + generator?: string; + /** + * The glTF version in the form of `.` that this asset targets. + */ + version: string; + /** + * The minimum glTF version in the form of `.` that this asset targets. This property **MUST NOT** be greater than the asset version. + */ + minVersion?: string; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A buffer points to binary geometry, animation, or skins. + */ +export interface Buffer { + /** + * The URI (or IRI) of the buffer. + */ + uri?: string; + /** + * The length of the buffer in bytes. + */ + byteLength: number; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A view into a buffer generally representing a subset of the buffer. + */ +export interface BufferView { + /** + * The index of the buffer. + */ + buffer: GlTfId; + /** + * The offset into the buffer in bytes. + */ + byteOffset?: number; + /** + * The length of the bufferView in bytes. + */ + byteLength: number; + /** + * The stride, in bytes. + */ + byteStride?: number; + /** + * The hint representing the intended GPU buffer type to use with this buffer view. + */ + target?: number | number | number; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * An orthographic camera containing properties to create an orthographic projection matrix. + */ +export interface CameraOrthographic { + /** + * The floating-point horizontal magnification of the view. This value **MUST NOT** be equal to zero. This value **SHOULD NOT** be negative. + */ + xmag: number; + /** + * The floating-point vertical magnification of the view. This value **MUST NOT** be equal to zero. This value **SHOULD NOT** be negative. + */ + ymag: number; + /** + * The floating-point distance to the far clipping plane. This value **MUST NOT** be equal to zero. `zfar` **MUST** be greater than `znear`. + */ + zfar: number; + /** + * The floating-point distance to the near clipping plane. + */ + znear: number; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A perspective camera containing properties to create a perspective projection matrix. + */ +export interface CameraPerspective { + /** + * The floating-point aspect ratio of the field of view. + */ + aspectRatio?: number; + /** + * The floating-point vertical field of view in radians. This value **SHOULD** be less than π. + */ + yfov: number; + /** + * The floating-point distance to the far clipping plane. + */ + zfar?: number; + /** + * The floating-point distance to the near clipping plane. + */ + znear: number; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A camera's projection. A node **MAY** reference a camera to apply a transform to place the camera in the scene. + */ +export interface Camera { + /** + * An orthographic camera containing properties to create an orthographic projection matrix. This property **MUST NOT** be defined when `perspective` is defined. + */ + orthographic?: CameraOrthographic; + /** + * A perspective camera containing properties to create a perspective projection matrix. This property **MUST NOT** be defined when `orthographic` is defined. + */ + perspective?: CameraPerspective; + /** + * Specifies if the camera uses a perspective or orthographic projection. + */ + type: any | any | string; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * Image data used to create a texture. Image **MAY** be referenced by an URI (or IRI) or a buffer view index. + */ +export interface Image { + /** + * The URI (or IRI) of the image. + */ + uri?: string; + /** + * The image's media type. This field **MUST** be defined when `bufferView` is defined. + */ + mimeType?: any | any | string; + /** + * The index of the bufferView that contains the image. This field **MUST NOT** be defined when `uri` is defined. + */ + bufferView?: GlTfId; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * Reference to a texture. + */ +export interface TextureInfo { + /** + * The index of the texture. + */ + index: GlTfId; + /** + * The set index of texture's TEXCOORD attribute used for texture coordinate mapping. + */ + texCoord?: number; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. + */ +export interface MaterialPbrMetallicRoughness { + /** + * The factors for the base color of the material. + */ + baseColorFactor?: number[]; + /** + * The base color texture. + */ + baseColorTexture?: TextureInfo; + /** + * The factor for the metalness of the material. + */ + metallicFactor?: number; + /** + * The factor for the roughness of the material. + */ + roughnessFactor?: number; + /** + * The metallic-roughness texture. + */ + metallicRoughnessTexture?: TextureInfo; + extensions?: any; + extras?: any; + [k: string]: any; +} +export interface MaterialNormalTextureInfo { + index?: any; + texCoord?: any; + /** + * The scalar parameter applied to each normal vector of the normal texture. + */ + scale?: number; + extensions?: any; + extras?: any; + [k: string]: any; +} +export interface MaterialOcclusionTextureInfo { + index?: any; + texCoord?: any; + /** + * A scalar multiplier controlling the amount of occlusion applied. + */ + strength?: number; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * The material appearance of a primitive. + */ +export interface Material { + name?: any; + extensions?: any; + extras?: any; + /** + * A set of parameter values that are used to define the metallic-roughness material model from Physically Based Rendering (PBR) methodology. When undefined, all the default values of `pbrMetallicRoughness` **MUST** apply. + */ + pbrMetallicRoughness?: MaterialPbrMetallicRoughness; + /** + * The tangent space normal texture. + */ + normalTexture?: MaterialNormalTextureInfo; + /** + * The occlusion texture. + */ + occlusionTexture?: MaterialOcclusionTextureInfo; + /** + * The emissive texture. + */ + emissiveTexture?: TextureInfo; + /** + * The factors for the emissive color of the material. + */ + emissiveFactor?: number[]; + /** + * The alpha rendering mode of the material. + */ + alphaMode?: any | any | any | string; + /** + * The alpha cutoff value of the material. + */ + alphaCutoff?: number; + /** + * Specifies whether the material is double sided. + */ + doubleSided?: boolean; + [k: string]: any; +} +/** + * Geometry to be rendered with the given material. + */ +export interface MeshPrimitive { + /** + * A plain JSON object, where each key corresponds to a mesh attribute semantic and each value is the index of the accessor containing attribute's data. + */ + attributes: { + [k: string]: GlTfId; + }; + /** + * The index of the accessor that contains the vertex indices. + */ + indices?: GlTfId; + /** + * The index of the material to apply to this primitive when rendering. + */ + material?: GlTfId; + /** + * The topology type of primitives to render. + */ + mode?: number | number | number | number | number | number | number | number; + /** + * An array of morph targets. + */ + targets?: { + [k: string]: GlTfId; + }[]; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A set of primitives to be rendered. Its global transform is defined by a node that references it. + */ +export interface Mesh { + /** + * An array of primitives, each defining geometry to be rendered. + */ + primitives: MeshPrimitive[]; + /** + * Array of weights to be applied to the morph targets. The number of array elements **MUST** match the number of morph targets. + */ + weights?: number[]; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A node in the node hierarchy. When the node contains `skin`, all `mesh.primitives` **MUST** contain `JOINTS_0` and `WEIGHTS_0` attributes. A node **MAY** have either a `matrix` or any combination of `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted to matrices and postmultiplied in the `T * R * S` order to compose the transformation matrix; first the scale is applied to the vertices, then the rotation, and then the translation. If none are provided, the transform is the identity. When a node is targeted for animation (referenced by an animation.channel.target), `matrix` **MUST NOT** be present. + */ +export interface Node { + /** + * The index of the camera referenced by this node. + */ + camera?: GlTfId; + /** + * The indices of this node's children. + */ + children?: GlTfId[]; + /** + * The index of the skin referenced by this node. + */ + skin?: GlTfId; + /** + * A floating-point 4x4 transformation matrix stored in column-major order. + */ + matrix?: number[]; + /** + * The index of the mesh in this node. + */ + mesh?: GlTfId; + /** + * The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar. + */ + rotation?: number[]; + /** + * The node's non-uniform scale, given as the scaling factors along the x, y, and z axes. + */ + scale?: number[]; + /** + * The node's translation along the x, y, and z axes. + */ + translation?: number[]; + /** + * The weights of the instantiated morph target. The number of array elements **MUST** match the number of morph targets of the referenced mesh. When defined, `mesh` **MUST** also be defined. + */ + weights?: number[]; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * Texture sampler properties for filtering and wrapping modes. + */ +export interface Sampler { + /** + * Magnification filter. + */ + magFilter?: number | number | number; + /** + * Minification filter. + */ + minFilter?: number | number | number | number | number | number | number; + /** + * S (U) wrapping mode. + */ + wrapS?: number | number | number | number; + /** + * T (V) wrapping mode. + */ + wrapT?: number | number | number | number; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * The root nodes of a scene. + */ +export interface Scene { + /** + * The indices of each root node. + */ + nodes?: GlTfId[]; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * Joints and matrices defining a skin. + */ +export interface Skin { + /** + * The index of the accessor containing the floating-point 4x4 inverse-bind matrices. + */ + inverseBindMatrices?: GlTfId; + /** + * The index of the node used as a skeleton root. + */ + skeleton?: GlTfId; + /** + * Indices of skeleton nodes, used as joints in this skin. + */ + joints: GlTfId[]; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * A texture and its sampler. + */ +export interface Texture { + /** + * The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering **SHOULD** be used. + */ + sampler?: GlTfId; + /** + * The index of the image used by this texture. When undefined, an extension or other mechanism **SHOULD** supply an alternate texture source, otherwise behavior is undefined. + */ + source?: GlTfId; + name?: any; + extensions?: any; + extras?: any; + [k: string]: any; +} +/** + * The root object for a glTF asset. + */ +export interface GlTf { + /** + * Names of glTF extensions used in this asset. + */ + extensionsUsed?: string[]; + /** + * Names of glTF extensions required to properly load this asset. + */ + extensionsRequired?: string[]; + /** + * An array of accessors. + */ + accessors?: Accessor[]; + /** + * An array of keyframe animations. + */ + animations?: Animation[]; + /** + * Metadata about the glTF asset. + */ + asset: Asset; + /** + * An array of buffers. + */ + buffers?: Buffer[]; + /** + * An array of bufferViews. + */ + bufferViews?: BufferView[]; + /** + * An array of cameras. + */ + cameras?: Camera[]; + /** + * An array of images. + */ + images?: Image[]; + /** + * An array of materials. + */ + materials?: Material[]; + /** + * An array of meshes. + */ + meshes?: Mesh[]; + /** + * An array of nodes. + */ + nodes?: Node[]; + /** + * An array of samplers. + */ + samplers?: Sampler[]; + /** + * The index of the default scene. + */ + scene?: GlTfId; + /** + * An array of scenes. + */ + scenes?: Scene[]; + /** + * An array of skins. + */ + skins?: Skin[]; + /** + * An array of textures. + */ + textures?: Texture[]; + extensions?: any; + extras?: any; + [k: string]: any; +} diff --git a/src/utils/gltf.ts b/src/utils/gltf.ts new file mode 100644 index 0000000..4fdc486 --- /dev/null +++ b/src/utils/gltf.ts @@ -0,0 +1,56 @@ +import { Accessor, MeshPrimitive } from "../types/Gltf"; + +function numberOfComponentsForType(type: string) { + switch (type) { + case "SCALAR": + return 1; + case "VEC2": + return 2; + case "VEC3": + return 3; + case "VEC4": + return 4; + default: + return 0; + } +} + +export function gpuFormatForAccessor(accessor: Accessor) { + const norm = accessor.normalized ? "norm" : "int"; + const count = numberOfComponentsForType(accessor.type); + const x = count > 1 ? `x${count}` : ""; + + switch (accessor.componentType) { + case WebGLRenderingContext.BYTE: + return `s${norm}8${x}`; + case WebGLRenderingContext.UNSIGNED_BYTE: + return `u${norm}8${x}`; + case WebGLRenderingContext.SHORT: + return `s${norm}16${x}`; + case WebGLRenderingContext.UNSIGNED_SHORT: + return `u${norm}16${x}`; + case WebGLRenderingContext.UNSIGNED_INT: + return `u${norm}32${x}`; + case WebGLRenderingContext.FLOAT: + return `float32${x}`; + default: + return ""; + } +} + +export function gpuPrimitiveTopologyForMode(mode: MeshPrimitive["mode"]) { + switch (mode) { + case WebGLRenderingContext.TRIANGLES: + return "triangle-list"; + case WebGLRenderingContext.TRIANGLE_STRIP: + return "triangle-strip"; + case WebGLRenderingContext.LINES: + return "line-list"; + case WebGLRenderingContext.LINE_STRIP: + return "line-strip"; + case WebGLRenderingContext.POINTS: + return "point-list"; + default: + return "triangle-list"; + } +}