# Why do OBJ files contain so many duplicate vertices if they use indices to define faces?

I have been experimenting with loading in OBJ files into memory using tinyobj. I have also been learning and analysing the OBJ file format specification. I have some code which loads the BMW car model here. The code loads in the model, but removes any duplicate vertices as it reads them from the tinyobj output array.

I counted the number of duplicate vertices contained in the OBJ file, and out of the 1 155 486 vertices read from file, 83% of them were duplicates.

I am not sure if I am misunderstanding something or not, but isn't this unnecessary and extremely inefficient storage-wise, given the fact that each face in the OBJ format uses an index into the array of vertices to define itself? Why duplicate so much of this data instead of having each vertex be unique with faces that use it using the same index?

I used a different OBJ file from a different source and got a similar result (78% duplicates), so I don't think this is an issue with the source (or could it be?).

For context, I am removing duplicates as they do in Vulkan tutorial 27 as I am passing them to the GPU using the vertex and index buffer method.

Maybe I am misunderstanding a fundamental aspect of 3d model storage, but if someone could explain this that would be great.

• It depends entirely on the program creating the OBJ. Ideally they would deduplicate everything before writing it out but different programs may or may not do that consistently. Jul 28 at 20:23
• Maybe because the model that was being exported wasn't indexed and the exporter couldn't really handle that well. Jul 28 at 20:45

There are many reasons not to merge vertices. Here's a few that I have used:

1. The file contains many logical objects. If the units don't share vertices they may be separated later for reuse.
2. You can circumvent render engine limitations by modeling. Say your modeling engine only support per vertex normals, but it does not support per face vertex normals. Well the next logical thing is just to split the vertex into two and your engine now does what you want.
3. The vertices happen to coincide but they aren't actually the same vertex. Because it's easier to model the ideal surface than it is to model an actual gap between the edges. Maybe this is so that your skinning can move other side independently from the other or something.
4. You want to make something like selection/partial loading easy in the other engine. Having the vertices arranged in logical chunks makes this easier.
5. Your just dumping out the data as it's presented to you. This makes the translator easy to make. Besides it's not like you necessarily know how the thing should be handled in any case. Just offering something minimal that works. The rest is the user's problem; in many cases good users can live with this and use it to their advantage.
• Nice list, merging all the vertices in a file blindly is bad for many reasons. A lot of files contain LOD, alternate objects, this list just goes on and on. Jul 29 at 18:56

The file has multiple objects in it. The car body, tires, bolts, rondel, and more. What is good here is that all the different parts are broken down so they can be extracted and used, edited, split and combined with other models. The downside is that there are a lot of duplicate vertices. In this case there are a whole lot of duplicates.

This is a common practice. Many file formats don't have an easy way to define a large number of vertices then define index sets that refer into them while still keeping all the parts separate.

When we load them into the GPU it is much better to optimize the models in some way, removing duplicates is a good first step. (there are programs that are devoted solely to optimizing a mesh)

Here is a list of the objects: (this file also has a lot of duplicate objects in it)


carShell 99488 faces
plate 132 face
carShell_001 512 faces
RondelBack 4640 faces
RondelFront 4640 faces
taillight 6270 faces
MStreak 1536 faces
Window 2016 faces
exhaust 1270 faces
rearDiffuser 2496 faces
lightsLid 1792 faces
Platform 232 faces
lightsGlass 472 faces
LowerIntake 10022 faces
lightsOutline 832 faces
lowerIntake 1408 faces
kidneyBars 3264 faces
kigneyChrome 720 faces
Interior 14604 faces
trimming 2080 faces
lightsAngelEyes 3072 faces
lightsReflector 20480 faces
lightsInterior2 2544 faces
lightsBulb 416 faces
lightSeperator 864 faces
lightsInterior 816 faces
SideMirror 128 faces
CenterConsole 658 faces
Inside_Window_Trim 512 faces
carRigOutline 0 faces
carRigArrow 0 faces
Brake3_Cube_Dupli_ 108 faces
Tire1_TireRim_Dupli_3 13600 faces
Tire3_TireBolts_Dupli_ 4480 faces
Tire3_TireRoundel_Dupli_2 4640 faces
Tire3_TireRubber_Dupli_1 17384 faces
Brake4_brakeAssembly_Dupli_1 350 faces
Brake3_Cube_Dupli_001 108 faces
Brake4_brakeAssembly_Dupli_002 350 faces
Tire1_TireRim_Dupli_004 13600 faces
Tire3_TireBolts_Dupli_001 4480 faces
Tire3_TireRoundel_Dupli_003 4640 faces
Tire3_TireRubber_Dupli_002 17384 faces
Tire3_TireRubber_Dupli_003 17384 faces
Brake4_brakeAssembly_Dupli_003 350 faces
Tire1_TireRim_Dupli_005 13600 faces
Tire3_TireBolts_Dupli_002 4480 faces
Tire3_TireRoundel_Dupli_004 4640 faces
Brake3_Cube_Dupli_002 108 faces
Tire3_TireRubber_Dupli_004 17384 faces
Tire1_TireRim_Dupli_006 13600 faces
Tire3_TireBolts_Dupli_003 4480 faces
Tire3_TireRoundel_Dupli_005 4640 faces
Brake4_brakeAssembly_Dupli_004 350 faces
Brake3_Cube_Dupli_003 108 faces
carShell001 34548 faces