Choosing texture formats for WebGL and WebGPU applications
A supplement to web.dev's Choosing the Right Image Format, adding context for texture formats used in WebGL and WebGPU development. If you aren't familiar with newer image formats like WebP or AVIF, consider reading the web.dev article first.
Image formats (PNG, JPEG, WebP, AVIF)
Most image formats used on the web are optimized for two goals:
- minimize file size transferred over a network
- maximize image quality
As of 2024, that generally means using WebP and AVIF for modern browsers, or PNG and JPEG for older browsers.[1] But when building for WebGL and WebGPU APIs, where high framerates and memory budgets are hard constraints, several more goals should be considered:
- avoid framerate stalls when the texture loads
- minimize memory cost
- maximize application performance
When users can do more than just look at an image, spending significant time in a 2D or 3D application — games, immersive AR/VR experiences, or interactive storytelling — framerate stalls can be disastrous. Most image formats (including all formats mentioned above) must be decompressed on the CPU, then uploaded without compression to GPU memory. One 4096x4096px image with mipmaps consumes 90 MB of memory, regardless of how small the original file might have been.
uncompressedSize = <width> ⨉ <height> ⨉ 4 ⨉ 1.333
On many platforms, RGB textures are expanded to RGBA for compatibility reasons, so it's safer to assume all four channels are stored in memory. Multiply by 1.333 to include mipmaps.
Decompression and upload prevent WebGL from doing other work, a very common source of dropped frames. In WebXR, users experience dropped frames as an unexpected lurch and a major cause of motion sickness.
Additional context for three.js users
By default, three.js decompresses images and uploads them to the GPU the first time the image is rendered in the scene. To upload the image sooner, use WebGLRenderer
's .initTexture()
method. Using ImageBitmapLoader
can take the decompression step off the main thread, reducing dropped frames somewhat.
Specialized GPU texture formats (ETC, ASTC, BC1–7, PVRTC)
To avoid these issues, GPUs are designed with built-in support for specialized image formats, called “GPU texture formats.” In these formats, pixel data remains compressed on the GPU, and each pixel is decompressed (with no meaningful performance cost) each time a shader samples the texture. No decompression is required before GPU upload, memory cost is reduced by 4–8x, and uploads complete 4–8x faster.
Inconveniently, different platforms and GPUs require different GPU texture formats. Historically, game developers supporting multiple platforms ship different compressed texture formats — ETC, ASTC, BC1–7, or PVRTC — for different platforms.
While this strategy would also work on the web, it can be burdensome for individual developers and small teams, and these textures tend to be larger files. And while GPU texture formats can provide excellent quality with the right format choices and compression settings, they're much less forgiving about configuration and quality than traditional image formats. Careful tuning of compression parameters should be expected.
Universal GPU texture formats (Basis ETC1S and UASTC)
Texture compression became far more practical in 2019, when Binomial (in partnership with Google) released the open source Basis Universal family of compression codecs. These codecs are designed to be efficiently transcoded (not decompressed) at runtime into a variety of GPU texture formats. Any modern device receiving Basis Universal textures can load and display them efficiently.[2]
The Basis Universal family includes two codecs:
- ETC1S: Also called “BasisLZ.” Low/medium quality, with small file sizes comparable to JPEG. Default choice in most cases. ETC1S works well on color textures, but not as well on data textures like normal maps.
- UASTC: Higher quality, comparable to BC7. Quality is sufficient for both color and data textures, including normal maps. UASTC data is larger than ETC1S, but benefits from an additional pass of lossless compression (”supercompression”). Most KTX2 compressions apply Zstandard compression[3] on top of UASTC, producing compressed files 1-2x larger than JPEG or ETC1S textures with the same resolution.
ETC1S and UASTC are low-level codecs, not file formats. To create files using either codec, use the KTX2 file format.[4] KTX2 is a thin container, carrying required metadata like texture dimensions and color spaces along with compressed or uncompressed pixel data. KTX2 supports Basis Universal codecs and many GPU texture formats.
Like GPU texture formats, Basis Universal formats require more careful compression settings than traditional image formats. The same options will not work for all models, but do generalize per material slot. For example, one set of compression options works well for diffuse color textures, another set of options works well for normal maps.
To get started with KTX2 and Basis Universal, see the KTX2 Artist Guide and KTX-Software project from the Khronos Group.
How to choose
When First Contentful Paint is the top priority, minimize file size by using modern image formats like WebP and AVIF. Simple 2D/3D visuals, like a landing page graphic, will probably fall into this category.
When users will spend more time with the application, or the application is loading a lot of textures interactively, then the advantages of KTX2 textures with Basis Universal compression are significant. In this situation, KTX2 should often be preferred over traditional image formats.
When it's most important for textures be compatible with many different applications, or to keep original image quality, stick to JPEG and PNG. Libraries of reusable textures and 3D models are good examples of this situation.
glTF 3D models and image compatibility
All glTF-compliant software supports JPEG and PNG textures. WebP support requires that a glTF loader implement the EXT_texture_webp
extension. AVIF support remains an early proposal to glTF. Support for KTX2 with Basis Universal requires the KHR_texture_basisu
extension. All of these are implemented by three.js's THREE.GLTFLoader, but web browser support for WebP and AVIF is also required, and varies:
Install the glTF Transform CLI to apply any of these compression methods to existing glTF 3D scenes.
LDR vs. HDR texture formats
Formats discussed throughout this article support values in the range 0–1, and (for our purposes) are limited to 8-bit precision. These “LDR” formats are appropriate for most texture roles in physically-based materials, including albedo/diffuse/base color, normal maps, occlusion, roughness, metalness, and almost anything else.
Certain texture roles benefit from having values >1, especially environment maps or image-based lighting (“IBL”). Light maps and emissive maps are often in this category as well. These are commonly referred to as high dynamic range (“HDR”) textures.
Common HDR formats include OpenEXR and HDR, which support higher precision and lossless compression. They're commonly used in VFX and film, but must be used sparingly on the web due to their larger size.
KTX2 supports HDR data, but the Basis Universal codecs do not. Available HDR formats in KTX2 are either uncompressed on the GPU (like RGBA16 or RGBA32) or supported only on a subset of devices (like BC6H). Tooling for BC6H compression is mostly targeted at game developers, and is unfortunately non-trivial for most web developers to add in their build and optimization systems.
No universal GPU texture format supports HDR data today. Using HDR formats in WebGL and WebGPU remains fairly difficult to optimize, and no clear best practice exists. When HDR textures are required, limit their resolution as much as possible. If you know of easy-to-use tools for creating BC6H textures, or better alternatives, please reach out!
For more help choosing among common texture formats, refer to glTF Texture Formats from the KTX2 Artist Guide.
1 Often we use SVGs and icon fonts instead of images, too. But that's less applicable with 3D graphics APIs, and outside the scope of this article.
2 Basis Universal transcoders select a target GPU texture format based on the current device's capabilities. The specific target format chosen is generally an implementation detail, but if you're looking for those details, see the Khronos Group's KTX2 WebGL Developer Guide.
3 Loaders can decode Zstandard supercompression off the main thread, and UASTC pixel data is still 4x smaller than JPEG or PNG, so Zstandard doesn't significantly reduce the memory benefits of Basis Universal formats.
4 When working with Basis Universal compression, you may see .basis
file extensions. This is a similar container format to KTX2, specifically for Basis Universal, and the compressed texture data will be identical. KTX2 is the standardized container format, provides additional texture formats and supercompression, and is required to use Basis Universal in glTF files.