Convert OBJ to glTF

Converting OBJ to glTF produces a single self-contained JSON document with the vertex data embedded as base64, one mesh per OBJ group and the bounds the specification requires. It is written to be worked on: readable, validatable, and ready for gltf-transform or a hand-edited material block.

  • Where it runs In your browser. The file is never uploaded.
  • Rebuilt GLTF works differently from an OBJ, so this is not the gradual degradation a lossy codec applies. What GLTF can express is reproduced faithfully; what it has no equivalent for does not survive at all.
  • File size limit Up to 100 MB per file, free, without an account.
  • Worth knowing Geometry only. Materials, colours, textures and animation are not carried across, and a model placed several times comes back with each copy baked into its own position.

Up to 100 files at once. Mixed formats are fine.

OBJ to glTF as the way into the glTF toolchain

Almost everything useful in the modern 3D web pipeline takes glTF as its input. gltf-transform prunes, welds, resamples and compresses it; the Khronos validator checks it; three.js and Babylon.js consume it; every asset-optimisation service assumes it. An OBJ is outside all of that, and the first step in bringing one in is a conversion that produces a document those tools accept.

The reason to target .gltf rather than .glb at this stage is that the JSON form is the one you can read and change. A GLB is the artefact you produce at the end of the pipeline, after the material is right and the compression pass has run. This page is for the beginning of that pipeline, where being able to open the file in an editor is worth more than being able to serve it in one request.

The structure of the glTF document written from an OBJ

An asset block declaring version 2.0 and Quinvert as the generator, a scene index of 0, one scene listing every node, one node per mesh, one mesh per OBJ group, and per mesh a single primitive with mode 4, a POSITION attribute and an indices accessor. Then the accessors, the buffer views and one buffer.

It is pretty-printed with two-space indentation, which makes it about a third larger than minified and readable without a formatter. For a document you are about to edit that is the right default; running it through a minifier is one command and reversing an unformatted 40,000-line JSON file is not.

The buffer arrives as a base64 data URI inside the glTF

glTF allows three ways to store vertex data: a separate .bin file, a base64 data URI in the buffer's uri field, or the binary chunk of a GLB. A conversion produces one file, so the data URI is the only option that keeps the output self-contained, and it is what is written.

Two consequences follow. The file is roughly a third larger than the same bytes stored binary, because base64 costs four characters per three bytes — acceptable for a working document and wasteful for delivery. And every tool that reads glTF handles it: gltf-transform, the validator and three.js all resolve embedded buffers without special configuration.

Adding a material to the converted glTF is a small edit

The document has no materials array, because the OBJ's MTL file is not read. Adding one is four lines: a materials array containing an object with a pbrMetallicRoughness block and a baseColorFactor of four numbers, then a material property on the primitive giving its index.

That gets you a coloured, correctly lit model in any glTF viewer without touching the geometry. Textures are a larger job, because they need images, samplers, textures and — critically — TEXCOORD_0 data that this conversion did not produce. If your OBJ had UVs and you need them, the honest route is Blender rather than a hand edit.

What the OBJ group names do in the glTF JSON

Every o or g line starts a new group, and each group becomes a named mesh. That name is the only piece of non-geometric information that crosses the conversion, and in a document you are going to edit it is what makes the difference between finding the part you meant and counting nodes.

A group with no name in the source gets no name in the output either. The mesh is written without a name property, and the node that references it carries nothing but the mesh index, so anything downstream has to invent a label for it. If the glTF is going to be read or edited by hand afterwards, name the groups in the OBJ first — a text edit there is easier than reconciling anonymous meshes in the JSON later.

Validating the glTF you get back from an OBJ

The output is written against the parts of the specification validators actually enforce. Buffer views are padded so that every accessor byte offset is a multiple of its component size, which is a requirement rather than a courtesy and a common cause of validator errors in hand-rolled writers. Every POSITION accessor carries min and max, which is required and which viewers use to frame the model.

A stride of zero is treated as tightly packed rather than as a stride equal to the element size, which is what the specification says and what several readers get wrong in the opposite direction. If you run the Khronos glTF Validator over the result, the interesting output is not errors but the info-level note that the meshes have no normals — which is true, and which the specification handles by telling viewers to compute flat ones.

How the OBJ is read before any glTF is written

Faces with four or more corners are fan-triangulated from the first corner. That is correct for convex quads and n-gons, which is what these files contain in practice, and it can produce overlapping triangles on a concave face — a limitation worth knowing if your OBJ came from a tool that emits complex polygons.

Negative face indices are resolved while reading, since they count backwards from the most recent vertex and cannot be interpreted out of order. Vertices are then welded per group at six decimal places, so a group that referenced twelve of a four-thousand-vertex file carries twelve vertices in the glTF rather than four thousand.

Why this glTF is larger than the equivalent GLB

Base64 inflation on the buffer, plus pretty-printed JSON around it. For a geometry-only model the buffer dominates, so expect roughly a third more bytes than the same model as GLB, before any compression.

That is the correct trade at this point in a pipeline and the wrong one at the end of it. The usual shape of the work is: convert to glTF here, edit or process it, then produce the GLB as the final artefact — either by converting back or by having gltf-transform write it, which is also where Draco or meshopt compression belongs.

When to convert an OBJ straight to GLB instead

If nothing is going to happen to the file between conversion and delivery, go to GLB directly. It is smaller, it is one request, and the editability you are paying for with the extra bytes is worth nothing if nobody opens it.

Choose glTF when there is a next step: a material to add, a validator to satisfy, a transform pass to run, or a repository where a text file diffs and a binary one does not. Both conversions run entirely in your browser with no engine to download, so the choice costs nothing but the extension.

How to convert OBJ to GLTF

  1. Drop your OBJ onto this page, or click to choose one.
  2. The groups are welded, indexed and written as glTF JSON in your browser.
  3. Download the .gltf and open it in an editor or a validator.

OBJ next to glTF: a flat list against a described document

OBJ compared with GLTF
OBJGLTF
Full nameWavefront ObjectGL Transmission Format
File extension.obj.gltf
Media typemodel/objmodel/gltf+json
First published19922015
Published byWavefront TechnologiesKhronos Group
SpecificationglTF 2.0
LicensingOpen standardOpen standard
Standing todayCurrentCurrent
Opens in a browserNo browserNo browser
Considered insteadPLY, STLGLB

Opening the result

Blender reads both OBJ and GLTF, so there is a way to check the result against the original without a second tool.

What each format is for

The two are aimed at different work: OBJ at moving data between programs and editing, GLTF at the web and handing a finished file over. That is worth weighing before converting, because the reason one exists is usually the reason the other is awkward.

GLTF comes from Khronos Group and dates from 2015, specified as glTF 2.0. Blender, three.js and Babylon.js all read it.

OBJ was published in 1992 and GLTF in 2015. The older one is generally the safer file to hand to somebody; the newer one usually does the job in fewer bytes.

OBJ to glTF: questions from people editing the output

Are my OBJ files uploaded anywhere?

No. This conversion runs entirely inside your browser, so the file never leaves your device. You can confirm it yourself: open the network tab of your browser's developer tools and convert something. You will see the page load, plus the analytics and advertising the site is paid for with — and nothing carrying your file.

Is the buffer embedded or is there a .bin file?

Embedded, as a base64 data URI on the single buffer entry, so the download is one self-contained .gltf. Nothing has to sit beside it and no relative path can break.

Can I add a material to the file afterwards?

Yes, and it is a small edit. Add a materials array with a pbrMetallicRoughness block, then add a material index to the primitive in the mesh you want it on. The document is pretty-printed JSON, so the edit is readable in any editor.

Will it pass the Khronos glTF Validator?

It is written to. The asset version is 2.0, every position accessor carries the required min and max, buffer views are padded so each accessor offset is a multiple of its component size, and the default scene is declared.

What does each OBJ group become in the glTF?

One mesh with one primitive, one node referencing it, and an entry in the default scene. The group name from the o or g line becomes the mesh name, so a multi-part OBJ stays legible in the JSON.

Are the indices uint16 or uint32?

uint32 for every mesh regardless of size. Narrower types would save bytes on small models at the cost of a conditional that silently changes behaviour past 65,535 vertices, which is not a trade worth making in a converter.

Are texture coordinates carried over?

No. The OBJ reader takes v, o, g and f lines only, so vt and vn lines are ignored and the primitive has a POSITION attribute and nothing else.

More about these formats