POLYMASON

Flat shading in Unity: the complete guide for low poly models

You export a low poly tree with crisp facets, drop it into Unity, and the facets are gone: the trunk looks like a rubber tube and the canopy like a green balloon. Nothing is wrong with the model. Unity has averaged the normals across neighbouring faces, which is the right default for a smooth character and the wrong one for anything low poly.

This guide is the whole subject: what flat shading is, why Unity undoes it, the three places it can be fixed, how to keep the colours as flat as the shading, and what the same problem looks like in Godot, Unreal and Blender. The three articles underneath go deeper: the importer's smoothing angle, a shader that shades flat on its own, and materials that keep the colours flat. If the file is an OBJ, the OBJ guide is the other half of getting it in.

What flat shading is

A mesh is triangles. Lighting is worked out from a normal, the direction a surface faces. There are two ways to give a triangle its normals:

  • One normal per face. Every pixel on the face is lit the same, so each face is one flat tone and the edges between faces are hard. This is flat shading, and it is the whole low poly look.
  • One normal per vertex, averaged. Where faces meet at a vertex, the vertex gets a normal that is the average of the faces around it, and the lighting blends across the edge. This is smooth shading, and it is what makes a 500-triangle sphere look round.

The mesh's geometry is identical in both cases. What changes is a small array of normals, and who wrote it.

Low poly models are drawn flat on purpose. The facets are the style, and the shading is doing work that texture and detail would do in a high-poly model. When an engine smooths them, the model does not look lower quality, it looks like a badly modelled smooth object, which is worse.

Why Unity smooths your model

Unity's model importer has to decide where normals come from. On the Model tab of a model file's import settings, under Geometry, the Normals setting has three values:

  • Import uses the normals in the file. This is the default.
  • Calculate throws the file's normals away and makes new ones from the geometry.
  • None stores no normals; the model renders as if unlit.

If the file has no normals, Import falls back to calculating them. And when Unity calculates, it decides which faces to blend across with the Smoothing Angle: any two faces meeting at an angle less than this share a smoothed normal at the vertices along their edge. The default is 60 degrees. On a low poly trunk with eight sides, adjacent faces meet at 45 degrees, so all of them blend, and the tube goes round. On a canopy of triangles, most edges are under 60 degrees too.

There is a second setting that matters. Weld Vertices, on by default, merges vertices that sit at the same position before the normals are worked out. A file that carries flat shading by giving every face its own vertices can still end up smoothed, because the weld joins those vertices back together first.

The smoothing angle article goes through every value on that tab, including the normals modes, and what each does to a low poly model.

Fix one: the importer

Select the model file in the Project window. On the Model tab, set Normals to Calculate and Smoothing Angle to 0, then Apply. Every face now gets its own normal, whatever the file said.

This works for any file and is the fastest fix for a model you already have. Its cost is that it is per file: the setting lives in the file's .meta, not in the model, so every new model needs it set, unless you make an importer preset and apply it to a folder. Presets are worth doing on a project that will take many models; the smoothing angle article shows how.

Fix two: the file

If the file carries flat normals, the importer's default, Import, uses them and there is nothing to set. In an OBJ that means a vn line per face corner, all three corners of a face pointing the same way; in FBX and glTF the normals are stored per vertex with the vertices unshared between faces. Weld Vertices does not undo this, because Unity welds by position and normal together and the normals differ.

Not every exporter writes flat normals. Blender writes whatever the mesh's shading is set to, so shade it flat before exporting: select the mesh, then Object menu, Shade Flat. Most low poly packs ship files already flat. Everything PolyMason exports is: the tree builder writes a normal per face corner into the OBJ, so its trees import flat with the importer on its defaults.

The file route is the one to prefer when you can, because it works in every engine and every viewer without a setting anywhere.

Fix three: a shader

A shader can ignore the mesh's normals altogether and work the face normal out for itself, from how the world position changes across the pixel: the cross product of the screen-space derivatives. In HLSL it is one line:

float3 n = normalize(cross(ddy(i.worldPos), ddx(i.worldPos)));

In Shader Graph the same thing is a Custom Function node with that line, wired into the fragment stage's Normal. It shades any mesh flat whatever its normals, costs nothing you can measure, and is the way to go when you want the look on models you cannot re-export, or want to switch it on and off. The flat shading shader article has a complete Built-in and URP version, and the caveats: no normal maps, and the sign flips with the platform's derivative convention, so the order of the cross product matters.

Keeping the colours flat too

Flat shading is half the look. The other half is that each face is one solid colour, with no texture detail inside it. There are three ways a low poly model carries colour, and they matter in Unity because they decide how many materials and draw calls the model costs:

  • A palette texture. One small image with each colour as a block, and every face's texture coordinates pointing at the middle of its block. One texture, one material, one draw call per mesh, and the whole model recolours by repainting a few pixels. This is the convention of every large low poly pack, and it is what PolyMason writes: the OBJ is one mesh, the .mtl beside it names one material, and that material's texture is the PolyMason palette, one PNG of the full spectrum as blocks that every model from here shares, so a whole scene of them is one material. Set the texture's Filter Mode to Point if you want to be certain of no bleed between blocks; with the corners sampling the middle of each block, Bilinear is fine too.
  • Vertex colours. The colour stored on each vertex, no texture. Cheap and editable, but OBJ cannot carry them, Unity's standard shaders ignore them unless a shader reads them, and every renderer treats them slightly differently.
  • One material per colour. A separate material slot for bark, leaves, and so on. Simple to understand and edit, but every material is a draw call, and a forest of trees with four materials each is four times the draw calls of one with a palette.

Whichever you use, the colour should be flat: no albedo texture with shading painted into it, no ambient occlusion baked into the colour. The lighting does the shading. The materials article sets each of the three up in Unity, in Built-in and URP, and shows the batching consequences.

URP, HDRP and the Built-in pipeline

Nothing above depends on the render pipeline. Normals are an importer matter and the importer is the same everywhere. The shader fix differs in syntax between Built-in and URP, and the materials differ in name, Standard against Lit, but the settings and the reasoning are identical. HDRP is overkill for flat-shaded models and its default lighting will make them look wet; if you are on HDRP, turn specular down on the material.

The same problem elsewhere

  • Godot uses the normals in the file and generates smooth ones only when there are none. There is no smoothing angle to set. A file with flat normals is done; a file without them needs re-exporting flat.
  • Unreal has Normal Import Method on the static mesh import dialog: Import Normals keeps the file's, Compute Normals makes new ones. Set it to Import for a file that has them.
  • Blender is where most of these files start. Shade Flat on the object, and check the exporter's normals option is on.
  • Three.js and the web compute normals with computeVertexNormals, which averages across shared vertices; use non-indexed geometry for flat results, or a material with flatShading: true.

How to check it worked

Put one directional light in the scene and look at the trunk. Each side should be a different flat tone with a hard line between them. If a gradient wraps round the trunk, the normals are still smooth. Select the mesh and open the Scene view's shading dropdown; the Normals debug view shows each face as one colour when the shading is flat.

The other guides

  • The OBJ file format, explained for game enginesEverything in an OBJ file and its MTL, everything it leaves out (units, axes, colours), how it compares with FBX, GLB and STL, and how Unity, Godot and Unreal each import one without the model arriving tiny, sideways or grey.
  • 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.