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 looks like a green balloon. Nothing's wrong with the model. Unity has averaged the normals across neighboring faces. That default suits a smooth character, but it's wrong for anything low poly.

This guide covers what flat shading is, why Unity undoes it, the three places you can fix it, how to keep the colors as flat as the shading, and what the same problem looks like in Godot, Unreal, Blender and Three.js. Three articles underneath go deeper. One is about the importer's smoothing angle, one builds a shader that shades flat on its own, and one sets up materials that keep the colors flat. If the file is an OBJ, the OBJ guide covers the rest of getting it into the engine.

  • Unity's smoothing angle and Model tab normalsWhat the Smoothing Angle in Unity's model importer does, how the Normals, Normals Mode and Weld Vertices settings affect it, which values give flat shading, and how to set them once for a whole folder with an importer preset.
  • A flat shading shader for Unity (Built-in, URP)How to shade any mesh flat from the shader alone by using screen-space derivatives to get the face normal. Includes a working Built-in shader, a URP version, the Shader Graph node, and the caveats.
  • Materials for low poly models in UnityThe three ways a low poly model can carry color and what each costs in draw calls and setup. How to set up a palette texture material in Built-in and URP with the right filtering, and how batching and instancing change the choice for a forest.

What flat shading is

A mesh is made of triangles, and lighting is worked out from a normal, which is 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 the low poly look depends on it.
  • One normal per vertex, averaged. Where faces meet at a vertex, the vertex gets a normal that's the average of the faces around it, and the lighting blends across the edge. This is smooth shading, and it's 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 whether the file or the engine wrote it.

Low poly models are drawn flat on purpose. The facets are the style, and the shading does the work that texture and detail would do in a high-poly model. Smooth those facets away and the model looks like a smooth object that somebody modeled badly.

Why Unity smooths your model

Unity's model importer has to decide where the 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, and the model renders as if unlit.

If the file has no normals, Import falls back to calculating them. When Unity calculates, the Smoothing Angle decides which faces it blends across. Any two faces that meet 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 they all blend and the trunk turns into a round tube. Most edges on a canopy of triangles are under 60 degrees too.

Weld Vertices matters as well. It's on by default, and it merges vertices that sit at the same position before the normals are worked out. A file that gets its flat shading by giving every face its own vertices can still come in 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 one 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 press Apply. Every face now gets its own normal, whatever the file said.

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

Fix two: the file

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

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

Prefer the file route 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. That's the cross product of the screen-space derivatives, and in HLSL it's 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 in it, wired into the fragment stage's Normal. It shades any mesh flat whatever its normals are, and it costs nothing you can measure. Use it when you want the look on models you can't re-export, or when you want to switch it on and off. The flat shading shader article has a complete Built-in version and a URP version. It also covers the caveats, which are that normal maps stop working and that the sign flips with the platform's derivative convention, so the order of the cross product matters.

Keeping the colors flat too

The look also needs each face to be one solid color, with no texture detail inside it. A low poly model can carry color in three ways, and the choice matters in Unity because it decides how many materials and draw calls the model costs:

  • A palette texture. One small image holds each color as a block, and every face's texture coordinates point at the middle of its block. That makes one texture, one material and one draw call per mesh, and you recolor the whole model by repainting a few pixels. Every large low poly pack does it this way, and so does PolyMason. The OBJ is one mesh, the .mtl beside it names one material, and that material's texture is the PolyMason palette. The palette is one PNG of the full spectrum as blocks, and every model from here shares it, so a whole scene of them is one material. Set the texture's Filter Mode to Point if you want to be certain nothing bleeds between blocks. Bilinear is fine too, because the corners sample the middle of each block.
  • Vertex colors. The color is stored on each vertex and there's no texture. They're cheap and editable, but OBJ can't carry them, Unity's standard shaders ignore them unless a shader reads them, and every renderer treats them slightly differently.
  • One material per color. There's a separate material slot for bark, another for leaves, and so on. It's simple to understand and edit, but every material is a draw call, so a forest of trees with four materials each costs four times the draw calls of the same forest with a palette.

Whichever you use, keep the color flat. That means no albedo texture with shading painted into it and no ambient occlusion baked into the color, because the lighting does the shading. The materials article sets up each of the three in Unity, in Built-in and URP, and shows what each does to batching.

URP, HDRP and the Built-in pipeline

Nothing above depends on the render pipeline. Normals are the importer's business, and the importer is the same in every pipeline. The shader fix differs in syntax between Built-in and URP, and the materials have different names, Standard in Built-in and Lit in URP, 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're 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's no smoothing angle to set. A file with flat normals needs nothing more, and a file without them has to be re-exported flat.
  • Unreal has Normal Import Method on the static mesh import dialog. Import Normals keeps the file's normals and Compute Normals makes new ones. Set it to Import Normals for a file that has them.
  • Blender is where most of these files start. Use Shade Flat on the object, and check that the exporter's normals option is on.
  • Three.js and the web compute normals with computeVertexNormals, which averages across shared vertices. For flat results use non-indexed geometry, 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 one side and the next. If a gradient wraps around the trunk, the normals are still smooth. The Scene view's draw mode list has no normals view to confirm it with. If you want to see the normals themselves, URP's Rendering Debugger has a world-space normals view under Material, and in the Built-in pipeline the Deferred draw mode shows them when the camera renders deferred. Most of the time the one-light test is enough.

The other guides

  • The OBJ file format, explained for game enginesWhat's in an OBJ file and its MTL, what it leaves out (units, axes, colors), how it compares with FBX, GLB and STL, and how to import one into Unity, Godot and Unreal so the model doesn't arrive tiny, sideways or gray.
  • LODs and billboards for low poly modelsHow levels of detail work, what triangle counts are reasonable for low poly trees, rocks, buildings and characters, how to make the levels so the swap doesn't 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 license allows, and which formats to prefer. It also covers importing an OBJ or GLB with its colors and flat shading intact, adding a collider, and scattering a forest.
  • How to make low poly trees for gamesThe method behind a low poly tree that looks like its species: the silhouette that identifies it, the trunk, where the boughs leave and how the trunk divides, foliage as a few closed meshes, two greens, triangle budgets, and the export a game engine wants. The same steps work in the builder and by hand in Blender.

Or build a tree and see what comes out.