How do I use MeshUtility.SetMeshCompression?

Does MeshUtility.SetMeshCompression actually do anything? I’ve been trying for hours to get it to work and compress my editor generated meshes, to zero effect.

I’ve tried using it

  • directly after mesh generation
  • after all vertex/indexdata has been set
  • after the asset has been created in the assetdatabase
  • after the asset has been imported and loaded
  • on meshes that are saved as either .mesh or .asset

And of course I’m calling AssetDatabase.SaveAssets after I’m done.

Calling GetMeshCompression on the mesh directly after use does return the setting I just applied, but only until the next reimport of the mesh, after which it’s back to “none”. There’s also no change in either the .mesh or the .meta file after using it in any of the above ways.

What am I missing? I’m on Unity 6.0.40f if that helps.

PS: If anyone knows of any other way I can use to compress my editor generated mesh assets (apart from reducing vertex data precision via vertex attribute descriptors, I’ve already done that - or exporting to fbx/obj), please let me know. Thanks in advance.

Seeing some code would help.

My first hunch is that it either requires Player Settings “optimize mesh data” to be enabled and/or perhaps requires calling mesh.Optimize() to apply the compression.

Try giving it a custom extension like .mymesh and write a custom ScriptedImporter for it.

I’m thinking that the MeshImporter probably takes precedence and overrules the settings. Particularly if you call SaveAssets since that saves more than just your mesh, and it may trigger the mesh importer to run.

It would be wise to monitor the AssetPostprocessor and AssetModificationProcessor callbacks for changes to the asset not originating from your script.

Which I left a note on the docs because SaveAssets means “save project” (everything). For individual assets SaveAssetIfDirty is the right call.

However in the doc’s code snippet, it’s wholly unnecessary to call any form of SaveAsset since it directly follows CreateAsset, which doesn’t “create” an asset but somehow leaves it unsaved.

Thanks for your answer, I’m back with more insight and some code. I stripped down my implementation to what was basically the code sample in the docs, and could confirm that this one at least works. The issue I’m facing seems to stem from me using the advanced mesh api to set the vertex buffer layout directly, which I need/want to do to reduce precision on some channels to improve runtime memory usage. After doing that, SetMeshCompression doesn’t do anything anymore, but also does not log any errors/warnings.

Here’s basically what I’m doing:

Method 1: Correct vertex buffer precision, but no compression
private static void CopyAndCompress(Mesh mesh, ModelImporterMeshCompression modelImporterMeshCompression)
{
    //get original mesh data
    var indexCount = Enumerable.Range(0, mesh.subMeshCount).Sum(i => mesh.GetSubMesh(i).indexCount);
    using var datas = Mesh.AcquireReadOnlyMeshData(mesh);
    var data = datas[0];
    var array = data.GetVertexData<GenericVertexBufferData9>(); //unmanaged struct holding 9x4 bytes of data, hardcoded for this test
    var indices = data.GetIndexData<ushort>(); //format is hardcoded for this test
    
    var mesh2 = new Mesh{name = mesh.name+"2"};
    //set vertex & index buffer data
    mesh2.SetVertexBufferParams(mesh.vertexCount, Enumerable.Range(0, mesh.vertexAttributeCount).Select(mesh.GetVertexAttribute).ToArray());
    mesh2.SetIndexBufferParams(indexCount, mesh.indexFormat);
    mesh2.SetVertexBufferData(array, 0,0, mesh.vertexCount);
    mesh2.SetIndexBufferData(indices,0,0, data.GetSubMesh(0).indexCount);
    mesh2.SetSubMeshes(Enumerable.Range(0, mesh.subMeshCount).Select(mesh.GetSubMesh).ToArray());
    
    //compress and create
    MeshUtility.SetMeshCompression(mesh2, modelImporterMeshCompression);
    AssetDatabase.CreateAsset(mesh2, AssetDatabase.GetAssetPath(mesh).Replace(".mesh",2+".mesh"));
}

This method copies a mesh fully utilizing the advanced mesh api. The original mesh already has reduced precision on some attributes, like this:


The produced mesh copy retains the vertex buffer layout with the correct precision. However, it is not compressed (same size on disk as original).

Method 2: Incorrect vertex buffer precision, but compression works
private static void CopyAndCompress2(Mesh mesh, ModelImporterMeshCompression modelImporterMeshCompression)
{
    //copy data arrays over one by one, let unity figure out the layout
    var mesh2 = new Mesh
    {
        name = mesh.name + "2",
        vertices = mesh.vertices,
        triangles = mesh.triangles,
        normals = mesh.normals,
        boneWeights = mesh.boneWeights,
        colors = mesh.colors,
    };
    var uvs4 = new List<Vector4>();
    var uvs3 = new List<Vector3>();
    var uvs2 = new List<Vector2>();
    //copy all texcoords over that exist, with their correct dimension
    for (var i = 0; i < 8; i++)
    {
        if (!mesh.HasVertexAttribute(VertexAttribute.TexCoord0 + i))
            continue;
        switch (mesh.GetVertexAttributeDimension(VertexAttribute.TexCoord0 + i))
        {
            case 2:
                mesh.GetUVs(i, uvs2);
                mesh2.SetUVs(i, uvs2);
                break;
            case 3:
                mesh.GetUVs(i, uvs3);
                mesh2.SetUVs(i, uvs3);
                break;
            case 4:
                mesh.GetUVs(i, uvs4);
                mesh2.SetUVs(i, uvs4);
                break;
        }
    }
    
    //compress and create
    MeshUtility.SetMeshCompression(mesh2, modelImporterMeshCompression);
    AssetDatabase.CreateAsset(mesh2, AssetDatabase.GetAssetPath(mesh).Replace(".mesh",2+".mesh"));
}

This method copies a mesh the traditional way. Since there is no way to alter the precision of vertex attributes on the new mesh, it automatically assumes full precision on all of them. The result is a significantly larger vertex buffer:


But at least, compression actually works here, so on disk the serialized mesh is smaller.

I’ve also tried some hybrid approach where I copy the arrays one by one and apply the vertex buffer params afterwards to force lower precision on the copied data, like this:

Method 3: Hybrid
private static void CopyAndCompress3(Mesh mesh, ModelImporterMeshCompression modelImporterMeshCompression)
{
    //copy data arrays over one by one
    var mesh2 = new Mesh
    {
        name = mesh.name + "2",
        vertices = mesh.vertices,
        triangles = mesh.triangles,
        normals = mesh.normals,
        boneWeights = mesh.boneWeights,
        colors = mesh.colors,
    };
    var uvs4 = new List<Vector4>();
    var uvs3 = new List<Vector3>();
    var uvs2 = new List<Vector2>();
    //copy all texcoords over that exist, with their correct dimension
    for (var i = 0; i < 8; i++)
    {
        if (!mesh.HasVertexAttribute(VertexAttribute.TexCoord0 + i))
            continue;
        switch (mesh.GetVertexAttributeDimension(VertexAttribute.TexCoord0 + i))
        {
            case 2:
                mesh.GetUVs(i, uvs2);
                mesh2.SetUVs(i, uvs2);
                break;
            case 3:
                mesh.GetUVs(i, uvs3);
                mesh2.SetUVs(i, uvs3);
                break;
            case 4:
                mesh.GetUVs(i, uvs4);
                mesh2.SetUVs(i, uvs4);
                break;
        }
    }
    
    //set vertex and index buffer layout after copying the arrays
    mesh2.SetVertexBufferParams(mesh.vertexCount, Enumerable.Range(0, mesh.vertexAttributeCount).Select(mesh.GetVertexAttribute).ToArray());
    var indexCount = Enumerable.Range(0, mesh.subMeshCount).Sum(i => mesh.GetSubMesh(i).indexCount);
    mesh2.SetIndexBufferParams(indexCount, mesh.indexFormat);
    mesh2.SetSubMeshes(Enumerable.Range(0, mesh.subMeshCount).Select(mesh.GetSubMesh).ToArray());
    
    //create and compress
    MeshUtility.SetMeshCompression(mesh2, modelImporterMeshCompression);
    AssetDatabase.CreateAsset(mesh2, AssetDatabase.GetAssetPath(mesh).Replace(".mesh",2+".mesh"));
}

But that produces the exact same result as method 1.
Same goes for saving the asset first and loading it again from the asset database before applying compression and saving it again - works if the mesh was created with method 2 (one by one array copy), but fails if the mesh was created with the advanced mesh api.

I’ve also tried fitting in mesh.Optimize and removed AssetDatabase.SaveAssets as you suggested (“optimize mesh data” in settings was already enabled), makes no difference sadly. I also don’t think there’s a mesh importer, just the model importer which does not process native mesh assets (at least I couldn’t find one). I’ve looked at the native format importer but that doesn’t have any options I could use.

For now it just seems that using the advanced mesh api locks you out from using compression, unless I’m still missing something. If anyone knows more, please let me know.

That sounds interesting, but I haven’t used a scripted importer before. What would be the idea behind it? I’m writing my own file format with my own compression, and then my own scripted importer to tell unity how to deserialize a mesh from that?

Okay so after a lot of additional tests I can conclude:

  • The advanced mesh api does work with compression, but only if you use default data types for vertex attributes (eg float32 for uvs/normals/pos, uint32 for blend indices, unorm8 for color)
  • Using anything other than default data types, no matter how and where you set them, will lock you out of mesh compression
    • I even tested this on imported .fbx file with an asset post processor, where I applied lower precision data formats to the uvs of all meshes in OnPostprocessModel. If you do this, even if you select mesh compression in the importer settings window, it will not compress your mesh, but you will still lose precision (seems your vertices are quantized but then not serialized in a compressed format).
  • Using a scriptable importer with a custom serialized data format is useless as well, while you can compress your mesh data in your project this way, you have no direct control over how unity compresses the native mesh asset that gets put in the build. This one follows the same rules as established earlier - choose between lower precision attributes or asset compression.

I’m pretty bummed by this as build size and runtime memory optimizations shouldn’t be mutually exclusive like this. If Unity does not know how to compress vertex data with lower precision data types, please let me do it myself. At the very least, there should be an error or warning when the compression fails because of this, as to not waste hours of unsuspecting users’ time.