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
Fields
Section titled “Fields”| 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 |
Mip chain
Section titled “Mip chain”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
Formats
Section titled “Formats”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
Risk of Rain 2 and Schedule I
Section titled “Risk of Rain 2 and Schedule I”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
Crunched DXT
Section titled “Crunched DXT”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
Empty textures
Section titled “Empty textures”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
BC6H holds unsigned half floats
Section titled “BC6H holds unsigned half floats”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
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.
ARGB4444 nibble order
Section titled “ARGB4444 nibble order”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
ARGB32 byte order
Section titled “ARGB32 byte order”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
Colour space and normal maps
Section titled “Colour space and normal maps”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
Engine default textures
Section titled “Engine default textures”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
Shared and global textures
Section titled “Shared and global textures”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: reserved textures
Section titled “On Direct3D 12: reserved textures”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
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.