www.xbdev.net
xbdev - software development
Wednesday February 5, 2025
Home | Contact | Support | glTF File Format The JPG of 3D Formats ...
     
 

glTF File Format Tutorials

Unlocking the power ...

 


Ascii or Binary


The glTF file format is one format - but can be stored in different ways:

• '.gltf' extension which uses 'ASCII' (both with seperate binary files and inline version)
• '.glb' binary version - all the data is stored in a single binary file.


The first thing you need to do is get at the data - once you've got the data you can decode it!


The initial functions determine if it's a `glb` or `gltf` by checking the digital signature (start of the file). If it's a `glb` we treat the file as binary - and extract the important information. It it's a `gltf` then it's a text file - and use the parseGLTF(..).

The code is written so the processing of the data (both glb and gltf) is done in the
parseGLTF(..)
function (same place).


const gltfFileUrl 'https://webgpulab.xbdev.net/var/resources/gltf/Bee/Bee.glb';
const 
gltfPath 'https://webgpulab.xbdev.net/var/resources/gltf/';


class 
glTFLoader {
    
async load(urlgltfPath) {
        try {
            const 
response await fetch(url);
            const 
arrayBuffer await response.arrayBuffer();

            
let gltf;
            if (
this.isBinaryGLTF(arrayBuffer)) {
                
gltf this.parseGLB(arrayBuffer);  // Parse GLB as a binary format
                
gltf.gltfPath gltfPath || ''// No external path needed for `.glb`
            
} else {
                const 
textDecoder = new TextDecoder();
                const 
jsonString textDecoder.decode(arrayBuffer);
                
gltf JSON.parse(jsonString);
                
gltf.gltfPath gltfPath || this.getBasePath(url); // Infer base path for `.gltf`
            
}

            const 
parsedData await this.parseGLTF(gltf); // Parse the content (same for both)
            
return parsedData;
        } catch (
error) {
            
console.error('Error loading GLTF/GLB file:'error);
            throw 
error;
        }
    }

    
isBinaryGLTF(arrayBuffer) {
        const 
magic = new TextDecoder().decode(arrayBuffer.slice(04));
        return 
magic === 'glTF';
    }

    
parseGLB(arrayBuffer) {
          
console.log('parseGLB');
        const 
dataView = new DataView(arrayBuffer);

        
// Header: Magic (4 bytes) + Version (4 bytes) + Length (4 bytes)
        
const magic dataView.getUint32(0true);
        const 
version dataView.getUint32(4true);
        const 
length dataView.getUint32(8true);

        if (
magic !== 0x46546C67) { // "glTF" in ASCII
            
throw new Error('Invalid GLB file magic.');
        }
        if (
version !== 2) {
            throw new 
Error('Unsupported GLB version.');
        }

        
let offset 12;
        
let jsonChunkbinChunk;

        
// Iterate through the chunks in the GLB file
        
while (offset length) {
            const 
chunkLength dataView.getUint32(offsettrue);
            const 
chunkType dataView.getUint32(offset 4true);
            const 
chunkData arrayBuffer.slice(offset 8offset chunkLength);

            if (
chunkType === 0x4E4F534A) { // "JSON" in ASCII
                
const textDecoder = new TextDecoder();
                
jsonChunk JSON.parse(textDecoder.decode(chunkData));
            } else if (
chunkType === 0x004E4942) { // "BIN" in ASCII
                
binChunk chunkData;
            }

            
offset += chunkLength;
        }

        if (!
jsonChunk) {
            throw new 
Error('Missing JSON chunk in GLB file.');
        }

        if (
binChunk) {
            
jsonChunk.buffers[0].binary binChunk// Attach binary data directly to buffers
        
}

        return 
jsonChunk;  // Return parsed JSON, now with binary buffers attached
    
}

    
async parseGLTF(gltf) {
          
console.log('parseGLTF');
      
        const 
parsedData = {
            
buffersawait this.loadBuffers(gltf),
            
bufferViewsgltf.bufferViews,
            
accessorsgltf.accessors,
            
meshesgltf.meshes,
            
nodesgltf.nodes,
            
materialsgltf.materials,
            
texturesgltf.textures,
            
imagesgltf.images,
            
animationsgltf.animations,
        };

          
console.log('gltf.meshes:'gltf.meshes );
      
        
this.extractMeshData(parsedData);
        
this.extractAnimations(parsedData);
        
this.extractTextures(parsedData);

        return 
parsedData;
    }

    
async loadBuffers(gltf) {
        const 
bufferPromises gltf.buffers.map((bufferindex) => {
            if (
buffer.binary) {
                
// Directly use binary data for GLB
                
return Promise.resolve(buffer.binary);
            } else if (
buffer.uri) {
                
console.log('loading bin:'gltf.gltfPath buffer.uri);
                return 
fetch(gltf.gltfPath buffer.uri).then(res => res.arrayBuffer());
            } else {
                throw new 
Error(`Buffer ${index} is missing both "binary" and "uri".`);
            }
        });
        return 
await Promise.all(bufferPromises);
    }

    
extractMeshData(parsedData) {
        
parsedData.meshes.forEach(mesh => {

            
mesh.primitives.forEach(primitive => {
                const 
positionAccessorIndex primitive.attributes.POSITION;
                const 
positionAccessor parsedData.accessors[positionAccessorIndex];
              
              
              
              const 
positionBufferView parsedData.bufferViews[positionAccessor.bufferView];
              
              
console.log('here');
              
              const 
positionBuffer parsedData.buffers[positionBufferView.buffer];

              
console.log('here2');
              
              
console.log('positionBufferView.byteOffset:'positionBufferView.byteOffset );
              
console.log('positionAccessor.byteOffset:'positionAccessor.byteOffset );
              
console.log('positionAccessor.count:'positionAccessor.count );
              
console.log('positionAccessor.type:'positionAccessor.type );
              
console.log('positionBuffer:'positionBuffer );
              
              const 
positions = new Float32Array(
                    
positionBuffer,
                    
positionBufferView.byteOffset + (positionAccessor.byteOffset || 0),
                    
positionAccessor.count * (positionAccessor.type === 'VEC3' 1)
              );
              
              
console.log('here3');
              
                
primitive.positions positions;

                if (
primitive.indices !== undefined) {
                    const 
indexAccessor parsedData.accessors[primitive.indices];
                    const 
indexBufferView parsedData.bufferViews[indexAccessor.bufferView];
                    const 
indexBuffer parsedData.buffers[indexBufferView.buffer];

                    const 
indices = new Uint16Array(
                        
indexBuffer,
                        
indexBufferView.byteOffset + (indexAccessor.byteOffset || 0),
                        
indexAccessor.count
                    
);
                    
primitive.indices indices;
                }

                if (
primitive.attributes.NORMAL !== undefined) {
                    const 
normalAccessorIndex primitive.attributes.NORMAL;
                    const 
normalAccessor parsedData.accessors[normalAccessorIndex];
                    const 
normalBufferView parsedData.bufferViews[normalAccessor.bufferView];
                    const 
normalBuffer parsedData.buffers[normalBufferView.buffer];

                    const 
normals = new Float32Array(
                        
normalBuffer,
                        
normalBufferView.byteOffset + (normalAccessor.byteOffset || 0),
                        
normalAccessor.count * (normalAccessor.type === 'VEC3' 1)
                    );
                    
primitive.normals normals;
                }

                if (
primitive.attributes.TEXCOORD_0 !== undefined) {
                    const 
texCoordAccessorIndex primitive.attributes.TEXCOORD_0;
                    const 
texCoordAccessor parsedData.accessors[texCoordAccessorIndex];
                    const 
texCoordBufferView parsedData.bufferViews[texCoordAccessor.bufferView];
                    const 
texCoordBuffer parsedData.buffers[texCoordBufferView.buffer];

                    const 
texCoords = new Float32Array(
                        
texCoordBuffer,
                        
texCoordBufferView.byteOffset + (texCoordAccessor.byteOffset || 0),
                        
texCoordAccessor.count * (texCoordAccessor.type === 'VEC2' 1)
                    );
                    
primitive.texCoords texCoords;
                }
            });
        });
    }

    
extractAnimations(parsedData) {
        if (
parsedData.animations) {
            
parsedData.animations.forEach(animation => {
                
animation.samplers.forEach(sampler => {
                    const 
inputAccessor parsedData.accessors[sampler.input];
                    const 
inputBufferView parsedData.bufferViews[inputAccessor.bufferView];
                    const 
inputBuffer parsedData.buffers[inputBufferView.buffer];

                    
sampler.inputData = new Float32Array(
                        
inputBuffer,
                        
inputBufferView.byteOffset + (inputAccessor.byteOffset || 0),
                        
inputAccessor.count
                    
);

                    const 
outputAccessor parsedData.accessors[sampler.output];
                    const 
outputBufferView parsedData.bufferViews[outputAccessor.bufferView];
                    const 
outputBuffer parsedData.buffers[outputBufferView.buffer];

                    
sampler.outputData = new Float32Array(
                        
outputBuffer,
                        
outputBufferView.byteOffset + (outputAccessor.byteOffset || 0),
                        
outputAccessor.count * (outputAccessor.type === 'VEC3' 1)
                    );
                });
            });
        }
    }

    
extractTextures(parsedData) {
        if (
parsedData.textures) {
            
parsedData.textures.forEach(texture => {
                if (
texture.source !== undefined) {
                    const 
image parsedData.images[texture.source];
                    
texture.imageUri image.uri;
                }
            });
        }
    }
}

// Function to display statistics
function displayStats(parsedData) {
    
    const 
stats = {
        
numberOfMeshesparsedData.meshes parsedData.meshes.length 0,
        
numberOfNodesparsedData.nodes parsedData.nodes.length 0,
        
numberOfMaterialsparsedData.materials parsedData.materials.length 0,
        
numberOfTexturesparsedData.textures parsedData.textures.length 0,
        
numberOfAnimationsparsedData.animations parsedData.animations.length 0,
    };

    
let details = `
        Number of Meshes: 
${stats.numberOfMeshes}<br>
        Number of Nodes: 
${stats.numberOfNodes}<br>
        Number of Materials: 
${stats.numberOfMaterials}<br>
        Number of Textures: 
${stats.numberOfTextures}<br>
        Number of Animations: 
${stats.numberOfAnimations}<br>
        <h2>Mesh Details:</h2>
    
`;

    
parsedData.meshes.forEach((meshmeshIndex) => {
        
details += `Mesh ${meshIndex} - Name: ${mesh.name || 'Unnamed'}<br>`;
        
mesh.primitives.forEach((primitiveprimitiveIndex) => {
            const 
verticesCount primitive.positions primitive.positions.length 0;
            const 
indicesCount primitive.indices primitive.indices.length 0;
            
details += `
                
${' '.repeat(8)}Primitive ${primitiveIndex}:<br>
                
${' '.repeat(16)}Number of Vertices: ${verticesCount}<br>
                
${' '.repeat(16)}Number of Indices: ${indicesCount}<br>
            
`;
        });
    });
  
    const 
statsDiv document.createElement('div');
    
document.body.appendChildstatsDiv );
      
statsDiv.innerHTML details;
}

let div document.createElement('div');
document.body.appendChilddiv );
div.innerHTML = `
Vanilla glTF loader/parser in native JavaScript<br><br>

File: 
${gltfFileUrl}<br><br>
`;

const 
loader = new glTFLoader();


try {
    const 
parsedData await loader.load(gltfFileUrlgltfPath);
  
    
displayStats(parsedData);

} catch (
error) {
    
console.error('An error occurred:'error);
}



Resources and Links


• glTF ascii and binary file dumper (LINK)



















Ray-Tracing with WebGPU kenwright WebGPU Development Cookbook - coding recipes for all your webgpu needs! WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Games WGSL 2d 3d interactive web-based fun learning WebGPU Compute WebGPU API - Owners WebGPU & WGSL Essentials: A Hands-On Approach to Interactive Graphics, Games, 2D Interfaces, 3D Meshes, Animation, Security and Production Kenwright graphics and animations using the webgpu api 12 week course kenwright learn webgpu api kenwright programming compute and graphics applications with html5 and webgpu api kenwright real-time 3d graphics with webgpu kenwright webgpu for dummies kenwright webgpu api develompent a quick start guide kenwright webgpu by example 2022 kenwright webgpu gems kenwright webgpu interactive compute and graphics visualization cookbook kenwright wgsl webgpu shading language cookbook kenwright WebGPU Shader Language Development: Vertex, Fragment, Compute Shaders for Programmers Kenwright wgsl webgpugems shading language cookbook kenwright WGSL Fundamentals book kenwright WebGPU Data Visualization Cookbook kenwright Special Effects Programming with WebGPU kenwright WebGPU Programming Guide: Interactive Graphics and Compute Programming with WebGPU & WGSL kenwright



 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.