I think because OBJ is a relatively braindead format, you want to keep the loader braindead as well.
It was also created before a lot of the other utilities that we have now… so applying proper triangulation just wasn’t part of the library at implementation time.
The format has lots of quirks.
For one.. the vertex indexing is 1 based. Ouch.
Here is a summary of other weirdnesses from Gemini:
Common Quirks of the Wavefront OBJ Format
The Wavefront OBJ format is the “Grandfather” of 3D assets. Designed in the 1980s for human readability, it contains several legacy behaviors that can break modern parsers or GPU-centric pipelines.
1. 1-Based & Negative Indexing
Unlike almost every modern programming language (C++, JS, Python), OBJ indices start at 1.
- Absolute:
f 1 2 3 refers to the first three vertices in the file.
- Relative (Negative):
f -3 -2 -1 refers to the three vertices defined immediately before the face declaration. This is often used when merging files to avoid recalculating global index counts.
Developer Note: Always remember to subtract 1 from indices when mapping to a Uint32Array or IndexBuffer.
2. Face Syntax Variations
Faces (f) are defined using a “shorthand” that changes based on available data (Position, UVs, Normals).
| Syntax |
Description |
f v1 v2 v3 |
Position indices only. |
f v1/vt1 v2/vt2 v3/vt3 |
Position + Texture (vt) indices. |
f v1//vn1 v2//vn2 v3//vn3 |
Position + Normal (vn) indices (Note the double slash). |
f v1/vt1/vn1 ... |
All three: Position, Texture, and Normal. |
The Gotcha: The indices for v, vt, and vn do not have to match. You might use the 10th vertex position but the 2nd texture coordinate.
3. “NGons” vs. Triangles
Modern graphics APIs strictly require triangles, but the OBJ spec allows polygons with any number of vertices (e.g., f 1 2 3 4 5).
- The Problem: You cannot simply upload OBJ face data to a GPU.
- The Fix: You must implement a triangulation strategy (like “Ear Clipping” or a basic Fan-Triangulation for convex faces) during the import process.
4. Separate Material Files (.mtl)
The OBJ file contains no color or texture data itself. It relies on an external companion file.
mtllib filename.mtl — Declares the library.
usemtl material_name — Applies a material to all subsequent faces.
- The Quirk: Path issues are rampant. If the OBJ contains an absolute path from the artist’s local machine (e.g.,
C:\Users\Artist\...), your loader will fail to find the textures.
5. Unofficial Vertex Colors
While not part of the official 1980s spec, many modern tools (ZBrush, MeshLab) append RGB values directly to the vertex line:
v 1.0 2.0 3.0 1.0 0.0 0.0 (XYZ + RGB)
- The Quirk: A strict parser expecting exactly 3 floats will crash or skip these lines. If you’re building a tool for high-poly sculpting assets, you need to check the element count on every
v line.
6. Smooth Shading Groups (s)
The s tag (e.g., s 1 or s off) controls normal interpolation across faces.
- This is a legacy feature from the fixed-function pipeline era.
- The Quirk: In modern PBR workflows, we usually rely on the explicit vertex normals (
vn) provided. However, if vn data is missing, you are expected to use these groups to manually calculate and average your normals.
7. Line Continuations
For extremely complex faces or long lists of data, OBJ supports the backslash (\) to split a single logical line across multiple physical lines.
Example:
f 1/1/1 2/2/2
3/3/3 4/4/4
Your parser needs to check for this character before processing the line, or you’ll end up with “missing” data.