Updated August 28, 2026
What a 3D conversion keeps, and what it costs
Converting a mesh is not free and not lossless. GLB and glTF keep everything. OBJ keeps the separate objects and their material names but drops vertex colour. STL keeps only triangles. PLY keeps vertex colour but flattens the scene to one mesh. The numbers below were measured, not estimated.
Every figure on this page comes from running the same converter this site runs, on real files, and reading the attributes back out of the bytes it wrote. Measured August 28, 2026 on three.js r179. The method is at the bottom, and the script is in the repository.
What does each format keep?#
A reference scene of two separately named cubes, each with its own named metal material, per-vertex colour, UV coordinates, and normals, was converted to all five targets. GLB and glTF came back complete. Every other format lost something, and each lost something different.
| Target | Two objects | UV coordinates | Vertex colour | Material names | Vertex count |
|---|---|---|---|---|---|
| GLB | Kept as 2 | Kept | Kept | Kept | 48 vertices |
| glTF | Kept as 2 | Kept | Kept | Kept | 48 vertices |
| OBJ | Kept as 2 | Kept | Lost | Kept | 72 vertices |
| STL | Merged into 1 | Lost | Lost | Lost | 72 vertices |
| PLY | Merged into 1 | Kept | Kept | Lost | 48 vertices |
Why do STL and PLY merge a scene into one mesh?#
Neither format has a scene graph. GLB, glTF, and OBJ can describe several named objects in one file; STL describes a single solid and PLY describes a single element list, so two cubes arrive as one mesh with no way to separate them again.
This is the loss that surprises people most, because nothing about it is visible. The converted model looks identical in a viewer. It is only when you try to select, re-material, or animate one part of it that you find the parts are gone.
If the model needs to stay separable, the target has to be GLB, glTF, or OBJ. If it is going to a slicer or a single-material print, the merge costs you nothing.
How much larger does the file get?#
Larger than most people expect. On a real generated mesh, OBJ came out 5.6 times the size of the source GLB, while PLY came out 1.4 times. Both carry the same geometry.
| Conversion | Source | Output | Ratio | Vertices written |
|---|---|---|---|---|
| mug.glb to GLB | 880 KB | 1,173 KB | 1.3x | 25,000 |
| mug.glb to glTF | 880 KB | 1,564 KB | 1.8x | 25,000 |
| mug.glb to OBJ | 880 KB | 4,928 KB | 5.6x | 150,000 |
| mug.glb to STL | 880 KB | 2,441 KB | 2.8x | 150,000 |
| mug.glb to PLY | 880 KB | 1,221 KB | 1.4x | 25,000 |
| lion.glb to GLB | 8,768 KB | 11,690 KB | 1.3x | 249,369 |
| lion.glb to glTF | 8,768 KB | 15,587 KB | 1.8x | 249,369 |
| lion.glb to OBJ | 8,768 KB | 51,880 KB | 5.9x | 1,496,202 |
| lion.glb to STL | 8,768 KB | 24,352 KB | 2.8x | 1,496,202 |
| lion.glb to PLY | 8,768 KB | 12,176 KB | 1.4x | 249,369 |
Why do OBJ and STL multiply the vertex count?#
Because neither output shares vertices between triangles. A well-indexed mesh stores each corner once and refers to it from every triangle that touches it; OBJ and STL as written here store every triangle's three corners separately. On the lion.glb test that turned 249,369 vertices into 1,496,202, about 6 times as many.
The geometry is unchanged. The same surface, the same triangles, the same silhouette; only the bookkeeping is more expensive, and the file grows roughly in proportion.
PLY is the useful counter-example. It keeps the index, so the same mesh writes at close to the source size while still carrying vertex colour. For a large mesh that only needs geometry preserved, PLY is the cheapest honest target on this list.
Text versus binary compounds it. glTF is JSON with its buffers base64-encoded, which costs about 33 percent over the identical GLB, because base64 turns every three bytes into four.
Why did the file grow even converting GLB to GLB?#
Because the converter computes vertex normals for any mesh that arrives without them, and then writes them out. A generated mesh often has no normal attribute at all, so the round trip adds a full extra vector per vertex.
The mug.glb test is a clean example: 880 KB in, 1,173 KB out, with no change to the geometry. The added normals are the difference.
This is deliberate rather than a defect. A mesh with no normals renders flat-shaded and faceted in most viewers, so the converter computes them once on load rather than leaving every downstream tool to guess.
Does converting change the shape?#
No. Every target was converted and then read back, and the surface came out identical: the same triangle count and the same total surface area, to the limit of the measurement, on a mesh of 498,734 triangles. Geometry is the part of a 3D file that survives conversion intact.
| Target | Triangles after | Surface area error | How coordinates are stored |
|---|---|---|---|
| GLB | unchanged | 0% | binary float32 |
| glTF | unchanged | 0% | base64 buffer |
| OBJ | unchanged | 0% | text at 17 decimal places |
| STL | unchanged | 0% | binary float32 |
| PLY | unchanged | 0% | binary float32 |
So is 3D conversion lossy or not?#
Both, and the distinction is the useful part. The geometry is not lossy: the shape you put in is the shape you get out. Everything wrapped around the geometry is lossy, and which parts you lose depends entirely on the target you picked.
Nothing here truncates coordinates. GLB, STL and PLY write binary 32-bit floats. glTF embeds the same binary buffer as base64 inside its JSON. OBJ is the only one that writes positions as decimal text, and it writes them at full precision rather than rounding, which is part of why its files are so much larger.
That means the usual worry, that each conversion degrades a model a little like re-saving a JPEG, does not apply. Converting GLB to OBJ and back does not soften the mesh.
What it does do is drop the things listed further up this page: the scene graph on STL and PLY, vertex colour on OBJ and STL, material names on STL and PLY, and UVs on STL. Those losses are permanent in the sense that converting back cannot invent them again.
Which target should you pick?#
Match the target to what must survive, not to the extension the receiving tool lists first. These are the defensible choices given what was measured above.
| If you need | Pick | Because |
|---|---|---|
| Everything preserved, one file | GLB | The only target that kept the scene graph, material names, UVs, and vertex colour together, at the smallest size of the complete formats. |
| Everything preserved, human-readable | glTF | Identical content to GLB, about 33 percent larger because the buffers are base64 in JSON. |
| Separate named objects in a desktop tool | OBJ | Kept both objects and both material names. Drops vertex colour, and writes the largest file of the five. |
| 3D printing | STL | Triangles and nothing else, which is all a slicer reads. Smallest of the non-glTF targets on a real mesh. |
| Geometry plus vertex colour, cheaply | PLY | The only non-glTF target that kept vertex colour, and it keeps the index, so it stays close to the source size. |
How this was measured#
The measurement runs the product, not a description of it. The script imports the same loaders and exporters as the browser converter, with the same options, and is in the repository so the numbers can be reproduced or disputed.
- Build a reference scene of two named cubes, each with a named metal material, per-vertex colour, UV coordinates, and normals.
- Load real generated meshes the same way the converter loads an upload, including the step that computes vertex normals when a mesh arrives without them.
- Export to GLB, glTF, OBJ, STL, and PLY through the same exporters the converter calls, with the same binary settings.
- Record the byte length of each output.
- Read the attributes back out of the written bytes rather than out of a re-loaded scene, because a loader invents what the file does not contain: an OBJ with no vn lines still re-loads with a synthesised normal attribute.
- Publish the table straight from that output. Measured August 28, 2026 on three.js r179.
Questions
- Does converting a 3D file lose quality?
- Not the shape. Every target was converted and read back, and the triangle count and total surface area came out identical on a 498,734-triangle mesh, with no coordinate truncation in any format. What is lost is everything around the geometry, and it differs by target: STL drops UVs, colour, materials and the scene graph; OBJ drops vertex colour; PLY drops material names and merges the scene; GLB and glTF kept all of it.
- Which 3D format is the smallest?
- GLB, on a real mesh. In these tests GLB wrote 1,173 KB where OBJ wrote 4,928 KB for the same model, because GLB is binary and keeps a shared vertex index while the OBJ output is text and stores every triangle corner separately.
- Why is my OBJ so much bigger than the GLB it came from?
- Two reasons, both measured: OBJ is text rather than binary, and the OBJ written here does not share vertices between triangles, so the vertex count multiplies by about 6 on a well-indexed mesh. The surface is identical; the bookkeeping is not.
- Does STL keep colour?
- No. A binary STL stores a facet normal and three corners per triangle and has no field for colour, UVs, materials, or object names. That was true of every STL written in these tests.
- Can I keep vertex colour without using GLB?
- Yes, with PLY. It was the only non-glTF target in these tests that declared a colour property in its header. It does flatten a multi-object scene into a single mesh, so you keep the colour and lose the parts.
- Does converting back and forth degrade a 3D model?
- No. Unlike re-saving a JPEG, a 3D conversion does not accumulate error: coordinates are copied as binary floats, or in the case of OBJ written as full-precision decimals. A round trip through any of these formats returned the identical surface. What does not come back is the material and scene information the target could not carry in the first place.
- Can I reproduce these numbers?
- Yes. The measurement script is in the repository at scripts/measure-conversions.mjs and writes the JSON this page reads. It was last run on August 28, 2026 against three.js r179.