Skip to content

Meshes

A Mesh (class 43) holds the vertices of one or more submeshes. Each submesh is a run of indices into the shared vertices; a renderer’s material i draws submesh i (scenes). The vertex data is either laid out in channels over interleaved streams, or compressed into bit-packed arrays. Observed

A mesh has up to 14 channels, always in this order of the m_Channels table: Vertex, Normal, Tangent, Color, TexCoord0 to TexCoord7, BlendWeight, BlendIndices. Sourced · Unity scripting reference Each channel names the stream it lives in, its offset in a vertex of that stream, a format and a dimension. A stream’s stride is the sum of its channels’ sizes; the streams follow one another in the vertex data, each starting on a 16-byte boundary. Observed Every one of SkateBIRD’s 7,491 stored meshes, 45 of them with more than one stream, ends where its vertex data ends when laid out so. Proven · SkateBIRD · every stored mesh of the player

Format id Name Bytes per component Confidence
0 Float 4 Observed
1 Float16 2 Observed
2 UNorm8 1 Observed
3 SNorm8 1 Sourced · Unity scripting reference
4 UNorm16 2 Observed
5 SNorm16 2 Observed
6 UInt8 1 Observed
7 SInt8 1 Sourced · Unity scripting reference
8 UInt16 2 Observed
9 SInt16 2 Sourced · Unity scripting reference
10 UInt32 4 Observed
11 SInt32 4 Sourced · Unity scripting reference

The vertex data is inside the mesh, or streamed: m_StreamData then names a range of the .resS beside the file (StreamingInfo). Observed · SkateBIRD, Sledding SkateBIRD’s playground (level12) has 95 meshes, 85 of them readable by scripts (m_IsReadable) and 10 not; the unreadable ones keep their vertex data in the stream file all the same. Observed · SkateBIRD

Some channels have more components than the data uses. SkateBIRD’s TexCoord0 often has four components (the mesh stores u, v and two more); the first two are the texture coordinates. Observed · SkateBIRD A normal is three components; the stored vertex can pad it to four. Observed

A mesh can store its vertices in m_CompressedMesh instead: arrays of PackedBitVectors. A packed vector has n items of bits bits each, little-endian bit order, and each item decodes to start + item * (range / (2^bits − 1)) in float. Sourced · UnityPy

Content Storage Confidence
positions packed floats, 3 per vertex Observed · Sledding
texture coordinates packed floats; m_UVInfo has a nibble per channel: bit 2 set means present, the low two bits are the dimension minus 1 Observed · Sledding
normals x and y packed, z rebuilt from the unit length with a stored sign: z = sqrt(1 − x² − y²), or (x, y, 0) normalised when that is negative Observed · Sledding
tangents x and y packed, z as for normals, and a sign for w Observed · Sledding
colours packed floats, 4 per vertex Observed · Sledding
indices packed integers Observed · Sledding

Over the meshes of Sledding Game’s level1 read by an independent decoder, the stored channels and index buffers of 552 meshes agree bit for bit, and 74 compressed meshes agree to the quantisation of their packed vectors: the dequantised values differ only by the f32 against f64 rounding. Observed · Sledding

That rounding is set by the size of the packed vector’s ends, max(|start|, |start + range|), not by the size of the decoded value: a value near zero carries the absolute error of the vector’s largest end. A tolerance of four f32 epsilons relative to max(1, |value|) refuses 9,552 vertex components near zero; the same tolerance relative to max(1, ends) accepts all of them. Observed · Sledding A rebuilt normal or tangent z = sqrt(1 − x² − y²) amplifies that error near z = 0 (the square root of an error e can be as large as sqrt(e)); the largest difference measured is 3.9 × 10⁻⁴. Observed · Sledding

Skin weights of compressed meshes

A compressed mesh can also hold bone weights (m_Weights in the compressed mesh). The readers used here do not decode them. No compressed mesh found has any: reading every Mesh object of the installs of Hollow Knight (1,007 files), Lethal Company (43), ULTRAKILL (302) and Risk of Rain 2 (1,799) with an independent reader finds 33 compressed meshes (3 in Lethal Company, 30 in Risk of Rain 2, none in the other two), and every one has an empty m_Weights and m_BoneIndices and no bind pose. Observed · the four installs Whether weights decode as the other packed vectors do stays open, since none of the 33 is skinned.

Indices are 16-bit or 32-bit. A submesh is firstByte (a byte offset into the index buffer), indexCount, topology and baseVertex; the base vertex is added to every index of the submesh. Observed A decoder that ignores it reads the wrong vertices for any submesh whose base vertex is not 0. Inferred

Topology Id Notes Confidence
Triangles 0 Observed
Quads 2 four indices per quad Observed
Lines 3 Observed
Line strip 4 one segment per consecutive pair Observed
Points 5 Observed

A submesh without indices draws nothing. A mesh none of whose submeshes has indices has no geometry. Observed

Unity uploads a mesh one stream to a buffer: each vertex buffer a draw binds holds exactly one stream’s bytes (vertex count times stride, without the padding before the next stream), and its index buffer holds the mesh’s whole m_IndexBuffer, every submesh’s indices as stored. In a title-screen frame of SkateBIRD on Direct3D 11, 183 of the 209 buffers the frame uses hold those bytes of a stored mesh: 76 first streams, 11 second and 10 third streams (the 11 meshes of more than one stream in the frame, each stream in its own buffer, none bound with the streams together) and 86 index buffers. The rest match no stored mesh. The same title screen on Direct3D 12 (-force-d3d12) uploads the same bytes: the same 183 of 209 buffers, by stream and index buffer alike. Observed · SkateBIRD

Meshes can store non-finite numbers. A non-finite position leaves a primitive impossible to place; a non-finite normal or tangent has no direction; a non-finite texture coordinate or colour has no value. Materials carry the same trap: a shader parameter such as SkateBIRD’s _CameraFadeParams can store infinity as a sentinel. Observed · SkateBIRD

Triangles’ front faces are clockwise in Unity’s left-handed frame. Sourced · Unity scripting reference See the frame for what mirroring it into a right-handed frame does to winding, normals and tangents.

SkateBIRD’s playable-level meshes are ProBuilder output and statically batched combined meshes; there are no LODs, no terrain and no lightmaps, and the playable extent is 200 to 400 m with about 150,000 triangles per level. Observed · SkateBIRD Sledding Game and PEAK use LODGroups (scenes). Observed · Sledding, PEAK

Sledding Game’s level1 has 626 meshes, 74 of them compressed. All use triangle topology and 16-bit indices, every texture coordinate channel is two-dimensional, and normals are stored as four half floats whose fourth is padding. One mesh, fireplace curve, has no vertices and a single submesh of zero indices. Observed · Sledding