Texture compression (KTX2 / Basis)
Storing textures in a format the GPU reads while still compressed, so a 4K image eats a fraction of the VRAM instead of unpacking.
See it
What it is
A JPEG is compressed on disk and nowhere else. The moment it reaches the GPU it is raw pixels: a 4096 by 4096 texture becomes 64MB of VRAM, plus another third for mipmaps, no matter how small the file was. GPU texture formats (BC7 on desktop, ASTC and ETC2 on mobile) stay compressed in memory and get decoded per texel as the GPU samples them, typically 4x to 8x smaller, and they never get expanded into raw pixels the way a JPEG does.
The problem used to be that every platform wanted a different format. KTX2 with Basis Universal solves that: you ship one container that transcodes at load time into whatever the device actually supports. Two flavors matter. ETC1S is tiny and lossy, good for base color and other content that tolerates a bit of mush. UASTC is much bigger but keeps detail, which is what you want for normal maps, sharp edges, clean gradients, and anything where an artifact would be obvious. Noise is not the easy case, by the way: high-frequency grain is exactly what ETC1S smears. Encode with toktx, basisu, or gltf-transform, and load in three.js via KTX2Loader with the transcoder wasm files copied next to your build.
Gotcha: a KTX2 file can be larger on disk than the JPEG it replaced, and people see that and revert. The win is VRAM and time to first frame, not always transfer size, and the transcode is not free either: Basis files unpack on the CPU into whatever native format the device supports, which costs real milliseconds on a mid-range phone. Second gotcha: block compression works on 4 by 4 pixel blocks, so ETC1S on a normal map produces blocky lighting garbage, and dimensions want to be multiples of four with a clean mip chain. WebGL2 does not require powers of two, but block alignment, mip levels, older compatibility targets, and memory efficiency all still push you toward tidy sizes.
Ask AI for it
Convert this glTF model's textures to KTX2 with Basis Universal and load them in three.js. Use gltf-transform: ETC1S (quality around 255) for base color, emissive, and other forgiving color maps; UASTC for normal maps, sharp detail, gradients, and artifact-sensitive data like roughness and metalness. Generate mipmaps, resize anything over 2048px, and keep dimensions multiples of four with a clean mip chain. On the loading side, wire up KTX2Loader with setTranscoderPath pointing at the copied basis wasm files and call detectSupport(renderer) before loading. Report the VRAM before and after, plus the transcode time on a mid-range phone.