viewhack

Game textures: DDS, TGA, EXR and HDR

Open a .dds from a game, mod or engine and see its DXGI format, every mip level and every face of a cube map, one channel at a time. A .tga, an OpenEXR .exr or a Radiance .hdr opens too, with exposure for the floating-point ones. Save what you are looking at as a PNG.

Everything is decoded by this tab on your device. The file is never uploaded.

What it shows

Reading a DDS header

A DDS file is the four bytes DDS followed by a 124-byte header: height, width, mip count, a pixel-format block and the capability flags that mark a cube map and which of its six faces are present. The pixel-format block either holds bit masks (an uncompressed layout such as A8R8G8B8) or a FourCC: DXT1 is BC1, DXT3 BC2, DXT5 BC3, ATI1 BC4 and ATI2 BC5. Formats newer than Direct3D 9, BC6H and BC7 among them, need the FourCC DX10 and a further 20-byte header that names a DXGI format and adds an array size and an alpha mode.

The pixels follow in a fixed order: for each array element (or cube face, in the order +X, −X, +Y, −Y, +Z, −Z), mip 0 first and then each smaller mip down to 1 × 1. Worked example: a 1024 × 1024 BC1 texture is 256 × 256 blocks of 8 bytes, 524,288 bytes; its ten smaller mips add 174,776 more (a third, as always), so with the 128-byte header the file is 699,192 bytes. The same texture in BC3 or BC7, at 16 bytes a block, is twice that. If a file is shorter than its header promises, the page lists which surfaces are missing and draws the ones that are there.

Block compression in one list

Why a BC5 normal map looks red and green: only X and Y are stored, and the shader rebuilds Z as √(1 − x² − y²). The page does the same and puts Z in blue, so the picture looks like the familiar lilac normal map; the probe still shows the two stored values.

TGA: no signature and two origins

A TGA starts with an 18-byte header and no magic number, so a TGA under the wrong name looks like random bytes. TGA 2.0 files end with a 26-byte footer that does carry one, TRUEVISION-XFILE., and that is how this page recognises them; a TGA 1.0 file is recognised by a plausible header when it is named .tga. Rows are stored bottom to top unless bit 5 of the image descriptor says otherwise, which is why a badly written reader shows TGAs upside down. RLE files are a series of packets: a byte whose top bit marks a run (one pixel repeated up to 128 times) or a raw stretch.

The low 4 bits of the descriptor give the number of alpha bits. When a 32-bit file declares 0, the fourth byte is ignored, as the format says. When it declares alpha but every alpha value is 0 (a common bug in exporters), the picture is shown opaque and the facts say so, rather than showing you an empty canvas.

EXR and HDR: exposure, curves and luminance

HDR pixels are linear light values that can go far above 1.0 (a sky can hold 50,000). To show them on an 8-bit screen the page multiplies by 2 to the power of the exposure, then either clamps at 1.0 or applies an ACES-like filmic curve (Krzysztof Narkowicz’s 2016 fit) that rolls highlights off instead of cutting them, then encodes for an sRGB display. Auto exposure puts the mean luminance at 18 % grey. Luminance is Y = 0.2126 R + 0.7152 G + 0.0722 B (Rec. 709 primaries); NaN and infinite pixels are counted and left out of the minimum, maximum and mean.

A Radiance .hdr stores each pixel as three 8-bit mantissas sharing one exponent (RGBE), a quarter of the size of 32-bit floats but only about 2 significant digits per pixel. Its EXPOSURE= line records a factor the values were already multiplied by; it is reported, not undone. An OpenEXR keeps 16- or 32-bit floats per channel: PIZ (wavelet) and ZIP are lossless, PXR24, B44 and DWA are lossy.

What it cannot do