Roblox Mesh Format

When a mesh is uploaded to Roblox, it is converted into an in-house format that the game engine can read. Roblox’s mesh format isn’t a format you can export to, but you can download meshes off of their servers, which you can use to view files like these. This article will cover the file specification for this mesh format, so that you might be able to write code externally that can read Roblox mesh files.

This format is always subject to change, so keep an eye out for changes to this article if anything breaks!

Version Header

Every mesh in Roblox starts with a header that is 12 characters in length, followed by a newline ( \n ) character. The header is represented in ASCII, and it is used to indicate what version of the mesh format is being used.

Currently, there are 4 versions that exist:

  • version 1.00
  • version 1.01
  • version 2.00
  • version 3.00

Each version has it’s own specific rules and quirks that may need to be addressed while reading the file.

version 1.00

This is the original version of Roblox’s mesh format, which is stored purely in ASCII and can be read by humans. These files are stored as 3 lines of text:

version 1.00
num_faces
(data...)

The num_faces line represents the number of polygons to expect in the data line.

The (data…) line represents a series of concatenated Vector3 pairs, stored in-between brackets with the XYZ coordinates separated by commas as such: [1.00,1.00,1.00]

Data Specification

For every polygon defined in the mesh, there are 3 vertex points that make up the face. Each vertex point contains 3 Vector3 pairs, representing the Position , Normal, and UV respectively.

Thus, you should expect to read num_faces * 9 concatenated Vector3 pairs in the (data…) line!

Each vertex is tokenized like so:
[pos_X,pos_Y,pos_Z][norm_X,norm_Y,norm_Z][tex_U,tex_V,tex_W]

Position

The 1st Vector3, [pos_X,pos_Y,pos_Z] is the position of the vertex point.

If the mesh is specified to be using version 1.01, then you can safely assume the mesh is using the correct scale.

Normal

The 2nd Vector3, [norm_X,norm_Y,norm_Z] is the normal vector of the vertex point, which is used to smooth out the shading of the mesh.

This Vector3 is expected to be a unit vector, so its Magnitude should be exactly 1. The mesh might have unexpected behavior if this isn’t the case!

UV

The 3rd Vector3, [tex_U,tex_V,tex_W] is the UV texture coordinate of the vertex point, which is used to determine how the mesh’s texture is applied to the mesh. The tex_W coordinate is unused, so you can expect its value to be 0 .

version 2.00

The version 2.00 format is stored in a binary format, and files may differ in structure depending on factors that aren’t based on the version number.

Data Specification

Once you have read past the version 2.00\n text at the beginning of the file, the binary data begins!

There will be three struct types used to read the file:

  • MeshHeader
  • MeshVertex
  • MeshFace

The variables in each of these structs are defined in a specific order, and the type of each variable specifies how many bytes should be sequentially read and copied into the variable.

MeshHeader

The first chunk of data is the MeshHeader, represented by the following struct definition:

struct MeshHeader
{
    short sizeof_MeshHeader;
    byte  sizeof_MeshVertex;
    byte  sizeof_MeshFace;

    uint numVertices;
    uint numFaces;
}

If you read MeshHeader.sizeof_MeshHeader and it does not share the same sizeof(MeshHeader) as the one in your code, then it is unlikely that you’ll be able to read it correctly! This could be due to a revision to the format though, as will be discussed below.

MeshVertex

Once you have read the MeshHeader, you should expect to read an array, MeshVertex[numVertices] vertices; using the following struct:

struct MeshVertex
{
   float px, py, pz; // XYZ position of the vertex's position
   float nx, ny, nz; // XYZ unit vector of the vertex's normal vector.
   float tu, tv, tw; // UV coordinate of the vertex (tw is reserved)
   byte  r, g, b, a; // RGBA color of the vertex
}

This array represents all of the vertices in the mesh, which can be linked together into faces.

MeshFace

Finally, you should expect to read an array, MeshFace[numFaces] faces; using the following struct:

struct MeshFace
{
    uint a; // 1st Vertex Index
    uint b; // 2nd Vertex Index
    uint c; // 3rd Vertex Index
}

This array represents indexes in the MeshVertex array that was noted earlier. The 3 MeshVertex structs that are indexed using the MeshFace are used to form a polygon in the mesh!

version 3.00

Version 3 of the Mesh format is a minor revision which introduces support for LOD meshes.

Firstly, here are the changes to the MeshHeader:

struct MeshHeader
{
	short sizeof_MeshHeader;
	byte  sizeof_MeshVertex; 
	byte  sizeof_MeshFace;
[+]	short sizeof_MeshLOD;
	
[+]	short numLODs;
	uint  numVerts; 
	uint  numFaces; 
}

After reading the faces of the mesh file, there will be (numLODs * 4) bytes at the end of the file, representing an array of numLODs ints, or just:

int mesh_LODs[numLODs];

The array uses integers because sizeof_MeshLOD should always have a value of 4 to be considered valid.


The mesh_LODs array represents a series of face ranges, the faces of which form meshes that can be used at various distances by Roblox’s mesh rendering system.

For example, you might have an array that looks like this:

{ 0, 1820, 2672, 3045 }

This values in this array are interpreted as follows:

  • The Main mesh is formed using faces [0 - 1819]
  • The 1st LOD mesh is formed using faces [1820 - 2671]
  • The 2nd LOD mesh is formed using faces [2672 - 3044]

All of these faces should be stored in whatever array of MeshFaces you have defined.

Wrapping up

Congratulations! You should now hopefully have a basic understanding of how Roblox’s mesh format works!

42 Likes

This is a highly needed subject! Good job you uploaded it as a load of people ask how this works.

2 Likes

This was previously a DevHub article that I wrote, but it was recently taken down for some reason. I guess they decided it was too niche to maintain, so I’ll take it from here.

3 Likes

DevHub articles are documentation. We do not document and do not promise anything about the mesh format, it’s purely internal and we can drop support for any format at any point once we convert all existing assets - so DevHub is not a good place for this information.

1 Like

How do you actually get the meshes from the roblox server?

This is another one of those “things I’m not entirely sure I will need but I’ll learn it anyways”.

Great post! I’m surprised you actually took the time to decipher their system.

3 Likes

The asset?id=[number] link always provides the raw asset. If you copy the link for an asset and paste it into your browser, Roblox will simply provide you the asset as a data stream. For example, using this decal, you can use the asset link, which returns just the data for the image. Web browsers will cause a download prompt to appear. You can then apply the .png extension locally, and your OS will correctly register that the image is a PNG file.image
This is also applicable to everything else that Roblox serves in this manner, e.g. audio and meshes.

4 Likes