├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── example ├── index.html ├── src │ ├── app.ts │ ├── camera.ts │ ├── cubemap.ts │ ├── inputs.ts │ ├── renderer.ts │ └── shaders │ │ ├── default-shader.ts │ │ └── shader-loader.ts └── static │ ├── environment │ ├── brdf_lut.png │ ├── diffuse_back.jpg │ ├── diffuse_bottom.jpg │ ├── diffuse_front.jpg │ ├── diffuse_left.jpg │ ├── diffuse_right.jpg │ ├── diffuse_top.jpg │ ├── specular_back.jpg │ ├── specular_bottom.jpg │ ├── specular_front.jpg │ ├── specular_left.jpg │ ├── specular_right.jpg │ └── specular_top.jpg │ ├── models │ ├── cesium │ │ ├── cesium.bin │ │ ├── cesium.gltf │ │ └── cesium.jpg │ ├── cylinder │ │ ├── cylinder.bin │ │ └── cylinder.gltf │ ├── launcher │ │ ├── launcher.bin │ │ ├── launcher.gltf │ │ ├── launcher_baseColor.png │ │ ├── launcher_emissive.png │ │ ├── launcher_normal.png │ │ └── launcher_occlusionRoughnessMetallic.png │ ├── platform │ │ ├── platform.bin │ │ ├── platform.gltf │ │ ├── platform_baseColor.png │ │ ├── platform_normal.png │ │ └── platform_occlusionRoughnessMetallic.png │ ├── robot │ │ ├── robot.bin │ │ ├── robot.gltf │ │ ├── robot_baseColor.png │ │ ├── robot_normal.png │ │ └── robot_occlusionRoughnessMetallic.png │ ├── suzanne │ │ ├── baseColor.png │ │ ├── roughness.png │ │ ├── suzanne.bin │ │ └── suzanne.gltf │ └── waterbottle │ │ ├── WaterBottle.bin │ │ ├── base-color.png │ │ ├── emissive.png │ │ ├── metallic-roughness.png │ │ ├── normal.png │ │ ├── occlusion.png │ │ └── waterbottle.gltf │ └── shaders │ ├── default.frag │ └── default.vert ├── package-lock.json ├── package.json ├── src ├── tsconfig.json └── webgl-gltf │ ├── animation.ts │ ├── animator.ts │ ├── gltf.ts │ ├── index.ts │ ├── mat.ts │ └── types │ ├── gltf.d.ts │ └── model.d.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "modules": true 8 | } 9 | }, 10 | "plugins": [ 11 | "@typescript-eslint" 12 | ], 13 | "extends": [ 14 | "plugin:@typescript-eslint/recommended" 15 | ], 16 | "rules": { 17 | "quotes": [2, "single"], 18 | "@typescript-eslint/explicit-function-return-type": "off", 19 | "@typescript-eslint/no-non-null-assertion": "off", 20 | "@typescript-eslint/explicit-module-boundary-types": "off" 21 | }, 22 | "overrides": [{ 23 | "files": ["*.js", "*.jsx"], 24 | "rules": { 25 | "@typescript-eslint/no-var-requires": "off" 26 | } 27 | }] 28 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Lars Jarlvik 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webgl-gltf 2 | A small GLTF loader For WebGL. Only depends on *gl-matrix*. 3 | 4 | Supports both WebGL and WebGL 2. 5 | 6 | Please see example folder for implementation. 7 | 8 | ## Documentation 9 | Please see the wiki for documentation 10 | [Wiki](https://github.com/larsjarlvik/webgl-gltf/wiki) 11 | 12 | ## Supports 13 | * Multiple meshes 14 | * Textures 15 | * PBR materials (base color, metal-roughness, normal, occlusion and emissive) 16 | * Animations 17 | * Animation blending 18 | * Multiple animation tracks 19 | * GLTF+bin files 20 | * GLTF+bin files over the Internet (CORS should be enabled for all files) 21 | ## Does NOT support 22 | * Fully binary (`.glb`) files 23 | * GLTF with embedded data 24 | * Any GLTF extensions 25 | * All animation interpolations are treated as LINEAR 26 | 27 | ## Demo 28 | * Robot: https://larsjarlvik.github.io/?model=robot 29 | * Waterbottle: https://larsjarlvik.github.io/?model=waterbottle 30 | * Cesium man (animation): https://larsjarlvik.github.io/?model=cesium 31 | 32 | ## Installation 33 | * `npm install webgl-gltf` 34 | 35 | ## Run example code 36 | * `npm install` 37 | * `npm run start` 38 | 39 | *Models curtesy of https://github.com/KhronosGroup/glTF-Sample-Models* 40 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGL GLTF Viewer 6 | 51 | 52 | 53 |
54 | 55 |
56 | 57 | -------------------------------------------------------------------------------- /example/src/app.ts: -------------------------------------------------------------------------------- 1 | import * as shader from './shaders/shader-loader'; 2 | import * as defaultShader from './shaders/default-shader'; 3 | import * as camera from './camera'; 4 | import * as inputs from './inputs'; 5 | import * as cubemap from './cubemap'; 6 | import { renderModel } from './renderer'; 7 | import { DefaultShader } from './shaders/default-shader'; 8 | 9 | import * as gltf from 'webgl-gltf'; 10 | 11 | const canvas = document.getElementById('canvas') as HTMLCanvasElement; 12 | const gl = canvas.getContext('webgl') as WebGLRenderingContext; 13 | 14 | const track = 'track'; 15 | const blendTime = 300; 16 | let lastFrame = 0; 17 | 18 | const cam = { 19 | rY: 0.0, 20 | rX: 0.0, 21 | distance: 3.0, 22 | } as camera.Camera; 23 | 24 | const setSize = () => { 25 | const devicePixelRatio = window.devicePixelRatio || 1; 26 | 27 | canvas.width = window.innerWidth * devicePixelRatio; 28 | canvas.height = window.innerHeight * devicePixelRatio; 29 | gl.viewport(0, 0, canvas.width, canvas.height); 30 | } 31 | 32 | if (!gl) { 33 | alert('WebGL not available') 34 | } 35 | 36 | const listAnimations = (models: gltf.Model[]) => { 37 | models.forEach(model => { 38 | if (Object.keys(model.animations).length === 0) return; 39 | 40 | gltf.pushAnimation(track, 'default', model.name, Object.keys(model.animations)[0]); 41 | 42 | const ui = document.getElementById('ui') as HTMLElement; 43 | Object.keys(model.animations).forEach(a => { 44 | const btn = document.createElement('button'); 45 | btn.innerText = a; 46 | btn.addEventListener('click', () => gltf.pushAnimation(track, 'default', model.name, a)); 47 | ui.appendChild(btn); 48 | }); 49 | }); 50 | }; 51 | 52 | const render = (uniforms: DefaultShader, models: gltf.Model[]) => { 53 | gl.clear(gl.COLOR_BUFFER_BIT); 54 | 55 | const cameraMatrix = camera.update(cam, canvas.width, canvas.height); 56 | gl.uniform3f(uniforms.cameraPosition, cameraMatrix.position[0], cameraMatrix.position[1], cameraMatrix.position[2]); 57 | gl.uniformMatrix4fv(uniforms.pMatrix, false, cameraMatrix.pMatrix); 58 | gl.uniformMatrix4fv(uniforms.vMatrix, false, cameraMatrix.vMatrix); 59 | 60 | models.forEach(model => { 61 | const animation = gltf.getActiveAnimations('default', model.name); 62 | 63 | if (animation) { 64 | const animationTransforms = gltf.getAnimationTransforms(model, animation, blendTime); 65 | gltf.applyToSkin(model, animationTransforms).forEach((x, i) => { 66 | gl.uniformMatrix4fv(uniforms.jointTransform[i], false, x); 67 | }); 68 | 69 | gl.uniform1i(uniforms.isAnimated, 1); 70 | } else { 71 | gl.uniform1i(uniforms.isAnimated, 0); 72 | } 73 | 74 | renderModel(gl, model, model.rootNode, model.nodes[model.rootNode].localBindTransform, uniforms); 75 | }); 76 | 77 | gltf.advanceAnimation(performance.now() - lastFrame); 78 | 79 | requestAnimationFrame(() => { 80 | render(uniforms, models); 81 | lastFrame = performance.now(); 82 | }); 83 | }; 84 | 85 | const startup = async () => { 86 | gl.clearColor(0.3, 0.3, 0.3, 1); 87 | gl.enable(gl.DEPTH_TEST); 88 | 89 | window.onresize = () => { setSize(); }; 90 | setSize(); 91 | 92 | const program = shader.createProgram(gl); 93 | gl.attachShader(program, await shader.loadShader(gl, 'default.vert', gl.VERTEX_SHADER)); 94 | gl.attachShader(program, await shader.loadShader(gl, 'default.frag', gl.FRAGMENT_SHADER)); 95 | shader.linkProgram(gl, program); 96 | 97 | const uniforms = defaultShader.getUniformLocations(gl, program); 98 | 99 | const environment = await cubemap.load(gl); 100 | const urlParams = new URLSearchParams(window.location.search); 101 | const modelNames = urlParams.get('model') || 'robot'; 102 | const models = await Promise.all(modelNames.split(',').map(m => gltf.loadModel(gl, `/models/${m}/${m}.gltf`))); 103 | listAnimations(models); 104 | console.log(models); 105 | 106 | cubemap.bind(gl, environment, uniforms.brdfLut, uniforms.environmentDiffuse, uniforms.environmentSpecular); 107 | 108 | document.getElementById('loading')?.remove(); 109 | 110 | render(uniforms, models); 111 | }; 112 | 113 | const rotate = (delta: inputs.Position) => { 114 | cam.rX += delta.y; 115 | cam.rY += delta.x; 116 | }; 117 | 118 | const zoom = (delta: number) => { 119 | cam.distance *= 1.0 + delta; 120 | if (cam.distance < 0.0) cam.distance = 0.0; 121 | }; 122 | 123 | inputs.listen(canvas, rotate, zoom); 124 | startup(); 125 | -------------------------------------------------------------------------------- /example/src/camera.ts: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from 'gl-matrix'; 2 | 3 | interface CameraMatrix { 4 | pMatrix: mat4; 5 | vMatrix: mat4; 6 | position: vec3; 7 | } 8 | 9 | interface Camera { 10 | rY: number; 11 | rX: number; 12 | distance: number; 13 | } 14 | 15 | 16 | const getPosition = (camera: Camera) => vec3.fromValues( 17 | camera.distance * Math.sin(-camera.rY) * Math.cos(-camera.rX), 18 | camera.distance * Math.sin(camera.rX), 19 | camera.distance * Math.cos(-camera.rY) * Math.cos(-camera.rX), 20 | ); 21 | 22 | const update = (camera: Camera, viewportWidth: number, viewportHeight: number): CameraMatrix => { 23 | const pMatrix = mat4.create(); 24 | const vMatrix = mat4.create(); 25 | const position = getPosition(camera); 26 | 27 | mat4.translate(vMatrix, vMatrix, vec3.fromValues(0.0, 0.0, -camera.distance)); 28 | mat4.rotateX(vMatrix, vMatrix, camera.rX); 29 | mat4.rotateY(vMatrix, vMatrix, camera.rY); 30 | 31 | mat4.perspective(pMatrix, 45.0, viewportWidth / viewportHeight, 0.1, 100.0); 32 | 33 | return { pMatrix, vMatrix, position }; 34 | }; 35 | 36 | export { 37 | Camera, 38 | update, 39 | }; 40 | -------------------------------------------------------------------------------- /example/src/cubemap.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | interface Environment { 4 | diffuse: WebGLTexture; 5 | specular: WebGLTexture; 6 | brdfLut: WebGLTexture; 7 | } 8 | 9 | const getImage = async (uri: string) => { 10 | return new Promise(resolve => { 11 | const img = new Image(); 12 | img.onload = () => { 13 | resolve(img); 14 | } 15 | img.src = uri; 16 | }); 17 | }; 18 | 19 | const createCubeMap = (gl: WebGLRenderingContext, textures: HTMLImageElement[]) => { 20 | const cubeMap = gl.createTexture(); 21 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeMap); 22 | textures.forEach((t, i) => { 23 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, t); 24 | }); 25 | gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 26 | gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 27 | gl.generateMipmap(gl.TEXTURE_CUBE_MAP); 28 | return cubeMap; 29 | }; 30 | 31 | const load = async (gl: WebGLRenderingContext): Promise => { 32 | const names = ['right', 'left', 'top', 'bottom', 'front', 'back']; 33 | const diffuseTextures = await Promise.all(names.map(n => getImage(`environment/diffuse_${n}.jpg`))); 34 | const specularTextures = await Promise.all(names.map(n => getImage(`environment/specular_${n}.jpg`))); 35 | 36 | const diffuse = createCubeMap(gl, diffuseTextures); 37 | const specular = createCubeMap(gl, specularTextures); 38 | 39 | const brdfLutTexture = await getImage('environment/brdf_lut.png'); 40 | const brdfLut = gl.createTexture(); 41 | gl.bindTexture(gl.TEXTURE_2D, brdfLut); 42 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, brdfLutTexture); 43 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 44 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 45 | gl.generateMipmap(gl.TEXTURE_2D); 46 | 47 | if (!diffuse || !specular || !brdfLut) { 48 | throw new Error('Failed to load environment!'); 49 | } 50 | 51 | return { 52 | diffuse, 53 | specular, 54 | brdfLut, 55 | }; 56 | }; 57 | 58 | const bind = (gl: WebGLRenderingContext, environment: Environment, brfdLutTarget: WebGLUniformLocation, diffuseTarget: WebGLUniformLocation, specularTarget: WebGLUniformLocation) => { 59 | gl.activeTexture(gl.TEXTURE5); 60 | gl.bindTexture(gl.TEXTURE_2D, environment.brdfLut); 61 | gl.uniform1i(brfdLutTarget, 5); 62 | 63 | gl.activeTexture(gl.TEXTURE6); 64 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, environment.diffuse); 65 | gl.uniform1i(diffuseTarget, 6); 66 | 67 | gl.activeTexture(gl.TEXTURE7); 68 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, environment.specular); 69 | gl.uniform1i(specularTarget, 7); 70 | }; 71 | 72 | export { 73 | load, 74 | bind, 75 | Environment, 76 | }; 77 | -------------------------------------------------------------------------------- /example/src/inputs.ts: -------------------------------------------------------------------------------- 1 | interface Position { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | const getPinchDistance = (event: TouchEvent) => { 7 | const xyDistance = { 8 | x: Math.abs(event.touches[0].clientX - event.touches[1].clientX), 9 | y: Math.abs(event.touches[0].clientY - event.touches[1].clientY), 10 | }; 11 | 12 | return Math.sqrt(xyDistance.x * xyDistance.x + xyDistance.y * xyDistance.y); 13 | } 14 | 15 | const listen = (canvas: HTMLCanvasElement, drag: (rotation: Position) => void, zoom: (zoom: number) => void) => { 16 | let lastPosition: Position | undefined; 17 | let zoomStart: number | undefined; 18 | 19 | const dragEvent = (event) => { 20 | const client = event.touches 21 | ? { x: event.touches[0].clientX, y: event.touches[0].clientY } 22 | : { x: event.clientX, y: event.clientY }; 23 | 24 | if (lastPosition !== undefined) { 25 | drag({ 26 | x: (client.x - lastPosition.x) / 100.0, 27 | y: (client.y - lastPosition.y) / 100.0 28 | }); 29 | } 30 | 31 | lastPosition = { 32 | x: client.x, 33 | y: client.y, 34 | }; 35 | }; 36 | 37 | canvas.addEventListener('wheel', (event) => { 38 | zoom(event.deltaY > 0 ? 0.05: -0.05); 39 | }); 40 | 41 | canvas.addEventListener('mousedown', () => { 42 | canvas.addEventListener('mousemove', dragEvent); 43 | }); 44 | 45 | canvas.addEventListener('mouseup', () => { 46 | canvas.removeEventListener('mousemove', dragEvent); 47 | lastPosition = undefined; 48 | }); 49 | 50 | 51 | canvas.addEventListener('touchmove', (event) => { 52 | if (event.touches.length === 1 && zoomStart === undefined) { 53 | dragEvent(event); 54 | return; 55 | } 56 | 57 | if (event.touches.length === 2 && zoomStart !== undefined) { 58 | const distance = getPinchDistance(event); 59 | zoom((zoomStart - distance) / 4000.0) 60 | } 61 | 62 | event.preventDefault(); 63 | event.stopPropagation(); 64 | }); 65 | 66 | canvas.addEventListener('touchstart', (event) => { 67 | zoomStart = getPinchDistance(event); 68 | }); 69 | 70 | canvas.addEventListener('touchend', (event) => { 71 | if (event.touches.length === 0) { 72 | zoomStart = undefined; 73 | lastPosition = undefined; 74 | } 75 | }); 76 | }; 77 | 78 | export { 79 | listen, 80 | Position, 81 | }; 82 | -------------------------------------------------------------------------------- /example/src/renderer.ts: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | import { Model, GLBuffer } from 'webgl-gltf'; 3 | import { DefaultShader } from './shaders/default-shader'; 4 | 5 | const bindBuffer = (gl: WebGLRenderingContext, position: number, buffer: GLBuffer | null) => { 6 | if (buffer === null) return; 7 | 8 | gl.enableVertexAttribArray(position); 9 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer); 10 | gl.vertexAttribPointer(position, buffer.size, buffer.type, false, 0, 0); 11 | 12 | return buffer; 13 | }; 14 | 15 | const applyTexture = (gl: WebGLRenderingContext, texture: WebGLTexture | null, textureTarget: number, textureUniform: WebGLUniformLocation, enabledUniform?: WebGLUniformLocation) => { 16 | if (texture) { 17 | gl.activeTexture(gl.TEXTURE0 + textureTarget); 18 | gl.bindTexture(gl.TEXTURE_2D, texture); 19 | gl.uniform1i(textureUniform, textureTarget); 20 | } 21 | 22 | if (enabledUniform !== undefined) gl.uniform1i(enabledUniform, texture ? 1 : 0); 23 | } 24 | 25 | const renderModel = (gl: WebGLRenderingContext, model: Model, node: number, transform: mat4, uniforms: DefaultShader) => { 26 | if (model.nodes[node].mesh !== undefined) { 27 | const mesh = model.meshes[model.nodes[node].mesh!]; 28 | const material = model.materials[mesh.material]; 29 | 30 | if (material) { 31 | applyTexture(gl, material.baseColorTexture, 0, uniforms.baseColorTexture, uniforms.hasBaseColorTexture); 32 | applyTexture(gl, material.metallicRoughnessTexture, 1, uniforms.metallicRoughnessTexture, uniforms.hasMetallicRoughnessTexture); 33 | applyTexture(gl, material.emissiveTexture, 2, uniforms.emissiveTexture, uniforms.hasEmissiveTexture); 34 | applyTexture(gl, material.normalTexture, 3, uniforms.normalTexture, uniforms.hasNormalTexture); 35 | applyTexture(gl, material.occlusionTexture, 4, uniforms.occlusionTexture, uniforms.hasOcclusionTexture); 36 | 37 | gl.uniform4f(uniforms.baseColorFactor, material.baseColorFactor[0], material.baseColorFactor[1], material.baseColorFactor[2], material.baseColorFactor[3]); 38 | gl.uniform1f(uniforms.metallicFactor, material.metallicFactor); 39 | gl.uniform1f(uniforms.roughnessFactor, material.roughnessFactor); 40 | gl.uniform3f(uniforms.emissiveFactor, material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2]); 41 | } 42 | 43 | bindBuffer(gl, uniforms.position, mesh.positions); 44 | bindBuffer(gl, uniforms.normal, mesh.normals); 45 | bindBuffer(gl, uniforms.tangent, mesh.tangents); 46 | bindBuffer(gl, uniforms.texCoord, mesh.texCoord); 47 | bindBuffer(gl, uniforms.joints, mesh.joints); 48 | bindBuffer(gl, uniforms.weights, mesh.weights); 49 | 50 | gl.uniformMatrix4fv(uniforms.mMatrix, false, transform); 51 | 52 | if (mesh.indices) { 53 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.indices); 54 | gl.drawElements(gl.TRIANGLES, mesh.elementCount, gl.UNSIGNED_SHORT, 0); 55 | } else { 56 | gl.drawArrays(gl.TRIANGLES, 0, mesh.elementCount); 57 | } 58 | } 59 | 60 | model.nodes[node].children.forEach(c => { 61 | renderModel(gl, model, c, transform, uniforms); 62 | }); 63 | }; 64 | 65 | export { 66 | renderModel, 67 | }; 68 | -------------------------------------------------------------------------------- /example/src/shaders/default-shader.ts: -------------------------------------------------------------------------------- 1 | export interface DefaultShader { 2 | pMatrix: WebGLUniformLocation; 3 | vMatrix: WebGLUniformLocation; 4 | mMatrix: WebGLUniformLocation; 5 | cameraPosition: WebGLUniformLocation; 6 | hasBaseColorTexture: WebGLUniformLocation; 7 | baseColorTexture: WebGLUniformLocation; 8 | hasMetallicRoughnessTexture: WebGLUniformLocation; 9 | metallicRoughnessTexture: WebGLUniformLocation; 10 | hasEmissiveTexture: WebGLUniformLocation; 11 | normalTexture: WebGLUniformLocation; 12 | hasNormalTexture: WebGLUniformLocation; 13 | emissiveTexture: WebGLUniformLocation; 14 | hasOcclusionTexture: WebGLUniformLocation; 15 | occlusionTexture: WebGLUniformLocation; 16 | baseColorFactor: WebGLUniformLocation; 17 | metallicFactor: WebGLUniformLocation; 18 | roughnessFactor: WebGLUniformLocation; 19 | emissiveFactor: WebGLUniformLocation; 20 | isAnimated: WebGLUniformLocation; 21 | jointTransform: WebGLUniformLocation[]; 22 | brdfLut: WebGLUniformLocation; 23 | environmentDiffuse: WebGLUniformLocation; 24 | environmentSpecular: WebGLUniformLocation; 25 | 26 | position: number; 27 | normal: number; 28 | tangent: number; 29 | texCoord: number; 30 | joints: number; 31 | weights: number; 32 | } 33 | 34 | const getUniformLocations = (gl: WebGLRenderingContext, program: WebGLProgram): DefaultShader => { 35 | const pMatrix = gl.getUniformLocation(program, 'uProjectionMatrix')!; 36 | const vMatrix = gl.getUniformLocation(program, 'uViewMatrix')!; 37 | const mMatrix = gl.getUniformLocation(program, 'uModelMatrix')!; 38 | const cameraPosition = gl.getUniformLocation(program, 'uCameraPosition')!; 39 | 40 | const isAnimated = gl.getUniformLocation(program, 'uIsAnimated')!; 41 | const hasBaseColorTexture = gl.getUniformLocation(program, 'uHasBaseColorTexture')!; 42 | const baseColorTexture = gl.getUniformLocation(program, 'uBaseColorTexture')!; 43 | const hasMetallicRoughnessTexture = gl.getUniformLocation(program, 'uHasMetallicRoughnessTexture')!; 44 | const metallicRoughnessTexture = gl.getUniformLocation(program, 'uMetallicRoughnessTexture')!; 45 | 46 | const hasEmissiveTexture = gl.getUniformLocation(program, 'uHasEmissiveTexture')!; 47 | const emissiveTexture = gl.getUniformLocation(program, 'uEmissiveTexture')!; 48 | 49 | const baseColorFactor = gl.getUniformLocation(program, 'uBaseColorFactor')!; 50 | const metallicFactor = gl.getUniformLocation(program, 'uMetallicFactor')!; 51 | const roughnessFactor = gl.getUniformLocation(program, 'uRoughnessFactor')!; 52 | const emissiveFactor = gl.getUniformLocation(program, 'uEmissiveFactor')!; 53 | 54 | const normalTexture = gl.getUniformLocation(program, 'uNormalTexture')!; 55 | const hasNormalTexture = gl.getUniformLocation(program, 'uHasNormalTexture')!; 56 | 57 | const occlusionTexture = gl.getUniformLocation(program, 'uOcclusionTexture')!; 58 | const hasOcclusionTexture = gl.getUniformLocation(program, 'uHasOcclusionTexture')!; 59 | 60 | const brdfLut = gl.getUniformLocation(program, 'uBrdfLut')!; 61 | const environmentDiffuse = gl.getUniformLocation(program, 'uEnvironmentDiffuse')!; 62 | const environmentSpecular = gl.getUniformLocation(program, 'uEnvironmentSpecular')!; 63 | 64 | const jointTransform: WebGLUniformLocation[] = []; 65 | for (let i = 0; i < 25; i ++) { 66 | jointTransform[i] = gl.getUniformLocation(program, `uJointTransform[${i}]`)! 67 | } 68 | 69 | const position = gl.getAttribLocation(program, 'vPosition'); 70 | const normal = gl.getAttribLocation(program, 'vNormal'); 71 | const tangent = gl.getAttribLocation(program, 'vTangent'); 72 | const texCoord = gl.getAttribLocation(program, 'vTexCoord'); 73 | const joints = gl.getAttribLocation(program, 'vJoints'); 74 | const weights = gl.getAttribLocation(program, 'vWeights'); 75 | 76 | return { 77 | pMatrix, 78 | vMatrix, 79 | mMatrix, 80 | cameraPosition, 81 | hasBaseColorTexture, 82 | baseColorTexture, 83 | hasMetallicRoughnessTexture, 84 | metallicRoughnessTexture, 85 | hasEmissiveTexture, 86 | normalTexture, 87 | hasNormalTexture, 88 | occlusionTexture, 89 | hasOcclusionTexture, 90 | emissiveTexture, 91 | baseColorFactor, 92 | metallicFactor, 93 | roughnessFactor, 94 | emissiveFactor, 95 | isAnimated, 96 | jointTransform, 97 | brdfLut, 98 | environmentDiffuse, 99 | environmentSpecular, 100 | 101 | position, 102 | normal, 103 | tangent, 104 | texCoord, 105 | joints, 106 | weights, 107 | }; 108 | }; 109 | 110 | export { 111 | getUniformLocations, 112 | }; 113 | -------------------------------------------------------------------------------- /example/src/shaders/shader-loader.ts: -------------------------------------------------------------------------------- 1 | 2 | const loadShader = async (gl: WebGLRenderingContext, name: string, type: number) => { 3 | const response = await fetch(`/shaders/${name}`); 4 | const content = await response.text(); 5 | 6 | const shader = gl.createShader(type); 7 | if (shader === null) throw new Error('gl.createShader returned null!'); 8 | 9 | gl.shaderSource(shader, content); 10 | gl.compileShader(shader); 11 | 12 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 13 | console.error(`Failed to load shader ${name}`); 14 | console.error(gl.getShaderInfoLog(shader)); 15 | } 16 | 17 | return shader; 18 | }; 19 | 20 | const createProgram = (gl: WebGLRenderingContext) => { 21 | const program = gl.createProgram(); 22 | if (program === null) throw new Error('gl.createProgram returned null!'); 23 | 24 | return program as WebGLProgram; 25 | }; 26 | 27 | const linkProgram = (gl: WebGLRenderingContext, program: WebGLProgram) => { 28 | gl.linkProgram(program); 29 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 30 | console.error(gl.getProgramInfoLog(program)); 31 | } 32 | 33 | gl.useProgram(program); 34 | }; 35 | 36 | export { 37 | loadShader, 38 | createProgram, 39 | linkProgram, 40 | }; 41 | -------------------------------------------------------------------------------- /example/static/environment/brdf_lut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/brdf_lut.png -------------------------------------------------------------------------------- /example/static/environment/diffuse_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_back.jpg -------------------------------------------------------------------------------- /example/static/environment/diffuse_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_bottom.jpg -------------------------------------------------------------------------------- /example/static/environment/diffuse_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_front.jpg -------------------------------------------------------------------------------- /example/static/environment/diffuse_left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_left.jpg -------------------------------------------------------------------------------- /example/static/environment/diffuse_right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_right.jpg -------------------------------------------------------------------------------- /example/static/environment/diffuse_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_top.jpg -------------------------------------------------------------------------------- /example/static/environment/specular_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_back.jpg -------------------------------------------------------------------------------- /example/static/environment/specular_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_bottom.jpg -------------------------------------------------------------------------------- /example/static/environment/specular_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_front.jpg -------------------------------------------------------------------------------- /example/static/environment/specular_left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_left.jpg -------------------------------------------------------------------------------- /example/static/environment/specular_right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_right.jpg -------------------------------------------------------------------------------- /example/static/environment/specular_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_top.jpg -------------------------------------------------------------------------------- /example/static/models/cesium/cesium.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/cesium/cesium.bin -------------------------------------------------------------------------------- /example/static/models/cesium/cesium.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "scenes": [ 7 | { 8 | "nodes": [ 9 | 0 10 | ] 11 | } 12 | ], 13 | "scene": 0, 14 | "nodes": [ 15 | { 16 | "children": [ 17 | 1 18 | ], 19 | "matrix": [ 20 | 1, 21 | 0, 22 | 0, 23 | 0, 24 | 0, 25 | 0, 26 | -1, 27 | 0, 28 | 0, 29 | 1, 30 | 0, 31 | 0, 32 | 0, 33 | 0, 34 | 0, 35 | 1 36 | ], 37 | "name": "Z_UP" 38 | }, 39 | { 40 | "children": [ 41 | 3, 42 | 2 43 | ], 44 | "matrix": [ 45 | -4.371139894487897e-8, 46 | -1, 47 | 0, 48 | 0, 49 | 1, 50 | -4.371139894487897e-8, 51 | 0, 52 | 0, 53 | 0, 54 | 0, 55 | 1, 56 | 0, 57 | 0, 58 | 0, 59 | 0, 60 | 1 61 | ], 62 | "name": "Armature" 63 | }, 64 | { 65 | "mesh": 0, 66 | "skin": 0, 67 | "name": "Cesium_Man" 68 | }, 69 | { 70 | "children": [ 71 | 12, 72 | 8, 73 | 4 74 | ], 75 | "translation": [ 76 | 1.57554005397742e-8, 77 | 0.004999836906790733, 78 | 0.6789999008178711 79 | ], 80 | "rotation": [ 81 | 0, 82 | -0.0378035344183445, 83 | 0, 84 | -0.9992852210998536 85 | ], 86 | "name": "Skeleton_torso_joint_1", 87 | "scale": [ 88 | 1, 89 | 1, 90 | 1 91 | ] 92 | }, 93 | { 94 | "children": [ 95 | 5 96 | ], 97 | "translation": [ 98 | 0.02855719067156315, 99 | -0.06803914159536362, 100 | -0.06295864284038544 101 | ], 102 | "rotation": [ 103 | 0, 104 | -0.6898291707038879, 105 | 0, 106 | -0.7239722013473511 107 | ], 108 | "name": "leg_joint_R_1", 109 | "scale": [ 110 | 1, 111 | 1, 112 | 1 113 | ] 114 | }, 115 | { 116 | "children": [ 117 | 6 118 | ], 119 | "translation": [ 120 | 0.26089081168174744, 121 | -0.009026050567626951, 122 | 0.05167089030146599 123 | ], 124 | "rotation": [ 125 | 0, 126 | -0.0941137745976448, 127 | 0, 128 | -0.9955614805221558 129 | ], 130 | "scale": [ 131 | 1.0000001192092896, 132 | 1, 133 | 1.0000001192092896 134 | ], 135 | "name": "leg_joint_R_2" 136 | }, 137 | { 138 | "children": [ 139 | 7 140 | ], 141 | "translation": [ 142 | 0.27546030282974243, 143 | -0.0014317259192466736, 144 | -0.014104830101132391 145 | ], 146 | "rotation": [ 147 | 0, 148 | 0.8666408061981201, 149 | 0, 150 | 0.4989325702190399 151 | ], 152 | "name": "leg_joint_R_3", 153 | "scale": [ 154 | 1, 155 | 1, 156 | 1 157 | ] 158 | }, 159 | { 160 | "translation": [ 161 | -0.06681963056325912, 162 | -0.001072264974936843, 163 | 0.026351310312747955 164 | ], 165 | "rotation": [ 166 | 0, 167 | 0.3269147574901581, 168 | 0, 169 | -0.9450538158416748 170 | ], 171 | "name": "leg_joint_R_5", 172 | "scale": [ 173 | 1, 174 | 1, 175 | 1 176 | ] 177 | }, 178 | { 179 | "children": [ 180 | 9 181 | ], 182 | "translation": [ 183 | 0.028519999235868457, 184 | 0.06803944706916809, 185 | -0.06295935809612274 186 | ], 187 | "rotation": [ 188 | 0, 189 | -0.32463353872299194, 190 | 0, 191 | -0.9458398818969728 192 | ], 193 | "name": "leg_joint_L_1", 194 | "scale": [ 195 | 1, 196 | 1, 197 | 1 198 | ] 199 | }, 200 | { 201 | "children": [ 202 | 10 203 | ], 204 | "translation": [ 205 | 0.20916390419006348, 206 | 0.009055502712726591, 207 | -0.16426950693130493 208 | ], 209 | "rotation": [ 210 | 0, 211 | -0.5294369459152222, 212 | 0, 213 | -0.8483493328094482 214 | ], 215 | "scale": [ 216 | 1.0000001192092896, 217 | 1, 218 | 1.0000001192092896 219 | ], 220 | "name": "leg_joint_L_2" 221 | }, 222 | { 223 | "children": [ 224 | 11 225 | ], 226 | "translation": [ 227 | 0.27579009532928467, 228 | 0.0013972519664093852, 229 | 0.004122479818761349 230 | ], 231 | "rotation": [ 232 | 0, 233 | -0.8377647399902344, 234 | 0, 235 | -0.5460314750671387 236 | ], 237 | "scale": [ 238 | 1, 239 | 0.9999999403953552, 240 | 1 241 | ], 242 | "name": "leg_joint_L_3" 243 | }, 244 | { 245 | "translation": [ 246 | -0.06558381021022797, 247 | 0.001090653007850051, 248 | 0.02929146029055119 249 | ], 250 | "rotation": [ 251 | 0, 252 | 0.3130458891391754, 253 | 0, 254 | -0.9497380256652832 255 | ], 256 | "name": "leg_joint_L_5", 257 | "scale": [ 258 | 1, 259 | 1, 260 | 1 261 | ] 262 | }, 263 | { 264 | "children": [ 265 | 13 266 | ], 267 | "translation": [ 268 | 0.0000133617004394182, 269 | -0.000013373800356930587, 270 | 0.14541690051555634 271 | ], 272 | "rotation": [ 273 | 0, 274 | -0.6573964357376099, 275 | 0, 276 | -0.7535448670387268 277 | ], 278 | "name": "Skeleton_torso_joint_2", 279 | "scale": [ 280 | 1, 281 | 1, 282 | 1 283 | ] 284 | }, 285 | { 286 | "children": [ 287 | 20, 288 | 17, 289 | 14 290 | ], 291 | "translation": [ 292 | -0.2505168914794922, 293 | 6.072219775887788e-7, 294 | -0.00007290810026461259 295 | ], 296 | "rotation": [ 297 | 0, 298 | 0.6227028965950012, 299 | 0, 300 | -0.7824583649635315 301 | ], 302 | "name": "torso_joint_3", 303 | "scale": [ 304 | 1, 305 | 1, 306 | 1 307 | ] 308 | }, 309 | { 310 | "children": [ 311 | 15 312 | ], 313 | "translation": [ 314 | -0.00003830249988823198, 315 | -0.09098774939775468, 316 | -0.000062032304413151 317 | ], 318 | "rotation": [ 319 | 0, 320 | 0.9909319281578064, 321 | 0, 322 | -0.13436488807201385 323 | ], 324 | "name": "Skeleton_arm_joint_R", 325 | "scale": [ 326 | 1, 327 | 1, 328 | 1 329 | ] 330 | }, 331 | { 332 | "children": [ 333 | 16 334 | ], 335 | "translation": [ 336 | -0.03554634004831314, 337 | -0.2154989987611771, 338 | 0.10423289984464645 339 | ], 340 | "rotation": [ 341 | 0, 342 | 0.8961479663848877, 343 | 0, 344 | 0.4437553286552429 345 | ], 346 | "scale": [ 347 | 0.9999999403953552, 348 | 1, 349 | 0.9999999403953552 350 | ], 351 | "name": "Skeleton_arm_joint_R__2_" 352 | }, 353 | { 354 | "translation": [ 355 | 0.03137021884322167, 356 | -0.1430010050535202, 357 | -0.11761169880628586 358 | ], 359 | "rotation": [ 360 | 0, 361 | 0.3792171180248261, 362 | 0, 363 | -0.9253078103065492 364 | ], 365 | "scale": [ 366 | 1.0000001192092896, 367 | 1, 368 | 1.0000001192092896 369 | ], 370 | "name": "Skeleton_arm_joint_R__3_" 371 | }, 372 | { 373 | "children": [ 374 | 18 375 | ], 376 | "translation": [ 377 | -0.00003837469921563752, 378 | 0.091013602912426, 379 | -0.00006143339851405472 380 | ], 381 | "rotation": [ 382 | 0, 383 | 0.9959768652915956, 384 | 0, 385 | 0.08961082249879837 386 | ], 387 | "name": "Skeleton_arm_joint_L__4_", 388 | "scale": [ 389 | 1, 390 | 1, 391 | 1 392 | ] 393 | }, 394 | { 395 | "children": [ 396 | 19 397 | ], 398 | "translation": [ 399 | 0.01322161965072155, 400 | 0.21549950540065768, 401 | 0.10933209955692293 402 | ], 403 | "rotation": [ 404 | 0, 405 | -0.0711694285273552, 406 | 0, 407 | -0.9974642395973206 408 | ], 409 | "name": "Skeleton_arm_joint_L__3_", 410 | "scale": [ 411 | 1, 412 | 1, 413 | 1 414 | ] 415 | }, 416 | { 417 | "translation": [ 418 | -0.09332461655139924, 419 | 0.1430000960826874, 420 | 0.07814791053533554 421 | ], 422 | "rotation": [ 423 | 0, 424 | -0.02254222705960274, 425 | 0, 426 | -0.9997459053993224 427 | ], 428 | "name": "Skeleton_arm_joint_L__2_", 429 | "scale": [ 430 | 1, 431 | 1, 432 | 1 433 | ] 434 | }, 435 | { 436 | "children": [ 437 | 21 438 | ], 439 | "translation": [ 440 | -0.000002366030003031483, 441 | 0.000002413989932392724, 442 | 0.06483621150255203 443 | ], 444 | "rotation": [ 445 | 0, 446 | -0.660634458065033, 447 | 0, 448 | -0.750707745552063 449 | ], 450 | "name": "Skeleton_neck_joint_1", 451 | "scale": [ 452 | 1, 453 | 1, 454 | 1 455 | ] 456 | }, 457 | { 458 | "translation": [ 459 | -0.05204017087817192, 460 | -3.3993298842460724e-8, 461 | -0.0000026607899599184748 462 | ], 463 | "rotation": [ 464 | 0, 465 | 0.9996904730796814, 466 | 0, 467 | 0.024879230186343193 468 | ], 469 | "scale": [ 470 | 1.0000001192092896, 471 | 1, 472 | 1.0000001192092896 473 | ], 474 | "name": "Skeleton_neck_joint_2" 475 | } 476 | ], 477 | "meshes": [ 478 | { 479 | "primitives": [ 480 | { 481 | "attributes": { 482 | "JOINTS_0": 1, 483 | "NORMAL": 2, 484 | "POSITION": 3, 485 | "TEXCOORD_0": 4, 486 | "WEIGHTS_0": 5 487 | }, 488 | "indices": 0, 489 | "mode": 4, 490 | "material": 0 491 | } 492 | ], 493 | "name": "Cesium_Man" 494 | } 495 | ], 496 | "animations": [ 497 | { 498 | "channels": [ 499 | { 500 | "sampler": 0, 501 | "target": { 502 | "node": 3, 503 | "path": "translation" 504 | } 505 | }, 506 | { 507 | "sampler": 1, 508 | "target": { 509 | "node": 3, 510 | "path": "rotation" 511 | } 512 | }, 513 | { 514 | "sampler": 2, 515 | "target": { 516 | "node": 3, 517 | "path": "scale" 518 | } 519 | }, 520 | { 521 | "sampler": 3, 522 | "target": { 523 | "node": 12, 524 | "path": "translation" 525 | } 526 | }, 527 | { 528 | "sampler": 4, 529 | "target": { 530 | "node": 12, 531 | "path": "rotation" 532 | } 533 | }, 534 | { 535 | "sampler": 5, 536 | "target": { 537 | "node": 12, 538 | "path": "scale" 539 | } 540 | }, 541 | { 542 | "sampler": 6, 543 | "target": { 544 | "node": 13, 545 | "path": "translation" 546 | } 547 | }, 548 | { 549 | "sampler": 7, 550 | "target": { 551 | "node": 13, 552 | "path": "rotation" 553 | } 554 | }, 555 | { 556 | "sampler": 8, 557 | "target": { 558 | "node": 13, 559 | "path": "scale" 560 | } 561 | }, 562 | { 563 | "sampler": 9, 564 | "target": { 565 | "node": 20, 566 | "path": "translation" 567 | } 568 | }, 569 | { 570 | "sampler": 10, 571 | "target": { 572 | "node": 20, 573 | "path": "rotation" 574 | } 575 | }, 576 | { 577 | "sampler": 11, 578 | "target": { 579 | "node": 20, 580 | "path": "scale" 581 | } 582 | }, 583 | { 584 | "sampler": 12, 585 | "target": { 586 | "node": 21, 587 | "path": "translation" 588 | } 589 | }, 590 | { 591 | "sampler": 13, 592 | "target": { 593 | "node": 21, 594 | "path": "rotation" 595 | } 596 | }, 597 | { 598 | "sampler": 14, 599 | "target": { 600 | "node": 21, 601 | "path": "scale" 602 | } 603 | }, 604 | { 605 | "sampler": 15, 606 | "target": { 607 | "node": 17, 608 | "path": "translation" 609 | } 610 | }, 611 | { 612 | "sampler": 16, 613 | "target": { 614 | "node": 17, 615 | "path": "rotation" 616 | } 617 | }, 618 | { 619 | "sampler": 17, 620 | "target": { 621 | "node": 17, 622 | "path": "scale" 623 | } 624 | }, 625 | { 626 | "sampler": 18, 627 | "target": { 628 | "node": 18, 629 | "path": "translation" 630 | } 631 | }, 632 | { 633 | "sampler": 19, 634 | "target": { 635 | "node": 18, 636 | "path": "rotation" 637 | } 638 | }, 639 | { 640 | "sampler": 20, 641 | "target": { 642 | "node": 18, 643 | "path": "scale" 644 | } 645 | }, 646 | { 647 | "sampler": 21, 648 | "target": { 649 | "node": 19, 650 | "path": "translation" 651 | } 652 | }, 653 | { 654 | "sampler": 22, 655 | "target": { 656 | "node": 19, 657 | "path": "rotation" 658 | } 659 | }, 660 | { 661 | "sampler": 23, 662 | "target": { 663 | "node": 19, 664 | "path": "scale" 665 | } 666 | }, 667 | { 668 | "sampler": 24, 669 | "target": { 670 | "node": 14, 671 | "path": "translation" 672 | } 673 | }, 674 | { 675 | "sampler": 25, 676 | "target": { 677 | "node": 14, 678 | "path": "rotation" 679 | } 680 | }, 681 | { 682 | "sampler": 26, 683 | "target": { 684 | "node": 14, 685 | "path": "scale" 686 | } 687 | }, 688 | { 689 | "sampler": 27, 690 | "target": { 691 | "node": 15, 692 | "path": "translation" 693 | } 694 | }, 695 | { 696 | "sampler": 28, 697 | "target": { 698 | "node": 15, 699 | "path": "rotation" 700 | } 701 | }, 702 | { 703 | "sampler": 29, 704 | "target": { 705 | "node": 15, 706 | "path": "scale" 707 | } 708 | }, 709 | { 710 | "sampler": 30, 711 | "target": { 712 | "node": 16, 713 | "path": "translation" 714 | } 715 | }, 716 | { 717 | "sampler": 31, 718 | "target": { 719 | "node": 16, 720 | "path": "rotation" 721 | } 722 | }, 723 | { 724 | "sampler": 32, 725 | "target": { 726 | "node": 16, 727 | "path": "scale" 728 | } 729 | }, 730 | { 731 | "sampler": 33, 732 | "target": { 733 | "node": 8, 734 | "path": "translation" 735 | } 736 | }, 737 | { 738 | "sampler": 34, 739 | "target": { 740 | "node": 8, 741 | "path": "rotation" 742 | } 743 | }, 744 | { 745 | "sampler": 35, 746 | "target": { 747 | "node": 8, 748 | "path": "scale" 749 | } 750 | }, 751 | { 752 | "sampler": 36, 753 | "target": { 754 | "node": 9, 755 | "path": "translation" 756 | } 757 | }, 758 | { 759 | "sampler": 37, 760 | "target": { 761 | "node": 9, 762 | "path": "rotation" 763 | } 764 | }, 765 | { 766 | "sampler": 38, 767 | "target": { 768 | "node": 9, 769 | "path": "scale" 770 | } 771 | }, 772 | { 773 | "sampler": 39, 774 | "target": { 775 | "node": 10, 776 | "path": "translation" 777 | } 778 | }, 779 | { 780 | "sampler": 40, 781 | "target": { 782 | "node": 10, 783 | "path": "rotation" 784 | } 785 | }, 786 | { 787 | "sampler": 41, 788 | "target": { 789 | "node": 10, 790 | "path": "scale" 791 | } 792 | }, 793 | { 794 | "sampler": 42, 795 | "target": { 796 | "node": 11, 797 | "path": "translation" 798 | } 799 | }, 800 | { 801 | "sampler": 43, 802 | "target": { 803 | "node": 11, 804 | "path": "rotation" 805 | } 806 | }, 807 | { 808 | "sampler": 44, 809 | "target": { 810 | "node": 11, 811 | "path": "scale" 812 | } 813 | }, 814 | { 815 | "sampler": 45, 816 | "target": { 817 | "node": 4, 818 | "path": "translation" 819 | } 820 | }, 821 | { 822 | "sampler": 46, 823 | "target": { 824 | "node": 4, 825 | "path": "rotation" 826 | } 827 | }, 828 | { 829 | "sampler": 47, 830 | "target": { 831 | "node": 4, 832 | "path": "scale" 833 | } 834 | }, 835 | { 836 | "sampler": 48, 837 | "target": { 838 | "node": 5, 839 | "path": "translation" 840 | } 841 | }, 842 | { 843 | "sampler": 49, 844 | "target": { 845 | "node": 5, 846 | "path": "rotation" 847 | } 848 | }, 849 | { 850 | "sampler": 50, 851 | "target": { 852 | "node": 5, 853 | "path": "scale" 854 | } 855 | }, 856 | { 857 | "sampler": 51, 858 | "target": { 859 | "node": 6, 860 | "path": "translation" 861 | } 862 | }, 863 | { 864 | "sampler": 52, 865 | "target": { 866 | "node": 6, 867 | "path": "rotation" 868 | } 869 | }, 870 | { 871 | "sampler": 53, 872 | "target": { 873 | "node": 6, 874 | "path": "scale" 875 | } 876 | }, 877 | { 878 | "sampler": 54, 879 | "target": { 880 | "node": 7, 881 | "path": "translation" 882 | } 883 | }, 884 | { 885 | "sampler": 55, 886 | "target": { 887 | "node": 7, 888 | "path": "rotation" 889 | } 890 | }, 891 | { 892 | "sampler": 56, 893 | "target": { 894 | "node": 7, 895 | "path": "scale" 896 | } 897 | } 898 | ], 899 | "samplers": [ 900 | { 901 | "input": 6, 902 | "interpolation": "LINEAR", 903 | "output": 7 904 | }, 905 | { 906 | "input": 6, 907 | "interpolation": "LINEAR", 908 | "output": 8 909 | }, 910 | { 911 | "input": 6, 912 | "interpolation": "LINEAR", 913 | "output": 9 914 | }, 915 | { 916 | "input": 10, 917 | "interpolation": "LINEAR", 918 | "output": 11 919 | }, 920 | { 921 | "input": 10, 922 | "interpolation": "LINEAR", 923 | "output": 12 924 | }, 925 | { 926 | "input": 10, 927 | "interpolation": "LINEAR", 928 | "output": 13 929 | }, 930 | { 931 | "input": 14, 932 | "interpolation": "LINEAR", 933 | "output": 15 934 | }, 935 | { 936 | "input": 14, 937 | "interpolation": "LINEAR", 938 | "output": 16 939 | }, 940 | { 941 | "input": 14, 942 | "interpolation": "LINEAR", 943 | "output": 17 944 | }, 945 | { 946 | "input": 18, 947 | "interpolation": "LINEAR", 948 | "output": 19 949 | }, 950 | { 951 | "input": 18, 952 | "interpolation": "LINEAR", 953 | "output": 20 954 | }, 955 | { 956 | "input": 18, 957 | "interpolation": "LINEAR", 958 | "output": 21 959 | }, 960 | { 961 | "input": 22, 962 | "interpolation": "LINEAR", 963 | "output": 23 964 | }, 965 | { 966 | "input": 22, 967 | "interpolation": "LINEAR", 968 | "output": 24 969 | }, 970 | { 971 | "input": 22, 972 | "interpolation": "LINEAR", 973 | "output": 25 974 | }, 975 | { 976 | "input": 26, 977 | "interpolation": "LINEAR", 978 | "output": 27 979 | }, 980 | { 981 | "input": 26, 982 | "interpolation": "LINEAR", 983 | "output": 28 984 | }, 985 | { 986 | "input": 26, 987 | "interpolation": "LINEAR", 988 | "output": 29 989 | }, 990 | { 991 | "input": 30, 992 | "interpolation": "LINEAR", 993 | "output": 31 994 | }, 995 | { 996 | "input": 30, 997 | "interpolation": "LINEAR", 998 | "output": 32 999 | }, 1000 | { 1001 | "input": 30, 1002 | "interpolation": "LINEAR", 1003 | "output": 33 1004 | }, 1005 | { 1006 | "input": 34, 1007 | "interpolation": "LINEAR", 1008 | "output": 35 1009 | }, 1010 | { 1011 | "input": 34, 1012 | "interpolation": "LINEAR", 1013 | "output": 36 1014 | }, 1015 | { 1016 | "input": 34, 1017 | "interpolation": "LINEAR", 1018 | "output": 37 1019 | }, 1020 | { 1021 | "input": 38, 1022 | "interpolation": "LINEAR", 1023 | "output": 39 1024 | }, 1025 | { 1026 | "input": 38, 1027 | "interpolation": "LINEAR", 1028 | "output": 40 1029 | }, 1030 | { 1031 | "input": 38, 1032 | "interpolation": "LINEAR", 1033 | "output": 41 1034 | }, 1035 | { 1036 | "input": 42, 1037 | "interpolation": "LINEAR", 1038 | "output": 43 1039 | }, 1040 | { 1041 | "input": 42, 1042 | "interpolation": "LINEAR", 1043 | "output": 44 1044 | }, 1045 | { 1046 | "input": 42, 1047 | "interpolation": "LINEAR", 1048 | "output": 45 1049 | }, 1050 | { 1051 | "input": 46, 1052 | "interpolation": "LINEAR", 1053 | "output": 47 1054 | }, 1055 | { 1056 | "input": 46, 1057 | "interpolation": "LINEAR", 1058 | "output": 48 1059 | }, 1060 | { 1061 | "input": 46, 1062 | "interpolation": "LINEAR", 1063 | "output": 49 1064 | }, 1065 | { 1066 | "input": 50, 1067 | "interpolation": "LINEAR", 1068 | "output": 51 1069 | }, 1070 | { 1071 | "input": 50, 1072 | "interpolation": "LINEAR", 1073 | "output": 52 1074 | }, 1075 | { 1076 | "input": 50, 1077 | "interpolation": "LINEAR", 1078 | "output": 53 1079 | }, 1080 | { 1081 | "input": 54, 1082 | "interpolation": "LINEAR", 1083 | "output": 55 1084 | }, 1085 | { 1086 | "input": 54, 1087 | "interpolation": "LINEAR", 1088 | "output": 56 1089 | }, 1090 | { 1091 | "input": 54, 1092 | "interpolation": "LINEAR", 1093 | "output": 57 1094 | }, 1095 | { 1096 | "input": 58, 1097 | "interpolation": "LINEAR", 1098 | "output": 59 1099 | }, 1100 | { 1101 | "input": 58, 1102 | "interpolation": "LINEAR", 1103 | "output": 60 1104 | }, 1105 | { 1106 | "input": 58, 1107 | "interpolation": "LINEAR", 1108 | "output": 61 1109 | }, 1110 | { 1111 | "input": 62, 1112 | "interpolation": "LINEAR", 1113 | "output": 63 1114 | }, 1115 | { 1116 | "input": 62, 1117 | "interpolation": "LINEAR", 1118 | "output": 64 1119 | }, 1120 | { 1121 | "input": 62, 1122 | "interpolation": "LINEAR", 1123 | "output": 65 1124 | }, 1125 | { 1126 | "input": 66, 1127 | "interpolation": "LINEAR", 1128 | "output": 67 1129 | }, 1130 | { 1131 | "input": 66, 1132 | "interpolation": "LINEAR", 1133 | "output": 68 1134 | }, 1135 | { 1136 | "input": 66, 1137 | "interpolation": "LINEAR", 1138 | "output": 69 1139 | }, 1140 | { 1141 | "input": 70, 1142 | "interpolation": "LINEAR", 1143 | "output": 71 1144 | }, 1145 | { 1146 | "input": 70, 1147 | "interpolation": "LINEAR", 1148 | "output": 72 1149 | }, 1150 | { 1151 | "input": 70, 1152 | "interpolation": "LINEAR", 1153 | "output": 73 1154 | }, 1155 | { 1156 | "input": 74, 1157 | "interpolation": "LINEAR", 1158 | "output": 75 1159 | }, 1160 | { 1161 | "input": 74, 1162 | "interpolation": "LINEAR", 1163 | "output": 76 1164 | }, 1165 | { 1166 | "input": 74, 1167 | "interpolation": "LINEAR", 1168 | "output": 77 1169 | }, 1170 | { 1171 | "input": 78, 1172 | "interpolation": "LINEAR", 1173 | "output": 79 1174 | }, 1175 | { 1176 | "input": 78, 1177 | "interpolation": "LINEAR", 1178 | "output": 80 1179 | }, 1180 | { 1181 | "input": 78, 1182 | "interpolation": "LINEAR", 1183 | "output": 81 1184 | } 1185 | ] 1186 | } 1187 | ], 1188 | "skins": [ 1189 | { 1190 | "inverseBindMatrices": 82, 1191 | "skeleton": 3, 1192 | "joints": [ 1193 | 3, 1194 | 12, 1195 | 13, 1196 | 20, 1197 | 21, 1198 | 17, 1199 | 14, 1200 | 18, 1201 | 15, 1202 | 19, 1203 | 16, 1204 | 8, 1205 | 4, 1206 | 9, 1207 | 5, 1208 | 10, 1209 | 6, 1210 | 11, 1211 | 7 1212 | ], 1213 | "name": "Armature" 1214 | } 1215 | ], 1216 | "accessors": [ 1217 | { 1218 | "bufferView": 0, 1219 | "byteOffset": 0, 1220 | "componentType": 5123, 1221 | "count": 14016, 1222 | "max": [ 1223 | 3272 1224 | ], 1225 | "min": [ 1226 | 0 1227 | ], 1228 | "type": "SCALAR" 1229 | }, 1230 | { 1231 | "bufferView": 1, 1232 | "byteOffset": 0, 1233 | "componentType": 5123, 1234 | "count": 3273, 1235 | "max": [ 1236 | 18, 1237 | 18, 1238 | 18, 1239 | 18 1240 | ], 1241 | "min": [ 1242 | 0, 1243 | 0, 1244 | 0, 1245 | 0 1246 | ], 1247 | "type": "VEC4" 1248 | }, 1249 | { 1250 | "bufferView": 2, 1251 | "byteOffset": 0, 1252 | "componentType": 5126, 1253 | "count": 3273, 1254 | "max": [ 1255 | 1, 1256 | 0.9999808073043824, 1257 | 0.9944415092468262 1258 | ], 1259 | "min": [ 1260 | -1, 1261 | -0.9999808073043824, 1262 | -1 1263 | ], 1264 | "type": "VEC3" 1265 | }, 1266 | { 1267 | "bufferView": 2, 1268 | "byteOffset": 39276, 1269 | "componentType": 5126, 1270 | "count": 3273, 1271 | "max": [ 1272 | 0.1809539943933487, 1273 | 0.569136917591095, 1274 | 1.5065499544143677 1275 | ], 1276 | "min": [ 1277 | -0.13100001215934753, 1278 | -0.5691370964050293, 1279 | 0 1280 | ], 1281 | "type": "VEC3" 1282 | }, 1283 | { 1284 | "bufferView": 1, 1285 | "byteOffset": 26184, 1286 | "componentType": 5126, 1287 | "count": 3273, 1288 | "max": [ 1289 | 0.990805983543396, 1290 | 0.9880298972129822 1291 | ], 1292 | "min": [ 1293 | 0.014079390093684196, 1294 | 0.008445978164672852 1295 | ], 1296 | "type": "VEC2" 1297 | }, 1298 | { 1299 | "bufferView": 3, 1300 | "byteOffset": 0, 1301 | "componentType": 5126, 1302 | "count": 3273, 1303 | "max": [ 1304 | 1, 1305 | 0.989919900894165, 1306 | 0.9510763883590698, 1307 | 0.9196179509162904 1308 | ], 1309 | "min": [ 1310 | 0.010080089792609217, 1311 | 0, 1312 | 0, 1313 | 0 1314 | ], 1315 | "type": "VEC4" 1316 | }, 1317 | { 1318 | "bufferView": 4, 1319 | "byteOffset": 0, 1320 | "componentType": 5126, 1321 | "count": 48, 1322 | "max": [ 1323 | 2 1324 | ], 1325 | "min": [ 1326 | 0.04166661947965622 1327 | ], 1328 | "type": "SCALAR" 1329 | }, 1330 | { 1331 | "bufferView": 5, 1332 | "byteOffset": 0, 1333 | "componentType": 5126, 1334 | "count": 48, 1335 | "max": [ 1336 | 3.880559873437051e-8, 1337 | -0.02000010944902897, 1338 | 0.7110069990158081 1339 | ], 1340 | "min": [ 1341 | 2.716890046272624e-9, 1342 | -0.030000129714608192, 1343 | 0.6399999856948853 1344 | ], 1345 | "type": "VEC3" 1346 | }, 1347 | { 1348 | "bufferView": 6, 1349 | "byteOffset": 0, 1350 | "componentType": 5126, 1351 | "count": 48, 1352 | "max": [ 1353 | 0.0001435002632206306, 1354 | -0.02146764844655991, 1355 | -0.000009948204024112783, 1356 | -0.9980905055999756 1357 | ], 1358 | "min": [ 1359 | -0.012384946458041668, 1360 | -0.06042621284723282, 1361 | -0.0041049933061003685, 1362 | -0.9997026920318604 1363 | ], 1364 | "type": "VEC4" 1365 | }, 1366 | { 1367 | "bufferView": 5, 1368 | "byteOffset": 576, 1369 | "componentType": 5126, 1370 | "count": 48, 1371 | "max": [ 1372 | 1.0000004768371584, 1373 | 1.0000001192092896, 1374 | 1.000000238418579 1375 | ], 1376 | "min": [ 1377 | 0.9999999403953552, 1378 | 0.9999998211860656, 1379 | 0.9999997615814208 1380 | ], 1381 | "type": "VEC3" 1382 | }, 1383 | { 1384 | "bufferView": 4, 1385 | "byteOffset": 192, 1386 | "componentType": 5126, 1387 | "count": 48, 1388 | "max": [ 1389 | 2 1390 | ], 1391 | "min": [ 1392 | 0.04166661947965622 1393 | ], 1394 | "type": "SCALAR" 1395 | }, 1396 | { 1397 | "bufferView": 5, 1398 | "byteOffset": 1152, 1399 | "componentType": 5126, 1400 | "count": 48, 1401 | "max": [ 1402 | 0.000013520900211005937, 1403 | 0.0009866129839792848, 1404 | 0.1454171985387802 1405 | ], 1406 | "min": [ 1407 | 0.000013436199878924528, 1408 | 0.0009865909814834597, 1409 | 0.145416796207428 1410 | ], 1411 | "type": "VEC3" 1412 | }, 1413 | { 1414 | "bufferView": 6, 1415 | "byteOffset": 768, 1416 | "componentType": 5126, 1417 | "count": 48, 1418 | "max": [ 1419 | 0.011148878373205662, 1420 | -0.7106494903564453, 1421 | 0.0006393495132215321, 1422 | -0.6787928938865662 1423 | ], 1424 | "min": [ 1425 | -0.008564536459743977, 1426 | -0.7339289784431458, 1427 | -0.025856714695692062, 1428 | -0.7034341096878052 1429 | ], 1430 | "type": "VEC4" 1431 | }, 1432 | { 1433 | "bufferView": 5, 1434 | "byteOffset": 1728, 1435 | "componentType": 5126, 1436 | "count": 48, 1437 | "max": [ 1438 | 1.0000004768371584, 1439 | 1.000000238418579, 1440 | 0.9999998211860656 1441 | ], 1442 | "min": [ 1443 | 0.9999999403953552, 1444 | 0.9999998807907104, 1445 | 0.9999989867210388 1446 | ], 1447 | "type": "VEC3" 1448 | }, 1449 | { 1450 | "bufferView": 4, 1451 | "byteOffset": 384, 1452 | "componentType": 5126, 1453 | "count": 48, 1454 | "max": [ 1455 | 2 1456 | ], 1457 | "min": [ 1458 | 0.04166661947965622 1459 | ], 1460 | "type": "SCALAR" 1461 | }, 1462 | { 1463 | "bufferView": 5, 1464 | "byteOffset": 2304, 1465 | "componentType": 5126, 1466 | "count": 48, 1467 | "max": [ 1468 | -0.25051650404930115, 1469 | 5.923209869251878e-7, 1470 | -0.00007277730037458241 1471 | ], 1472 | "min": [ 1473 | -0.25051701068878174, 1474 | 5.58793999516638e-7, 1475 | -0.00007287789776455611 1476 | ], 1477 | "type": "VEC3" 1478 | }, 1479 | { 1480 | "bufferView": 6, 1481 | "byteOffset": 1536, 1482 | "componentType": 5126, 1483 | "count": 48, 1484 | "max": [ 1485 | 0.13804565370082855, 1486 | 0.6359269618988037, 1487 | -0.003375347936525941, 1488 | -0.7641801238059998 1489 | ], 1490 | "min": [ 1491 | -0.06163197010755539, 1492 | 0.6225405335426331, 1493 | -0.0653248131275177, 1494 | -0.7825579643249512 1495 | ], 1496 | "type": "VEC4" 1497 | }, 1498 | { 1499 | "bufferView": 5, 1500 | "byteOffset": 2880, 1501 | "componentType": 5126, 1502 | "count": 48, 1503 | "max": [ 1504 | 1.000001072883606, 1505 | 1.0000003576278689, 1506 | 1 1507 | ], 1508 | "min": [ 1509 | 1.0000004768371584, 1510 | 0.9999999403953552, 1511 | 0.999999463558197 1512 | ], 1513 | "type": "VEC3" 1514 | }, 1515 | { 1516 | "bufferView": 4, 1517 | "byteOffset": 576, 1518 | "componentType": 5126, 1519 | "count": 48, 1520 | "max": [ 1521 | 2 1522 | ], 1523 | "min": [ 1524 | 0.04166661947965622 1525 | ], 1526 | "type": "SCALAR" 1527 | }, 1528 | { 1529 | "bufferView": 5, 1530 | "byteOffset": 3456, 1531 | "componentType": 5126, 1532 | "count": 48, 1533 | "max": [ 1534 | -0.000002384189883741783, 1535 | 0.000002458690005369135, 1536 | 0.06483876705169678 1537 | ], 1538 | "min": [ 1539 | -0.000002536919964768458, 1540 | 0.000002384189883741783, 1541 | 0.06483828276395798 1542 | ], 1543 | "type": "VEC3" 1544 | }, 1545 | { 1546 | "bufferView": 6, 1547 | "byteOffset": 2304, 1548 | "componentType": 5126, 1549 | "count": 48, 1550 | "max": [ 1551 | 0.0364987850189209, 1552 | -0.6325404644012451, 1553 | 0.04193282127380371, 1554 | -0.749859094619751 1555 | ], 1556 | "min": [ 1557 | -0.02474863827228546, 1558 | -0.6592763066291809, 1559 | -0.03008362464606762, 1560 | -0.7735469341278076 1561 | ], 1562 | "type": "VEC4" 1563 | }, 1564 | { 1565 | "bufferView": 5, 1566 | "byteOffset": 4032, 1567 | "componentType": 5126, 1568 | "count": 48, 1569 | "max": [ 1570 | 1, 1571 | 1.000000238418579, 1572 | 1.000000238418579 1573 | ], 1574 | "min": [ 1575 | 0.9999996423721313, 1576 | 0.9999995231628418, 1577 | 0.9999995827674866 1578 | ], 1579 | "type": "VEC3" 1580 | }, 1581 | { 1582 | "bufferView": 4, 1583 | "byteOffset": 768, 1584 | "componentType": 5126, 1585 | "count": 48, 1586 | "max": [ 1587 | 2 1588 | ], 1589 | "min": [ 1590 | 0.04166661947965622 1591 | ], 1592 | "type": "SCALAR" 1593 | }, 1594 | { 1595 | "bufferView": 5, 1596 | "byteOffset": 4608, 1597 | "componentType": 5126, 1598 | "count": 48, 1599 | "max": [ 1600 | -0.0520395003259182, 1601 | 7.450579708745408e-9, 1602 | -0.000002585350102890516 1603 | ], 1604 | "min": [ 1605 | -0.05204005911946297, 1606 | -5.96045985901128e-8, 1607 | -0.0000026747600259113824 1608 | ], 1609 | "type": "VEC3" 1610 | }, 1611 | { 1612 | "bufferView": 6, 1613 | "byteOffset": 3072, 1614 | "componentType": 5126, 1615 | "count": 48, 1616 | "max": [ 1617 | 0.04680187255144119, 1618 | 0.999507486820221, 1619 | 0.002036086050793529, 1620 | 0.09058715403079988 1621 | ], 1622 | "min": [ 1623 | -0.093629889190197, 1624 | 0.9950671792030336, 1625 | -0.00258980062790215, 1626 | 0.0184526015073061 1627 | ], 1628 | "type": "VEC4" 1629 | }, 1630 | { 1631 | "bufferView": 5, 1632 | "byteOffset": 5184, 1633 | "componentType": 5126, 1634 | "count": 48, 1635 | "max": [ 1636 | 1.0000003576278689, 1637 | 1.000000238418579, 1638 | 1.0000009536743164 1639 | ], 1640 | "min": [ 1641 | 0.9999998807907104, 1642 | 0.9999996423721313, 1643 | 1.000000238418579 1644 | ], 1645 | "type": "VEC3" 1646 | }, 1647 | { 1648 | "bufferView": 4, 1649 | "byteOffset": 960, 1650 | "componentType": 5126, 1651 | "count": 48, 1652 | "max": [ 1653 | 2 1654 | ], 1655 | "min": [ 1656 | 0.04166661947965622 1657 | ], 1658 | "type": "SCALAR" 1659 | }, 1660 | { 1661 | "bufferView": 5, 1662 | "byteOffset": 5760, 1663 | "componentType": 5126, 1664 | "count": 48, 1665 | "max": [ 1666 | -0.00003742050103028305, 1667 | 0.08800023794174194, 1668 | -0.00005880460230400786 1669 | ], 1670 | "min": [ 1671 | -0.000037621699448209256, 1672 | 0.08799994736909866, 1673 | -0.000059304802562110126 1674 | ], 1675 | "type": "VEC3" 1676 | }, 1677 | { 1678 | "bufferView": 6, 1679 | "byteOffset": 3840, 1680 | "componentType": 5126, 1681 | "count": 48, 1682 | "max": [ 1683 | 0.2951536476612091, 1684 | 0.9301012754440308, 1685 | -0.2815393805503845, 1686 | 0.3835828900337219 1687 | ], 1688 | "min": [ 1689 | -0.13552021980285645, 1690 | 0.8065234422683716, 1691 | -0.4443180561065674, 1692 | -0.17752912640571597 1693 | ], 1694 | "type": "VEC4" 1695 | }, 1696 | { 1697 | "bufferView": 5, 1698 | "byteOffset": 6336, 1699 | "componentType": 5126, 1700 | "count": 48, 1701 | "max": [ 1702 | 1.0000005960464478, 1703 | 1.0000001192092896, 1704 | 1.0000003576278689 1705 | ], 1706 | "min": [ 1707 | 0.9999999403953552, 1708 | 0.9999996423721313, 1709 | 0.9999998211860656 1710 | ], 1711 | "type": "VEC3" 1712 | }, 1713 | { 1714 | "bufferView": 4, 1715 | "byteOffset": 1152, 1716 | "componentType": 5126, 1717 | "count": 48, 1718 | "max": [ 1719 | 2 1720 | ], 1721 | "min": [ 1722 | 0.04166661947965622 1723 | ], 1724 | "type": "SCALAR" 1725 | }, 1726 | { 1727 | "bufferView": 5, 1728 | "byteOffset": 6912, 1729 | "componentType": 5126, 1730 | "count": 48, 1731 | "max": [ 1732 | 0.013221889734268188, 1733 | 0.215499609708786, 1734 | 0.10933230072259904 1735 | ], 1736 | "min": [ 1737 | 0.01322161965072155, 1738 | 0.2154994010925293, 1739 | 0.10933209955692293 1740 | ], 1741 | "type": "VEC3" 1742 | }, 1743 | { 1744 | "bufferView": 6, 1745 | "byteOffset": 4608, 1746 | "componentType": 5126, 1747 | "count": 48, 1748 | "max": [ 1749 | 0.023567700758576393, 1750 | 0.02101488783955574, 1751 | 0.176296666264534, 1752 | -0.971515953540802 1753 | ], 1754 | "min": [ 1755 | -0.0574759915471077, 1756 | -0.18002526462078097, 1757 | -0.15063291788101196, 1758 | -0.998132348060608 1759 | ], 1760 | "type": "VEC4" 1761 | }, 1762 | { 1763 | "bufferView": 5, 1764 | "byteOffset": 7488, 1765 | "componentType": 5126, 1766 | "count": 48, 1767 | "max": [ 1768 | 0.9999998211860656, 1769 | 0.9999998211860656, 1770 | 0.9999999403953552 1771 | ], 1772 | "min": [ 1773 | 0.9999991059303284, 1774 | 0.9999993443489076, 1775 | 0.9999994039535524 1776 | ], 1777 | "type": "VEC3" 1778 | }, 1779 | { 1780 | "bufferView": 4, 1781 | "byteOffset": 1344, 1782 | "componentType": 5126, 1783 | "count": 48, 1784 | "max": [ 1785 | 2 1786 | ], 1787 | "min": [ 1788 | 0.04166661947965622 1789 | ], 1790 | "type": "SCALAR" 1791 | }, 1792 | { 1793 | "bufferView": 5, 1794 | "byteOffset": 8064, 1795 | "componentType": 5126, 1796 | "count": 48, 1797 | "max": [ 1798 | -0.09332455694675446, 1799 | 0.1430000960826874, 1800 | 0.07814794778823853 1801 | ], 1802 | "min": [ 1803 | -0.09332473576068878, 1804 | 0.14299990236759189, 1805 | 0.07814773917198181 1806 | ], 1807 | "type": "VEC3" 1808 | }, 1809 | { 1810 | "bufferView": 6, 1811 | "byteOffset": 5376, 1812 | "componentType": 5126, 1813 | "count": 48, 1814 | "max": [ 1815 | 0.03372078761458397, 1816 | 0.0026474546175450087, 1817 | 0.207317128777504, 1818 | -0.9705979824066162 1819 | ], 1820 | "min": [ 1821 | 0.006105833686888218, 1822 | -0.12215615808963776, 1823 | 0.003784916130825877, 1824 | -0.9994208216667176 1825 | ], 1826 | "type": "VEC4" 1827 | }, 1828 | { 1829 | "bufferView": 5, 1830 | "byteOffset": 8640, 1831 | "componentType": 5126, 1832 | "count": 48, 1833 | "max": [ 1834 | 1.0000007152557373, 1835 | 1.0000003576278689, 1836 | 1.0000008344650269 1837 | ], 1838 | "min": [ 1839 | 1.0000001192092896, 1840 | 0.9999998211860656, 1841 | 1.000000238418579 1842 | ], 1843 | "type": "VEC3" 1844 | }, 1845 | { 1846 | "bufferView": 4, 1847 | "byteOffset": 1536, 1848 | "componentType": 5126, 1849 | "count": 48, 1850 | "max": [ 1851 | 2 1852 | ], 1853 | "min": [ 1854 | 0.04166661947965622 1855 | ], 1856 | "type": "SCALAR" 1857 | }, 1858 | { 1859 | "bufferView": 5, 1860 | "byteOffset": 9216, 1861 | "componentType": 5126, 1862 | "count": 48, 1863 | "max": [ 1864 | -0.00003894419933203608, 1865 | -0.0879998430609703, 1866 | -0.00005919210161664523 1867 | ], 1868 | "min": [ 1869 | -0.0000392795009247493, 1870 | -0.08800008893013, 1871 | -0.00005960090129519813 1872 | ], 1873 | "type": "VEC3" 1874 | }, 1875 | { 1876 | "bufferView": 6, 1877 | "byteOffset": 6144, 1878 | "componentType": 5126, 1879 | "count": 48, 1880 | "max": [ 1881 | 0.2377220243215561, 1882 | 0.942186713218689, 1883 | 0.37760788202285767, 1884 | 0.2007839232683182 1885 | ], 1886 | "min": [ 1887 | -0.2700891792774201, 1888 | 0.8732703924179077, 1889 | 0.2710656225681305, 1890 | -0.2673804461956024 1891 | ], 1892 | "type": "VEC4" 1893 | }, 1894 | { 1895 | "bufferView": 5, 1896 | "byteOffset": 9792, 1897 | "componentType": 5126, 1898 | "count": 48, 1899 | "max": [ 1900 | 1.000000238418579, 1901 | 1.0000003576278689, 1902 | 1.0000001192092896 1903 | ], 1904 | "min": [ 1905 | 0.999999701976776, 1906 | 0.9999997615814208, 1907 | 0.9999997615814208 1908 | ], 1909 | "type": "VEC3" 1910 | }, 1911 | { 1912 | "bufferView": 4, 1913 | "byteOffset": 1728, 1914 | "componentType": 5126, 1915 | "count": 48, 1916 | "max": [ 1917 | 2 1918 | ], 1919 | "min": [ 1920 | 0.04166661947965622 1921 | ], 1922 | "type": "SCALAR" 1923 | }, 1924 | { 1925 | "bufferView": 5, 1926 | "byteOffset": 10368, 1927 | "componentType": 5126, 1928 | "count": 48, 1929 | "max": [ 1930 | -0.035546209663152695, 1931 | -0.21549880504608157, 1932 | 0.10423330217599867 1933 | ], 1934 | "min": [ 1935 | -0.03554638102650643, 1936 | -0.21549910306930545, 1937 | 0.10423299670219421 1938 | ], 1939 | "type": "VEC3" 1940 | }, 1941 | { 1942 | "bufferView": 6, 1943 | "byteOffset": 6912, 1944 | "componentType": 5126, 1945 | "count": 48, 1946 | "max": [ 1947 | -0.00792065542191267, 1948 | 0.9315358996391296, 1949 | 0.0024673622101545334, 1950 | 0.41479358077049255 1951 | ], 1952 | "min": [ 1953 | -0.15234939754009247, 1954 | 0.9063802361488342, 1955 | -0.08167753368616104, 1956 | 0.3280625641345978 1957 | ], 1958 | "type": "VEC4" 1959 | }, 1960 | { 1961 | "bufferView": 5, 1962 | "byteOffset": 10944, 1963 | "componentType": 5126, 1964 | "count": 48, 1965 | "max": [ 1966 | 1.0000005960464478, 1967 | 1.0000001192092896, 1968 | 1.000000238418579 1969 | ], 1970 | "min": [ 1971 | 1, 1972 | 0.9999996423721313, 1973 | 0.9999996423721313 1974 | ], 1975 | "type": "VEC3" 1976 | }, 1977 | { 1978 | "bufferView": 4, 1979 | "byteOffset": 1920, 1980 | "componentType": 5126, 1981 | "count": 48, 1982 | "max": [ 1983 | 2 1984 | ], 1985 | "min": [ 1986 | 0.04166661947965622 1987 | ], 1988 | "type": "SCALAR" 1989 | }, 1990 | { 1991 | "bufferView": 5, 1992 | "byteOffset": 11520, 1993 | "componentType": 5126, 1994 | "count": 48, 1995 | "max": [ 1996 | 0.03137049078941345, 1997 | -0.1430007964372635, 1998 | -0.11761150509119034 1999 | ], 2000 | "min": [ 2001 | 0.03137030825018883, 2002 | -0.1430010050535202, 2003 | -0.11761169880628586 2004 | ], 2005 | "type": "VEC3" 2006 | }, 2007 | { 2008 | "bufferView": 6, 2009 | "byteOffset": 7680, 2010 | "componentType": 5126, 2011 | "count": 48, 2012 | "max": [ 2013 | 0.22148266434669495, 2014 | 0.3926030695438385, 2015 | 0.08952529728412628, 2016 | -0.9178923964500428 2017 | ], 2018 | "min": [ 2019 | 0.055695075541734695, 2020 | 0.277910977602005, 2021 | -0.015314305201172829, 2022 | -0.9438881278038024 2023 | ], 2024 | "type": "VEC4" 2025 | }, 2026 | { 2027 | "bufferView": 5, 2028 | "byteOffset": 12096, 2029 | "componentType": 5126, 2030 | "count": 48, 2031 | "max": [ 2032 | 1.0000004768371584, 2033 | 1.0000004768371584, 2034 | 1.0000004768371584 2035 | ], 2036 | "min": [ 2037 | 0.9999997615814208, 2038 | 0.9999997615814208, 2039 | 0.9999998807907104 2040 | ], 2041 | "type": "VEC3" 2042 | }, 2043 | { 2044 | "bufferView": 4, 2045 | "byteOffset": 2112, 2046 | "componentType": 5126, 2047 | "count": 48, 2048 | "max": [ 2049 | 2 2050 | ], 2051 | "min": [ 2052 | 0.04166661947965622 2053 | ], 2054 | "type": "SCALAR" 2055 | }, 2056 | { 2057 | "bufferView": 5, 2058 | "byteOffset": 12672, 2059 | "componentType": 5126, 2060 | "count": 48, 2061 | "max": [ 2062 | 0.028520189225673676, 2063 | 0.06762184202671051, 2064 | -0.06295985728502274 2065 | ], 2066 | "min": [ 2067 | 0.028520019724965096, 2068 | 0.06762173771858215, 2069 | -0.06296010315418243 2070 | ], 2071 | "type": "VEC3" 2072 | }, 2073 | { 2074 | "bufferView": 6, 2075 | "byteOffset": 8448, 2076 | "componentType": 5126, 2077 | "count": 48, 2078 | "max": [ 2079 | 0.013129070401191711, 2080 | 0.10440785437822342, 2081 | 0.004284336231648922, 2082 | -0.7728573679924011 2083 | ], 2084 | "min": [ 2085 | -0.013805897906422617, 2086 | -0.6344362497329712, 2087 | -0.03212129324674606, 2088 | -0.9994977116584778 2089 | ], 2090 | "type": "VEC4" 2091 | }, 2092 | { 2093 | "bufferView": 5, 2094 | "byteOffset": 13248, 2095 | "componentType": 5126, 2096 | "count": 48, 2097 | "max": [ 2098 | 1.0000001192092896, 2099 | 1.0000004768371584, 2100 | 1.000000238418579 2101 | ], 2102 | "min": [ 2103 | 0.9999995231628418, 2104 | 1, 2105 | 0.9999995827674866 2106 | ], 2107 | "type": "VEC3" 2108 | }, 2109 | { 2110 | "bufferView": 4, 2111 | "byteOffset": 2304, 2112 | "componentType": 5126, 2113 | "count": 48, 2114 | "max": [ 2115 | 2 2116 | ], 2117 | "min": [ 2118 | 0.04166661947965622 2119 | ], 2120 | "type": "SCALAR" 2121 | }, 2122 | { 2123 | "bufferView": 5, 2124 | "byteOffset": 13824, 2125 | "componentType": 5126, 2126 | "count": 48, 2127 | "max": [ 2128 | 0.209164097905159, 2129 | 0.009055494330823421, 2130 | -0.16426970064640045 2131 | ], 2132 | "min": [ 2133 | 0.20916390419006348, 2134 | 0.009055464528501034, 2135 | -0.1642698049545288 2136 | ], 2137 | "type": "VEC3" 2138 | }, 2139 | { 2140 | "bufferView": 6, 2141 | "byteOffset": 9216, 2142 | "componentType": 5126, 2143 | "count": 48, 2144 | "max": [ 2145 | 0.009955321438610554, 2146 | -0.2965533435344696, 2147 | 0.003957682754844427, 2148 | -0.1911347657442093 2149 | ], 2150 | "min": [ 2151 | -0.00983923487365246, 2152 | -0.9813112020492554, 2153 | -0.02193812094628811, 2154 | -0.9549583792686462 2155 | ], 2156 | "type": "VEC4" 2157 | }, 2158 | { 2159 | "bufferView": 5, 2160 | "byteOffset": 14400, 2161 | "componentType": 5126, 2162 | "count": 48, 2163 | "max": [ 2164 | 1.000000238418579, 2165 | 0.999999463558197, 2166 | 1.0000001192092896 2167 | ], 2168 | "min": [ 2169 | 0.999999463558197, 2170 | 0.999998927116394, 2171 | 0.9999993443489076 2172 | ], 2173 | "type": "VEC3" 2174 | }, 2175 | { 2176 | "bufferView": 4, 2177 | "byteOffset": 2496, 2178 | "componentType": 5126, 2179 | "count": 48, 2180 | "max": [ 2181 | 2 2182 | ], 2183 | "min": [ 2184 | 0.04166661947965622 2185 | ], 2186 | "type": "SCALAR" 2187 | }, 2188 | { 2189 | "bufferView": 5, 2190 | "byteOffset": 14976, 2191 | "componentType": 5126, 2192 | "count": 48, 2193 | "max": [ 2194 | 0.2757900059223175, 2195 | 0.0013972820015624166, 2196 | 0.004122554790228605 2197 | ], 2198 | "min": [ 2199 | 0.27578991651535034, 2200 | 0.0013972449814900756, 2201 | 0.004122436046600342 2202 | ], 2203 | "type": "VEC3" 2204 | }, 2205 | { 2206 | "bufferView": 6, 2207 | "byteOffset": 9984, 2208 | "componentType": 5126, 2209 | "count": 48, 2210 | "max": [ 2211 | 0.01264618057757616, 2212 | -0.8448027968406677, 2213 | 0.03285584971308708, 2214 | -0.15347692370414737 2215 | ], 2216 | "min": [ 2217 | -0.045710742473602295, 2218 | -0.9879721403121948, 2219 | 0.007757793180644512, 2220 | -0.5345877408981323 2221 | ], 2222 | "type": "VEC4" 2223 | }, 2224 | { 2225 | "bufferView": 5, 2226 | "byteOffset": 15552, 2227 | "componentType": 5126, 2228 | "count": 48, 2229 | "max": [ 2230 | 1.0000008344650269, 2231 | 1.0000009536743164, 2232 | 1.0000004768371584 2233 | ], 2234 | "min": [ 2235 | 1.000000238418579, 2236 | 1.0000003576278689, 2237 | 0.9999997615814208 2238 | ], 2239 | "type": "VEC3" 2240 | }, 2241 | { 2242 | "bufferView": 4, 2243 | "byteOffset": 2688, 2244 | "componentType": 5126, 2245 | "count": 48, 2246 | "max": [ 2247 | 2 2248 | ], 2249 | "min": [ 2250 | 0.04166661947965622 2251 | ], 2252 | "type": "SCALAR" 2253 | }, 2254 | { 2255 | "bufferView": 5, 2256 | "byteOffset": 16128, 2257 | "componentType": 5126, 2258 | "count": 48, 2259 | "max": [ 2260 | -0.06558377295732498, 2261 | 0.00109061598777771, 2262 | 0.029291389510035515 2263 | ], 2264 | "min": [ 2265 | -0.06558384746313095, 2266 | 0.001090570935048163, 2267 | 0.029291240498423576 2268 | ], 2269 | "type": "VEC3" 2270 | }, 2271 | { 2272 | "bufferView": 6, 2273 | "byteOffset": 10752, 2274 | "componentType": 5126, 2275 | "count": 48, 2276 | "max": [ 2277 | 0.022798636928200725, 2278 | 0.5332140922546387, 2279 | -0.003377946326509118, 2280 | -0.844382643699646 2281 | ], 2282 | "min": [ 2283 | 0.007516560610383749, 2284 | 0.22626954317092896, 2285 | -0.04913739487528801, 2286 | -0.972984254360199 2287 | ], 2288 | "type": "VEC4" 2289 | }, 2290 | { 2291 | "bufferView": 5, 2292 | "byteOffset": 16704, 2293 | "componentType": 5126, 2294 | "count": 48, 2295 | "max": [ 2296 | 1.0000008344650269, 2297 | 1.0000003576278689, 2298 | 1.0000003576278689 2299 | ], 2300 | "min": [ 2301 | 0.9999998211860656, 2302 | 0.999999701976776, 2303 | 0.9999995231628418 2304 | ], 2305 | "type": "VEC3" 2306 | }, 2307 | { 2308 | "bufferView": 4, 2309 | "byteOffset": 2880, 2310 | "componentType": 5126, 2311 | "count": 48, 2312 | "max": [ 2313 | 2 2314 | ], 2315 | "min": [ 2316 | 0.04166661947965622 2317 | ], 2318 | "type": "SCALAR" 2319 | }, 2320 | { 2321 | "bufferView": 5, 2322 | "byteOffset": 17280, 2323 | "componentType": 5126, 2324 | "count": 48, 2325 | "max": [ 2326 | 0.028557300567626953, 2327 | -0.0684543326497078, 2328 | -0.06295845657587051 2329 | ], 2330 | "min": [ 2331 | 0.028557060286402702, 2332 | -0.06845436990261078, 2333 | -0.0629587471485138 2334 | ], 2335 | "type": "VEC3" 2336 | }, 2337 | { 2338 | "bufferView": 6, 2339 | "byteOffset": 11520, 2340 | "componentType": 5126, 2341 | "count": 48, 2342 | "max": [ 2343 | 0.04037770628929138, 2344 | -0.2803998589515686, 2345 | 0.02151232957839966, 2346 | -0.32386553287506104 2347 | ], 2348 | "min": [ 2349 | -0.009615562856197357, 2350 | -0.9458208084106444, 2351 | -0.006491139996796846, 2352 | -0.9590301513671876 2353 | ], 2354 | "type": "VEC4" 2355 | }, 2356 | { 2357 | "bufferView": 5, 2358 | "byteOffset": 17856, 2359 | "componentType": 5126, 2360 | "count": 48, 2361 | "max": [ 2362 | 1.000000238418579, 2363 | 1.0000005960464478, 2364 | 1.0000005960464478 2365 | ], 2366 | "min": [ 2367 | 0.9999994039535524, 2368 | 0.9999999403953552, 2369 | 0.9999998211860656 2370 | ], 2371 | "type": "VEC3" 2372 | }, 2373 | { 2374 | "bufferView": 4, 2375 | "byteOffset": 3072, 2376 | "componentType": 5126, 2377 | "count": 48, 2378 | "max": [ 2379 | 2 2380 | ], 2381 | "min": [ 2382 | 0.04166661947965622 2383 | ], 2384 | "type": "SCALAR" 2385 | }, 2386 | { 2387 | "bufferView": 5, 2388 | "byteOffset": 18432, 2389 | "componentType": 5126, 2390 | "count": 48, 2391 | "max": [ 2392 | 0.2608909010887146, 2393 | -0.00902603566646576, 2394 | 0.05167100951075554 2395 | ], 2396 | "min": [ 2397 | 0.2608906924724579, 2398 | -0.009026064537465572, 2399 | 0.05167080089449883 2400 | ], 2401 | "type": "VEC3" 2402 | }, 2403 | { 2404 | "bufferView": 6, 2405 | "byteOffset": 12288, 2406 | "componentType": 5126, 2407 | "count": 48, 2408 | "max": [ 2409 | 0.02468797937035561, 2410 | 0.19154119491577148, 2411 | 0.017835097387433052, 2412 | -0.6250466108322144 2413 | ], 2414 | "min": [ 2415 | -0.013421673327684404, 2416 | -0.7804162502288818, 2417 | -0.031287722289562225, 2418 | -0.9999792575836182 2419 | ], 2420 | "type": "VEC4" 2421 | }, 2422 | { 2423 | "bufferView": 5, 2424 | "byteOffset": 19008, 2425 | "componentType": 5126, 2426 | "count": 48, 2427 | "max": [ 2428 | 1.0000003576278689, 2429 | 1.0000007152557373, 2430 | 1.000001072883606 2431 | ], 2432 | "min": [ 2433 | 0.999999463558197, 2434 | 1, 2435 | 0.999999701976776 2436 | ], 2437 | "type": "VEC3" 2438 | }, 2439 | { 2440 | "bufferView": 4, 2441 | "byteOffset": 3264, 2442 | "componentType": 5126, 2443 | "count": 48, 2444 | "max": [ 2445 | 2 2446 | ], 2447 | "min": [ 2448 | 0.04166661947965622 2449 | ], 2450 | "type": "SCALAR" 2451 | }, 2452 | { 2453 | "bufferView": 5, 2454 | "byteOffset": 19584, 2455 | "componentType": 5126, 2456 | "count": 48, 2457 | "max": [ 2458 | 0.2754603922367096, 2459 | -0.0014316890155896544, 2460 | -0.014104750007390976 2461 | ], 2462 | "min": [ 2463 | 0.27546021342277527, 2464 | -0.0014317409368231893, 2465 | -0.014104840345680714 2466 | ], 2467 | "type": "VEC3" 2468 | }, 2469 | { 2470 | "bufferView": 6, 2471 | "byteOffset": 13056, 2472 | "componentType": 5126, 2473 | "count": 48, 2474 | "max": [ 2475 | 0.022092316299676895, 2476 | 0.9990847110748292, 2477 | 0.04779285565018654, 2478 | 0.4428757429122925 2479 | ], 2480 | "min": [ 2481 | -0.001671039150096476, 2482 | 0.8965795040130615, 2483 | 0.002310338197275996, 2484 | 0.0384783074259758 2485 | ], 2486 | "type": "VEC4" 2487 | }, 2488 | { 2489 | "bufferView": 5, 2490 | "byteOffset": 20160, 2491 | "componentType": 5126, 2492 | "count": 48, 2493 | "max": [ 2494 | 0.9999999403953552, 2495 | 0.9999996423721313, 2496 | 1.000000238418579 2497 | ], 2498 | "min": [ 2499 | 0.9999994039535524, 2500 | 0.9999991655349731, 2501 | 0.9999996423721313 2502 | ], 2503 | "type": "VEC3" 2504 | }, 2505 | { 2506 | "bufferView": 4, 2507 | "byteOffset": 3456, 2508 | "componentType": 5126, 2509 | "count": 48, 2510 | "max": [ 2511 | 2 2512 | ], 2513 | "min": [ 2514 | 0.04166661947965622 2515 | ], 2516 | "type": "SCALAR" 2517 | }, 2518 | { 2519 | "bufferView": 5, 2520 | "byteOffset": 20736, 2521 | "componentType": 5126, 2522 | "count": 48, 2523 | "max": [ 2524 | -0.06681966781616211, 2525 | -0.0010721459984779358, 2526 | 0.026351390406489372 2527 | ], 2528 | "min": [ 2529 | -0.06681978702545166, 2530 | -0.001072190934792161, 2531 | 0.02635126002132893 2532 | ], 2533 | "type": "VEC3" 2534 | }, 2535 | { 2536 | "bufferView": 6, 2537 | "byteOffset": 13824, 2538 | "componentType": 5126, 2539 | "count": 48, 2540 | "max": [ 2541 | 0.003402489935979247, 2542 | 0.4966025054454804, 2543 | 0.1101396307349205, 2544 | -0.8675833940505981 2545 | ], 2546 | "min": [ 2547 | -0.027623889967799187, 2548 | 0.26874634623527527, 2549 | -0.02591408602893353, 2550 | -0.9565747380256652 2551 | ], 2552 | "type": "VEC4" 2553 | }, 2554 | { 2555 | "bufferView": 5, 2556 | "byteOffset": 21312, 2557 | "componentType": 5126, 2558 | "count": 48, 2559 | "max": [ 2560 | 1.0000004768371584, 2561 | 0.9999998211860656, 2562 | 0.9999994039535524 2563 | ], 2564 | "min": [ 2565 | 0.9999995231628418, 2566 | 0.999999225139618, 2567 | 0.9999986886978148 2568 | ], 2569 | "type": "VEC3" 2570 | }, 2571 | { 2572 | "bufferView": 7, 2573 | "byteOffset": 0, 2574 | "componentType": 5126, 2575 | "count": 19, 2576 | "max": [ 2577 | 0.9971418380737304, 2578 | -4.371139894487897e-8, 2579 | 0.9996265172958374, 2580 | 0, 2581 | 4.3586464215650273e-8, 2582 | 1, 2583 | 4.3695074225524884e-8, 2584 | 0, 2585 | 0.9999366402626038, 2586 | 0, 2587 | 0.9971418380737304, 2588 | 0, 2589 | 1.1374080181121828, 2590 | 0.44450080394744873, 2591 | 1.0739599466323853, 2592 | 1 2593 | ], 2594 | "min": [ 2595 | -0.9999089241027832, 2596 | -4.371139894487897e-8, 2597 | -0.9999366402626038, 2598 | 0, 2599 | -4.3707416352845037e-8, 2600 | 1, 2601 | -4.37086278282095e-8, 2602 | 0, 2603 | -0.9996265172958374, 2604 | 0, 2605 | -0.9999089241027832, 2606 | 0, 2607 | -1.189831018447876, 2608 | -0.45450031757354736, 2609 | -1.058603048324585, 2610 | 1 2611 | ], 2612 | "type": "MAT4" 2613 | } 2614 | ], 2615 | "materials": [ 2616 | { 2617 | "pbrMetallicRoughness": { 2618 | "baseColorTexture": { 2619 | "index": 0, 2620 | "texCoord": 0 2621 | }, 2622 | "metallicFactor": 0, 2623 | "baseColorFactor": [ 2624 | 1, 2625 | 1, 2626 | 1, 2627 | 1 2628 | ], 2629 | "roughnessFactor": 1 2630 | }, 2631 | "emissiveFactor": [ 2632 | 0, 2633 | 0, 2634 | 0 2635 | ], 2636 | "name": "Cesium_Man-effect", 2637 | "alphaMode": "OPAQUE", 2638 | "doubleSided": false 2639 | } 2640 | ], 2641 | "textures": [ 2642 | { 2643 | "sampler": 0, 2644 | "source": 0 2645 | } 2646 | ], 2647 | "images": [ 2648 | { 2649 | "uri": "cesium.jpg" 2650 | } 2651 | ], 2652 | "samplers": [ 2653 | { 2654 | "magFilter": 9729, 2655 | "minFilter": 9986, 2656 | "wrapS": 10497, 2657 | "wrapT": 10497 2658 | } 2659 | ], 2660 | "bufferViews": [ 2661 | { 2662 | "buffer": 0, 2663 | "byteOffset": 0, 2664 | "byteLength": 28032, 2665 | "target": 34963 2666 | }, 2667 | { 2668 | "buffer": 0, 2669 | "byteOffset": 28032, 2670 | "byteLength": 52368, 2671 | "byteStride": 8, 2672 | "target": 34962 2673 | }, 2674 | { 2675 | "buffer": 0, 2676 | "byteOffset": 80400, 2677 | "byteLength": 78552, 2678 | "byteStride": 12, 2679 | "target": 34962 2680 | }, 2681 | { 2682 | "buffer": 0, 2683 | "byteOffset": 158952, 2684 | "byteLength": 52368, 2685 | "byteStride": 16, 2686 | "target": 34962 2687 | }, 2688 | { 2689 | "buffer": 0, 2690 | "byteOffset": 211320, 2691 | "byteLength": 3648 2692 | }, 2693 | { 2694 | "buffer": 0, 2695 | "byteOffset": 214968, 2696 | "byteLength": 21888 2697 | }, 2698 | { 2699 | "buffer": 0, 2700 | "byteOffset": 236856, 2701 | "byteLength": 14592 2702 | }, 2703 | { 2704 | "buffer": 0, 2705 | "byteOffset": 251448, 2706 | "byteLength": 1216 2707 | } 2708 | ], 2709 | "buffers": [ 2710 | { 2711 | "uri": "cesium.bin", 2712 | "byteLength": 252664 2713 | } 2714 | ] 2715 | } -------------------------------------------------------------------------------- /example/static/models/cesium/cesium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/cesium/cesium.jpg -------------------------------------------------------------------------------- /example/static/models/cylinder/cylinder.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/cylinder/cylinder.bin -------------------------------------------------------------------------------- /example/static/models/cylinder/cylinder.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v1.1.45", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "name" : "Scene", 10 | "nodes" : [ 11 | 3 12 | ] 13 | } 14 | ], 15 | "nodes" : [ 16 | { 17 | "name" : "Bone.001", 18 | "rotation" : [ 19 | -0.001818173099309206, 20 | -5.644409184820943e-17, 21 | -5.624599863511524e-17, 22 | 0.9999984502792358 23 | ], 24 | "translation" : [ 25 | 0, 26 | 1.963363528251648, 27 | -5.877471754111438e-39 28 | ] 29 | }, 30 | { 31 | "children" : [ 32 | 0 33 | ], 34 | "name" : "Bone", 35 | "rotation" : [ 36 | -6.047974270962333e-33, 37 | -5.447530934610462e-17, 38 | -5.447530934610462e-17, 39 | 1 40 | ] 41 | }, 42 | { 43 | "mesh" : 0, 44 | "name" : "Cylinder", 45 | "skin" : 0 46 | }, 47 | { 48 | "children" : [ 49 | 2, 50 | 1 51 | ], 52 | "name" : "Armature" 53 | } 54 | ], 55 | "animations" : [ 56 | { 57 | "channels" : [ 58 | { 59 | "sampler" : 0, 60 | "target" : { 61 | "node" : 1, 62 | "path" : "translation" 63 | } 64 | }, 65 | { 66 | "sampler" : 1, 67 | "target" : { 68 | "node" : 1, 69 | "path" : "rotation" 70 | } 71 | }, 72 | { 73 | "sampler" : 2, 74 | "target" : { 75 | "node" : 1, 76 | "path" : "scale" 77 | } 78 | }, 79 | { 80 | "sampler" : 3, 81 | "target" : { 82 | "node" : 0, 83 | "path" : "translation" 84 | } 85 | }, 86 | { 87 | "sampler" : 4, 88 | "target" : { 89 | "node" : 0, 90 | "path" : "rotation" 91 | } 92 | }, 93 | { 94 | "sampler" : 5, 95 | "target" : { 96 | "node" : 0, 97 | "path" : "scale" 98 | } 99 | } 100 | ], 101 | "name" : "ArmatureAction", 102 | "samplers" : [ 103 | { 104 | "input" : 6, 105 | "interpolation" : "LINEAR", 106 | "output" : 7 107 | }, 108 | { 109 | "input" : 6, 110 | "interpolation" : "LINEAR", 111 | "output" : 8 112 | }, 113 | { 114 | "input" : 6, 115 | "interpolation" : "LINEAR", 116 | "output" : 9 117 | }, 118 | { 119 | "input" : 6, 120 | "interpolation" : "LINEAR", 121 | "output" : 10 122 | }, 123 | { 124 | "input" : 6, 125 | "interpolation" : "LINEAR", 126 | "output" : 11 127 | }, 128 | { 129 | "input" : 6, 130 | "interpolation" : "LINEAR", 131 | "output" : 12 132 | } 133 | ] 134 | } 135 | ], 136 | "meshes" : [ 137 | { 138 | "name" : "Cylinder", 139 | "primitives" : [ 140 | { 141 | "attributes" : { 142 | "POSITION" : 0, 143 | "NORMAL" : 1, 144 | "JOINTS_0" : 2, 145 | "WEIGHTS_0" : 3 146 | }, 147 | "indices" : 4 148 | } 149 | ] 150 | } 151 | ], 152 | "skins" : [ 153 | { 154 | "inverseBindMatrices" : 5, 155 | "joints" : [ 156 | 1, 157 | 0 158 | ], 159 | "name" : "Armature" 160 | } 161 | ], 162 | "accessors" : [ 163 | { 164 | "bufferView" : 0, 165 | "componentType" : 5126, 166 | "count" : 2074, 167 | "max" : [ 168 | 1, 169 | 3.9558234214782715, 170 | 1 171 | ], 172 | "min" : [ 173 | -1.0000001192092896, 174 | 0, 175 | -1 176 | ], 177 | "type" : "VEC3" 178 | }, 179 | { 180 | "bufferView" : 1, 181 | "componentType" : 5126, 182 | "count" : 2074, 183 | "type" : "VEC3" 184 | }, 185 | { 186 | "bufferView" : 2, 187 | "componentType" : 5123, 188 | "count" : 2074, 189 | "type" : "VEC4" 190 | }, 191 | { 192 | "bufferView" : 3, 193 | "componentType" : 5126, 194 | "count" : 2074, 195 | "type" : "VEC4" 196 | }, 197 | { 198 | "bufferView" : 4, 199 | "componentType" : 5123, 200 | "count" : 3828, 201 | "type" : "SCALAR" 202 | }, 203 | { 204 | "bufferView" : 5, 205 | "componentType" : 5126, 206 | "count" : 2, 207 | "type" : "MAT4" 208 | }, 209 | { 210 | "bufferView" : 6, 211 | "componentType" : 5126, 212 | "count" : 50, 213 | "max" : [ 214 | 2.0833333333333335 215 | ], 216 | "min" : [ 217 | 0.041666666666666664 218 | ], 219 | "type" : "SCALAR" 220 | }, 221 | { 222 | "bufferView" : 7, 223 | "componentType" : 5126, 224 | "count" : 50, 225 | "type" : "VEC3" 226 | }, 227 | { 228 | "bufferView" : 8, 229 | "componentType" : 5126, 230 | "count" : 50, 231 | "type" : "VEC4" 232 | }, 233 | { 234 | "bufferView" : 9, 235 | "componentType" : 5126, 236 | "count" : 50, 237 | "type" : "VEC3" 238 | }, 239 | { 240 | "bufferView" : 10, 241 | "componentType" : 5126, 242 | "count" : 50, 243 | "type" : "VEC3" 244 | }, 245 | { 246 | "bufferView" : 11, 247 | "componentType" : 5126, 248 | "count" : 50, 249 | "type" : "VEC4" 250 | }, 251 | { 252 | "bufferView" : 12, 253 | "componentType" : 5126, 254 | "count" : 50, 255 | "type" : "VEC3" 256 | } 257 | ], 258 | "bufferViews" : [ 259 | { 260 | "buffer" : 0, 261 | "byteLength" : 24888, 262 | "byteOffset" : 0 263 | }, 264 | { 265 | "buffer" : 0, 266 | "byteLength" : 24888, 267 | "byteOffset" : 24888 268 | }, 269 | { 270 | "buffer" : 0, 271 | "byteLength" : 16592, 272 | "byteOffset" : 49776 273 | }, 274 | { 275 | "buffer" : 0, 276 | "byteLength" : 33184, 277 | "byteOffset" : 66368 278 | }, 279 | { 280 | "buffer" : 0, 281 | "byteLength" : 7656, 282 | "byteOffset" : 99552 283 | }, 284 | { 285 | "buffer" : 0, 286 | "byteLength" : 128, 287 | "byteOffset" : 107208 288 | }, 289 | { 290 | "buffer" : 0, 291 | "byteLength" : 200, 292 | "byteOffset" : 107336 293 | }, 294 | { 295 | "buffer" : 0, 296 | "byteLength" : 600, 297 | "byteOffset" : 107536 298 | }, 299 | { 300 | "buffer" : 0, 301 | "byteLength" : 800, 302 | "byteOffset" : 108136 303 | }, 304 | { 305 | "buffer" : 0, 306 | "byteLength" : 600, 307 | "byteOffset" : 108936 308 | }, 309 | { 310 | "buffer" : 0, 311 | "byteLength" : 600, 312 | "byteOffset" : 109536 313 | }, 314 | { 315 | "buffer" : 0, 316 | "byteLength" : 800, 317 | "byteOffset" : 110136 318 | }, 319 | { 320 | "buffer" : 0, 321 | "byteLength" : 600, 322 | "byteOffset" : 110936 323 | } 324 | ], 325 | "buffers" : [ 326 | { 327 | "byteLength" : 111536, 328 | "uri" : "cylinder.bin" 329 | } 330 | ] 331 | } 332 | -------------------------------------------------------------------------------- /example/static/models/launcher/launcher.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher.bin -------------------------------------------------------------------------------- /example/static/models/launcher/launcher.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v1.1.45", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "name" : "Scene", 10 | "nodes" : [ 11 | 0 12 | ] 13 | } 14 | ], 15 | "nodes" : [ 16 | { 17 | "mesh" : 0, 18 | "name" : "Cube", 19 | "translation" : [ 20 | 0, 21 | -0.051240984350442886, 22 | 0 23 | ] 24 | } 25 | ], 26 | "materials" : [ 27 | { 28 | "doubleSided" : true, 29 | "emissiveFactor" : [ 30 | 1, 31 | 1, 32 | 1 33 | ], 34 | "emissiveTexture" : { 35 | "index" : 0, 36 | "texCoord" : 0 37 | }, 38 | "name" : "Material.002", 39 | "normalTexture" : { 40 | "index" : 1, 41 | "texCoord" : 0 42 | }, 43 | "pbrMetallicRoughness" : { 44 | "baseColorTexture" : { 45 | "index" : 2, 46 | "texCoord" : 0 47 | }, 48 | "metallicFactor" : 0, 49 | "metallicRoughnessTexture" : { 50 | "index" : 3, 51 | "texCoord" : 0 52 | } 53 | } 54 | } 55 | ], 56 | "meshes" : [ 57 | { 58 | "name" : "Cube.001", 59 | "primitives" : [ 60 | { 61 | "attributes" : { 62 | "POSITION" : 0, 63 | "NORMAL" : 1, 64 | "TEXCOORD_0" : 2 65 | }, 66 | "indices" : 3, 67 | "material" : 0 68 | } 69 | ] 70 | } 71 | ], 72 | "textures" : [ 73 | { 74 | "source" : 0 75 | }, 76 | { 77 | "source" : 1 78 | }, 79 | { 80 | "source" : 2 81 | }, 82 | { 83 | "source" : 3 84 | } 85 | ], 86 | "images" : [ 87 | { 88 | "mimeType" : "image/png", 89 | "name" : "launcher_emissive", 90 | "uri" : "launcher_emissive.png" 91 | }, 92 | { 93 | "mimeType" : "image/png", 94 | "name" : "launcher_normal", 95 | "uri" : "launcher_normal.png" 96 | }, 97 | { 98 | "mimeType" : "image/png", 99 | "name" : "launcher_baseColor", 100 | "uri" : "launcher_baseColor.png" 101 | }, 102 | { 103 | "mimeType" : "image/png", 104 | "name" : "launcher_occlusionRoughnessMetallic", 105 | "uri" : "launcher_occlusionRoughnessMetallic.png" 106 | } 107 | ], 108 | "accessors" : [ 109 | { 110 | "bufferView" : 0, 111 | "componentType" : 5126, 112 | "count" : 124, 113 | "max" : [ 114 | 1.6412103176116943, 115 | 0.09945563971996307, 116 | 0.39042335748672485 117 | ], 118 | "min" : [ 119 | 1.363183617591858, 120 | -0.04249188303947449, 121 | -0.39042335748672485 122 | ], 123 | "type" : "VEC3" 124 | }, 125 | { 126 | "bufferView" : 1, 127 | "componentType" : 5126, 128 | "count" : 124, 129 | "type" : "VEC3" 130 | }, 131 | { 132 | "bufferView" : 2, 133 | "componentType" : 5126, 134 | "count" : 124, 135 | "type" : "VEC2" 136 | }, 137 | { 138 | "bufferView" : 3, 139 | "componentType" : 5123, 140 | "count" : 204, 141 | "type" : "SCALAR" 142 | } 143 | ], 144 | "bufferViews" : [ 145 | { 146 | "buffer" : 0, 147 | "byteLength" : 1488, 148 | "byteOffset" : 0 149 | }, 150 | { 151 | "buffer" : 0, 152 | "byteLength" : 1488, 153 | "byteOffset" : 1488 154 | }, 155 | { 156 | "buffer" : 0, 157 | "byteLength" : 992, 158 | "byteOffset" : 2976 159 | }, 160 | { 161 | "buffer" : 0, 162 | "byteLength" : 408, 163 | "byteOffset" : 3968 164 | } 165 | ], 166 | "buffers" : [ 167 | { 168 | "byteLength" : 4376, 169 | "uri" : "launcher.bin" 170 | } 171 | ] 172 | } 173 | -------------------------------------------------------------------------------- /example/static/models/launcher/launcher_baseColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_baseColor.png -------------------------------------------------------------------------------- /example/static/models/launcher/launcher_emissive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_emissive.png -------------------------------------------------------------------------------- /example/static/models/launcher/launcher_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_normal.png -------------------------------------------------------------------------------- /example/static/models/launcher/launcher_occlusionRoughnessMetallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_occlusionRoughnessMetallic.png -------------------------------------------------------------------------------- /example/static/models/platform/platform.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform.bin -------------------------------------------------------------------------------- /example/static/models/platform/platform.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v1.1.45", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "name" : "Scene", 10 | "nodes" : [ 11 | 0 12 | ] 13 | } 14 | ], 15 | "nodes" : [ 16 | { 17 | "mesh" : 0, 18 | "name" : "Cylinder", 19 | "translation" : [ 20 | 0, 21 | -0.051240984350442886, 22 | 0 23 | ] 24 | } 25 | ], 26 | "materials" : [ 27 | { 28 | "doubleSided" : true, 29 | "emissiveFactor" : [ 30 | 0, 31 | 0, 32 | 0 33 | ], 34 | "name" : "Material.001", 35 | "normalTexture" : { 36 | "index" : 0, 37 | "texCoord" : 0 38 | }, 39 | "pbrMetallicRoughness" : { 40 | "baseColorTexture" : { 41 | "index" : 1, 42 | "texCoord" : 0 43 | }, 44 | "metallicFactor" : 0, 45 | "metallicRoughnessTexture" : { 46 | "index" : 2, 47 | "texCoord" : 0 48 | } 49 | } 50 | } 51 | ], 52 | "meshes" : [ 53 | { 54 | "name" : "Cylinder", 55 | "primitives" : [ 56 | { 57 | "attributes" : { 58 | "POSITION" : 0, 59 | "NORMAL" : 1, 60 | "TANGENT" : 2, 61 | "TEXCOORD_0" : 3 62 | }, 63 | "indices" : 4, 64 | "material" : 0 65 | } 66 | ] 67 | } 68 | ], 69 | "textures" : [ 70 | { 71 | "source" : 0 72 | }, 73 | { 74 | "source" : 1 75 | }, 76 | { 77 | "source" : 2 78 | } 79 | ], 80 | "images" : [ 81 | { 82 | "mimeType" : "image/png", 83 | "name" : "platform_normal", 84 | "uri" : "platform_normal.png" 85 | }, 86 | { 87 | "mimeType" : "image/png", 88 | "name" : "platform_baseColor", 89 | "uri" : "platform_baseColor.png" 90 | }, 91 | { 92 | "mimeType" : "image/png", 93 | "name" : "platform_occlusionRoughnessMetallic", 94 | "uri" : "platform_occlusionRoughnessMetallic.png" 95 | } 96 | ], 97 | "accessors" : [ 98 | { 99 | "bufferView" : 0, 100 | "componentType" : 5126, 101 | "count" : 318, 102 | "max" : [ 103 | 1.3858191967010498, 104 | 0.05000000074505806, 105 | 1.3858191967010498 106 | ], 107 | "min" : [ 108 | -1.385819435119629, 109 | -0.05000000447034836, 110 | -1.3858193159103394 111 | ], 112 | "type" : "VEC3" 113 | }, 114 | { 115 | "bufferView" : 1, 116 | "componentType" : 5126, 117 | "count" : 318, 118 | "type" : "VEC3" 119 | }, 120 | { 121 | "bufferView" : 2, 122 | "componentType" : 5126, 123 | "count" : 318, 124 | "type" : "VEC4" 125 | }, 126 | { 127 | "bufferView" : 3, 128 | "componentType" : 5126, 129 | "count" : 318, 130 | "type" : "VEC2" 131 | }, 132 | { 133 | "bufferView" : 4, 134 | "componentType" : 5123, 135 | "count" : 324, 136 | "type" : "SCALAR" 137 | } 138 | ], 139 | "bufferViews" : [ 140 | { 141 | "buffer" : 0, 142 | "byteLength" : 3816, 143 | "byteOffset" : 0 144 | }, 145 | { 146 | "buffer" : 0, 147 | "byteLength" : 3816, 148 | "byteOffset" : 3816 149 | }, 150 | { 151 | "buffer" : 0, 152 | "byteLength" : 5088, 153 | "byteOffset" : 7632 154 | }, 155 | { 156 | "buffer" : 0, 157 | "byteLength" : 2544, 158 | "byteOffset" : 12720 159 | }, 160 | { 161 | "buffer" : 0, 162 | "byteLength" : 648, 163 | "byteOffset" : 15264 164 | } 165 | ], 166 | "buffers" : [ 167 | { 168 | "byteLength" : 15912, 169 | "uri" : "platform.bin" 170 | } 171 | ] 172 | } 173 | -------------------------------------------------------------------------------- /example/static/models/platform/platform_baseColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform_baseColor.png -------------------------------------------------------------------------------- /example/static/models/platform/platform_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform_normal.png -------------------------------------------------------------------------------- /example/static/models/platform/platform_occlusionRoughnessMetallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform_occlusionRoughnessMetallic.png -------------------------------------------------------------------------------- /example/static/models/robot/robot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot.bin -------------------------------------------------------------------------------- /example/static/models/robot/robot_baseColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot_baseColor.png -------------------------------------------------------------------------------- /example/static/models/robot/robot_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot_normal.png -------------------------------------------------------------------------------- /example/static/models/robot/robot_occlusionRoughnessMetallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot_occlusionRoughnessMetallic.png -------------------------------------------------------------------------------- /example/static/models/suzanne/baseColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/suzanne/baseColor.png -------------------------------------------------------------------------------- /example/static/models/suzanne/roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/suzanne/roughness.png -------------------------------------------------------------------------------- /example/static/models/suzanne/suzanne.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/suzanne/suzanne.bin -------------------------------------------------------------------------------- /example/static/models/suzanne/suzanne.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors" : [ 3 | { 4 | "bufferView" : 0, 5 | "byteOffset" : 0, 6 | "componentType" : 5123, 7 | "count" : 11808, 8 | "max" : [ 9 | 11807 10 | ], 11 | "min" : [ 12 | 0 13 | ], 14 | "type" : "SCALAR" 15 | }, 16 | { 17 | "bufferView" : 1, 18 | "byteOffset" : 0, 19 | "componentType" : 5126, 20 | "count" : 11808, 21 | "max" : [ 22 | 1.336914, 23 | 0.950195, 24 | 0.825684 25 | ], 26 | "min" : [ 27 | -1.336914, 28 | -0.974609, 29 | -0.800781 30 | ], 31 | "type" : "VEC3" 32 | }, 33 | { 34 | "bufferView" : 2, 35 | "byteOffset" : 0, 36 | "componentType" : 5126, 37 | "count" : 11808, 38 | "max" : [ 39 | 0.996339, 40 | 0.999958, 41 | 0.999929 42 | ], 43 | "min" : [ 44 | -0.996339, 45 | -0.985940, 46 | -0.999994 47 | ], 48 | "type" : "VEC3" 49 | }, 50 | { 51 | "bufferView" : 3, 52 | "byteOffset" : 0, 53 | "componentType" : 5126, 54 | "count" : 11808, 55 | "max" : [ 56 | 0.998570, 57 | 0.999996, 58 | 0.999487, 59 | 1.000000 60 | ], 61 | "min" : [ 62 | -0.999233, 63 | -0.999453, 64 | -0.999812, 65 | 1.000000 66 | ], 67 | "type" : "VEC4" 68 | }, 69 | { 70 | "bufferView" : 4, 71 | "byteOffset" : 0, 72 | "componentType" : 5126, 73 | "count" : 11808, 74 | "max" : [ 75 | 0.999884, 76 | 0.884359 77 | ], 78 | "min" : [ 79 | 0.000116, 80 | 0.000116 81 | ], 82 | "type" : "VEC2" 83 | } 84 | ], 85 | "asset" : { 86 | "generator" : "VKTS glTF 2.0 exporter", 87 | "version" : "2.0" 88 | }, 89 | "bufferViews" : [ 90 | { 91 | "buffer" : 0, 92 | "byteLength" : 23616, 93 | "byteOffset" : 0, 94 | "target" : 34963 95 | }, 96 | { 97 | "buffer" : 0, 98 | "byteLength" : 141696, 99 | "byteOffset" : 23616, 100 | "target" : 34962 101 | }, 102 | { 103 | "buffer" : 0, 104 | "byteLength" : 141696, 105 | "byteOffset" : 165312, 106 | "target" : 34962 107 | }, 108 | { 109 | "buffer" : 0, 110 | "byteLength" : 188928, 111 | "byteOffset" : 307008, 112 | "target" : 34962 113 | }, 114 | { 115 | "buffer" : 0, 116 | "byteLength" : 94464, 117 | "byteOffset" : 495936, 118 | "target" : 34962 119 | } 120 | ], 121 | "buffers" : [ 122 | { 123 | "byteLength" : 590400, 124 | "uri" : "suzanne.bin" 125 | } 126 | ], 127 | "images" : [ 128 | { 129 | "uri" : "baseColor.png" 130 | }, 131 | { 132 | "uri" : "roughness.png" 133 | } 134 | ], 135 | "materials" : [ 136 | { 137 | "name" : "Suzanne", 138 | "pbrMetallicRoughness" : { 139 | "baseColorTexture" : { 140 | "index" : 0 141 | }, 142 | "metallicRoughnessTexture" : { 143 | "index" : 1 144 | } 145 | } 146 | } 147 | ], 148 | "meshes" : [ 149 | { 150 | "name" : "Suzanne", 151 | "primitives" : [ 152 | { 153 | "attributes" : { 154 | "NORMAL" : 2, 155 | "POSITION" : 1, 156 | "TANGENT" : 3, 157 | "TEXCOORD_0" : 4 158 | }, 159 | "indices" : 0, 160 | "material" : 0, 161 | "mode" : 4 162 | } 163 | ] 164 | } 165 | ], 166 | "nodes" : [ 167 | { 168 | "mesh" : 0, 169 | "name" : "Suzanne" 170 | } 171 | ], 172 | "samplers" : [ 173 | {} 174 | ], 175 | "scene" : 0, 176 | "scenes" : [ 177 | { 178 | "nodes" : [ 179 | 0 180 | ] 181 | } 182 | ], 183 | "textures" : [ 184 | { 185 | "sampler" : 0, 186 | "source" : 0 187 | }, 188 | { 189 | "sampler" : 0, 190 | "source" : 1 191 | } 192 | ] 193 | } 194 | -------------------------------------------------------------------------------- /example/static/models/waterbottle/WaterBottle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/WaterBottle.bin -------------------------------------------------------------------------------- /example/static/models/waterbottle/base-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/base-color.png -------------------------------------------------------------------------------- /example/static/models/waterbottle/emissive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/emissive.png -------------------------------------------------------------------------------- /example/static/models/waterbottle/metallic-roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/metallic-roughness.png -------------------------------------------------------------------------------- /example/static/models/waterbottle/normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/normal.png -------------------------------------------------------------------------------- /example/static/models/waterbottle/occlusion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/occlusion.png -------------------------------------------------------------------------------- /example/static/models/waterbottle/waterbottle.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "bufferView": 0, 5 | "componentType": 5126, 6 | "count": 2549, 7 | "type": "VEC2" 8 | }, 9 | { 10 | "bufferView": 1, 11 | "componentType": 5126, 12 | "count": 2549, 13 | "type": "VEC3" 14 | }, 15 | { 16 | "bufferView": 2, 17 | "componentType": 5126, 18 | "count": 2549, 19 | "type": "VEC4" 20 | }, 21 | { 22 | "bufferView": 3, 23 | "componentType": 5126, 24 | "count": 2549, 25 | "type": "VEC3", 26 | "max": [ 27 | 0.05445001, 28 | 0.130220339, 29 | 0.0544500239 30 | ], 31 | "min": [ 32 | -0.05445001, 33 | -0.130220339, 34 | -0.0544500239 35 | ] 36 | }, 37 | { 38 | "bufferView": 4, 39 | "componentType": 5123, 40 | "count": 13530, 41 | "type": "SCALAR" 42 | } 43 | ], 44 | "asset": { 45 | "generator": "glTF Tools for Unity", 46 | "version": "2.0" 47 | }, 48 | "bufferViews": [ 49 | { 50 | "buffer": 0, 51 | "byteLength": 20392 52 | }, 53 | { 54 | "buffer": 0, 55 | "byteOffset": 20392, 56 | "byteLength": 30588 57 | }, 58 | { 59 | "buffer": 0, 60 | "byteOffset": 50980, 61 | "byteLength": 40784 62 | }, 63 | { 64 | "buffer": 0, 65 | "byteOffset": 91764, 66 | "byteLength": 30588 67 | }, 68 | { 69 | "buffer": 0, 70 | "byteOffset": 122352, 71 | "byteLength": 27060 72 | } 73 | ], 74 | "buffers": [ 75 | { 76 | "uri": "WaterBottle.bin", 77 | "byteLength": 149412 78 | } 79 | ], 80 | "extensionsUsed": [ 81 | "KHR_materials_pbrSpecularGlossiness" 82 | ], 83 | "images": [ 84 | { 85 | "uri": "base-color.png" 86 | }, 87 | { 88 | "uri": "metallic-roughness.png" 89 | }, 90 | { 91 | "uri": "normal.png" 92 | }, 93 | { 94 | "uri": "emissive.png" 95 | }, 96 | { 97 | "uri": "occlusion.png" 98 | }, 99 | { 100 | "uri": "WaterBottle_diffuse.png" 101 | }, 102 | { 103 | "uri": "WaterBottle_specularGlossiness.png" 104 | } 105 | ], 106 | "meshes": [ 107 | { 108 | "primitives": [ 109 | { 110 | "attributes": { 111 | "TEXCOORD_0": 0, 112 | "NORMAL": 1, 113 | "TANGENT": 2, 114 | "POSITION": 3 115 | }, 116 | "indices": 4, 117 | "material": 0 118 | } 119 | ], 120 | "name": "WaterBottle" 121 | } 122 | ], 123 | "materials": [ 124 | { 125 | "pbrMetallicRoughness": { 126 | "baseColorTexture": { 127 | "index": 0 128 | }, 129 | "metallicRoughnessTexture": { 130 | "index": 1 131 | } 132 | }, 133 | "normalTexture": { 134 | "index": 2 135 | }, 136 | "occlusionTexture": { 137 | "index": 4 138 | }, 139 | "emissiveFactor": [ 140 | 1.0, 141 | 1.0, 142 | 1.0 143 | ], 144 | "emissiveTexture": { 145 | "index": 3 146 | }, 147 | "name": "BottleMat", 148 | "extensions": { 149 | "KHR_materials_pbrSpecularGlossiness": { 150 | "diffuseTexture": { 151 | "index": 5 152 | }, 153 | "specularGlossinessTexture": { 154 | "index": 6 155 | } 156 | } 157 | } 158 | } 159 | ], 160 | "nodes": [ 161 | { 162 | "mesh": 0, 163 | "name": "WaterBottle" 164 | } 165 | ], 166 | "scene": 0, 167 | "scenes": [ 168 | { 169 | "nodes": [ 170 | 0 171 | ] 172 | } 173 | ], 174 | "textures": [ 175 | { 176 | "source": 0 177 | }, 178 | { 179 | "source": 1 180 | }, 181 | { 182 | "source": 2 183 | }, 184 | { 185 | "source": 3 186 | }, 187 | { 188 | "source": 4 189 | }, 190 | { 191 | "source": 5 192 | }, 193 | { 194 | "source": 6 195 | } 196 | ] 197 | } -------------------------------------------------------------------------------- /example/static/shaders/default.frag: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | precision highp float; 4 | 5 | #define LIGHT_INTENSITY 1.0 6 | #define LIGHT_DIRECTION vec3(-0.7, -0.7, 1.0) 7 | #define LIGHT_COLOR vec3(1.0) 8 | #define M_PI 3.141592653589793 9 | 10 | uniform sampler2D uBaseColorTexture; 11 | uniform int uHasBaseColorTexture; 12 | uniform vec4 uBaseColorFactor; 13 | 14 | uniform sampler2D uMetallicRoughnessTexture; 15 | uniform int uHasMetallicRoughnessTexture; 16 | uniform float uMetallicFactor; 17 | uniform float uRoughnessFactor; 18 | 19 | uniform sampler2D uEmissiveTexture; 20 | uniform int uHasEmissiveTexture; 21 | uniform vec3 uEmissiveFactor; 22 | 23 | uniform sampler2D uNormalTexture; 24 | uniform int uHasNormalTexture; 25 | 26 | uniform sampler2D uOcclusionTexture; 27 | uniform int uHasOcclusionTexture; 28 | 29 | uniform sampler2D uBrdfLut; 30 | uniform samplerCube uEnvironmentDiffuse; 31 | uniform samplerCube uEnvironmentSpecular; 32 | 33 | uniform vec3 uCameraPosition; 34 | 35 | varying vec2 texCoord; 36 | varying vec3 normal; 37 | varying vec3 position; 38 | varying mat3 tangent; 39 | 40 | struct MaterialInfo { 41 | vec3 reflectance0; 42 | float alphaRoughness; 43 | vec3 diffuseColor; 44 | vec3 specularColor; 45 | vec3 reflectance90; 46 | float perceptualRoughness; 47 | }; 48 | 49 | 50 | vec3 linearToSrgb(vec3 color) { 51 | return pow(color, vec3(1.0 / 2.2)); 52 | } 53 | 54 | vec4 srgbToLinear(vec4 srgbIn) { 55 | return vec4(pow(srgbIn.xyz, vec3(2.2)), srgbIn.w); 56 | } 57 | 58 | vec3 specularReflection(MaterialInfo materialInfo, float VdotH) { 59 | return materialInfo.reflectance0 + (materialInfo.reflectance90 - materialInfo.reflectance0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0); 60 | } 61 | 62 | float microfacetDistribution(MaterialInfo materialInfo, float NdotH) { 63 | float alphaRoughnessSq = materialInfo.alphaRoughness * materialInfo.alphaRoughness; 64 | float f = (NdotH * alphaRoughnessSq - NdotH) * NdotH + 1.0; 65 | return alphaRoughnessSq / (M_PI * f * f); 66 | } 67 | 68 | vec3 calculateDirectionalLight(MaterialInfo materialInfo, vec3 normal, vec3 view) { 69 | vec3 pointToLight = -LIGHT_DIRECTION; 70 | 71 | vec3 n = normalize(normal); // Outward direction of surface point 72 | vec3 v = normalize(view); // Direction from surface point to view 73 | vec3 l = normalize(pointToLight); // Direction from surface point to light 74 | vec3 h = normalize(l + v); // Direction of the vector between l and v 75 | 76 | float NdotL = clamp(dot(n, l), 0.0, 1.0); 77 | float NdotH = clamp(dot(n, h), 0.0, 1.0); 78 | float VdotH = clamp(dot(v, h), 0.0, 1.0); 79 | float NdotV = clamp(dot(n, v), 0.0, 1.0); 80 | 81 | if (NdotL > 0.0 || NdotV > 0.0) { 82 | vec3 F = specularReflection(materialInfo, VdotH); 83 | float D = microfacetDistribution(materialInfo, NdotH); 84 | vec3 diffuseContrib = (1.0 - F) * (materialInfo.diffuseColor / M_PI); 85 | vec3 specContrib = F * D; 86 | 87 | return LIGHT_INTENSITY * LIGHT_COLOR * NdotL * (diffuseContrib + specContrib); 88 | } 89 | 90 | return vec3(0.0); 91 | } 92 | 93 | vec3 getIBLContribution(MaterialInfo materialInfo, vec3 n, vec3 v) { 94 | float NdotV = clamp(dot(n, v), 0.0, 1.0); 95 | vec2 brdfSamplePoint = clamp(vec2(NdotV, materialInfo.perceptualRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0)); 96 | 97 | vec2 brdf = texture2D(uBrdfLut, brdfSamplePoint).rg; 98 | vec4 diffuseSample = vec4(0.1, 0.1, 0.1, 1.0); 99 | vec4 specularSample = vec4(0.3); 100 | 101 | vec3 diffuseLight = srgbToLinear(textureCube(uEnvironmentDiffuse, n)).rgb * 0.1; 102 | vec3 specularLight = srgbToLinear(textureCube(uEnvironmentSpecular, n)).rgb * 0.2; 103 | 104 | vec3 diffuse = diffuseLight * materialInfo.diffuseColor; 105 | vec3 specular = specularLight * (materialInfo.specularColor * brdf.x + brdf.y); 106 | 107 | return diffuse + specular; 108 | } 109 | 110 | vec4 getBaseColor() { 111 | if (uHasBaseColorTexture == 1) { 112 | return srgbToLinear(texture2D(uBaseColorTexture, texCoord)) * uBaseColorFactor; 113 | } 114 | return uBaseColorFactor; 115 | } 116 | 117 | vec2 getRoughnessMetallic() { 118 | if (uHasMetallicRoughnessTexture == 1) { 119 | return texture2D(uMetallicRoughnessTexture, texCoord).gb; 120 | } 121 | return vec2(1.0, 1.0); 122 | } 123 | 124 | vec4 getEmissive() { 125 | if (uHasEmissiveTexture == 1) { 126 | return texture2D(uEmissiveTexture, texCoord) * vec4(uEmissiveFactor, 1.0); 127 | } 128 | return vec4(0.0); 129 | } 130 | 131 | float getOcclusion() { 132 | if (uHasOcclusionTexture == 1) { 133 | return texture2D(uOcclusionTexture, texCoord).r; 134 | } 135 | return 1.0; 136 | } 137 | 138 | MaterialInfo getMaterialInfo() { 139 | vec3 f0 = vec3(0.04); 140 | 141 | vec2 mrSample = getRoughnessMetallic(); 142 | float perceptualRoughness = mrSample.r * uRoughnessFactor; 143 | float metallic = mrSample.g * uMetallicFactor; 144 | 145 | vec4 baseColor = getBaseColor(); 146 | vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - f0) * (1.0 - metallic); 147 | vec3 specularColor = mix(f0, baseColor.rgb, metallic); 148 | 149 | float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b); 150 | vec3 reflectance0 = specularColor.rgb; 151 | vec3 reflectance90 = vec3(clamp(reflectance * 50.0, 0.0, 1.0)); 152 | float alphaRoughness = perceptualRoughness * perceptualRoughness; 153 | 154 | return MaterialInfo( 155 | reflectance0, 156 | alphaRoughness, 157 | diffuseColor, 158 | specularColor, 159 | reflectance90, 160 | perceptualRoughness 161 | ); 162 | } 163 | 164 | void main() { 165 | MaterialInfo materialInfo = getMaterialInfo(); 166 | 167 | vec3 n = normal; 168 | if (uHasNormalTexture == 1) { 169 | n = texture2D(uNormalTexture, texCoord).rgb; 170 | n = normalize(tangent * (2.0 * n - 1.0)); 171 | } 172 | 173 | vec3 view = normalize(uCameraPosition - position); 174 | vec3 color = calculateDirectionalLight(materialInfo, n, view); 175 | 176 | color += getIBLContribution(materialInfo, n, view); 177 | color += getEmissive().rgb; 178 | color = clamp(color, 0.0, 1.0); 179 | color = mix(color, color * getOcclusion(), 1.0); 180 | 181 | gl_FragColor = vec4(linearToSrgb(color), 1.0); 182 | } 183 | -------------------------------------------------------------------------------- /example/static/shaders/default.vert: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | attribute vec3 vPosition; 4 | attribute vec3 vNormal; 5 | attribute vec4 vTangent; 6 | attribute vec2 vTexCoord; 7 | attribute vec4 vJoints; 8 | attribute vec4 vWeights; 9 | 10 | uniform mat4 uProjectionMatrix; 11 | uniform mat4 uViewMatrix; 12 | uniform mat4 uModelMatrix; 13 | uniform mat4 uJointTransform[25]; 14 | 15 | uniform int uIsAnimated; 16 | 17 | varying vec2 texCoord; 18 | varying vec3 normal; 19 | varying vec3 position; 20 | varying mat3 tangent; 21 | 22 | void main() { 23 | mat4 skinMatrix = mat4(1.0); 24 | if (uIsAnimated == 1) { 25 | skinMatrix = vWeights.x * uJointTransform[int(vJoints.x)] + 26 | vWeights.y * uJointTransform[int(vJoints.y)] + 27 | vWeights.z * uJointTransform[int(vJoints.z)] + 28 | vWeights.w * uJointTransform[int(vJoints.w)]; 29 | } 30 | 31 | vec3 n = normalize(vNormal); 32 | vec4 t = normalize(vTangent); 33 | 34 | mat4 normalMatrix = skinMatrix; 35 | vec3 normalW = normalize(vec3(normalMatrix * vec4(n.xyz, 0.0))); 36 | vec3 tangentW = normalize(vec3(uModelMatrix * vec4(t.xyz, 0.0))); 37 | vec3 bitangentW = cross(normalW, tangentW) * t.w; 38 | 39 | tangent = mat3(tangentW, bitangentW, normalW); 40 | normal = normalize(mat3(normalMatrix) * vec3(skinMatrix * vec4(vNormal, 1.0))); 41 | texCoord = vTexCoord; 42 | position = vec3(mat3(uModelMatrix) * vPosition); 43 | 44 | gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * skinMatrix * vec4(vPosition, 1.0); 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgl-gltf", 3 | "version": "0.3.3", 4 | "description": "GLTF 2.0 loader for WebGL", 5 | "keywords": [ 6 | "WebGL", 7 | "glTF", 8 | "3D", 9 | "Mesh", 10 | "loader" 11 | ], 12 | "homepage": "https://github.com/larsjarlvik/webgl-gltf", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/larsjarlvik/webgl-gltf.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/larsjarlvik/webgl-gltf/issues" 19 | }, 20 | "main": "dist/index.js", 21 | "types": "dist/index.d.ts", 22 | "files": [ 23 | "dist" 24 | ], 25 | "scripts": { 26 | "start": "webpack-dev-server --mode development", 27 | "build": "webpack --mode production", 28 | "prepublish": "rimraf dist/ && node node_modules/typescript/bin/tsc --project ./src && cp -r src/webgl-gltf/types dist/" 29 | }, 30 | "author": "Lars Jarlvik", 31 | "license": "ISC", 32 | "devDependencies": { 33 | "@typescript-eslint/eslint-plugin": "^4.32.0", 34 | "@typescript-eslint/parser": "^4.32.0", 35 | "clean-webpack-plugin": "^4.0.0", 36 | "eslint": "^7.32.0", 37 | "eslint-loader": "^3.0.3", 38 | "html-webpack-plugin": "^5.3.2", 39 | "rimraf": "^3.0.2", 40 | "ts-loader": "^9.2.6", 41 | "typescript": "^4.4.3", 42 | "webpack": "^5.55.0", 43 | "webpack-cli": "^4.8.0", 44 | "webpack-dev-server": "^4.3.0" 45 | }, 46 | "dependencies": { 47 | "gl-matrix": "^3.3.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "target": "es6", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "outDir": "../dist", 8 | "baseUrl": "webgl-gltf", 9 | "allowSyntheticDefaultImports": true, 10 | "noImplicitAny": false, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "esModuleInterop": true, 18 | "sourceMap": true, 19 | "declaration": true, 20 | } 21 | } -------------------------------------------------------------------------------- /src/webgl-gltf/animation.ts: -------------------------------------------------------------------------------- 1 | interface ActiveAnimation { 2 | key: string; 3 | elapsed: number; 4 | } 5 | 6 | interface Animations { 7 | [model: string]: ActiveAnimation[][]; 8 | } 9 | 10 | const activeAnimations: Animations = {}; 11 | 12 | const getAnimationFromLast = (track: string, key: string, offset = 0) => { 13 | if (activeAnimations[track] === undefined || activeAnimations[track][key] === undefined || activeAnimations[track][key].length - offset - 1 < 0) { 14 | return null; 15 | } 16 | 17 | return activeAnimations[track][key][activeAnimations[track][key].length - offset - 1]; 18 | } 19 | 20 | /** 21 | * Sets the active animation 22 | * @param track Animation track 23 | * @param key Animation set key 24 | * @param model GLTF Model 25 | * @param animation Animation key 26 | */ 27 | const pushAnimation = (track: string, key: string, model: string, animation: string) => { 28 | const k = `${key}_${model}`; 29 | if (!activeAnimations[track]) activeAnimations[track] = []; 30 | if (!activeAnimations[track][k]) activeAnimations[track][k] = []; 31 | if (getAnimationFromLast(track, k)?.key === animation) return; 32 | 33 | activeAnimations[track][k].push({ 34 | key: animation, 35 | elapsed: 0, 36 | }); 37 | 38 | activeAnimations[track][k].slice(activeAnimations[track][k].length - 2); 39 | }; 40 | 41 | /** 42 | * Gets the current and previous animation 43 | * @param key Animation set key 44 | * @param model GLTF Model 45 | */ 46 | const getActiveAnimations = (key: string, model: string) => { 47 | const k = `${key}_${model}`; 48 | const aa = {}; 49 | 50 | if (Object.keys(activeAnimations).length === 0) return null; 51 | 52 | Object.keys(activeAnimations).forEach(c => { 53 | if (!activeAnimations[c][k]) return; 54 | aa[c] = activeAnimations[c][k].slice(activeAnimations[c][k].length - 2); 55 | }); 56 | 57 | return aa; 58 | }; 59 | 60 | /** 61 | * Advances the animation 62 | * @param elapsed Time elasped since last update 63 | * @param key Animation set key 64 | */ 65 | const advanceAnimation = (elapsed: number, key?: string) => { 66 | Object.keys(activeAnimations).forEach(c => { 67 | Object.keys(activeAnimations[c]).forEach(m => { 68 | if (key && m.indexOf(key) !== 0) return; 69 | 70 | const current = getAnimationFromLast(c, m); 71 | const previous = getAnimationFromLast(c, m, 1); 72 | 73 | if (current) current.elapsed += elapsed; 74 | if (previous) previous.elapsed += elapsed; 75 | }); 76 | }); 77 | }; 78 | 79 | export { 80 | pushAnimation, 81 | getActiveAnimations, 82 | advanceAnimation, 83 | ActiveAnimation, 84 | } 85 | -------------------------------------------------------------------------------- /src/webgl-gltf/animator.ts: -------------------------------------------------------------------------------- 1 | 2 | import { mat4, vec3, quat } from 'gl-matrix'; 3 | import { KeyFrame, Model, Skin, Transform } from './types/model'; 4 | import { ActiveAnimation } from './animation'; 5 | 6 | const getPreviousAndNextKeyFrame = (keyFrames: KeyFrame[], animationTime: number) => { 7 | let next = keyFrames[0]; 8 | let previous = keyFrames[0]; 9 | 10 | for (let i = 1; i < keyFrames.length; i ++) { 11 | next = keyFrames[i]; 12 | if (next.time > animationTime) break; 13 | 14 | previous = keyFrames[i]; 15 | } 16 | 17 | return { previous, next }; 18 | }; 19 | 20 | const getTransform = (keyFrames: KeyFrame[], duration: number) => { 21 | if (keyFrames.length === 1) { 22 | switch(keyFrames[0].type) { 23 | case 'translation': 24 | case 'scale': 25 | return keyFrames[0].transform as vec3; 26 | case 'rotation': 27 | return keyFrames[0].transform as quat; 28 | } 29 | } 30 | 31 | const animationTime = duration / 1000.0 % keyFrames[keyFrames.length - 1].time; 32 | const frames = getPreviousAndNextKeyFrame(keyFrames, animationTime); 33 | const progression = (animationTime - frames.previous.time) / (frames.next.time - frames.previous.time); 34 | 35 | switch(frames.previous.type) { 36 | case 'translation': 37 | case 'scale': { 38 | const result = vec3.create(); 39 | vec3.lerp(result, frames.previous.transform as vec3, frames.next.transform as vec3, progression); 40 | return result; 41 | } 42 | case 'rotation': { 43 | const result = quat.create(); 44 | quat.slerp(result, frames.previous.transform as quat, frames.next.transform as quat, progression); 45 | return result; 46 | } 47 | } 48 | }; 49 | 50 | interface TransformMatrices { 51 | [key: number]: mat4; 52 | } 53 | 54 | const get = (c: Transform, elapsed: number) => { 55 | const t = c && c.translation.length > 0 ? getTransform(c.translation, elapsed) as vec3 : vec3.create(); 56 | const r = c && c.rotation.length > 0 ? getTransform(c.rotation, elapsed) as quat : quat.create(); 57 | const s = c && c.scale.length > 0 ? getTransform(c.scale, elapsed) as vec3 : vec3.fromValues(1, 1, 1); 58 | return { t, r, s }; 59 | }; 60 | 61 | const applyTransform = (model: Model, appliedTransforms: mat4[], transforms: TransformMatrices, matrix: mat4, skin: Skin, nodeIndex: number, inverse: boolean) => { 62 | const node = model.nodes[nodeIndex]; 63 | const transformIndex = skin.joints.indexOf(node.id); 64 | 65 | if (transforms[node.id] !== undefined) { 66 | mat4.multiply(matrix, matrix, transforms[node.id]); 67 | } 68 | 69 | if (inverse) { 70 | const ibt = skin.inverseBindTransforms[transformIndex]; 71 | 72 | if (ibt) { 73 | appliedTransforms[transformIndex] = mat4.create(); 74 | mat4.multiply(appliedTransforms[transformIndex], matrix, ibt); 75 | } 76 | } else { 77 | appliedTransforms[transformIndex] = matrix; 78 | } 79 | 80 | node.children.forEach(childNode => { 81 | applyTransform(model, appliedTransforms, transforms, mat4.clone(matrix), skin, childNode, inverse); 82 | }); 83 | }; 84 | 85 | /** 86 | * Blends two animations and returns their transform matrices 87 | * @param model GLTF Model 88 | * @param activeAnimations Currently running animations 89 | * @param blendTime Length of animation blend in milliseconds 90 | */ 91 | const getAnimationTransforms = (model: Model, activeAnimations: Record, blendTime = 0) => { 92 | const transforms: { [key: number]: mat4 } = {}; 93 | 94 | Object.keys(activeAnimations).forEach(track => { 95 | activeAnimations[track].forEach(rootAnimation => { 96 | const blend = -((rootAnimation.elapsed - blendTime) / blendTime); 97 | 98 | Object.keys(model.animations[rootAnimation.key]).forEach(c => { 99 | const transform = get(model.animations[rootAnimation.key][c], rootAnimation.elapsed); 100 | 101 | activeAnimations[track].forEach(ac => { 102 | if (rootAnimation.key == ac.key || blend <= 0) return; 103 | 104 | const cTransform = get(model.animations[ac.key][c], ac.elapsed); 105 | vec3.lerp(transform.t, transform.t, cTransform.t, blend); 106 | quat.slerp(transform.r, transform.r, cTransform.r, blend); 107 | vec3.lerp(transform.s, transform.s, cTransform.s, blend); 108 | }); 109 | 110 | const localTransform = mat4.create(); 111 | const rotTransform = mat4.create(); 112 | mat4.fromQuat(rotTransform, transform.r as quat); 113 | 114 | mat4.translate(localTransform, localTransform, transform.t as vec3); 115 | mat4.multiply(localTransform, localTransform, rotTransform); 116 | mat4.scale(localTransform, localTransform, transform.s as vec3); 117 | 118 | transforms[c] = localTransform; 119 | }); 120 | }); 121 | }); 122 | 123 | return transforms; 124 | }; 125 | 126 | /** 127 | * Applies transforms to skin 128 | * @param model GLTF Model 129 | * @param transforms Raw transforms 130 | * @param blendTime Use inverse bind transform 131 | */ 132 | const applyToSkin = (model: Model, transforms: { [key: number]: mat4 }, inverse = true) => { 133 | const appliedTransforms: mat4[] = []; 134 | 135 | model.skins.forEach(skin => { 136 | const root = model.rootNode; 137 | applyTransform(model, appliedTransforms, transforms, mat4.create(), skin, root, inverse); 138 | }); 139 | 140 | return appliedTransforms; 141 | }; 142 | 143 | export { 144 | getAnimationTransforms, 145 | applyToSkin, 146 | }; 147 | -------------------------------------------------------------------------------- /src/webgl-gltf/gltf.ts: -------------------------------------------------------------------------------- 1 | import * as gltf from './types/gltf'; 2 | import { mat4, quat, vec3, vec4 } from 'gl-matrix'; 3 | import { createMat4FromArray, applyRotationFromQuat } from './mat'; 4 | import { Channel, Node, Mesh, Model, KeyFrame, Skin, Material, GLBuffer, Animation } from './types/model'; 5 | 6 | type GLContext = WebGLRenderingContext | WebGL2RenderingContext; 7 | 8 | const accessorSizes = { 9 | 'SCALAR': 1, 10 | 'VEC2': 2, 11 | 'VEC3': 3, 12 | 'VEC4': 4, 13 | 'MAT2': 4, 14 | 'MAT3': 9, 15 | 'MAT4': 16 16 | }; 17 | 18 | export interface Buffer { 19 | data: Float32Array | Int16Array; 20 | size: number; 21 | type: string; 22 | componentType: BufferType; 23 | glBuffer: WebGLBuffer; 24 | } 25 | 26 | export enum BufferType { 27 | Float = 5126, 28 | Short = 5123, 29 | } 30 | 31 | const resolveEmbeddedBuffer = (uri: string): string => { 32 | const content = uri.split(',')[1]; 33 | const binaryData = atob(content); 34 | const arrayBuffer = new ArrayBuffer(binaryData.length); 35 | const uint8Array = new Uint8Array(arrayBuffer); 36 | 37 | for (let i = 0; i < binaryData.length; i++) { 38 | uint8Array[i] = binaryData.charCodeAt(i); 39 | } 40 | 41 | const blob = new Blob([uint8Array], { type: 'application/octet-stream' }); // Crea un Blob 42 | return URL.createObjectURL(blob); 43 | } 44 | 45 | const EMBEDDED_DATA_REGEXP = /(.*)data:(.*?)(;base64)?,(.*)$/; 46 | 47 | const getBuffer = async (path: string, buffer: string) => { 48 | const dir = path.split('/').slice(0, -1).join('/'); 49 | const finalPath = EMBEDDED_DATA_REGEXP.test(buffer) ? resolveEmbeddedBuffer(buffer) : `${dir}/${buffer}`; 50 | const response = await fetch(finalPath); 51 | return await response.arrayBuffer(); 52 | }; 53 | 54 | const getTexture = async (gl: GLContext, uri: string) => { 55 | return new Promise(resolve => { 56 | const img = new Image(); 57 | img.onload = () => { 58 | const texture = gl.createTexture(); 59 | gl.bindTexture(gl.TEXTURE_2D, texture); 60 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); 61 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); 62 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 63 | 64 | const ext = gl.getExtension('EXT_texture_filter_anisotropic'); 65 | if (ext) { 66 | const max = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT); 67 | gl.texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, max); 68 | } 69 | 70 | gl.generateMipmap(gl.TEXTURE_2D); 71 | resolve(texture!); 72 | } 73 | img.src = EMBEDDED_DATA_REGEXP.test(uri) ? resolveEmbeddedBuffer(uri) : uri; 74 | img.crossOrigin = 'undefined'; 75 | }); 76 | }; 77 | 78 | const readBufferFromFile = (gltf: gltf.GlTf, buffers: ArrayBuffer[], accessor: gltf.Accessor) => { 79 | const bufferView = gltf.bufferViews![accessor.bufferView as number]; 80 | const size = accessorSizes[accessor.type]; 81 | const componentType = accessor.componentType as BufferType; 82 | const type = accessor.type; 83 | 84 | const data = componentType == BufferType.Float 85 | ? new Float32Array(buffers[bufferView.buffer], (accessor.byteOffset || 0) + (bufferView.byteOffset || 0), accessor.count * size) 86 | : new Int16Array(buffers[bufferView.buffer], (accessor.byteOffset || 0) + (bufferView.byteOffset || 0), accessor.count * size); 87 | 88 | return { 89 | size, 90 | data, 91 | type, 92 | componentType, 93 | } as Buffer; 94 | }; 95 | 96 | const getAccessor = (gltf: gltf.GlTf, mesh: gltf.Mesh, attributeName: string) => { 97 | const attribute = mesh.primitives[0].attributes[attributeName]; 98 | return gltf.accessors![attribute]; 99 | }; 100 | 101 | const getBufferFromName = (gl: GLContext, gltf: gltf.GlTf, buffers: ArrayBuffer[], mesh: gltf.Mesh, name: string) => { 102 | if (mesh.primitives[0].attributes[name] === undefined) { 103 | return null; 104 | } 105 | 106 | const accessor = getAccessor(gltf, mesh, name); 107 | const bufferData = readBufferFromFile(gltf, buffers, accessor); 108 | 109 | const buffer = gl.createBuffer(); 110 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 111 | gl.bufferData(gl.ARRAY_BUFFER, bufferData.data, gl.STATIC_DRAW); 112 | 113 | return { 114 | buffer, 115 | size: bufferData.size, 116 | type: bufferData.componentType, 117 | } as GLBuffer; 118 | }; 119 | 120 | const loadNodes = (index: number, node: gltf.Node): Node => { 121 | const transform = mat4.create(); 122 | 123 | if (node.translation !== undefined) mat4.translate(transform, transform, vec3.fromValues(node.translation[0], node.translation[1], node.translation[1])); 124 | if (node.rotation !== undefined) applyRotationFromQuat(transform, node.rotation); 125 | if (node.scale !== undefined) mat4.scale(transform, transform, vec3.fromValues(node.scale[0], node.scale[1], node.scale[1])); 126 | if (node.matrix !== undefined) createMat4FromArray(node.matrix); 127 | 128 | return { 129 | id: index, 130 | name: node.name, 131 | children: node.children || [], 132 | localBindTransform: transform, 133 | animatedTransform: mat4.create(), 134 | skin: node.skin, 135 | mesh: node.mesh 136 | } as Node; 137 | }; 138 | 139 | const loadAnimation = (gltf: gltf.GlTf, animation: gltf.Animation, buffers: ArrayBuffer[]) => { 140 | const channels = animation.channels.map(c => { 141 | const sampler = animation.samplers[c.sampler]; 142 | const time = readBufferFromFile(gltf, buffers, gltf.accessors![sampler.input]); 143 | const buffer = readBufferFromFile(gltf, buffers, gltf.accessors![sampler.output]); 144 | 145 | return { 146 | node: c.target.node, 147 | type: c.target.path, 148 | time, 149 | buffer, 150 | interpolation: sampler.interpolation ? sampler.interpolation : 'LINEAR', 151 | }; 152 | }); 153 | 154 | const c: Channel = {}; 155 | channels.forEach((channel) => { 156 | if (c[channel.node!] === undefined) { 157 | c[channel.node!] = { 158 | translation: [], 159 | rotation: [], 160 | scale: [], 161 | }; 162 | } 163 | 164 | for (let i = 0; i < channel.time.data.length; i ++) { 165 | const size = channel.interpolation === 'CUBICSPLINE' ? channel.buffer.size * 3 : channel.buffer.size; 166 | const offset = channel.interpolation === 'CUBICSPLINE' ? channel.buffer.size : 0; 167 | 168 | const transform = channel.type === 'rotation' 169 | ? quat.fromValues( 170 | channel.buffer.data[i * size + offset], 171 | channel.buffer.data[i * size + offset + 1], 172 | channel.buffer.data[i * size + offset + 2], 173 | channel.buffer.data[i * size + offset + 3] 174 | ) 175 | : vec3.fromValues( 176 | channel.buffer.data[i * size + offset], 177 | channel.buffer.data[i * size + offset + 1], 178 | channel.buffer.data[i * size + offset + 2] 179 | ); 180 | 181 | c[channel.node!][channel.type].push({ 182 | time: channel.time.data[i], 183 | transform: transform, 184 | type: channel.type, 185 | } as KeyFrame) 186 | } 187 | }); 188 | 189 | return c; 190 | }; 191 | 192 | const loadMesh = (gl: GLContext, gltf: gltf.GlTf, mesh: gltf.Mesh, buffers: ArrayBuffer[]) => { 193 | let indices: WebGLBuffer | null = null; 194 | let elementCount = 0; 195 | 196 | if (mesh.primitives[0].indices !== undefined) { 197 | const indexAccessor = gltf.accessors![mesh.primitives[0].indices!]; 198 | const indexBuffer = readBufferFromFile(gltf, buffers, indexAccessor); 199 | 200 | indices = gl.createBuffer(); 201 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices); 202 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.data, gl.STATIC_DRAW); 203 | 204 | elementCount = indexBuffer.data.length; 205 | } else { 206 | const accessor = getAccessor(gltf, mesh, 'POSITION'); 207 | elementCount = accessor.count; 208 | } 209 | 210 | return { 211 | indices, 212 | elementCount, 213 | positions: getBufferFromName(gl, gltf, buffers, mesh, 'POSITION'), 214 | normals: getBufferFromName(gl, gltf, buffers, mesh, 'NORMAL'), 215 | tangents: getBufferFromName(gl, gltf, buffers, mesh, 'TANGENT'), 216 | texCoord: getBufferFromName(gl, gltf, buffers, mesh, 'TEXCOORD_0'), 217 | joints: getBufferFromName(gl, gltf, buffers, mesh, 'JOINTS_0'), 218 | weights: getBufferFromName(gl, gltf, buffers, mesh, 'WEIGHTS_0'), 219 | material: mesh.primitives[0].material, 220 | } as Mesh; 221 | }; 222 | 223 | const loadMaterial = async (gl: GLContext, material: gltf.Material, path: string, images?: gltf.Image[]): Promise => { 224 | const dir = path.split('/').slice(0, -1).join('/'); 225 | 226 | let baseColorTexture: WebGLTexture | null = null; 227 | let metallicRoughnessTexture: WebGLTexture | null = null; 228 | let emissiveTexture: WebGLTexture | null = null; 229 | let normalTexture: WebGLTexture | null = null; 230 | let occlusionTexture: WebGLTexture | null = null; 231 | 232 | let baseColorFactor = vec4.fromValues(1.0, 1.0, 1.0, 1.0); 233 | let roughnessFactor = 0.0; 234 | let metallicFactor = 1.0; 235 | let emissiveFactor = vec3.fromValues(1.0, 1.0, 1.0); 236 | 237 | const pbr = material.pbrMetallicRoughness; 238 | if (pbr) { 239 | if (pbr.baseColorTexture) { 240 | const uri = images![pbr.baseColorTexture.index].uri!; 241 | baseColorTexture = await getTexture(gl, `${dir}/${uri}`); 242 | } 243 | if (pbr.baseColorFactor) { 244 | baseColorFactor = vec4.fromValues(pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3]); 245 | } 246 | 247 | if (pbr.metallicRoughnessTexture) { 248 | const uri = images![pbr.metallicRoughnessTexture.index].uri!; 249 | metallicRoughnessTexture = await getTexture(gl, `${dir}/${uri}`); 250 | } 251 | 252 | metallicFactor = pbr.metallicFactor !== undefined ? pbr.metallicFactor : 1.0; 253 | roughnessFactor = pbr.roughnessFactor !== undefined ? pbr.roughnessFactor : 1.0; 254 | } 255 | 256 | if (material.emissiveTexture) { 257 | const uri = images![material.emissiveTexture.index].uri!; 258 | emissiveTexture = await getTexture(gl, `${dir}/${uri}`); 259 | } 260 | 261 | if (material.normalTexture) { 262 | const uri = images![material.normalTexture.index].uri!; 263 | normalTexture = await getTexture(gl, `${dir}/${uri}`); 264 | } 265 | 266 | if (material.occlusionTexture) { 267 | const uri = images![material.occlusionTexture.index].uri!; 268 | occlusionTexture = await getTexture(gl, `${dir}/${uri}`); 269 | } 270 | 271 | if (material.emissiveFactor) { 272 | emissiveFactor = vec3.fromValues(material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2]) 273 | } 274 | 275 | 276 | return { 277 | baseColorTexture, 278 | baseColorFactor, 279 | metallicRoughnessTexture, 280 | metallicFactor, 281 | roughnessFactor, 282 | emissiveTexture, 283 | emissiveFactor, 284 | normalTexture, 285 | occlusionTexture, 286 | } as Material; 287 | }; 288 | 289 | /** 290 | * Loads a GLTF model and its assets 291 | * @param gl Web GL context 292 | * @param uri URI to model 293 | */ 294 | const loadModel = async (gl: GLContext, uri: string) => { 295 | const response = await fetch(uri); 296 | const gltf = await response.json() as gltf.GlTf; 297 | 298 | if (gltf.accessors === undefined || gltf.accessors.length === 0) { 299 | throw new Error('GLTF File is missing accessors') 300 | } 301 | 302 | const buffers = await Promise.all( 303 | gltf.buffers!.map(async (b) => await getBuffer(uri, b.uri!) 304 | )); 305 | 306 | const scene = gltf.scenes![gltf.scene || 0]; 307 | const meshes = gltf.meshes!.map(m => loadMesh(gl, gltf, m, buffers)); 308 | const materials = gltf.materials ? await Promise.all(gltf.materials.map(async (m) => await loadMaterial(gl, m, uri, gltf.images))) : []; 309 | 310 | const rootNode = scene.nodes![0]; 311 | const nodes = gltf.nodes!.map((n, i) => loadNodes(i, n)); 312 | 313 | const animations = {} as Animation; 314 | gltf.animations?.forEach(anim => animations[anim.name as string] = loadAnimation(gltf, anim, buffers)); 315 | 316 | const skins = gltf.skins ? gltf.skins.map(x => { 317 | const bindTransforms = readBufferFromFile(gltf, buffers, gltf.accessors![x.inverseBindMatrices!]); 318 | const inverseBindTransforms = x.joints.map((_, i) => createMat4FromArray(bindTransforms.data.slice(i * 16, i * 16 + 16))); 319 | 320 | return { 321 | joints: x.joints, 322 | inverseBindTransforms, 323 | }; 324 | }) : [] as Skin[]; 325 | 326 | const name = uri.split('/').slice(-1)[0]; 327 | return { 328 | name, 329 | meshes, 330 | nodes, 331 | rootNode, 332 | animations, 333 | skins, 334 | materials, 335 | } as Model; 336 | }; 337 | 338 | 339 | /** 340 | * Deletes GL buffers and textures 341 | * @param gl Web GL context 342 | * @param model Model to dispose 343 | */ 344 | const dispose = (gl: GLContext, model: Model) => { 345 | model.meshes.forEach(m => { 346 | gl.deleteBuffer(m.indices); 347 | if (m.joints) gl.deleteBuffer(m.joints.buffer); 348 | if (m.normals) gl.deleteBuffer(m.normals.buffer); 349 | if (m.positions) gl.deleteBuffer(m.positions.buffer); 350 | if (m.tangents) gl.deleteBuffer(m.tangents.buffer); 351 | if (m.texCoord) gl.deleteBuffer(m.texCoord.buffer); 352 | if (m.weights) gl.deleteBuffer(m.weights.buffer); 353 | 354 | m.indices = null 355 | m.joints = null; 356 | m.normals = null; 357 | m.tangents = null; 358 | m.texCoord = null; 359 | m.weights = null; 360 | }); 361 | 362 | model.materials.forEach(m => { 363 | if (m.baseColorTexture) gl.deleteTexture(m.baseColorTexture); 364 | if (m.emissiveTexture) gl.deleteTexture(m.emissiveTexture); 365 | if (m.normalTexture) gl.deleteTexture(m.normalTexture); 366 | if (m.occlusionTexture) gl.deleteTexture(m.occlusionTexture); 367 | if (m.metallicRoughnessTexture) gl.deleteTexture(m.metallicRoughnessTexture); 368 | 369 | m.baseColorTexture = null; 370 | m.emissiveTexture = null; 371 | m.normalTexture = null; 372 | m.occlusionTexture = null; 373 | m.metallicRoughnessTexture = null; 374 | }); 375 | }; 376 | 377 | export { 378 | loadModel, 379 | dispose, 380 | }; 381 | -------------------------------------------------------------------------------- /src/webgl-gltf/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | loadModel, 3 | dispose, 4 | } from './gltf'; 5 | 6 | export { 7 | getAnimationTransforms, 8 | applyToSkin, 9 | } from './animator'; 10 | 11 | export { 12 | pushAnimation, 13 | getActiveAnimations, 14 | advanceAnimation, 15 | ActiveAnimation, 16 | } from './animation'; 17 | 18 | export { 19 | Model, 20 | GLBuffer, 21 | Node, 22 | Skin, 23 | Animation, 24 | Channel, 25 | Transform, 26 | KeyFrame, 27 | Mesh, 28 | Material, 29 | } from './types/model' 30 | -------------------------------------------------------------------------------- /src/webgl-gltf/mat.ts: -------------------------------------------------------------------------------- 1 | import { mat4, quat } from 'gl-matrix'; 2 | 3 | export const createMat4FromArray = (array: Float32Array | Int16Array | number[]) => { 4 | return mat4.fromValues( 5 | array[0], array[1], array[2], array[3], 6 | array[4], array[5], array[6], array[7], 7 | array[8], array[9], array[10], array[11], 8 | array[12], array[13], array[14], array[15] 9 | ); 10 | }; 11 | 12 | export const applyRotationFromQuat = (transform: mat4, rotation: number[]) => { 13 | const rotationMatrix = mat4.create(); 14 | mat4.fromQuat(rotationMatrix, quat.fromValues(rotation[0], rotation[1], rotation[2], rotation[3])); 15 | mat4.multiply(transform, rotationMatrix, transform); 16 | } -------------------------------------------------------------------------------- /src/webgl-gltf/types/gltf.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export type GlTfId = number; 3 | /** 4 | * Indices of those attributes that deviate from their initialization value. 5 | */ 6 | export interface AccessorSparseIndices { 7 | /** 8 | * The index of the bufferView with sparse indices. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target. 9 | */ 10 | 'bufferView': GlTfId; 11 | /** 12 | * The offset relative to the start of the bufferView in bytes. Must be aligned. 13 | */ 14 | 'byteOffset'?: number; 15 | /** 16 | * The indices data type. 17 | */ 18 | 'componentType': 5121 | 5123 | 5125 | number; 19 | 'extensions'?: any; 20 | 'extras'?: any; 21 | [k: string]: any; 22 | } 23 | /** 24 | * Array of size `accessor.sparse.count` times number of components storing the displaced accessor attributes pointed by `accessor.sparse.indices`. 25 | */ 26 | export interface AccessorSparseValues { 27 | /** 28 | * The index of the bufferView with sparse values. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target. 29 | */ 30 | 'bufferView': GlTfId; 31 | /** 32 | * The offset relative to the start of the bufferView in bytes. Must be aligned. 33 | */ 34 | 'byteOffset'?: number; 35 | 'extensions'?: any; 36 | 'extras'?: any; 37 | [k: string]: any; 38 | } 39 | /** 40 | * Sparse storage of attributes that deviate from their initialization value. 41 | */ 42 | export interface AccessorSparse { 43 | /** 44 | * Number of entries stored in the sparse array. 45 | */ 46 | 'count': number; 47 | /** 48 | * Index array of size `count` that points to those accessor attributes that deviate from their initialization value. Indices must strictly increase. 49 | */ 50 | 'indices': AccessorSparseIndices; 51 | /** 52 | * Array of size `count` times number of components, storing the displaced accessor attributes pointed by `indices`. Substituted values must have the same `componentType` and number of components as the base accessor. 53 | */ 54 | 'values': AccessorSparseValues; 55 | 'extensions'?: any; 56 | 'extras'?: any; 57 | [k: string]: any; 58 | } 59 | /** 60 | * A typed view into a bufferView. A bufferView contains raw binary data. An accessor provides a typed view into a bufferView or a subset of a bufferView similar to how WebGL's `vertexAttribPointer()` defines an attribute in a buffer. 61 | */ 62 | export interface Accessor { 63 | /** 64 | * The index of the bufferView. 65 | */ 66 | 'bufferView'?: GlTfId; 67 | /** 68 | * The offset relative to the start of the bufferView in bytes. 69 | */ 70 | 'byteOffset'?: number; 71 | /** 72 | * The datatype of components in the attribute. 73 | */ 74 | 'componentType': 5120 | 5121 | 5122 | 5123 | 5125 | 5126 | number; 75 | /** 76 | * Specifies whether integer data values should be normalized. 77 | */ 78 | 'normalized'?: boolean; 79 | /** 80 | * The number of attributes referenced by this accessor. 81 | */ 82 | 'count': number; 83 | /** 84 | * Specifies if the attribute is a scalar, vector, or matrix. 85 | */ 86 | 'type': 'SCALAR' | 'VEC2' | 'VEC3' | 'VEC4' | 'MAT2' | 'MAT3' | 'MAT4' | string; 87 | /** 88 | * Maximum value of each component in this attribute. 89 | */ 90 | 'max'?: number[]; 91 | /** 92 | * Minimum value of each component in this attribute. 93 | */ 94 | 'min'?: number[]; 95 | /** 96 | * Sparse storage of attributes that deviate from their initialization value. 97 | */ 98 | 'sparse'?: AccessorSparse; 99 | 'name'?: any; 100 | 'extensions'?: any; 101 | 'extras'?: any; 102 | [k: string]: any; 103 | } 104 | /** 105 | * The index of the node and TRS property that an animation channel targets. 106 | */ 107 | export interface AnimationChannelTarget { 108 | /** 109 | * The index of the node to target. 110 | */ 111 | 'node'?: GlTfId; 112 | /** 113 | * The name of the node's TRS property to modify, 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. 114 | */ 115 | 'path': 'translation' | 'rotation' | 'scale' | 'weights' | string; 116 | 'extensions'?: any; 117 | 'extras'?: any; 118 | [k: string]: any; 119 | } 120 | /** 121 | * Targets an animation's sampler at a node's property. 122 | */ 123 | export interface AnimationChannel { 124 | /** 125 | * The index of a sampler in this animation used to compute the value for the target. 126 | */ 127 | 'sampler': GlTfId; 128 | /** 129 | * The index of the node and TRS property to target. 130 | */ 131 | 'target': AnimationChannelTarget; 132 | 'extensions'?: any; 133 | 'extras'?: any; 134 | [k: string]: any; 135 | } 136 | /** 137 | * Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target). 138 | */ 139 | export interface AnimationSampler { 140 | /** 141 | * The index of an accessor containing keyframe input values, e.g., time. 142 | */ 143 | 'input': GlTfId; 144 | /** 145 | * Interpolation algorithm. 146 | */ 147 | 'interpolation'?: 'LINEAR' | 'STEP' | 'CUBICSPLINE' | string; 148 | /** 149 | * The index of an accessor, containing keyframe output values. 150 | */ 151 | 'output': GlTfId; 152 | 'extensions'?: any; 153 | 'extras'?: any; 154 | [k: string]: any; 155 | } 156 | /** 157 | * A keyframe animation. 158 | */ 159 | export interface Animation { 160 | /** 161 | * An array of channels, each of which targets an animation's sampler at a node's property. Different channels of the same animation can't have equal targets. 162 | */ 163 | 'channels': AnimationChannel[]; 164 | /** 165 | * An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target). 166 | */ 167 | 'samplers': AnimationSampler[]; 168 | 'name'?: any; 169 | 'extensions'?: any; 170 | 'extras'?: any; 171 | [k: string]: any; 172 | } 173 | /** 174 | * Metadata about the glTF asset. 175 | */ 176 | export interface Asset { 177 | /** 178 | * A copyright message suitable for display to credit the content creator. 179 | */ 180 | 'copyright'?: string; 181 | /** 182 | * Tool that generated this glTF model. Useful for debugging. 183 | */ 184 | 'generator'?: string; 185 | /** 186 | * The glTF version that this asset targets. 187 | */ 188 | 'version': string; 189 | /** 190 | * The minimum glTF version that this asset targets. 191 | */ 192 | 'minVersion'?: string; 193 | 'extensions'?: any; 194 | 'extras'?: any; 195 | [k: string]: any; 196 | } 197 | /** 198 | * A buffer points to binary geometry, animation, or skins. 199 | */ 200 | export interface Buffer { 201 | /** 202 | * The uri of the buffer. 203 | */ 204 | 'uri'?: string; 205 | /** 206 | * The length of the buffer in bytes. 207 | */ 208 | 'byteLength': number; 209 | 'name'?: any; 210 | 'extensions'?: any; 211 | 'extras'?: any; 212 | [k: string]: any; 213 | } 214 | /** 215 | * A view into a buffer generally representing a subset of the buffer. 216 | */ 217 | export interface BufferView { 218 | /** 219 | * The index of the buffer. 220 | */ 221 | 'buffer': GlTfId; 222 | /** 223 | * The offset into the buffer in bytes. 224 | */ 225 | 'byteOffset'?: number; 226 | /** 227 | * The total byte length of the buffer view. 228 | */ 229 | 'byteLength': number; 230 | /** 231 | * The stride, in bytes. 232 | */ 233 | 'byteStride'?: number; 234 | /** 235 | * The target that the GPU buffer should be bound to. 236 | */ 237 | 'target'?: 34962 | 34963 | number; 238 | 'name'?: any; 239 | 'extensions'?: any; 240 | 'extras'?: any; 241 | [k: string]: any; 242 | } 243 | /** 244 | * An orthographic camera containing properties to create an orthographic projection matrix. 245 | */ 246 | export interface CameraOrthographic { 247 | /** 248 | * The floating-point horizontal magnification of the view. Must not be zero. 249 | */ 250 | 'xmag': number; 251 | /** 252 | * The floating-point vertical magnification of the view. Must not be zero. 253 | */ 254 | 'ymag': number; 255 | /** 256 | * The floating-point distance to the far clipping plane. `zfar` must be greater than `znear`. 257 | */ 258 | 'zfar': number; 259 | /** 260 | * The floating-point distance to the near clipping plane. 261 | */ 262 | 'znear': number; 263 | 'extensions'?: any; 264 | 'extras'?: any; 265 | [k: string]: any; 266 | } 267 | /** 268 | * A perspective camera containing properties to create a perspective projection matrix. 269 | */ 270 | export interface CameraPerspective { 271 | /** 272 | * The floating-point aspect ratio of the field of view. 273 | */ 274 | 'aspectRatio'?: number; 275 | /** 276 | * The floating-point vertical field of view in radians. 277 | */ 278 | 'yfov': number; 279 | /** 280 | * The floating-point distance to the far clipping plane. 281 | */ 282 | 'zfar'?: number; 283 | /** 284 | * The floating-point distance to the near clipping plane. 285 | */ 286 | 'znear': number; 287 | 'extensions'?: any; 288 | 'extras'?: any; 289 | [k: string]: any; 290 | } 291 | /** 292 | * A camera's projection. A node can reference a camera to apply a transform to place the camera in the scene. 293 | */ 294 | export interface Camera { 295 | /** 296 | * An orthographic camera containing properties to create an orthographic projection matrix. 297 | */ 298 | 'orthographic'?: CameraOrthographic; 299 | /** 300 | * A perspective camera containing properties to create a perspective projection matrix. 301 | */ 302 | 'perspective'?: CameraPerspective; 303 | /** 304 | * Specifies if the camera uses a perspective or orthographic projection. 305 | */ 306 | 'type': 'perspective' | 'orthographic' | string; 307 | 'name'?: any; 308 | 'extensions'?: any; 309 | 'extras'?: any; 310 | [k: string]: any; 311 | } 312 | /** 313 | * Image data used to create a texture. Image can be referenced by URI or `bufferView` index. `mimeType` is required in the latter case. 314 | */ 315 | export interface Image { 316 | /** 317 | * The uri of the image. 318 | */ 319 | 'uri'?: string; 320 | /** 321 | * The image's MIME type. Required if `bufferView` is defined. 322 | */ 323 | 'mimeType'?: 'image/jpeg' | 'image/png' | string; 324 | /** 325 | * The index of the bufferView that contains the image. Use this instead of the image's uri property. 326 | */ 327 | 'bufferView'?: GlTfId; 328 | 'name'?: any; 329 | 'extensions'?: any; 330 | 'extras'?: any; 331 | [k: string]: any; 332 | } 333 | /** 334 | * Reference to a texture. 335 | */ 336 | export interface TextureInfo { 337 | /** 338 | * The index of the texture. 339 | */ 340 | 'index': GlTfId; 341 | /** 342 | * The set index of texture's TEXCOORD attribute used for texture coordinate mapping. 343 | */ 344 | 'texCoord'?: number; 345 | 'extensions'?: any; 346 | 'extras'?: any; 347 | [k: string]: any; 348 | } 349 | /** 350 | * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. 351 | */ 352 | export interface MaterialPbrMetallicRoughness { 353 | /** 354 | * The material's base color factor. 355 | */ 356 | 'baseColorFactor'?: number[]; 357 | /** 358 | * The base color texture. 359 | */ 360 | 'baseColorTexture'?: TextureInfo; 361 | /** 362 | * The metalness of the material. 363 | */ 364 | 'metallicFactor'?: number; 365 | /** 366 | * The roughness of the material. 367 | */ 368 | 'roughnessFactor'?: number; 369 | /** 370 | * The metallic-roughness texture. 371 | */ 372 | 'metallicRoughnessTexture'?: TextureInfo; 373 | 'extensions'?: any; 374 | 'extras'?: any; 375 | [k: string]: any; 376 | } 377 | export interface MaterialNormalTextureInfo { 378 | 'index'?: any; 379 | 'texCoord'?: any; 380 | /** 381 | * The scalar multiplier applied to each normal vector of the normal texture. 382 | */ 383 | 'scale'?: number; 384 | 'extensions'?: any; 385 | 'extras'?: any; 386 | [k: string]: any; 387 | } 388 | export interface MaterialOcclusionTextureInfo { 389 | 'index'?: any; 390 | 'texCoord'?: any; 391 | /** 392 | * A scalar multiplier controlling the amount of occlusion applied. 393 | */ 394 | 'strength'?: number; 395 | 'extensions'?: any; 396 | 'extras'?: any; 397 | [k: string]: any; 398 | } 399 | /** 400 | * The material appearance of a primitive. 401 | */ 402 | export interface Material { 403 | 'name'?: any; 404 | 'extensions'?: any; 405 | 'extras'?: any; 406 | /** 407 | * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of `pbrMetallicRoughness` apply. 408 | */ 409 | 'pbrMetallicRoughness'?: MaterialPbrMetallicRoughness; 410 | /** 411 | * The normal map texture. 412 | */ 413 | 'normalTexture'?: MaterialNormalTextureInfo; 414 | /** 415 | * The occlusion map texture. 416 | */ 417 | 'occlusionTexture'?: MaterialOcclusionTextureInfo; 418 | /** 419 | * The emissive map texture. 420 | */ 421 | 'emissiveTexture'?: TextureInfo; 422 | /** 423 | * The emissive color of the material. 424 | */ 425 | 'emissiveFactor'?: number[]; 426 | /** 427 | * The alpha rendering mode of the material. 428 | */ 429 | 'alphaMode'?: 'OPAQUE' | 'MASK' | 'BLEND' | string; 430 | /** 431 | * The alpha cutoff value of the material. 432 | */ 433 | 'alphaCutoff'?: number; 434 | /** 435 | * Specifies whether the material is double sided. 436 | */ 437 | 'doubleSided'?: boolean; 438 | [k: string]: any; 439 | } 440 | /** 441 | * Geometry to be rendered with the given material. 442 | */ 443 | export interface MeshPrimitive { 444 | /** 445 | * A dictionary object, where each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data. 446 | */ 447 | 'attributes': { 448 | [k: string]: GlTfId; 449 | }; 450 | /** 451 | * The index of the accessor that contains the indices. 452 | */ 453 | 'indices'?: GlTfId; 454 | /** 455 | * The index of the material to apply to this primitive when rendering. 456 | */ 457 | 'material'?: GlTfId; 458 | /** 459 | * The type of primitives to render. 460 | */ 461 | 'mode'?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | number; 462 | /** 463 | * An array of Morph Targets, each Morph Target is a dictionary mapping attributes (only `POSITION`, `NORMAL`, and `TANGENT` supported) to their deviations in the Morph Target. 464 | */ 465 | 'targets'?: { 466 | [k: string]: GlTfId; 467 | }[]; 468 | 'extensions'?: any; 469 | 'extras'?: any; 470 | [k: string]: any; 471 | } 472 | /** 473 | * A set of primitives to be rendered. A node can contain one mesh. A node's transform places the mesh in the scene. 474 | */ 475 | export interface Mesh { 476 | /** 477 | * An array of primitives, each defining geometry to be rendered with a material. 478 | */ 479 | 'primitives': MeshPrimitive[]; 480 | /** 481 | * Array of weights to be applied to the Morph Targets. 482 | */ 483 | 'weights'?: number[]; 484 | 'name'?: any; 485 | 'extensions'?: any; 486 | 'extras'?: any; 487 | [k: string]: any; 488 | } 489 | /** 490 | * A node in the node hierarchy. When the node contains `skin`, all `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. A node can 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), only TRS properties may be present; `matrix` will not be present. 491 | */ 492 | export interface Node { 493 | /** 494 | * The index of the camera referenced by this node. 495 | */ 496 | 'camera'?: GlTfId; 497 | /** 498 | * The indices of this node's children. 499 | */ 500 | 'children'?: GlTfId[]; 501 | /** 502 | * The index of the skin referenced by this node. 503 | */ 504 | 'skin'?: GlTfId; 505 | /** 506 | * A floating-point 4x4 transformation matrix stored in column-major order. 507 | */ 508 | 'matrix'?: number[]; 509 | /** 510 | * The index of the mesh in this node. 511 | */ 512 | 'mesh'?: GlTfId; 513 | /** 514 | * The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar. 515 | */ 516 | 'rotation'?: number[]; 517 | /** 518 | * The node's non-uniform scale, given as the scaling factors along the x, y, and z axes. 519 | */ 520 | 'scale'?: number[]; 521 | /** 522 | * The node's translation along the x, y, and z axes. 523 | */ 524 | 'translation'?: number[]; 525 | /** 526 | * The weights of the instantiated Morph Target. Number of elements must match number of Morph Targets of used mesh. 527 | */ 528 | 'weights'?: number[]; 529 | 'name'?: any; 530 | 'extensions'?: any; 531 | 'extras'?: any; 532 | [k: string]: any; 533 | } 534 | /** 535 | * Texture sampler properties for filtering and wrapping modes. 536 | */ 537 | export interface Sampler { 538 | /** 539 | * Magnification filter. 540 | */ 541 | 'magFilter'?: 9728 | 9729 | number; 542 | /** 543 | * Minification filter. 544 | */ 545 | 'minFilter'?: 9728 | 9729 | 9984 | 9985 | 9986 | 9987 | number; 546 | /** 547 | * s wrapping mode. 548 | */ 549 | 'wrapS'?: 33071 | 33648 | 10497 | number; 550 | /** 551 | * t wrapping mode. 552 | */ 553 | 'wrapT'?: 33071 | 33648 | 10497 | number; 554 | 'name'?: any; 555 | 'extensions'?: any; 556 | 'extras'?: any; 557 | [k: string]: any; 558 | } 559 | /** 560 | * The root nodes of a scene. 561 | */ 562 | export interface Scene { 563 | /** 564 | * The indices of each root node. 565 | */ 566 | 'nodes'?: GlTfId[]; 567 | 'name'?: any; 568 | 'extensions'?: any; 569 | 'extras'?: any; 570 | [k: string]: any; 571 | } 572 | /** 573 | * Joints and matrices defining a skin. 574 | */ 575 | export interface Skin { 576 | /** 577 | * The index of the accessor containing the floating-point 4x4 inverse-bind matrices. The default is that each matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied. 578 | */ 579 | 'inverseBindMatrices'?: GlTfId; 580 | /** 581 | * The index of the node used as a skeleton root. 582 | */ 583 | 'skeleton'?: GlTfId; 584 | /** 585 | * Indices of skeleton nodes, used as joints in this skin. 586 | */ 587 | 'joints': GlTfId[]; 588 | 'name'?: any; 589 | 'extensions'?: any; 590 | 'extras'?: any; 591 | [k: string]: any; 592 | } 593 | /** 594 | * A texture and its sampler. 595 | */ 596 | export interface Texture { 597 | /** 598 | * The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used. 599 | */ 600 | 'sampler'?: GlTfId; 601 | /** 602 | * The index of the image used by this texture. When undefined, it is expected that an extension or other mechanism will supply an alternate texture source, otherwise behavior is undefined. 603 | */ 604 | 'source'?: GlTfId; 605 | 'name'?: any; 606 | 'extensions'?: any; 607 | 'extras'?: any; 608 | [k: string]: any; 609 | } 610 | /** 611 | * The root object for a glTF asset. 612 | */ 613 | export interface GlTf { 614 | /** 615 | * Names of glTF extensions used somewhere in this asset. 616 | */ 617 | 'extensionsUsed'?: string[]; 618 | /** 619 | * Names of glTF extensions required to properly load this asset. 620 | */ 621 | 'extensionsRequired'?: string[]; 622 | /** 623 | * An array of accessors. 624 | */ 625 | 'accessors'?: Accessor[]; 626 | /** 627 | * An array of keyframe animations. 628 | */ 629 | 'animations'?: Animation[]; 630 | /** 631 | * Metadata about the glTF asset. 632 | */ 633 | 'asset': Asset; 634 | /** 635 | * An array of buffers. 636 | */ 637 | 'buffers'?: Buffer[]; 638 | /** 639 | * An array of bufferViews. 640 | */ 641 | 'bufferViews'?: BufferView[]; 642 | /** 643 | * An array of cameras. 644 | */ 645 | 'cameras'?: Camera[]; 646 | /** 647 | * An array of images. 648 | */ 649 | 'images'?: Image[]; 650 | /** 651 | * An array of materials. 652 | */ 653 | 'materials'?: Material[]; 654 | /** 655 | * An array of meshes. 656 | */ 657 | 'meshes'?: Mesh[]; 658 | /** 659 | * An array of nodes. 660 | */ 661 | 'nodes'?: Node[]; 662 | /** 663 | * An array of samplers. 664 | */ 665 | 'samplers'?: Sampler[]; 666 | /** 667 | * The index of the default scene. 668 | */ 669 | 'scene'?: GlTfId; 670 | /** 671 | * An array of scenes. 672 | */ 673 | 'scenes'?: Scene[]; 674 | /** 675 | * An array of skins. 676 | */ 677 | 'skins'?: Skin[]; 678 | /** 679 | * An array of textures. 680 | */ 681 | 'textures'?: Texture[]; 682 | 'extensions'?: any; 683 | 'extras'?: any; 684 | [k: string]: any; 685 | } -------------------------------------------------------------------------------- /src/webgl-gltf/types/model.d.ts: -------------------------------------------------------------------------------- 1 | import { mat4, vec3, quat, vec4 } from 'gl-matrix'; 2 | 3 | /** 4 | * Model root 5 | */ 6 | export interface Model { 7 | name: string; 8 | meshes: Mesh[]; 9 | nodes: Node[]; 10 | rootNode: number; 11 | animations: Animation; 12 | skins: Skin[]; 13 | materials: Material[]; 14 | } 15 | 16 | /** 17 | * Model node hiearchy with animation transforms and reference to mesh + skin 18 | */ 19 | export interface Node { 20 | id: number; 21 | name: string; 22 | children: number[]; 23 | localBindTransform: mat4; 24 | skin?: number; 25 | mesh?: number; 26 | } 27 | 28 | /** 29 | * Skinning information with the inversed bind transform and affected joints 30 | */ 31 | export interface Skin { 32 | joints: number[]; 33 | inverseBindTransforms: mat4[]; 34 | } 35 | 36 | /** 37 | * Root for each animation 38 | */ 39 | export interface Animation { 40 | [name: string]: Channel; 41 | } 42 | 43 | /** 44 | * List of keyframes for each animation 45 | */ 46 | export interface Channel { 47 | [key: number]: Transform; 48 | } 49 | 50 | /** 51 | * Animation keyFrames 52 | */ 53 | export interface Transform { 54 | translation: KeyFrame[]; 55 | rotation: KeyFrame[]; 56 | scale: KeyFrame[]; 57 | } 58 | 59 | /** 60 | * Transform executed at specific time. 61 | */ 62 | export interface KeyFrame { 63 | time: number; 64 | transform: vec3 | quat; 65 | type: 'translation' | 'rotation' | 'scale'; 66 | } 67 | 68 | /** 69 | * WebGL buffer information 70 | */ 71 | export interface GLBuffer { 72 | buffer: WebGLBuffer; 73 | type: number; 74 | size: number; 75 | } 76 | 77 | /** 78 | * Mesh buffers and associated material 79 | */ 80 | export interface Mesh { 81 | elementCount: number; 82 | indices: WebGLBuffer | null; 83 | positions: GLBuffer; 84 | normals: GLBuffer | null; 85 | tangents: GLBuffer | null; 86 | texCoord: GLBuffer | null; 87 | joints: GLBuffer | null; 88 | weights: GLBuffer | null; 89 | material: number; 90 | } 91 | 92 | /** 93 | * Textures and material info for PBR. 94 | */ 95 | export interface Material { 96 | baseColorTexture: WebGLTexture | null; 97 | baseColorFactor: vec4; 98 | metallicRoughnessTexture: WebGLTexture | null; 99 | metallicFactor: number; 100 | roughnessFactor: number; 101 | emissiveTexture: WebGLTexture | null; 102 | emissiveFactor: vec3; 103 | normalTexture: WebGLTexture | null; 104 | occlusionTexture: WebGLTexture | null; 105 | } 106 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "target": "es6", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "outDir": "./dist", 8 | "baseUrl": "src", 9 | "allowSyntheticDefaultImports": true, 10 | "noImplicitAny": false, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "esModuleInterop": true, 18 | "sourceMap": true, 19 | } 20 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: './example/src/app.ts', 7 | resolve: { 8 | extensions: ['.ts', '.tsx', '.js'], 9 | modules: [ 10 | path.resolve('./src'), 11 | path.resolve('./node_modules'), 12 | ], 13 | }, 14 | devtool: 'source-map', 15 | module: { 16 | rules: [{ 17 | test: /\.ts$/, 18 | include: [path.resolve('./example'), path.resolve('./src')], 19 | use: [{ loader: 'ts-loader' }] 20 | }, 21 | { 22 | test: /\.ts$/, 23 | enforce: 'pre', 24 | use: [{ 25 | options: { eslintPath: require.resolve('eslint') }, 26 | loader: require.resolve('eslint-loader'), 27 | }], 28 | exclude: /node_modules/, 29 | } 30 | ]}, 31 | output: { 32 | path: __dirname + '/example/dist', 33 | publicPath: '/', 34 | filename: 'bundle.[hash].js', 35 | }, 36 | plugins: [ 37 | new CleanWebpackPlugin(), 38 | new HtmlWebpackPlugin({ template: './example/index.html' }), 39 | ], 40 | devServer: { 41 | port: 8080, 42 | static: { 43 | directory: path.join(__dirname, 'example/static'), 44 | }, 45 | }, 46 | } 47 | --------------------------------------------------------------------------------