How to Build a Texture Atlas for 3D Assets
Plan, unwrap, pack, bake, validate, and ship texture atlases that reduce useful state without causing density loss, mip bleeding, broken variants, or worse culling.
A texture atlas places imagery for several surfaces or objects into one larger image and remaps their UV coordinates. It can help multiple meshes share a material and reduce texture bindings or draw partitions. It can also waste memory, blur important areas, create mip bleeding, duplicate reusable materials, and force unrelated objects into one lifecycle.
Atlas only when the target scene, renderer, material, and streaming behavior make sharing valuable. The goal is fewer useful state changes at an acceptable visual and memory cost—not one enormous image for its own sake.
Confirm that an atlas addresses the bottleneck#
Profile the production scene. Record CPU render submission, GPU frame time, draw calls, material switches, texture bindings, visibility, texture memory, and streaming. Replace candidate materials with one simple shared material as a controlled test.
An atlas is promising when many small static objects are visible together, use the same shader and render state, and differ mainly by texture region. It is less useful when objects are rarely visible together, use different alpha modes, require independent streaming, or are GPU-bound on fill and lighting.
Write the target:
- assets and instances in the atlas group;
- shared shader and material inputs;
- opaque, masked, or blended state;
- maximum decoded and download bytes;
- texture dimensions by device tier;
- required texel density;
- mip and filtering behavior;
- wrap and tiling requirements;
- variant and customization rules;
- expected draw or binding reduction.
If the profile shows 3 milliseconds of fragment cost and only 0.3 milliseconds of submission, an atlas may not improve the limiting stage.
Choose a coherent atlas group#
Group assets by simultaneous visibility, material model, update frequency, streaming region, resolution need, and ownership. A room kit can share an atlas; every prop in a 10-level game usually should not.
Avoid mixing:
- opaque and blended materials;
- unrelated shader features;
- a hero label needing 4K with tiny background props needing 256;
- frequently changed user-generated decals with immutable surfaces;
- assets loaded in different levels;
- repeating tiled materials with unique unwraps unless rebaked intentionally;
- licensed content whose derivative or redistribution rules differ;
- objects maintained by teams with incompatible release schedules.
Create an atlas manifest with stable region IDs. Do not make object names or UV positions the only mapping between source and atlas.
Plan rollback. Preserve original material assignments, UVs, and source textures.
Decide what gets atlased#
Atlas material inputs that share dimensions, UV mapping, and sampling behavior. Base color, normal, metallic-roughness, occlusion, and emissive commonly need corresponding layouts when they use the same UV set.
Do not combine color and data into one ordinary color image without a deliberate channel contract. Base color needs color-space conversion; roughness and metallic values must remain linear data. Channel packing is different from spatial atlasing, though both can be used together.
Uniform materials may not need large image regions. Represent a flat color or scalar with factors or a tiny palette region if filtering and variation permit. Keep physically different shader behavior separate.
If 12 props use one base-color atlas but each retains a unique normal-map material, draw sharing may not improve. Audit the complete material state.
Set texel density from screen use#
Choose pixels per meter or another consistent measure based on closest camera and display. Then assign higher density to labels, faces, trim, and focal surfaces, and lower density to undersides or hidden backs.
Suppose a 2-meter cabinet and a 0.2-meter control panel share a 2048 atlas. Equal world density might allocate the cabinet much more area, yet the panel contains readable text and receives close inspection. Use perceptual importance as an explicit exception.
Calculate occupied area. A 2048 × 2048 atlas contains about 4.2 million texels. With 12% reserved for padding and unusable gaps, only about 3.7 million remain. Budget each asset before packing.
Do not upscale a 512 source into a 2048 region and claim new detail. Preserve source resolution limits in the report.
Create separate atlas tiers if mobile and desktop density differ significantly.
Handle tiling and wrap modes#
UVs outside 0–1 rely on repeat or mirrored-repeat sampling. Placing that material in a subregion of an atlas causes it to sample neighboring regions unless the shader transforms and clamps safely.
Options include baking the tiled appearance into unique UV space, using texture arrays where the runtime supports them, keeping repeat materials separate, or changing the shader to use an indirection scheme. Each has memory and batching consequences.
Baking a wall with 8 texture repeats into one atlas region can require 8 times the linear texel coverage to retain detail. That may cost more than the draw it saves.
Clamp-to-edge alone does not solve mip bleeding inside an atlas because lower mip levels combine neighboring texels. Every region needs duplicated border padding.
Record sampler filter and wrap state as part of grouping compatibility.
Create final UVs#
Duplicate the source UV set and work on a named atlas UV. Apply transforms and final topology before unwrapping. Triangulate deterministically before final baking.
Mark seams to balance distortion, island count, hard edges, and hidden placement. Keep islands reasonably contiguous. Thousands of tiny islands increase padding waste and can create more vertex splits.
Normalize or deliberately vary texel density. Use a checker pattern to inspect stretch, orientation, and scale. Protect directional patterns, readable text, and asymmetric wear.
Pack islands within each assigned atlas region. Blender's Pack Islands tools can rotate, scale, and place islands with a margin, but a global automatic pack does not know which product features deserve more pixels.
Keep a machine-readable region rectangle and UV transform for each asset. Validate that all atlas UVs remain within their region plus intended gutter.
Calculate padding for mipmaps#
Padding must survive bilinear filtering, anisotropic sampling, compression blocks, and the full mip chain. A 2-pixel gutter at 4096 becomes 1 pixel at 2048 and vanishes soon afterward.
Extrude edge colors outward from every island and reserve space between asset regions. The required number depends on atlas size, maximum useful mip, filtering, UV derivatives, and platform. Test rather than repeating one folklore value.
As a starting experiment, 8–16 pixels at 2048 may be reasonable for ordinary islands, with larger region-level separation and additional padding for aggressive minification. Generate mips and view the asset at its farthest intended distance.
Compressed block formats introduce block alignment considerations, commonly 4 × 4 texels for many formats. Aligning regions and padding to block boundaries can avoid cross-region contamination.
Use dilation on every atlased map, including normal and scalar maps, with semantically correct edge values.
Bake each material channel correctly#
Bake or composite from original high-quality source into the atlas UV. Use a controlled cage for geometry transfer and enough supersampling for edges.
For base color, avoid baking view-dependent highlights unless the target intentionally uses baked lighting. For normal maps, use the final low mesh, final triangulation, final normals, and target tangent convention. For roughness, metallic, and occlusion, preserve linear values.
Clear unused atlas pixels to safe values: a neutral normal, plausible roughness, nonmetal, and transparent or opaque base color according to the material. Then dilate island edges.
Compare the bake at 1:1 texel view and on the model. Fix projection misses, skew, seams, and cage intersections before compression.
Save source-resolution working maps separately from delivery encodings.
Rebuild materials and mesh partitions#
Create one shared material per compatible atlas set, not necessarily one for the entire project. Assign it to all participating primitives and point each mesh to the atlas UV set.
Merge primitives only when transform, culling, collision, animation, selection, and lifecycle permit. Sharing a material can reduce state without joining meshes. Joining can further reduce draws but may keep invisible geometry submitted.
For static props visible together, one merged mesh with a shared atlas can be effective. For 100 rooms streamed independently, preserve room or cell partitions.
Keep semantic object IDs in an external table, secondary vertex attribute, or stable node structure if selection is needed. Do not rely on material slot numbers after consolidation.
Compare actual runtime draws; some engines can batch shared materials without manual mesh joining, while other scene structures still prevent it.
Treat variants and customization explicitly#
If users can recolor a surface, a mask atlas may be better than baking each color. Keep the stable material basis and apply a factor or palette. For labels or decals that vary per product, a small separate texture can preserve sharing for the main body.
Avoid generating a separate 4096 atlas for every one of 20 color variants. That multiplies download and memory. Separate invariant normal and property maps from variable color where the runtime can share them.
Document which atlas revision each mesh UV layout requires. Updating atlas placement without rebuilding dependent geometry creates silent texture mismatches.
Use immutable content hashes for both image and mesh. Publish them as one compatible set.
Encode for the target#
Choose conventional or GPU-ready encodings by material semantics and loader support. KTX 2.0 with KHR_texture_basisu can reduce GPU memory and transfer for cross-platform glTF, but it requires a configured transcoder.
Compare ETC1S and UASTC quality on normal maps, text, masks, and atlas boundaries. Block artifacts near one island can leak into padding or damage small labels.
Generate and inspect mips. Record atlas encoded bytes and decoded GPU estimate. A 4096 × 4096 RGBA atlas is about 64 MiB at the top level and roughly 85 MiB with a full mip chain before GPU block compression.
One 4K atlas is not automatically cheaper than four 1K textures in decoded uncompressed area: both contain about 16.8 million texels. The benefit comes from packing efficiency, formats, and reduced state—not the word atlas.
Validate visual and runtime results#
Calculate whether the atlas pays for itself#
Compare a representative frame before committing source work. Suppose 24 props produce 48 draws and 1.4 milliseconds of CPU submission. A shared atlas and compatible material reduce them to 18 draws and 0.9 milliseconds, while decoded texture memory rises from 36 MiB to 52 MiB. On a CPU-limited kiosk with memory margin, that trade may pass; on a memory-constrained phone already evicting textures, it may fail.
Include build and patch cost. Changing one label inside a monolithic 4096 atlas can invalidate a multi-megabyte artifact, whereas a separate 256 label updates cheaply. If products release independently, use smaller atlas groups or composable overlays.
Render source and atlas versions from fixed cameras at closest and farthest views. Inspect seams, density, orientation, normal response, labels, color, roughness, alpha, and mip transitions.
Use an exaggerated mip or UV-region visualization to expose bleeding. Test anisotropic grazing views and motion. Dark or bright lines appearing only at distance indicate insufficient dilation or region spacing.
Profile the production scene. Compare:
- draw calls and CPU submission;
- GPU frame time;
- texture bindings and shader variants;
- transfer and decoded memory;
- first upload and shader readiness;
- culling and streaming behavior;
- build size and patch size;
- variant switch cost;
- visual metric and human review;
- lower-tier device behavior.
Reject the atlas if it saves 20 draws but adds 60 MiB of always-resident texture memory or prevents effective streaming.
Texture-atlas checklist#
- Profiling shows state or submission cost worth addressing.
- Assets share visibility, material model, sampling, quality, and lifecycle.
- Atlas membership and stable region IDs are in a manifest.
- Color, scalar, normal, alpha, and channel semantics remain correct.
- Texel density follows screen value and source resolution.
- Tiling, wrap, and repeat materials have an explicit solution.
- Final topology, UVs, region bounds, and triangulation are stable.
- Padding survives mips, filtering, and compression blocks.
- Baking uses correct color spaces, cages, tangents, and safe fill values.
- Shared materials do not force harmful mesh merging or culling.
- Variants reuse invariant data and version with dependent meshes.
- Visual, memory, streaming, draw, and frame comparisons prove a net win.
Sources and further reading#
- Blender Manual: Pack Islands
- Blender Manual: baking
- Khronos glTF 2.0 specification: materials and textures
- Khronos KTX 2.0 specification
- Unity Manual: optimizing draw calls
A successful atlas groups content that really renders and ships together. Allocate texels deliberately, preserve sampling semantics, build enough padding for the whole mip chain, and keep it only when target-scene captures prove that the material sharing outweighs memory, streaming, and maintenance costs.