Field Guides

How to Optimize Textures for Real-Time 3D Models

Audit, resize, repack, encode, mip, deliver, and validate real-time texture sets using projected detail, decoded memory, color semantics, and target GPU evidence.

Textures often dominate both GLB transfer size and runtime memory. The number printed beside a PNG or JPEG does not reveal its GPU cost: a small encoded file expands when decoded, gains mip levels, and may be uploaded in an uncompressed format. Optimization must track visual value, network bytes, decode or transcode time, and GPU residency separately.

Start with an inventory and fixed visual references. Never resize or recompress the only source images.

Define the texture contract#

Record the target engines or web loaders, lowest devices, supported image and GPU formats, normal-map convention, material model, closest camera, render resolution, and quality tiers.

Set budgets for:

  • initial texture transfer bytes;
  • full-detail transfer bytes;
  • decoded CPU peak;
  • resident GPU memory;
  • maximum dimensions by semantic and tier;
  • texture count and material bindings;
  • first transcode and upload time;
  • acceptable visual error;
  • mip behavior at distance;
  • fallback for unsupported formats.

A 2048 label on a fullscreen product may be necessary; the same 2048 image on a 40-pixel mobile prop is not.

Build a semantic inventory#

For every image record path or buffer view, content hash, dimensions, aspect ratio, channels, bit depth, encoded format and bytes, material slots, UV set, sampler, wrap, color space, and expected alpha.

Classify base color, emissive, normal, metallic, roughness, occlusion, height, opacity, clearcoat, transmission, masks, lightmaps, environment maps, and lookup data. Do not infer semantics only from names.

Map reuse. One image referenced by 5 materials should not be counted 5 times in transfer, though it can create separate sampling states. Conversely, byte-identical images embedded twice waste package bytes.

Flag missing, unused, duplicate, 1-pixel, extreme aspect-ratio, non-power-of-two where the target cares, 4096 or 8192, and opaque images carrying alpha.

Calculate decoded memory#

For an uncompressed 8-bit RGBA texture, top-level bytes are width × height × 4. A 2048 × 2048 image is 16 MiB; 4096 × 4096 is 64 MiB; 8192 × 8192 is 256 MiB. A full mip chain adds roughly one third, yielding about 21.3, 85.3, and 341.3 MiB respectively.

RGB source images may still upload as 4-channel GPU resources depending on the API and format. GPU block compression changes the resident calculation, but its rate depends on the chosen device format.

Sum all simultaneously resident textures, including environment maps, shadow maps, lightmaps, post-processing targets, and other scene assets. Texture memory does not belong to one GLB in isolation.

Record peak CPU memory during image decode or KTX transcode. Several large images decoded concurrently can exceed a mobile memory limit before upload.

Measure projected texel need#

Determine the largest useful screen footprint for each surface. Compare texture resolution with UV coverage and camera distance. A 2048 image whose important label uses 10% of width gives that label about 205 pixels before projection and filtering.

Use a texel-density visualization. Allocate more density to faces, labels, controls, fabric weave, and product-defining marks; less to uniform hidden surfaces. Avoid uniform resolution policy when visual value is not uniform.

Check source sharpness. Upscaling a blurry 1024 photo to 4096 increases memory by 16 times without creating captured detail.

For each candidate size, render the closest approved view at target pixel ratio. Compare 4096, 2048, 1024, and 512 outputs. Stop when the lower tier changes a protected feature, not when a generic ratio says so.

Improve UV efficiency#

Measure how much of the image contains used islands and how much important surface area those islands represent. Fragmented UVs and generous empty space reduce effective resolution.

Repack only when the mesh can be rebaked and all dependent maps can change together. Normalize texel density with deliberate exceptions. Keep seams away from focal and deforming regions. Add padding for mips and compression blocks.

Use atlases for assets that render, stream, and share materials together. Do not atlas unrelated assets just to increase occupancy. Texture arrays or separate images may preserve tiling and streaming better.

For repeating surfaces, retain tiled textures if baking uniqueness would multiply required texels. Separate a unique label or dirt overlay instead of making the whole wall unique.

Version geometry UVs and texture layout as one compatible release.

Remove redundant data#

Deduplicate exact images. Remove unreferenced images and material slots. Strip alpha only when all pixels are opaque and the material does not use it. Crop unused borders while updating UV transforms safely.

Replace large uniform maps with material factors. A solid roughness image can become one scalar. A constant normal map can be removed. Small palette, mask, or lookup textures may replace many nearly identical color variants.

Channel-pack compatible grayscale signals according to the runtime convention, such as occlusion, roughness, and metallic. Confirm independent color-space treatment and sampling. Do not pack unrelated signals that need different UVs, resolutions, or filtering.

Avoid storing the same detail in geometry, normal, height, and base color without purpose.

Apply correct color spaces#

Base color and emissive normally represent color and use the appropriate nonlinear transfer into linear shading. Normal, metallic, roughness, occlusion, masks, and most height data are numeric signals and must not receive color gamma.

An incorrect color-space tag changes material response. Roughness read through color conversion can become too glossy or too matte. Normal components changed by gamma no longer describe the intended direction.

Keep high-precision working files in a documented color space. Apply display transforms only for viewing. Export delivery images according to the renderer's material contract.

Test wide-gamut or HDR sources deliberately; do not silently clip or reinterpret them through an 8-bit sRGB path.

Treat normal maps as data#

Confirm tangent-space versus object-space and the green-channel convention expected by the target. Bake against final low topology, UVs, triangulation, normals, and tangent basis.

Inspect seams, mirrored islands, skew, and compression blocks under grazing light. Resize normal maps with vector-aware filtering and renormalize texels. Ordinary color resizing can shorten or bias normals.

Use a flat neutral normal outside islands and semantic dilation at borders. Lossy color compression can create lighting dents, especially at low quality.

Sometimes additional vertices are cheaper and cleaner than a very high-resolution 16-bit normal map. Profile the actual mobile renderer rather than assuming texture detail always beats geometry.

Choose encodings by signal and platform#

PNG provides lossless storage and alpha but can decode to large GPU resources. JPEG is useful for suitable opaque color imagery but lacks alpha and can damage sharp or data signals. WebP or AVIF support depends on the image-loading and runtime path and may still upload uncompressed.

KTX 2.0 is designed to carry GPU texture data and can use Basis Universal supercompression. In glTF, KHR_texture_basisu connects those textures to materials. The loader must transcode to a supported GPU format.

ETC1S generally targets smaller distribution; UASTC generally targets higher quality at larger size. Test both on base color, normals, text, masks, and alpha. Adjust rate-distortion settings and supercompression using the current tool documentation.

Record encoder version and full settings. Two quality numbers from different codecs are not equivalent.

Generate and validate mipmaps#

Mipmaps reduce aliasing and improve sampling when a texture is smaller on screen. Generate the full useful chain with semantic filtering. Preserve alpha coverage for cutout materials where the pipeline supports it.

Inspect every level for color shifts, normal weakening, disappearing labels, alpha halos, and atlas bleeding. Padding must extend far enough to survive lower mips. An 8-pixel gutter at 2048 is only 1 pixel by the 256 level.

Use anisotropic filtering selectively for grazing surfaces. Higher anisotropy improves clarity but adds sampling work.

Do not disable mips to make one screenshot sharper. Motion shimmer and cache behavior usually become worse at normal viewing distances.

Stage texture loading#

Separate an initial recognizable tier from optional close detail. Load a small base or lower mip range first when the runtime supports streaming, then refine without showing an empty or incorrect material.

Prioritize visible, identity-bearing textures. A product's base color and label may precede underside normal detail. Cancel obsolete requests when a variant or model changes.

Use immutable hashed URLs, long caching for artifacts, correct MIME and CORS headers, and a service-worker policy that does not retain every catalog texture forever.

Prewarm shared transcoders before first critical use when user intent justifies it. Measure cold and warm paths separately.

Control variants and reuse#

Share invariant normal, roughness, and occlusion maps across color variants. Express simple colors as factors or a palette mask rather than copying entire texture sets.

For 12 colors with 4 separate 2048 maps each, naive duplication can create 48 images. If only base color varies, reuse 3 invariant maps and load one of 12 color maps—or use a tintable neutral base where physically and artistically correct.

Keep variant IDs and material mapping stable. Dispose textures only when no active consumer uses them. Test rapid variant changes for races and memory growth.

Avoid sharing a mutable texture object when one consumer changes sampling or transform state for all others.

Compare candidates quantitatively and visually#

Example texture ledger#

Consider a GLB with 1 base-color map at 4096, 1 normal map at 4096, and metallic, roughness, and occlusion maps at 2048. The encoded images total 13.4 MB, but their uncompressed top levels represent 64 + 64 + 16 + 16 + 16 = 176 MiB before mips.

After inspecting UVs, keep base color at 2048, reduce the normal to 2048, pack the 3 scalar signals into one 1024 RGB image, and remove opaque alpha. The uncompressed top-level area becomes about 16 + 16 + 4 = 36 MiB, nearly an 80% reduction. A target GPU block format can reduce residency further.

Approve the result only after checking label clarity, grazing normal response, and roughness masks. If the normal loses a protected embossed logo, move that detail into geometry or allocate a focused region instead of restoring every source map to 4096.

Measure the cold KTX2 transcode and upload. The new ledger should show transfer, working peak, residency, and first-use time beside visual approval.

Repeat the ledger at maximum production population. Shared images should count once when the renderer truly reuses them, while per-instance render targets, video textures, or dynamically generated maps must be counted according to their actual allocation.

For every texture set report encoded bytes, decoded or GPU estimate, transcode and upload time, dimensions, format, mip count, and quality settings.

Render fixed cameras under neutral and production light. Inspect protected details at 100% display, closest normal camera, and distant motion. Use perceptual image differences as a triage aid.

Test:

  • text and logos;
  • facial or product identity;
  • normal response at grazing angles;
  • roughness highlight width;
  • metal and emissive regions;
  • alpha and cutout edges;
  • seams and mirrored UVs;
  • lower mips and motion;
  • bright and dark environments;
  • lower-tier device decode and memory.

The smallest network set can lose if it adds a 900-millisecond transcode stall or visible normal artifacts.

Texture optimization checklist#

  • Runtime, devices, camera, formats, normal convention, and budgets are explicit.
  • Every image has semantic, dimension, channel, color, UV, sampler, and byte metadata.
  • Encoded, decoded, mip, GPU, and peak working memory are distinguished.
  • Projected texel need and UV occupancy direct resizing.
  • Duplicate, unused, constant, opaque-alpha, and redundant data are removed.
  • Atlas, tiling, arrays, channel packing, and variants follow real lifecycle needs.
  • Color and numeric signals use correct transfer behavior.
  • Normal maps match final topology, tangent basis, resize, and compression rules.
  • Codec and KTX2 choices match configured platform loaders.
  • Mips, gutters, alpha coverage, and anisotropy pass distance tests.
  • Loading tiers, cancellation, hashing, caching, and disposal are verified.
  • Quantitative and visual comparisons pass target devices.

Sources and further reading#

Texture optimization allocates pixels and formats to visible evidence. Inventory semantics, calculate real memory, improve UV use, encode color and data correctly, preserve mips, and choose the candidate that improves acquisition and residency without degrading the material under target lighting.

Keep learning

Related guides

Field Guides12 min

The Production 3D Asset Optimization Checklist

Use a complete release checklist for source preservation, geometry, shading, textures, animation, compression, accessibility, SEO, delivery, profiling, and rollback.

3d optimizationrelease checklistGLB
Field Guides10 min

How to Reduce a 20 MB GLB to Under 1 MB

Use an evidence-driven geometry, texture, structure, and compression workflow to shrink a large GLB below 1 MB without approving invisible damage.

GLB optimizationfile sizemesh compression
Field Guides12 min

Preparing a 3D Model for Printing: Manifold Geometry, Wall Thickness, and Watertight Meshes

Learn what makes a mesh physically printable: watertight manifold geometry, consistent normals, merged shells, real wall thickness, and correct scale.

3d printingmanifold geometrywatertight mesh