POLYMASON

The OBJ file format, explained for game engines

OBJ is the plain-text 3D format from 1990 that never went away. Every modelling tool writes it, every engine reads it, and you can open one in a text editor and understand it. That last part is worth doing once, because most import problems come from the things an OBJ does not say.

This is the whole subject: what is in the file and its material library, what is not and what each engine assumes instead, how it compares with the formats you would use in its place, and how to get one into Unity, Godot and Unreal at the right size, the right way up, in colour. The articles underneath take the comparison with FBX, GLB and STL and the import into Unity, Godot and Unreal in full; what happens to the shading once it is in is flat shading in Unity.

What is in the file

An OBJ is lines of text, each starting with a keyword. The ones you will meet:

# a comment
mtllib tree.mtl        the material library, a second file
o oak                  an object, and its name
v 0.12 0.0 -0.08       a vertex position: x y z
vt 0.5 0.5             a texture coordinate: u v
vn 0.0 1.0 0.0         a normal: x y z
usemtl palette         the material for the faces that follow
s off                  smoothing group: off means flat
f 1/1/1 2/2/2 3/3/3    a face: vertex/texture/normal per corner

Faces refer to vertices by number, counting from one in the order the v lines appear, and the same for texture coordinates and normals. f 1/1/1 2/2/2 3/3/3 is a triangle whose corners are vertices one, two and three, each with its own texture coordinate and normal. The slashes separate the three kinds of index; f 1//1 is a corner with a normal but no texture coordinate, f 1 a bare vertex. A negative index counts back from the most recent vertex, which some exporters use. A face can have four or more corners, and engines triangulate it on import, so quads and n-gons in a file are not a problem unless they are not flat.

Two other keywords group things. o starts a named object; g starts a named group inside one. Engines differ on what they do with them: Unity makes a child GameObject per object and per material, Godot a surface per material, Unreal a material slot per material. s sets a smoothing group, a number or off, which older tools used to decide which faces to blend across when the file has no normals. Modern engines mostly ignore it in favour of their own normal settings.

The material library, the .mtl file, is the same kind of thing:

newmtl palette         a material, and its name
Kd 1.0 1.0 1.0         diffuse colour, red green blue in 0 to 1
Ka 0 0 0               ambient colour
Ks 0 0 0               specular colour
Ns 10                  specular exponent
d 1.0                  opacity
illum 1                lighting model
map_Kd tree.png        a texture for the diffuse colour, by file name

Kd is the colour; map_Kd is a texture in its place. A file with several newmtl blocks gives the model several materials, each used by whatever faces follow its usemtl.

That is the whole format. It has no rig, no animation, no hierarchy beyond names, and no compression; a text file with numbers written out is large for what it holds, though for a low poly model that means kilobytes, not megabytes.

What is not in the file

Units. An OBJ has no unit. The numbers are just numbers, and every tool decides what they mean:

  • Unity reads an OBJ at one unit to one metre. A model made in metres arrives at the right size.
  • Godot is the same: one unit is one metre.
  • Unreal works in centimetres. A model made in metres arrives a hundred times too small. On the import dialog, set Import Uniform Scale to 100.
  • Blender exports at one unit to one metre unless told otherwise, so a model made there in metres is right for Unity and Godot. Its FBX exporter is a different story, and is where the famous "everything is a hundred times too big" comes from.

Which way is up. OBJ has no axis convention. Unity and Godot are Y-up; Unreal is Z-up; Blender is Z-up and its exporter has a Forward and Up setting to convert. Engines convert on import when they know the source, but a file exported with the wrong axes lands lying on its side. A tree flat on the ground is this.

Colours. An OBJ has no colours of its own; they are in the .mtl. If the .mtl is missing or was not put in the same folder, the model imports grey, with a material slot per usemtl for you to fill. Some tools write vertex colours as three extra numbers on each v line; that is a non-standard extension and most engines ignore it.

Normals. Optional. If the file has vn lines, engines use them. If not, the engine works normals out itself, and Unity's default smoothing turns a low poly model smooth: flat shading in Unity is that problem and its fixes. A file with flat normals in it imports right everywhere.

The origin. The file's zero point becomes the object's pivot. A tree whose origin is at the middle of its trunk floats half underground when dropped on terrain; one whose origin is at the base stands. Good exporters put a prop's origin where it touches the ground.

Scale of texture coordinates. vt values run from 0 to 1 across the texture, with v increasing upward, the opposite of most image formats. Engines handle the flip, but a tool that gets it wrong shows the texture upside down.

What PolyMason writes

The tree builder writes an OBJ in metres, Y-up, standing on the ground at the origin, as one mesh with one material. The .mtl beside it points at the PolyMason palette, one PNG of the full spectrum as blocks that every model from here shares; every face's texture coordinates point at the block nearest its colour, and there is a normal per face corner so it shades flat in any engine. So a whole set, the tree at three levels of detail and in every state of its life, and every other model built here, is one material in the engine. It also writes STL for printing and 3MF with the colours in it. GLB is next.

OBJ, FBX, GLB or STL

Four formats cover almost everything a game needs, and each is right for something:

  • OBJ: text, simple, universal; no animation, no rig, colours in a second file. Right for static props and environment pieces, and for anything you want to be able to read.
  • FBX: binary, holds rigs, animation, blend shapes and embedded textures; Autodesk's format, read by every engine through Autodesk's library. Right for characters and anything animated, and the format Unity and Unreal pipelines assume.
  • GLB: the binary form of glTF 2.0, compact, with PBR materials and textures in one file, and an open specification. The web's format and Godot's preferred one; Unity reads it with a package, Unreal natively. Right for most things once you have it.
  • STL: triangles and nothing else, no colour, no units, no materials; the format 3D printers want and no engine does.

The comparison article has the table: what each carries, what each engine does with it, file sizes, and which to ask for from a pack.

Importing one, engine by engine

Unity: drop the .obj, its .mtl and any texture into the Assets folder together. Unity makes a prefab with a child per object and a material per usemtl. If it shades smooth, that is the Model tab's normals; if it is the wrong size, that is Scale Factor, though a file in metres needs none. The Unity article is the step by step, with the materials tab and colliders.

Godot 4: drop the .obj into the project with the .mtl beside it. It imports as a mesh resource you can give to a MeshInstance3D, with a surface per material. To have a scene with a node per object instead, change the importer in the Import dock. The Godot article covers that, scale, reimporting and collision.

Unreal: Import in the Content Browser and choose Static Mesh. Set Import Uniform Scale to 100 for a file in metres, and Normal Import Method to Import Normals to keep flat shading. Unreal makes a material slot per usemtl and, if the .mtl and textures are found, materials to fill them. The Unreal article goes through the dialog.

When OBJ is the wrong choice

For a rigged or animated model, OBJ cannot carry what you need; use FBX or GLB. For a whole scene with a hierarchy of objects and their transforms, GLB holds it and OBJ flattens it. For a model going to the web, GLB is a third the size. And for anything going to a printer, STL or 3MF, not OBJ, because printers do not read it.

The other guides

  • Flat shading in Unity for low poly modelsWhy a low poly model goes smooth when it lands in Unity, what the importer's normals and smoothing angle do, the three places to fix it, and how to keep the colours flat too. The complete guide, with the detail in three articles underneath.
  • LODs and billboards for low poly modelsHow levels of detail work, what triangle counts are reasonable for low poly trees, rocks and buildings, how to make the levels so the swap does not pop, billboards and impostors for the far distance, and the LOD setup in Unity, Godot and Unreal.
  • Free low poly assets for GodotWhere to get free low poly models that work in Godot 4, what each source's licence allows, which formats to prefer, how to import an OBJ or GLB with its colours and flat shading intact, how to add a collider, and how to scatter a forest. The complete guide.

Or build a tree and see what comes out.