Skip to content

Texture2D

A Texture2D (class 28) stores a width, height, a pixel format, a chain of mip levels and the sampler settings the engine uses. The pixels are either inside the object (image data) or streamed from a .resS (StreamingInfo). Observed

Field Meaning Confidence
m_Width, m_Height size of level 0 Observed
m_TextureFormat Unity’s TextureFormat id (table below) Observed
m_MipCount number of levels Observed
m_ColorSpace 1 sRGB, 0 linear Observed
m_LightmapFormat the texture usage mode; 3 = a normal map stored as DXT5nm, 4 = a normal map stored as plain RGB Observed
m_TextureSettings.m_FilterMode 0 point, 1 bilinear, 2 trilinear Observed
m_TextureSettings.m_WrapU, m_WrapV 0 repeat, 1 clamp, 2 mirror, 3 mirror once Observed
m_TextureSettings.m_MipBias added to the level of detail the texture is sampled at Observed
m_TextureSettings.m_Aniso anisotropic filtering level; above 1 means anisotropic Observed
m_StreamData offset, size, path of the pixels in a stream file; empty (or size 0) means the pixels are in image data Observed

The levels follow each other, largest first, with no padding: level i is max(1, width >> i) by max(1, height >> i) pixels, and the levels’ sizes add up to the whole of the pixel data. Observed · Sledding A reader can therefore reject a texture whose stated sizes do not match its data. Observed

Rows are stored bottom first. Unity’s texture origin is the bottom left, so row 0 is v = 0. An image-space (top-left origin) consumer flips the rows; for uncompressed and BC1 to BC5 whole-block levels that is an exact in-place swap of rows and block rows, for others a decode. Observed

The TextureFormat ids met in the games, and whether they decode exactly. A census read the m_TextureFormat of every Texture2D object of every serialized file and bundle of the four installs (SkateBIRD’s 31 loose files, Sledding Game’s 7 loose files and 50 Addressables bundles, PEAK’s 13 loose files and 7 level bundles, Big Walk’s data.unity3d): 259,045 objects, none refused. Which ids occur is Proven for those builds; the counts below are objects, not distinct images.

Id Format Bytes SkateBIRD Sledding Game PEAK Big Walk Confidence
1 Alpha8 1 per pixel 82 67 467 93 Proven
2 ARGB4444 2 per pixel 0 0 0 0 Proven that none occurs
3 RGB24 3 per pixel 9 11 280 14 Proven
4 RGBA32 4 per pixel 31 81 864 93 Proven
5 ARGB32 4 per pixel 0 0 8 17 Proven
9 R16 2 per pixel 0 0 0 1 Proven
10 DXT1 (BC1) 8 per 4×4 block 501 548 1,881 86 Proven
12 DXT5 (BC3) 16 per 4×4 block 486 881 3,957 841 Proven
13 RGBA4444 2 per pixel 0 0 0 0 Proven that none occurs
15 RHalf 2 per pixel 0 0 0 2 Proven
17 RGBAHalf 8 per pixel 0 0 0 1 Proven
19 RGFloat 8 per pixel 0 0 0 2 Proven
20 RGBAFloat 16 per pixel 0 0 0 4 Proven
24 BC6H 16 per 4×4 block 0 0 123,848 2 Proven
25 BC7 16 per 4×4 block 0 4 27 4 Proven
26 BC4 8 per 4×4 block 0 0 123,848 1 Proven
27 BC5 16 per 4×4 block 0 0 0 0 Proven that none occurs
63 R8 1 per pixel 0 1 0 0 Proven
28 crunched DXT1 variable 0 1 0 1 Proven
29, 64, 65 crunched DXT5, ETC, ETC2 variable 0 0 0 0 Proven that none occurs

No other id occurs in the four installs, and every texture is a plain 2D one (m_TextureDimension 2). Proven · SkateBIRD, Sledding, PEAK, Big Walk For every texture of a format in the table other than the crunched ones, m_CompleteImageSize equals the sum of the levels’ sizes at the format’s bytes per pixel or block (258,915 textures); the only 128 that differ are empty 0×0 font atlases of size 0. Proven · SkateBIRD, Sledding, PEAK, Big Walk The ids and names are those of Unity’s TextureFormat enum. Sourced · Unity scripting reference

Crunched formats (Unity’s own compression layer) need Unity’s crunch decoder: Sledding Game’s Fish (2048², 12 levels) and Big Walk’s Metal032_2K_Roughness are the only two crunched textures in the four installs; SkateBIRD and PEAK have none. Proven · SkateBIRD, Sledding, PEAK, Big Walk

Every Texture2D of Risk of Rain 2 (its loose files and its Addressables bundles, 10,816 textures) and of Schedule I (3,104) has the name, format, size, level count and pixel byte count an independent type tree reader gives it, and its levels split to exactly those bytes. Proven · RoR2, Schedule I The formats they hold, in textures:

Id Format Risk of Rain 2 Schedule I
1 Alpha8 4,264 79
2 ARGB4444 34 34
3 RGB24 187 21
4 RGBA32 520 63
5 ARGB32 5 1
9 R16 1 0
10 DXT1 (BC1) 1,766 899
12 DXT5 (BC3) 3,925 1,921
19 RGFloat 0 6
20 RGBAFloat 0 4
24 BC6H 0 2
25 BC7 79 72
26 BC4 2 0
27 BC5 1 0
28 crunched DXT1 2 2
29 crunched DXT5 30 0

Proven · RoR2, Schedule I Both games’ 34 ARGB4444 textures are those of unity default resources, as in the installs above. Observed · RoR2, Schedule I

A crunched texture (id 28, 29; Unity 2017.3 and later) stores one crunch file in place of its levels: a header that begins Hx and names the block format, Huffman-coded palettes of endpoints and selectors, and per level a stream of tiles that index them. Unpacking gives the DXT1 or DXT5 blocks of every level. Sourced · Unity’s crunch fork An unpacker written from the Rust port of that decoder gives, for each of the 36 crunched textures of Risk of Rain 2 and Schedule I, a first level that decodes to the same RGBA as UnityPy’s decoding of the crunch file, and every level the size the texture’s format and dimensions give. Proven · RoR2, Schedule I UnityPy’s decoder is a build of the same fork, so the agreement shows the unpacker follows the fork, not that the fork is the engine’s decoder. Inferred

A texture of width or height 0 has no pixel data and no levels. Risk of Rain 2 holds 17 (Font Texture, a font’s dynamic atlas, and a TextMeshPro LiberationSans SDF Atlas) and Schedule I 18 (all Font Texture), every one 0×0 with a level count of 1 and 0 bytes. Proven · RoR2, Schedule I

Against an independent decoder over the textures of Sledding Game’s mountain scene, every texture’s size, level count and colour space agree; level 0 decodes to the same RGBA8 for uncompressed formats and BC7 exactly, and for DXT1 and DXT5 within 1: the difference is the rounding of the interpolated palette entries, which the two decoders do differently. Observed · Sledding

A BC6H block does not say whether its endpoints are signed; the texture’s format decides. Unity’s BC6H (id 24) is the unsigned variant (Direct3D’s BC6H_UF16): decoding 5,030 of the 123,850 BC6H textures of the installs both ways, the unsigned decode gives finite, non-negative, smooth values (largest value in any texture 8.43, mean of each texture 0.15 to 0.82) and the signed decode of the same blocks gives a largest value of at least 22,416 (up to 65,504, the half-float maximum) and a mean of at least 2,720 in every one of the 5,030, and negative values in 734 of them. Observed · PEAK, Big Walk The sample is the 1,016 BC6H textures of PEAK’s loose level1, level3 and level4, one level scene of each of PEAK’s seven bundles (4,012 textures) and Big Walk’s two 2048² BC6H textures (Mask - 2k Noise, Mask - 2k Square, linear colour space, 12 levels): the unsigned decode of those is a noise field and a rounded square, the signed decode is saturated. Observed · PEAK, Big Walk A second implementation of both variants agrees with the first on the unsigned decode to within one 8-bit step on 24 sampled textures, and on the signed decode on all but 0.04% of the samples. Observed · PEAK

PEAK stores 123,848 BC6H textures and as many BC4 textures, all unnamed and with one level, in 25 sizes (132×276 is the most common, 53,272 of them). Proven · PEAK SkateBIRD and Sledding Game have none. Proven · SkateBIRD, Sledding

Nibble order of RGBA4444

No texture of the four installs uses id 2 or 13, and none of the four-bit textures of three further installs (below) is id 13, (nor any texture of Schedule I), so the order of RGBA4444’s channels is still not established by data. Community decoders disagree about it. No frame capture of any Unity game read (SkateBIRD on Direct3D 11 and 12, Hollow Knight on both, Lethal Company, ULTRAKILL, Risk of Rain 2) holds a texture in a four-bit Direct3D format (B4G4R4A4, DXGI 115) or one of the engine’s four-bit textures at all, so what the GPU is given for them is not observed either. What would settle it: a frame of any Unity game on Direct3D 11 that draws an RGBA4444 texture (id 13) next to the file’s bytes for it, which needs a game that ships one; none of the seven installs read has any.

Hollow Knight, Lethal Company, ULTRAKILL and Risk of Rain 2 each hold ARGB4444 (id 2) textures: 32, 34, 34 and 34 of them, all in the engine’s built-in unity default resources file (the IMGUI skin, the watermarks and WarningSign). Observed · four installs Read as a little-endian 16-bit value, the nibble at bits 12 to 15 is alpha, the one at bits 0 to 3 is blue, and the ones at bits 8 to 11 and 4 to 7 are red and green. In Hollow Knight’s file the grey textures (EscToExit_Text, box, the scrollbars and sliders of the skin) have the nibbles at bits 8 to 11, 4 to 7 and 0 to 3 equal in every pixel and only the top one differing, which places alpha apart from the three colour channels; WarningSign (64², one level) has a blue nibble of 0 to 3, a top nibble that is 0 or 15 over almost all of its pixels, and decodes to the amber warning triangle (mean over its opaque pixels 196, 149, 10) when bits 8 to 11 are red and bits 4 to 7 green, and to a lime green one when they are swapped. Observed · one install’s file The red and green positions rest on that colour alone. Inferred

Byte 0 of an ARGB32 (id 5) pixel is alpha. Observed · Big Walk Four of Big Walk’s GlobalTint terrain textures (128², no alpha in the tint) have byte 0 equal to 255 in every pixel while the other three bytes vary; so the stored order is A, R, G, B. Observed · Big Walk

m_ColorSpace 1 marks a texture Unity samples as sRGB; 0 means the raw values are used as linear data. A linear texture used as a base colour is sampled as linear, but glTF base colour slots are always decoded as sRGB, so such a texture used as it is renders darker than in the game (SnowIceURP base maps up to 52/255 off). Observed · Sledding

A normal map has usage mode 3 (DXT5nm: x in alpha, y in green) or 4 (plain: x in red). Unity’s normal maps are green up with z rebuilt from the unit length; URP unpacks x from red times alpha, which is alpha for DXT5nm and red for plain maps. Sourced · Universal Render Pipeline source

A shader property can declare a default texture by name (white, black, gray, grey, bump, red, linearGray, linearGrey). A property left unassigned samples that default. The players hold no texture with these names (none in SkateBIRD’s or Sledding Game’s files), so they are built into the engine. Observed · SkateBIRD, Sledding An editor (Unity 6000.6.4f1, Gamma colour space, Direct3D 12) that samples each default into a render texture gives:

Default RGBA sampled
white 1, 1, 1, 1
black 0, 0, 0, 0 (alpha 0)
red 1, 0, 0, 0 (alpha 0)
gray, grey, linearGray, linearGrey 127/255 in all four channels
bump 127/255, 127/255, 1, 1
no default, or a name that is no default 128/255 in all four channels

Observed Those values come from a newer editor than the players’ builds and one colour space; white, black and red have channels of 0 or 1 and are the same in either colour space, the greys and bump are measured for Gamma only. Observed

The engine also supplies textures that no material has a property for: the shadow map (_ShadowMapTexture in the built-in pipeline, _MainLightShadowmapTexture in URP), reflection probe cubes (unity_SpecCube0, unity_SpecCube1, _GlossyEnvironmentCubeMap), light-probe volumes, the camera depth and grab textures. SkateBIRD’s programs name them in their texture tables; Sledding Game’s URP shader objects keep a variant’s textures in a per-variant parameter blob (shader objects), where they are named the same way. Observed · SkateBIRD, Sledding

On Direct3D 12, SkateBIRD’s Unity makes a large texture as a reserved resource (CreateReservedResource) whose 64 KiB tiles it maps into heaps of its own, and a small one as an ordinary committed resource. In SkateBIRD (Unity 2019.4.20f1, -force-d3d12), a capture of the title screen with locum held 72 reserved textures: every one a 2D BC1 or BC3 texture with a full mip chain, 512 (6), 1024 (26) or 2048 (40) texels wide; every mipped BC texture of 256 or less (11) was committed. One texture’s tiles can come from two heaps (a 2048x2048 BC1 texture: 32 tiles in one, 11 in another), and the packed mips (the levels smaller than a tile) share one tile. All 2680 tiles of the 72 were mapped when the frame first used them, and the frame changed no mappings. Observed · SkateBIRD

A tool that reads a Unity D3D12 frame has to follow the tile mappings to read these textures: a reserved resource has no heap of its own to read. Inferred

When does Unity map fewer tiles than a texture has?

The title frame had every tile of every reserved texture mapped. Whether mip streaming, or memory pressure in a larger scene, leaves levels unmapped (and which levels) has not been looked at.