Texture Compression for 3D JPEG vs PNG vs KTX2 vs WebP
Textures account for the majority of a 3D file's size. This guide compares four compression formats on file size, GPU memory usage, visual quality, and compatibility so you can pick the right one for your models.
The Biggest File Size Win
On a typical 3D model, textures make up 60% to 90% of the total file size. A character with four 2048x2048 texture maps carries over 60 MB of raw pixel data before any compression. The geometry itself might be 2 MB.
Choosing the right texture format can cut your file size by an order of magnitude, slash GPU memory usage by 4x, and eliminate visual artifacts on critical maps like normals and roughness. The wrong choice wastes bandwidth, eats VRAM, or introduces shading errors that are difficult to debug.
Why Texture Compression Matters
A single uncompressed 2048x2048 RGBA texture occupies 16 MB of memory (2048 × 2048 pixels × 4 bytes per pixel). Most 3D models use multiple texture maps: a base color (albedo), a normal map, a combined metallic/roughness map, and often an occlusion map. Four maps at that resolution total 64 MB of raw data.
That number matters in two different ways. First, download size: every megabyte adds roughly 250 milliseconds on a 4G mobile connection. E-commerce platforms enforce strict limits. Shopify caps 3D models at 15 MB total. Google Merchant Center also limits GLB files to 15 MB. Apple's AR Quick Look documentation recommends staying under 10 MB for a smooth experience on iPhone.
Second, GPU memory (VRAM). Once a texture reaches the graphics card, it must be stored in a format the GPU can sample from during rendering. JPEG, PNG, and WebP all decompress to full RGBA on the GPU. A model with 64 MB of raw texture data uses 64 MB of VRAM regardless of how small the JPEG or PNG file was on disk.
GPU compressed formats solve both problems at once. They reduce file size for download and stay compressed in VRAM. This is why KTX2 exists, and why understanding the tradeoffs between these four formats matters for anyone building with 3D on the web.
How GPU Texture Loading Works
Understanding why some formats save VRAM and others do not requires a brief look at how textures get from a file on disk to pixels on screen.
When a 3D renderer loads a JPEG or PNG texture, it first decompresses the image data on the CPU into a raw pixel buffer (typically RGBA, 4 bytes per pixel). That uncompressed buffer then gets uploaded to the GPU through an API like WebGL or WebGPU. The GPU stores it in VRAM at its full uncompressed size. No matter how aggressively the JPEG was compressed on disk, the GPU holds the full 16 MB for a 2K texture.
GPU compressed texture formats work differently. Formats like BC7 (used on desktop GPUs from NVIDIA and AMD), ASTC (used on mobile GPUs from ARM and Qualcomm), and ETC2 (a baseline mobile format) are designed so the GPU can sample directly from the compressed data without decompressing it first. The texture stays compressed in VRAM and gets decompressed on the fly during rendering, one small block at a time.
This is the fundamental advantage of GPU native formats: they reduce both the download and the memory footprint. A 2K RGBA texture that costs 16 MB uncompressed uses only 4 MB with BC7 or ASTC 4x4 compression, a 4x reduction in VRAM.
The challenge is that every GPU family supports different compressed formats. Desktop GPUs speak BCn. Mobile GPUs speak ASTC or ETC2. Older hardware speaks ETC1 or PVRTC. A single compressed texture file cannot target all these platforms. This is the problem that Basis Universal and the KTX2 container were designed to solve.
JPEG: The Familiar Default
JPEG has been part of the glTF specification since version 1.0. It is one of only two image formats supported in the glTF 2.0 core spec (alongside PNG), which means every glTF viewer and engine can load JPEG textures without extensions.
JPEG uses lossy DCT (Discrete Cosine Transform) compression. It divides the image into 8x8 pixel blocks, transforms each block into frequency components, and quantizes those components to discard high frequency detail. At quality level 90, a 2048x2048 color texture typically compresses to 200 to 400 KB, a massive reduction from the 16 MB uncompressed original.
Where JPEG works well: base color (albedo) textures where minor compression artifacts blend into the surface appearance. For a product photo on a white background or a painted diffuse map, JPEG at quality 85 to 90 is often indistinguishable from lossless.
Where JPEG falls short:
JPEG does not support alpha channels (transparency). If your material needs opacity or alpha masking, you cannot use JPEG.
JPEG's block artifacts are destructive on data textures like normal maps, metallic/roughness maps, and occlusion maps. These textures encode precise numerical values (surface directions, material properties) rather than photographs. The 8x8 block quantization corrupts those values and produces visible shading errors at runtime. More on this in the normal maps section below.
JPEG offers zero GPU memory savings. The texture decompresses to full RGBA on the CPU before GPU upload.
JPEG is the default for base color textures in glTF because every viewer supports it. But it should never be used for normal maps, metallic/roughness maps, or any texture that encodes non-color data.
PNG: Lossless but Large
PNG is the other core format in the glTF 2.0 specification. It uses lossless DEFLATE compression, which means the decompressed output is bit-for-bit identical to the original. PNG also supports an alpha channel for transparency.
A 2048x2048 RGBA texture typically compresses to 500 KB to 1.5 MB in PNG format. The wide range depends on image content: textures with large flat color areas compress better than noisy, detailed surfaces.
Where PNG works well: normal maps and data textures where preserving exact values matters. Since PNG is lossless, the normal vectors encoded in each pixel survive compression perfectly. PNG is also the right choice when a texture requires an alpha channel and you are working within the glTF core spec without extensions.
Where PNG falls short:
File size. PNG files are typically 3x to 5x larger than JPEG at comparable visual quality for color textures. For a model with four 2K textures, that difference can mean 4 MB versus 1.2 MB of texture data.
Like JPEG, PNG provides no GPU memory savings. The lossless compressed data must be fully decompressed before GPU upload.
Compression speed is also a factor. PNG encoding is slower than JPEG at production quality settings. For batch processing hundreds of textures in an asset pipeline, this adds up.
PNG is the safest choice for normal maps and textures with alpha channels when you need maximum compatibility. The cost is larger file sizes.
WebP: The Modern Middle Ground
WebP was developed by Google and released in 2010. It supports both lossy and lossless compression with alpha channel support in both modes. Browser support is now effectively universal: Chrome, Firefox, Safari, and Edge all decode WebP natively.
In lossy mode, Google's own compression studies show WebP producing files 25% to 34% smaller than JPEG at equivalent visual quality measured by SSIM (Structural Similarity Index). In lossless mode, WebP files are roughly 26% smaller than PNG.
For glTF files, WebP is available through the
EXT_texture_webp vendor extension. This
is not part of the core specification, but support is
widespread: three.js, Babylon.js, glTF Viewer, and
most major engines recognize it.
Where WebP works well: as a drop-in replacement for JPEG and PNG when you want smaller downloads without changing your texture workflow. WebP's lossy mode is well suited for base color textures where a 25% to 30% file size reduction over JPEG is meaningful. WebP lossless with alpha is a strong choice for opacity maps where you need both lossless quality and transparency.
Where WebP falls short:
Like JPEG and PNG, WebP textures decompress fully on the CPU before GPU upload. A 2K WebP texture still occupies 16 MB of VRAM. The file size savings only help with download and disk storage, not with GPU memory.
WebP lossy compression uses a VP8 codec with block based quantization similar to JPEG. While the artifacts are generally less severe than JPEG, the same principle applies: lossy WebP is not ideal for normal maps or other data textures.
The EXT_texture_webp extension is not
universally supported by every glTF tool. Some
asset pipelines and older viewers may not recognize
it. The extension specification recommends including
a PNG or JPEG fallback image for compatibility.
KTX2 and Basis Universal: GPU Native Compression
KTX2 is a texture container format standardized by the Khronos Group (the same organization behind OpenGL, Vulkan, and glTF). The format was designed specifically for GPU texture data and supports mipmaps, cube maps, texture arrays, and various pixel formats in a single file.
What makes KTX2 transformative for 3D delivery is its integration with Basis Universal, a "supercompression" codec developed by Binomial and contributed to Khronos by Google in 2020. Basis Universal stores texture data in a compact intermediate representation that can be rapidly transcoded at runtime into whichever GPU native format the current device supports.
How transcoding works: when a viewer loads a KTX2 texture, a small JavaScript or WebAssembly transcoder (roughly 200 KB) detects the GPU's capabilities and converts the Basis Universal data into the appropriate hardware format. On a desktop machine with an NVIDIA or AMD GPU, it transcodes to BC7. On a modern phone with an ARM Mali or Qualcomm Adreno GPU, it transcodes to ASTC. On older mobile hardware, it falls back to ETC2. The result is a single file that works everywhere with optimal GPU memory usage on each device.
Basis Universal offers two compression modes:
ETC1S provides the highest compression. A 2048x2048 texture typically compresses to 80 to 150 KB, representing a 70% to 90% file size reduction compared to PNG. The quality is lower than UASTC, with some visible softening and loss of fine detail. ETC1S is best suited for base color and diffuse textures where the compression artifacts are masked by the surface shading.
UASTC provides higher visual fidelity at the cost of larger files. A 2K texture in UASTC mode is typically 400 to 800 KB. The quality is substantially higher than ETC1S and approaches lossless for most practical purposes. UASTC is the recommended mode for normal maps, metallic/roughness maps, occlusion maps, and any texture where accurate values matter for correct shading.
Both modes can be further compressed using Zstandard (zstd) supercompression within the KTX2 container, which provides additional savings during transfer. The zstd layer is stripped during transcoding, so it does not affect GPU memory usage.
In glTF, KTX2 textures are embedded using the
KHR_texture_basisu extension. This is a
Khronos ratified extension (the KHR prefix indicates
official standard status), giving it the highest level
of specification maturity among glTF texture
extensions.
KTX2 is the only texture format that reduces both download size and GPU memory usage. JPEG, PNG, and WebP only reduce download size.
Format Comparison Table
All file sizes below are for a single 2048x2048 RGBA texture. GPU memory is per texture after upload.
| Property | JPEG | PNG | WebP | KTX2 (ETC1S) | KTX2 (UASTC) |
|---|---|---|---|---|---|
| Compression type | Lossy | Lossless | Lossy or lossless | Lossy (GPU native) | Near lossless (GPU native) |
| Typical file size (2K) | 200 to 400 KB | 500 KB to 1.5 MB | 150 to 300 KB | 80 to 150 KB | 400 to 800 KB |
| GPU memory (2K) | 16 MB | 16 MB | 16 MB | 2 to 4 MB | 4 MB |
| Alpha channel | No | Yes | Yes | Yes | Yes |
| Normal map quality | Poor (block artifacts) | Perfect (lossless) | Fair (lossy) to perfect (lossless) | Fair | Very good |
| glTF support | Core spec | Core spec | EXT_texture_webp | KHR_texture_basisu | KHR_texture_basisu |
| Decoder overhead | None (native) | None (native) | None (native) | ~200 KB WASM | ~200 KB WASM |
| Best for | Base color, diffuse | Normal maps, alpha | All purpose, smaller downloads | Color textures, web delivery | Normal maps, data textures |
Normal Maps: Where Format Choice Matters Most
Normal maps deserve special attention because they are the texture type most sensitive to compression artifacts, and the one where choosing the wrong format produces the most visible damage.
A normal map stores surface direction vectors as RGB pixel values. The red channel encodes the X direction, green encodes Y, and blue encodes Z. These values are not colors. They are mathematical data that the shader uses to calculate lighting at each pixel. Even small deviations from the correct values produce incorrect light reflections.
Why JPEG destroys normal maps: JPEG's DCT compression divides the image into 8x8 pixel blocks and rounds frequency components to reduce file size. On a photograph, this rounding removes detail the eye barely notices. On a normal map, it changes the surface direction vectors. The result at runtime is faceted shading on surfaces that should be smooth, shimmering highlights that shift as the camera moves, and hard edges at the boundaries between compression blocks.
These artifacts are especially obvious on glossy or metallic materials where specular highlights amplify tiny errors in the surface normals. A JPEG normal map on a polished chrome surface will show a visible grid pattern under directional lighting.
Recommended formats for normal maps:
PNG is the safe default. Lossless compression preserves every normal vector exactly. The file size is larger, but the shading is correct.
KTX2 with UASTC is the best option when file size and GPU memory matter. UASTC produces high quality compressed output that transcodes to BC7 or ASTC on the GPU. The quality loss is minimal and typically invisible on most models. This is the approach recommended by the Khronos Group's KTX Artist Guide and by Don McCurdy (the author of glTF-Transform, the most widely used glTF processing library).
KTX2 with ETC1S is acceptable for normal maps on assets where file size is the top priority (such as large scene environments viewed from a distance), but expect some softening of fine surface detail.
WebP lossless preserves normal vectors exactly, like PNG, and is roughly 26% smaller. It is a good choice when KTX2 tooling is unavailable but file size still matters.
GPU Memory by Format
This table shows how much VRAM a single 2048x2048 RGBA texture consumes on the GPU after upload. The file size on disk is irrelevant here: what matters is the format the GPU stores and samples from.
| GPU Format | Bits per Pixel | VRAM for 2K Texture | Platform |
|---|---|---|---|
| Uncompressed RGBA | 32 | 16 MB | All (JPEG, PNG, WebP decode to this) |
| BC1 (DXT1) | 4 | 2 MB | Desktop (no alpha) |
| BC3 (DXT5) | 8 | 4 MB | Desktop (with alpha) |
| BC7 | 8 | 4 MB | Desktop (highest quality) |
| ETC2 RGB | 4 | 2 MB | Mobile baseline |
| ETC2 RGBA | 8 | 4 MB | Mobile baseline (with alpha) |
| ASTC 4x4 | 8 | 4 MB | Modern mobile |
| ASTC 6x6 | 3.6 | 1.8 MB | Modern mobile (lower quality) |
| ASTC 8x8 | 2 | 1 MB | Modern mobile (lowest quality) |
Basis Universal automatically selects the best GPU format during transcoding. On desktop, it targets BC7 for the highest quality. On modern mobile devices (most phones and tablets from 2017 onward), it targets ASTC. On older mobile hardware, ETC2 serves as the fallback. This selection happens automatically in the transcoder and requires no additional work from the developer.
For a model with four 2K textures, the VRAM difference is substantial: 64 MB with PNG/JPEG/WebP versus 16 MB with KTX2 using BC7 or ASTC 4x4. On mobile devices with limited VRAM, this can be the difference between smooth rendering and GPU memory pressure causing frame drops or texture eviction.
glTF and GLB Compatibility
The glTF 2.0 core specification only supports JPEG and PNG as texture image formats. Any other format requires an extension, and not all viewers implement all extensions. Here is the current state of support:
JPEG and PNG (core spec): supported by every glTF viewer, engine, and tool. No extensions needed. If you need guaranteed compatibility with every consumer of your file, use these.
KTX2 via KHR_texture_basisu:
this is a Khronos ratified extension, meaning it has
gone through the official Khronos review and approval
process. Support is strong and growing: three.js
(KTX2Loader), Babylon.js, PlayCanvas, Google's
model-viewer, Cesium, glTF-Transform, and both Unity
and Unreal Engine (via plugins). The three.js
implementation requires loading the Basis Universal
WASM transcoder, which is roughly 200 KB.
WebP via EXT_texture_webp:
this is a vendor extension (EXT prefix). It is
supported by three.js, Babylon.js, and model-viewer.
The specification recommends providing a fallback
PNG or JPEG image for viewers that do not support the
extension. Support is less universal than
KHR_texture_basisu.
When building GLB files for the broadest possible audience, a practical approach is to include KTX2 textures as the primary source with PNG/JPEG fallbacks defined in the base texture slot. Viewers that understand KHR_texture_basisu will use the KTX2 textures. Viewers that do not will fall back to the PNG/JPEG versions. The glTF extension mechanism is designed to support exactly this pattern.
Tooling and Conversion Workflows
Converting textures between formats requires different tools depending on the source and target format.
glTF-Transform is the most versatile command line tool for glTF texture work. Created by Don McCurdy, it can convert textures within a glTF or GLB file to KTX2 (both ETC1S and UASTC modes), WebP, AVIF, or standard PNG/JPEG. A single command can convert all textures in a file:
gltf-transform ktx2 input.glb output.glb
The tool automatically selects ETC1S for base color textures and UASTC for normal maps by default. You can override this behavior per texture type.
KTX Software is the Khronos reference
toolset for working with KTX2 files directly. The
toktx command converts PNG or JPEG source
images into KTX2 containers with configurable Basis
Universal settings.
gltfpack (part of meshoptimizer) can apply Basis Universal texture compression alongside geometry compression. It is useful when you want to compress both textures and geometry in a single pass.
Blender supports exporting glTF/GLB with KTX2 textures through the built in glTF exporter. Under Format > Images, select "KTX2 / Basis Universal" to automatically convert texture images during export.
Online tools like Polyforge can convert between 3D formats and apply optimization including texture compression. This is useful when you need a quick conversion without installing command line tools.
Which Format Should You Use?
The right choice depends on what you are building and what tradeoffs you can accept.
For web delivery (product viewers, web apps, e-commerce): use KTX2 with ETC1S for base color textures and UASTC for normal maps and data textures. This gives you the smallest file sizes and the lowest GPU memory usage. Include PNG/JPEG fallbacks in the glTF for maximum compatibility.
For broad compatibility (sharing files with clients, uploading to platforms): use JPEG for base color textures and PNG for normal maps and alpha textures. Every glTF viewer supports these formats without extensions. When platform limits are tight, switch JPEG to WebP via EXT_texture_webp for a 25% to 30% size reduction.
For mobile and AR: use KTX2 with ASTC transcoding. Mobile devices have the most constrained VRAM budgets, so GPU compressed textures have the largest impact here. Apple's AR Quick Look viewer for USDZ files uses its own compression pipeline, but GLB files served through model-viewer or custom WebGL benefit significantly from KTX2.
For game development: most game engines (Unity, Unreal, Godot) handle texture compression internally during their import pipeline. If you are preparing assets for a game engine, export textures as PNG (lossless source) and let the engine's importer handle format-specific compression for each target platform.
For archival and source assets: always keep lossless originals (PNG or TIFF). Generate compressed versions (KTX2, WebP, JPEG) as part of your build or export pipeline. Never discard the lossless source. You cannot recover quality once lossy compression has been applied.
When in doubt: PNG for source assets, KTX2 for web delivery, JPEG for maximum compatibility. Avoid JPEG on normal maps in every scenario.
FAQ
What texture format should I use for glTF and GLB files?
The glTF 2.0 core specification supports only PNG and JPEG. For better compression, use the KHR_texture_basisu extension to embed KTX2 textures with Basis Universal compression, or the EXT_texture_webp extension for WebP. KTX2 provides the best combination of file size reduction and GPU memory savings.
Why should I avoid JPEG for normal maps?
JPEG uses lossy DCT block compression that introduces artifacts at 8x8 pixel block boundaries. On normal maps, these artifacts corrupt surface direction data and cause visible lighting errors: faceted shading on smooth surfaces, shimmering under moving lights, and hard edges where the geometry is actually curved. Use PNG (lossless) or KTX2 with UASTC compression for normal maps instead.
Does KTX2 reduce GPU memory usage?
Yes. KTX2 textures stay compressed in GPU memory using hardware formats like BC7 on desktop or ASTC on mobile. A 2048x2048 RGBA texture uses 16 MB uncompressed but only 4 MB with BC7 or ASTC 4x4 compression. JPEG, PNG, and WebP all decompress to full uncompressed RGBA in GPU memory, so they offer no VRAM savings despite being smaller on disk.
What is the difference between ETC1S and UASTC in KTX2?
ETC1S and UASTC are two compression modes within the Basis Universal codec used by KTX2. ETC1S produces much smaller files (typically 80 to 150 KB for a 2K texture) and is best for color and diffuse textures where some quality loss is acceptable. UASTC produces larger files (400 to 800 KB) but with significantly higher visual fidelity, making it the right choice for normal maps, roughness maps, and other data textures where accuracy matters.
How does WebP compare to KTX2 for 3D textures?
WebP reduces download size by 25% to 34% compared to JPEG at equivalent quality, but like JPEG and PNG, it must be decompressed to full RGBA before uploading to the GPU. KTX2 textures stay compressed on the GPU, using 4 to 8 times less video memory. WebP is a good middle ground when KTX2 tooling is not available or when you need broad compatibility without extensions.
Do all browsers support KTX2 textures?
KTX2 decoding happens through Basis Universal transcoders in JavaScript or WebAssembly, not through native browser APIs. Any browser that supports WebGL (Chrome, Firefox, Safari, Edge) can use KTX2 textures, as the transcoder converts them to whichever GPU compressed format the device supports: BC7 on desktop, ASTC on modern mobile, or ETC2 as a fallback. The transcoder library is approximately 200 KB.