A generated crate lands in Unity looking chalky and slightly plastic, and the metal banding reads as dull grey rather than metal. Nothing is broken. The mesh imported, the textures imported, the material got assigned. What happened is that Unity read the roughness channel as ambient occlusion and never found a metallic value at all, because glTF and Unity pack those maps differently and nothing warns you.
That specific failure accounts for most “the model looked better in the preview” reports I get. The rest come from scale, tangents, and lightmap UVs. Here is the checklist, in the order I run it, with the values I actually use on a URP project.
Step 1: decide how the file gets in
Unity imports FBX and OBJ natively. It does not import GLB without a package. For glTF, install com.atteneder.gltfast (or the Unity-maintained fork) via the package manager and it will handle GLB as a first-class asset, including the material conversion described below.
My default is FBX for anything static or animated that goes through the standard model importer, and glTFast for anything I want loaded at runtime from a server. Mixing both in one project is fine as long as you are consistent per-asset-type, because the material conversion paths differ.
Step 2: the Model tab
| Setting | Value | Why |
|---|---|---|
| Scale Factor | 1 | Fix scale at export, not import |
| Convert Units | On | Handles the FBX centimetre default |
| Bake Axis Conversion | On for props | Local axes match Unity, no rotated root |
| Import BlendShapes | Off unless used | Saves memory and import time |
| Import Visibility / Cameras / Lights | Off | Generated files often carry junk nodes |
| Preserve Hierarchy | Off | Collapses a pointless single-child chain |
| Mesh Compression | Off for hero, Low for props | It quantises positions and UVs |
| Read/Write Enabled | Off | On doubles mesh memory, CPU copy plus GPU copy |
| Optimize Mesh | On | Vertex cache reorder, free win |
| Generate Colliders | Off | Never ship a mesh collider on a dense mesh |
| Normals | Import | Calculate at 60° only if it arrives faceted |
| Tangents | Calculate Mikktspace | Must match whatever baked the normal map |
The scale check is thirty seconds and worth doing every time. Drop the prefab next to a 1 × 1 × 1 m cube and look at it. A 1.7 m character that reads 170 or 0.017 tells you exactly which unit assumption broke.
Step 3: the texture channel problem
This is the one that costs people an afternoon. glTF, and therefore most generators, pack three greyscale maps into one RGB texture: occlusion in red, roughness in green, metallic in blue. Unity’s Standard and URP Lit shaders want something different.
| Channel | glTF ORM texture | Unity Metallic map | Unity Occlusion map |
|---|---|---|---|
| R | Ambient occlusion | Metallic | ignored |
| G | Roughness | ignored | Occlusion |
| B | Metallic | ignored | ignored |
| A | unused | Smoothness (1 minus roughness) | ignored |
Two consequences. Unity reads smoothness, not roughness, so the value has to be inverted. And URP Lit samples occlusion from the green channel, so if you shortcut by plugging the ORM texture straight into the Occlusion slot you get roughness rendered as ambient occlusion, which is the chalky look from the first paragraph.
glTFast does this repack automatically on import. If you are on the FBX path you have to do it yourself. Ten lines of Pillow, run once per asset:
from PIL import Image, ImageOps
o, r, m = Image.open("crate_orm.png").convert("RGB").split()
# Unity metallic map: metallic in R, smoothness in A
Image.merge("RGBA", (m, m, m, ImageOps.invert(r)))
.save("crate_metallic.png")
# URP Lit reads occlusion from the GREEN channel
Image.merge("RGB", (o, o, o)).save("crate_occlusion.png")
Step 4: texture import settings
Set the type before anything else, because it changes the compression format Unity picks.
- Base colour: sRGB checked, format BC7 on desktop, ASTC 6×6 on mobile, max size 2048.
- Normal map: Texture Type set to Normal map. Unity then uses BC5, which stores two channels at higher precision and reconstructs the third. If you leave it as Default you get visible banding on smooth curves.
- Metallic and occlusion: sRGB unchecked. These are data, not colour. Leaving sRGB on applies a gamma curve to your roughness values and everything comes out too shiny in the midtones.
- Streaming Mip Maps: on for anything in a large scene.
Memory arithmetic worth memorising: a 2048 texture at BC7 with a full mip chain is 5.6 MB. Three of those per material is 16.8 MB. Forty props with unique 2048 sets is 672 MB of texture memory, which is over budget on almost everything. Dropping props to 1024 puts each set at 4.2 MB and the same forty props at 168 MB.
| Resolution | BC7 / BC5 with mips | BC1 with mips | Uncompressed RGBA |
|---|---|---|---|
| 4096 | 22.4 MB | 11.2 MB | 89.5 MB |
| 2048 | 5.6 MB | 2.8 MB | 22.4 MB |
| 1024 | 1.4 MB | 0.7 MB | 5.6 MB |
| 512 | 0.35 MB | 0.18 MB | 1.4 MB |
Step 5: materials
On the Materials tab, set Material Creation Mode to Standard and then use Extract Materials to write real material assets into your project folder. Materials living inside the imported model are read-only and get regenerated every time someone re-imports, silently discarding tweaks.
If the asset came in with Built-in pipeline materials and you are on URP, run Window > Rendering > Render Pipeline Converter before hand-editing anything. It handles the common slots and leaves the exotic ones pink, which is a useful list of what needs attention.
Step 6: lightmap UVs, only if you need them
If the object is static and receives baked lighting, tick Generate Lightmap UVs. My defaults: Hard Angle 88, Angle Error 8, Area Error 15, Pack Margin 4. Then check the chart count in the model preview, because generated meshes with dense irregular triangles routinely produce 300 to 600 charts, and every chart eats padding.
If the count is over about 120 for a single prop, you are better off authoring a proper second UV channel in Blender than fighting the generator. That is a twenty-minute job and it pays for itself the first time you rebake lighting.
Step 7: colliders and LODs
Never put a Mesh Collider on a 90k-triangle generated mesh. Use a box or capsule where the silhouette allows it, or author a 200-triangle convex proxy. The physics cost difference is roughly two orders of magnitude and nobody has ever noticed the approximation on a crate.
For LODs, four levels with screen relative transition heights of 0.5, 0.25, 0.1 and 0.02 works for most environment props. Generate the lower levels with a decimate modifier at 50%, 25% and 8% of the source triangle count.
Do and don’t
- Do extract materials and textures into project folders on first import.
- Do uncheck sRGB on every non-colour map. This is the most common single mistake.
- Do set Tangents to Calculate Mikktspace and confirm the baker used the same basis.
- Do check the mesh’s vertex count in the importer preview, not the triangle count. Split UVs and hard edges duplicate vertices, and a 38k-triangle mesh with 62k vertices is telling you something about its normals.
- Don’t leave Read/Write Enabled on because a tutorial said to. It doubles mesh memory permanently.
- Don’t use Mesh Compression on hero assets. It quantises UVs and you will see it on tight texel density.
- Don’t import at 4096 “just in case”. You will forget, and forty of them will blow your texture budget.
- Don’t edit materials that live inside the model asset. Re-import wipes them.
A two-minute verification pass
- Prefab next to a 1 m cube. Scale correct?
- Rotate a directional light around it. Do the highlights travel correctly, or do they smear across seams? Smearing means tangent mismatch.
- Set the material to full metallic and full smoothness temporarily. Any region that stays matte means the metallic map is not reaching the shader.
- Zoom out until the object is 40 px on screen. Any dark halo at the silhouette means normal map compression or mip bleed.
- Open the Frame Debugger and confirm it is one draw call, not six because of stray submeshes.
Where to start tomorrow
Make a preset. Unity lets you save importer settings as a Preset asset and apply it automatically by folder, so put a ModelImporter preset on Assets/Models/Props and a TextureImporter preset on Assets/Models/Props/Textures. Everything after that arrives configured, and the checklist collapses into the two things a preset cannot do for you: the scale check and the ORM channel repack.
Then script the repack. If you pull models from a studio like MeshyFlix and get GLB out of it, run the Pillow snippet above as a batch job over the texture folder before the files ever touch Unity, and the chalky-crate problem stops existing.