Benchmark
What happens to a texture when only the model file is sent on
Why does a model lose its texture when someone sends it to me?
Result
Only GLB survives the journey, and the reason is that it is the only one of the five holding the image bytes inside the file. Exporting to OBJ produced three files rather than one: the model at 759 bytes, a material library at 198 bytes, and the texture as a separate 184 byte PNG. Neither of the first two contains a single byte of image data, and the path between them is two hops long, each a bare filename with no directory and no fallback. Copying just the model file to an empty folder, which is what happens when somebody attaches the model to a message, left the geometry intact and the texture gone. The most important column is the last one: not one of the five formats produced a warning, an error or a log entry when the texture failed to arrive. Nothing is malformed at any point, so there is no stage at which a reader could reasonably report a problem, which is why this fault routinely reaches the end of a pipeline before anyone notices.
What the export produced, and what survived being sent alone
| Format | Files written | Image bytes inside the model file | Texture in place | Texture after being sent alone | Survives | Anything warned |
|---|---|---|---|---|---|---|
| OBJ | 3 | No | 64 x 64 | 2 x 2 | No | No |
| GLB | 1 | Yes | 64 x 64 | 64 x 64 | Yes | No |
| PLY | 1 | No | 2 x 2 | 2 x 2 | No | No |
| STL | 1 | No | no material at all | no material at all | No | No |
| 3MF | 1 | No | no material at all | no material at all | No | No |
The authored texture is 64 by 64 pixels. A 2 by 2 result is the stand-in this particular reader substitutes when it follows a reference and finds nothing; other programs substitute grey, white, magenta or a checkerboard instead. Read the last column across all five rows: every entry is no. The formats that never had a texture and the format that lost one are equally quiet about it.
Where the image bytes actually are
| Format | Model file size (bytes) | Extra files the exporter wrote | Files containing image data | Reference hops |
|---|---|---|---|---|
| OBJ | 759 | material.mtl, material_0.png | material_0.png | 2 |
| GLB | 1,720 | none | model.glb | 0 |
| PLY | 628 | none | none | 0 |
| STL | 684 | none | none | 0 |
| 3MF | 1,291 | none | none | 0 |
Every file written for each format was searched for the byte markers that begin a PNG or a JPEG. For OBJ the answer is the third file only, reached through two hops: the model names the material library, and the material library names the image. Each hop is a filename with no directory component, so both are resolved against whichever folder the file is sitting in at the time.
Referencing is a promise the folder keeps, not the format
The distinction that decides every row is whether the image bytes are inside the file or named from it. A referenced texture is a promise that another file will be next to this one, and nothing in the format can enforce that or even check it. This is why the failure is so reliably silent. The OBJ is valid, the reference is well formed, and the reader does exactly what it was told: it follows the name, finds nothing, substitutes a default, and reports success. There is no point in that sequence where a program could reasonably declare the file broken.
The model is not untextured, it is wearing the fallback
The material file our exporter wrote declares Kd 0.40 0.40 0.40 alongside the map_Kd line that names the image. That is a flat mid grey, written without being asked for. When the image cannot be found the material is still found, so the surface renders in the colour that was sitting on the line underneath all along. This is the answer to the most common way the question gets asked, which is why a model turned grey rather than why it lost its texture. An exporter writing Kd 1.0 1.0 1.0 produces a white model from exactly the same failure.
Two rows say less than they appear to
PLY and 3MF show no texture even before the file is moved, and that is a statement about the file this exporter wrote rather than about the formats. 3MF has a materials and properties extension capable of carrying a texture inside the archive, and PLY files from scan software often carry a TextureFile comment naming an image beside them. Neither was written here. The OBJ and GLB rows are the format level result, because referencing and embedding respectively are what those two formats do by design.
How this was measured
A 20 mm box carrying real UV coordinates and a flat 64 by 64 pixel texture of RGB 200, 60, 60. The colour is deliberately saturated so that any substitute a reader supplies for a texture it cannot find is obvious in the mean pixel value rather than needing to be judged by eye. Each format was exported into its own empty folder, so every file that appeared is one the exporter decided it needed, and the folder contents, the file sizes and the outward pointing lines were read off disk rather than inferred. The model was then loaded twice: once in place, and once after copying only the file carrying the model's name into a second empty folder. Warnings raised during each load were captured, because a reader that substitutes a placeholder silently is the reason this failure travels so far.
- Software and versions
- trimesh 5.1.0, numpy 2.5.2, Pillow
- Date measured
- 2026-09-15
- Script
scripts/measure-texture-survival.py- Raw data
- texture-survival-2026-09-15.json
This data is published under CC BY 4.0. Reuse the numbers freely, with a link back so a reader can check the method.
What this does not show
Stated so the result is not read wider than it goes.
- What each program substitutes for a texture it cannot find. The library used here returns a two pixel stand-in; Blender shows the material's diffuse colour, many engines show white, and some viewers show magenta on purpose. The format level fact is the one in the table: the bytes are not in the file and the reference is a bare relative filename. What gets drawn instead is the reader's choice.
- Case sensitivity, which cannot be shown on the machine this ran on. Windows resolves a reference to Texture.PNG against a file named texture.png and Linux does not, so an OBJ that works on the desktop it was made on can lose its texture the moment it reaches a Linux server or a web viewer. This is a real and common failure and it needs a Linux run to measure rather than an assertion here.
- COLLADA, which references textures the same way OBJ does and would belong in the table. The library used here cannot write it without an optional dependency, so it is left out rather than guessed at.
- Whether the texture is correct, as opposed to present. A surviving image still has to line up with the UVs, and nothing here checks that the model looks right, only that the picture arrived.
- The difference between what a format permits and what this exporter wrote. 3MF has a materials and properties extension that can carry a texture inside the archive, and PLY files from scan software often carry a TextureFile comment naming an image beside them. Neither was written here, so those two rows say that the file produced arrived without a texture, not that the format is incapable of one. The OBJ and GLB rows are the format level result, because referencing and embedding respectively are what those two formats do by design.