cadtomesh

OBJ → GLB

OBJ to GLB Conversion

Wavefront OBJ is line-oriented text of v, vn, vt and f records with no declared unit. The converter reads those tokens, drops vt coordinates, .mtl names and group labels, then emits a GLB: a 12-byte glTF header, a JSON chunk and a BIN chunk whose bufferViews and accessors describe one mesh with position and normal arrays plus a default material.

Drop a .obj file here, or

Processed on this device ยท nothing uploaded

16input formats
7output formats
0 Buploaded, ever

How to convert OBJ to GLB

  1. Read the OBJ rowsThe in-page three.js OBJLoader scans the text, collecting v positions, vn normals, vt coordinates and f corner indices while skipping hash comments and empty lines.
  2. Leave .mtl pointers untouchedAn mtllib or usemtl statement does not trigger a library fetch; no map_Kd image is decoded, so colour and texture inputs stay outside the geometry.
  3. Merge to one geometryFaces are triangulated and every object, group and smoothing flag folds into a single BufferGeometry that retains only float32 position and normal attributes.
  4. Write the glTF JSONThe exporter declares asset.version 2.0, one scene, one node, one mesh and one primitive, plus bufferViews and accessors for positions, normals and index values.
  5. Append the binary payloadFloat32 position and normal triples together with unsigned index values are packed into the BIN chunk, then the header and two padded chunks are sealed into one .glb file.

What happens when OBJ is converted to GLB

The twelve-byte container header

A GLB begins with three consecutive little-endian uint32 fields occupying the first twelve bytes. The first is the magic value 0x46546C67, whose four bytes spell glTF in ASCII, and a reader that finds anything else should stop. The second holds the version number, fixed at 2 for glTF 2.0. The third is the total length of the entire file counted in bytes, including this header and every chunk that follows. That length lets a parser bound its reads and reject a truncated stream before it walks off the end of the buffer. Nothing in the header names a unit, an axis or a coordinate system, so the numeric scale of the OBJ is carried forward implicitly rather than being declared.

Chunk framing and alignment

Two chunks follow the header, and each opens with its own uint32 chunkLength followed by a uint32 chunkType. The JSON chunk is tagged 0x4E4F534A, the bytes spelling JSON, and its payload is padded with 0x20 spaces to a four-byte boundary. The binary chunk carries 0x004E4942, representing BIN with a trailing zero byte, and its data is padded with zero bytes to the same boundary. chunkLength includes that padding, so a decoder must subtract it when reading the meaningful text. Order matters here: glTF expects JSON before BIN, and the single buffer object inside the JSON points at the BIN chunk by omitting any uri. This framing is what makes the whole asset one contiguous file.

What the JSON chunk declares

Inside the padded text sits a compact glTF 2.0 document. An asset object records version 2.0 and a generator string, while scene selects the single entry in scenes. That scene references one node, the node references one mesh, and the mesh holds exactly one primitive. The primitive lists mode 4 for triangles and maps POSITION and NORMAL to accessor indices, with indices naming a third accessor. A buffers array describes one buffer whose byteLength covers the packed data and which has no uri, binding it to that chunk. One entry in materials supplies a default surface. There are no images, samplers, textures, skins, cameras or animations arrays in what this exporter writes.

Accessors and bufferViews for the arrays

bufferView objects carve the BIN chunk into typed regions using byteOffset and byteLength, with target 34962 for vertex attributes and 34963 for index data. Accessors then interpret those regions: POSITION and NORMAL use componentType 5126 for 32-bit floats and type VEC3, while the index accessor uses componentType 5123 or 5125 for unsigned short or unsigned int scalars of type SCALAR. POSITION carries min and max arrays so a consumer can compute bounds without scanning every vertex. Because the flattened geometry owns no UV channel, no TEXCOORD_0 accessor exists and no material references a texture coordinate set. Byte strides are absent, so the arrays are tightly packed rather than interleaved within a single bufferView.

Why a single default material and no textures

The OBJ loader deliberately does not follow mtllib declarations, and the flattening stage strips any material index along with vt coordinates. By the time geometry reaches the exporter it is a BufferGeometry with position and normal attributes and nothing else, so no UV set is available for a texture and no source material survives to be translated. The writer therefore emits one default material entry, typically an empty or minimally populated pbrMetallicRoughness object, and binds the primitive to it. No image files, samplers or textures are written into the JSON, and no bytes for them are placed in the BIN chunk. External .mtl libraries and texture bitmaps referenced by the OBJ remain outside the GLB entirely.

Flattening OBJ into one primitive

OBJ permits separate indices for positions, normals and texture coordinates on each face corner, plus negative one-based references and n-gons. The parser resolves all of them into a single vertex table, triangulates every polygon, and concatenates the resulting triangles into one index buffer. Group names o and g, smoothing statements s and usemtl switches disappear at this stage. Since the output glTF contains one node with no children, one mesh and one primitive, there is no place to store a hierarchy of parts. Coordinate values are written verbatim: glTF conventionally assumes metres and Y-up, but this converter attaches no unit metadata and applies no axis rotation or scale to the OBJ numbers.

OBJ vs GLB

OBJ sourceGLB output
FeatureOBJ sourceGLB output
File structurePlain text, one statement per lineBinary file with header and two chunks
Starting bytesArbitrary text, often a commentmagic 0x46546C67, version 2, length
Chunk metadataNone4-byte length plus 4-byte type each
Vertex storagev lines of decimal numbersfloat32 POSITION accessor in BIN
Normal storagevn lines of decimal numbersfloat32 NORMAL accessor in BIN
Face indexing1-based f corner references0-based unsigned index accessor
UV channelOptional vt pairsAbsent; no TEXCOORD_0 accessor
MaterialsOptional .mtl sidecarOne default material, no texture refs
Scene structureFlat records and group namesscene, node, mesh, primitive
Unit declarationNone presentNone; values copied as-is

Supported formats

FormatReadWriteNotes
STEP (.step .stp)Yes–ISO 10303 B-rep, AP203 / AP214 / AP242
IGES (.iges .igs)Yes–Entity-based surfaces, up to 5.3
BREP (.brep)Yes–OpenCascade native B-rep
STL (.stl)YesYesBinary and ASCII triangles, no units
OBJ (.obj)YesYesv / vn / f records; .mtl references ignored
PLY (.ply)YesYesBinary position and normal arrays
GLB / glTF (.glb .gltf)YesYesglTF 2.0, binary or JSON with data URI
3MF (.3mf)YesYesOPC/ZIP package, declares millimetres
FBX (.fbx)Yes–Binary and ASCII, static mesh only
DAE (.dae)Yes–Collada scene graph is flattened
3DS (.3ds)Yes–Legacy chunk-based 3D Studio format
AMF (.amf)Yes–XML, ZIP-compressed packages supported
USDZ (.usdz)YesYesZIP containing a USD stage

Known limitations and why a conversion can fail

No .mtl or texture bytes are embedded

An OBJ often points to a companion material library with mtllib and to bitmaps through map_Kd entries inside it. Those references are inert in this pipeline: the mesh loader does not open the library, and the flattened geometry has no UV coordinates with which to place an image. The GLB document therefore contains no images, samplers or textures arrays, and the BIN chunk holds only position, normal and index data. A common misconception is that a single GLB automatically bundles sidecars; here it consolidates geometry only. Any shading assets must be rebuilt against the converted mesh after export rather than expected inside the file.

UV coordinates are absent from the primitive

The vt lines that an OBJ may carry are read but discarded during flattening, because the intermediate BufferGeometry is deliberately reduced to position and normal attributes. As a result the primitive lists only POSITION and NORMAL in its attributes map, with no TEXCOORD_0 accessor and no corresponding bufferView in the BIN chunk. A consumer that tries to apply a texture will find no coordinate set to sample against. Even the default material cannot reference a texture, since no image object exists. Restoring texture mapping requires generating a fresh UV layout on the triangulated surface outside this conversion.

Groups and object names collapse

Wavefront files frequently separate a model using o and g records and assign shading with usemtl. None of those partitions reach the GLB, because the exporter flattens everything into one mesh with a single primitive and one index buffer. The glTF node has no children, so there is no named hierarchy that a viewer could expand to select an individual bolt or panel. Smoothing groups s are likewise discarded and folded into the unified normal array. If downstream editing depends on sub-object selection, that structure has to be reconstructed by segmenting the triangles again, which this converter does not attempt.

No unit metadata and no coordinate conversion

OBJ declares no physical unit, and the glTF written here carries no unit or axis declaration either, so the decimal values from v lines are copied into float32 arrays unchanged. glTF is conventionally interpreted as metres with a Y-up axis, but that is a consumer assumption rather than something the file states. A model authored in inches or millimetres will therefore appear at the wrong scale to a viewer that assumes metres. The export applies no rescaling, no handedness conversion and no rotation; adjusting size or orientation means editing the source coordinates before conversion or transforming the node afterwards.

Animation, skins, cameras and morph targets are out of scope

glTF 2.0 can describe animations, skins, morph targets, cameras and extension hooks, yet the OBJ source has no such content and this writer emits none of them. The JSON chunk contains no animations array, no skins array, no camera definitions and no morph target dictionaries on the primitive. There is a single static node with an identity placement rather than a keyframed transform. Consequently the GLB is a rigid snapshot of the triangulated surface. Anyone needing motion, skeletal deformation or a turntable must add those constructs with a separate glTF editing tool after the geometry has been exported.

Frequently asked questions

Are my .mtl file and textures embedded in the GLB?

No. The OBJ loader treats mtllib and usemtl as inert statements and never fetches the referenced library, so no .mtl or bitmap bytes reach the exporter. The flattened geometry also lacks UV coordinates, which any bound texture would need. The output JSON contains no images, samplers or textures arrays, and the BIN chunk stores only positions, normals and indices. Consolidation into one file covers geometry, not sidecar assets.

Why is there no TEXCOORD_0 attribute?

During flattening the converter keeps only position and normal attributes on the single BufferGeometry, so vt data from the OBJ is dropped. The primitive therefore declares POSITION and NORMAL in its attributes map and nothing more. No bufferView is allocated for texture coordinates and no accessor of type VEC2 exists. Because the default material has no image to sample, a UV set would serve no purpose here. Generating one requires unwrapping the triangulated mesh in a separate step.

What does the 12-byte header contain?

Three little-endian uint32 fields sit at the start. The first is the magic constant 0x46546C67, whose bytes read glTF in ASCII, letting a parser confirm the file type. The second is the version, fixed at 2 for this glTF generation. The third is the total byte length of the whole GLB, header and chunks included, which bounds reading and detects truncation. No unit, axis or generator name is stored in those twelve bytes.

How are positions and normals stored in the BIN chunk?

Each array gets a bufferView with a byteOffset, a byteLength and a target of 34962 for vertex attributes, while indices use target 34963. Accessors then label the float arrays with componentType 5126 and type VEC3, and the index array with componentType 5123 or 5125 and type SCALAR. POSITION accessors include min and max bounds. The values are tightly packed 32-bit floats and unsigned integers, with no interleaving and no compression applied.

Does the GLB keep OBJ groups or object names?

It does not. Records such as o, g, s and usemtl are discarded when every face is merged into one index buffer inside a single primitive. The glTF document contains one scene, one node with no children, one mesh and one primitive, so there is no named hierarchy to browse. Selecting an individual part by its original group label is impossible from this file. Any such separation must be recreated by splitting the triangles downstream.

Is the output still more portable than the original OBJ?

Yes, in a narrow structural sense. An OBJ often travels with a .mtl library and several image files, and a missing sidecar leaves the geometry intact but unshaded. A GLB folds vertices, normals, indices and a default material into one binary file with a declared header and chunk layout, so a single artifact can be moved and opened. That portability comes from consolidating geometry into one container, not from packaging the sidecars, which are not copied here.

Does glTF record the OBJ unit or scale?

No. OBJ has no unit declaration, and the converter writes none into the glTF JSON, so the decimal coordinates from v lines become float32 values verbatim. glTF is commonly read as metres with a Y-up axis, but that is a viewer convention rather than metadata in this file. A model measured in centimetres or inches will look wrongly sized to a consumer assuming metres. No rescaling or axis rotation happens during export.

Can the GLB contain animation or multiple meshes?

Not from this conversion. The exporter merges the parsed OBJ into a single BufferGeometry and writes one primitive, so there are no extra meshes, no child nodes and no separate transforms to animate. The JSON chunk has no animations or skins arrays and the primitive carries no morph targets. Everything is a static triangulated surface. Adding motion or multiple parts requires editing the glTF document after the fact with another tool.

Related converters

All converters

Last updated: