What Is OBJ?
OBJ (Wavefront Object) is a geometry definition format developed by Wavefront Technologies in the 1980s for their Advanced Visualizer animation package. It became the de facto standard for 3D model interchange between modeling, animation, and rendering applications. Unlike STL, OBJ supports polygon meshes (not just triangles), vertex normals, texture coordinates, multiple materials, and an associated .mtl material file.
OBJ is plain ASCII text — human-readable, easy to parse, and supported by virtually every 3D application: Blender, Maya, 3ds Max, Cinema 4D, ZBrush, Substance Painter, Unreal Engine, Unity, and hundreds of converters and renderers.
OBJ File Format
An OBJ file consists of directives, one per line, with the type indicated by the keyword at the start of the line:
# OBJ file generated by Blender
# Author: Jane Doe
mtllib model.mtl # reference to material file
o Cube # object name
# Vertices
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
# Texture coordinates (UV)
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
# Vertex normals
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
# Material assignment
usemtl Material
s off # smoothing groups off
# Faces: f vertex/texture/normal
f 1/1/1 4/2/1 3/3/1
f 1/1/1 3/3/1 2/4/1
Key Directives
| Directive | Meaning |
|---|---|
# |
Comment |
v x y z |
Geometric vertex (position) |
vt u v |
Texture coordinate (UV) |
vn x y z |
Vertex normal |
vp u v w |
Parameter space vertex (for curves) |
f |
Face definition |
o name |
Object/group name |
g name |
Group assignment |
mtllib file.mtl |
Material library reference |
usemtl name |
Apply material to subsequent faces |
s group |
Smoothing group number (or off) |
l |
Line element |
p |
Point element |
Face Syntax
Face definitions reference vertices by 1-based index (negative indices count from the end of the vertex list):
f v1 v2 v3 # triangle, positions only
f v1/vt1 v2/vt2 v3/vt3 # with texture coords
f v1//vn1 v2//vn2 v3//vn3 # with normals, no UV
f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 # full: pos/uv/normal
f v1 v2 v3 v4 # quad face
f v1 v2 v3 v4 v5 # polygon face (any n-gon)
OBJ supports n-gon faces (quads, pentagons, etc.) unlike STL which is triangles-only. However, most rendering pipelines triangulate quads and n-gons before rendering.
MTL Material File
Materials are defined in a separate .mtl file referenced by mtllib:
# Material file for model.obj
newmtl Material
Ka 0.1 0.1 0.1 # ambient color (r g b, 0-1)
Kd 0.8 0.2 0.2 # diffuse color (red)
Ks 1.0 1.0 1.0 # specular color (white)
Ns 50.0 # specular exponent (shininess)
d 1.0 # dissolve (opacity, 1.0 = opaque)
illum 2 # illumination model (2 = Phong)
map_Kd texture.png # diffuse texture map
map_Ks specular.png # specular texture map
map_bump normal.png # bump/normal map
map_d alpha.png # opacity map
newmtl Glass
Kd 0.9 0.9 1.0
d 0.3 # 30% opacity
MTL Illumination Models
illum |
Description |
|---|---|
| 0 | Color on, ambient off |
| 1 | Color on, ambient on |
| 2 | Highlight on (Phong shading) |
| 3 | Reflection on, ray trace on |
| 4 | Transparency: glass on |
| 6 | Refraction: Fresnel on |
| 9 | Invisibility (hidden) |
OBJ Smoothing Groups
Smoothing groups control how normals are averaged across faces for smooth shading:
g Body
s 1
f 1/1/1 2/2/2 3/3/3 # these faces share smooth normals
s off
f 4/4/4 5/5/5 6/6/6 # flat shading for these faces
Smoothing group off or 0 produces flat shading. Each numbered group produces smooth (averaged) normals at shared vertices.
OBJ Limitations and Gotchas
- ASCII only: no binary format — large models can be tens of MB of text.
- MTL is primitive: no PBR (Physically Based Rendering) support natively — most tools extend MTL or use FBX/GLTF instead.
- No animation: purely static geometry — for animation use FBX, GLTF/GLB, or USD.
- No scene hierarchy: groups are flat — no parent-child relationships.
- Index base 1: vertices are 1-indexed, not 0-indexed, confusing programmers used to arrays.
- MTL path is relative: the MTL file must be in the same directory as the OBJ for most tools.
- Duplicate vertices: no vertex sharing for different UV sets — vertices may be duplicated for different texture coordinates.
- No unit system: like STL, units are implicit.
- Large files: a million-polygon model may be 100+ MB of text.
OBJ vs. Other 3D Formats
| Format | Textures | Animation | PBR | Binary | Scene hierarchy | Best for |
|---|---|---|---|---|---|---|
| OBJ | Yes (MTL) | No | No | No | No | Model interchange |
| STL | No | No | No | Yes | No | 3D printing |
| FBX | Yes | Yes | Partial | Yes | Yes | Game/film pipeline |
| GLTF/GLB | Yes | Yes | Yes | Yes (GLB) | Yes | Web/real-time |
| USD/USDA | Yes | Yes | Yes | Yes (USDC) | Yes | VFX/Pixar pipeline |
| COLLADA | Yes | Yes | Partial | No | Yes | Legacy game/VR |
| 3MF | Yes | No | No | No | Yes | 3D printing |
Parsing OBJ in Python
class OBJParser:
def __init__(self):
self.vertices = []
self.uvs = []
self.normals = []
self.faces = []
def parse(self, filepath):
with open(filepath, 'r') as f:
for line in f:
parts = line.strip().split()
if not parts:
continue
directive = parts[0]
if directive == 'v':
self.vertices.append(tuple(float(x) for x in parts[1:4]))
elif directive == 'vt':
self.uvs.append(tuple(float(x) for x in parts[1:3]))
elif directive == 'vn':
self.normals.append(tuple(float(x) for x in parts[1:4]))
elif directive == 'f':
self.faces.append(parts[1:])
return self
parser = OBJParser().parse('model.obj')
print(f"Vertices: {len(parser.vertices)}, Faces: {len(parser.faces)}")
Popular Python libraries: trimesh, pywavefront, pyobjloader.
OBJ in 3D Pipelines
Game development: OBJ is an intermediate format — assets are exported from Maya/Blender as OBJ, then imported into Unity/Unreal where they are converted to engine-native formats (mesh assets).
Web 3D: OBJ is supported by Three.js (OBJLoader) and Babylon.js, though GLTF/GLB is now preferred for its binary efficiency, animation support, and PBR materials.
3D Scanning: photogrammetry tools (Agisoft Metashape, RealityCapture, Polycam) export textured OBJ by default — the mesh with UV coordinates and a separate texture image.
Rendering: OBJ is natively supported by most renderers: Arnold, V-Ray, Cycles, Blender Internal, POV-Ray, LuxCoreRender.
Converting OBJ
- OBJ → STL: Blender, MeshLab, FreeCAD — triangulates quads.
- OBJ → GLTF/GLB: Blender export, or
obj2gltf(Node.js tool). - OBJ → FBX: Blender, Maya, Autodesk FBX Converter.
- OBJ → USD: Blender (via USD addon), or USD's
usdcattool. - STL → OBJ: MeshLab, Blender — adds dummy material and UV coordinates.
- FBX → OBJ: Blender, Autodesk FBX Converter.
- GLTF → OBJ:
gltf-pipeline, Blender import + export.
Best Practices
- Always keep OBJ and MTL together — they are separate files that reference each other.
- Use relative paths in MTL — avoid absolute paths that break on other machines.
- Triangulate before export if the target application does not handle n-gons.
- Pack textures with the model — zip OBJ + MTL + all texture files together for distribution.
- Use GLTF/GLB instead of OBJ for web 3D, real-time rendering, or anything requiring PBR.
- Use FBX instead of OBJ for game engine pipelines with animations.
- Flip UVs vertically when switching between DirectX (V flipped) and OpenGL (standard) conventions.
- For large models, consider binary formats (GLB, FBX) — OBJ's ASCII size is a bottleneck.
- Verify normals after export — some tools invert winding order between coordinate systems.
- Use trimesh in Python for efficient OBJ loading with boolean operations and mesh analysis.
Related conversions
Frequent conversions across the catalogue: