Convert OBJ to GLB

Converting OBJ to GLB gives you the one binary file that model-viewer, three.js and Babylon.js all load in a single request, with the bounding box viewers need to frame the shot. Know the cost before you build around it: the OBJ's MTL file is not read, so the model arrives as untextured, flat-shaded geometry.

  • Where it runs In your browser. The file is never uploaded.
  • Rebuilt GLB works differently from an OBJ, so this is not the gradual degradation a lossy codec applies. What GLB 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.

Why an OBJ has to become a GLB before it goes on a page

No browser reads OBJ, and none reads GLB natively either — a .glb navigated to directly downloads rather than renders. What differs is the library ecosystem. three.js, Babylon.js and the model-viewer web component all take a GLB as their primary input and load it in one fetch, while OBJ support is a side loader that also needs the MTL and every texture as separate requests.

So the practical question is not which format the browser understands but which one the runtime you are about to include expects. For anything published after 2018 that answer is GLB, and for a product page where the model is the content, collapsing four or five requests into one is a measurable difference on a phone.

The MTL your OBJ points at is never opened

An OBJ file has no materials in it. It has an mtllib line naming a .mtl beside it and usemtl lines switching between the entries in that file, and the .mtl holds the diffuse colour, the specular values and the paths to the texture images. This converter reads v, o, g and f lines and ignores the rest.

The consequence is unambiguous and worth planning around: your GLB contains geometry and group names, and your viewer draws it in whatever default material it has. If the model was going to be shown with its textures, the route is Blender — import the OBJ with its MTL, then export glTF Binary, which packs the images into the GLB. This page is for the case where the shape is the deliverable.

What goes into the GLB the converter builds

A glTF 2.0 binary: twelve-byte header, a JSON chunk padded with spaces to a four-byte boundary, then a binary chunk padded with zeroes. Inside the JSON, one mesh per OBJ group, one node per mesh, one scene listing them all, an asset version of 2.0, and per mesh a float32 VEC3 position accessor and a uint32 SCALAR index accessor.

Every position accessor carries min and max. That is a specification requirement and it is also the practical difference between a model that appears when you load it and one that does not — model-viewer and three.js both derive the initial camera distance from those bounds, and files written without them are a common cause of an apparently empty scene.

Each OBJ group becomes a mesh, and each mesh costs a draw call

The reader starts a new group at every o or g line and the writer emits one glTF mesh per group, each in its own node with its own primitive. An OBJ exported from CAD with a group per face patch can carry hundreds of groups, and the GLB will faithfully contain hundreds of meshes.

On a page that is a real cost. Each primitive is a separate draw call and a separate GPU buffer binding, and a few hundred of them on a mid-range phone is where the frame rate goes. Merging objects in Blender before exporting the OBJ, or stripping the g lines with a text editor if the groups carry no meaning, is a two-minute fix worth more than most of what happens after.

Flat shading, and why the GLB looks like a low-poly model

No NORMAL attribute is written. The glTF specification is explicit about what a viewer must then do: compute flat face normals. So a sphere that looked smooth in your modelling software renders in the browser with every triangle visible as a distinct facet, which reads as a deliberate low-poly style and usually is not.

There are two clean fixes. In three.js, call computeVertexNormals on the loaded geometry, which averages the face normals per vertex and needs the mesh to be properly welded — which it is, since positions are merged at six decimal places during conversion. Or do the normal pass upstream in Blender and export glTF from there, which also brings the materials along.

Up axis and scale: two things an OBJ never declares

glTF fixes both. It is right-handed, Y-up, and its linear unit is the metre. OBJ fixes neither: it is a list of numbers with no header, no unit and no axis convention, and every application that writes one has its own habit.

No conversion is applied here, so a Z-up OBJ produces a GLB whose model lies on its back and a centimetre-authored OBJ produces one two hundred metres tall. Both are trivially fixable once you know which you have — a minus 90 degree rotation about X, and a uniform scale — and both are invisible until you load the file. Checking the bounding box against a dimension you know is the fastest diagnosis.

What the OBJ reader does before the GLB is written

Faces with more than three corners are fan-triangulated from the first corner, which is correct for the convex quads and n-gons these files normally contain and can produce overlapping triangles on a concave face. Negative face indices, which count backwards from the most recent vertex, are resolved as the file is read rather than afterwards, because their meaning depends on the position in the file.

Positions are then welded: matched at six decimal places, so a coordinate appearing in twenty triangles is stored once and referenced twenty times. That both shrinks the buffer and gives the browser a mesh whose adjacency is real, which is what makes computeVertexNormals produce smooth shading instead of the same flat result.

The size of the GLB, and how to make it smaller

Twelve bytes per vertex for positions and twelve bytes per triangle for indices, plus a small JSON header. A 200,000-triangle model with 100,000 vertices is therefore around 3.6 MB, and nothing in that is compressed. Indices are uint32 without exception, which costs four bytes where two would do below 65,535 vertices and removes a conditional that changes behaviour silently at a threshold.

If that number is too large for the page, the answers are upstream and downstream, not here: decimate the mesh in Blender or MeshLab first, and run gltf-transform afterwards for Draco or meshopt compression — remembering that both require the browser to load a matching decoder, which is its own cost on a page that shows one model.

When an OBJ is not ready for the web yet

If the model needs its textures, convert through Blender instead so the MTL and its images come with it. If the model has half a million triangles, decimate before converting rather than shipping it and hoping. If you need it to look smooth, deal with normals in the viewer or in the exporter, because this route will not give you any.

What this page is good for is the honest middle case: a clean OBJ of a part, a prop or a scan that needs to appear on a page as a shape, in one file, without uploading the model to anybody. That conversion runs entirely in your browser, with no engine to download and a 100 MB ceiling, and it produces a file every current web viewer will take.

How to convert OBJ to GLB

  1. Drop your OBJ onto this page, or click to choose one.
  2. The geometry is welded, indexed and packed into a GLB in your browser.
  3. Download the .glb and point your viewer at it.

OBJ against GLB: what a browser needs that an OBJ lacks

OBJ compared with GLB
OBJGLB
Full nameWavefront ObjectBinary glTF
File extension.obj.glb
Media typemodel/objmodel/gltf-binary
First published19922016
Published byWavefront TechnologiesKhronos Group
SpecificationglTF 2.0
LicensingOpen standardOpen standard
Standing todayCurrentCurrent
Opens in a browserNo browserNo browser
Considered insteadGLTF, PLY, STLGLTF

Opening the result

Blender reads both OBJ and GLB, 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, GLB 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.

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

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

OBJ to GLB: questions from people shipping to a page

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.

Why is my model grey after converting OBJ to GLB?

Because the MTL file is not read. An OBJ keeps its materials and texture paths in a separate .mtl referenced by an mtllib line, and this converter carries geometry only, so the GLB has no material and your viewer applies its default.

Why does the model look faceted in three.js?

No normals are written into the GLB. The glTF specification tells viewers to compute flat face normals for a mesh that has none, so a curved surface shows every triangle. Computing smooth normals in the viewer, or a normal pass in Blender before exporting, is the fix.

Will model-viewer frame the model correctly?

Yes, provided the scale is sane. Every position accessor is written with the min and max the specification requires, which is what viewers use to compute a camera framing. A file without them opens zoomed to nothing in several of them.

My model appears lying on its side. What happened?

No axis conversion is performed. glTF is defined as Y-up and right-handed, and an OBJ exported from a Z-up tool without a conversion carries Z-up coordinates. Rotate minus 90 degrees about X in the viewer, or re-export with Y-up selected.

How many draw calls will the GLB cost?

One per OBJ group, at minimum. Every o or g section becomes its own glTF mesh with its own node and its own primitive, so an OBJ split into two hundred groups becomes two hundred meshes. Merging groups before conversion is the cheapest optimisation available.

Does the model get compressed?

No. Positions are float32 and indices uint32, uncompressed. Draco or meshopt compression needs a separate pass with gltf-transform, and the browser needs the matching decoder to read it.

More about these formats