Mesh.DrawVBO optimizations

Hi, we have 14 hexes that are mapped to a 1024x1024 texture. It is to note that each map hex can have a different height set, so we cannot merge them. We have multiple different texture pages that are set as shared materials. We have those 14 hexes put in a kind of manager which as follows :

public GameObject[] HexMeshCache;
public Material[] HexMaterialCache;

//--- OVERLAY ON HEXES ---
public Material HexTeamColor;
public Material HexSelectionHighlight;
private Dictionary<int, Mesh> sharedMeshPrefabs;
private Dictionary<int, Material> sharedPrimaryMaterials;
private Dictionary<Color, Material> sharedTeamColorMaterials;
private Dictionary<HighlightState, Material> sharedSelectionMaterials;
private Dictionary<HighlightState, Color> highlightColors;

public GameObject HexSelectionHighlightPrefab;
public GameObject HexTeamHighlightPrefab;

private static CombatTerrainHexCache instance;

private const int BASE_MATERIAL = 0;
private const int TEAM_MATERIAL = 1;
private const int HIGHLIGHT_MATERIAL = 2;

public static CombatTerrainHexCache Instance
{
    get
    {
        if (instance == null)
        {
            Debug.LogError("This prefab object is required in the battle scene !!!");
        }
        return instance;
    }
}
void OnDestroy() { instance = null; }

void Awake()
{
    instance = this;

    //--- INIT OF TEAM AND COLOR MATERIALS ---
    sharedTeamColorMaterials = new Dictionary<Color, Material>();
    sharedSelectionMaterials = new Dictionary<HighlightState, Material>();
    sharedMeshPrefabs = new Dictionary<int, Mesh>();
    sharedPrimaryMaterials = new Dictionary<int, Material>();

    int i = 0;
    foreach (Material matPrefab in HexMaterialCache)
    {
        Material cachedMat = Instantiate(matPrefab) as Material;
        if(ByHumans.Safe(cachedMat))
        {
            cachedMat.name = cachedMat.name.Replace("(Clone)", "");
            Debug.Log(string.Format("CREATED {0} material in cache for hexes", cachedMat.name));
            sharedPrimaryMaterials.Add(i++, cachedMat);
        }
    }

    Vector3 farAwayLand = new Vector3(10000f, 10000f, 10000f);
    i = 0;
    foreach (GameObject meshPrefab in HexMeshCache)
    {
        GameObject cachedObj = Instantiate(meshPrefab) as GameObject;
        if (ByHumans.Safe(cachedObj))
        {
            cachedObj.transform.position = farAwayLand;
            cachedObj.SetActiveRecursively(false);
            MeshFilter mFilter = cachedObj.GetComponent<MeshFilter>();
            if(ByHumans.Safe<MeshFilter>(mFilter))
            {
                cachedObj.name = cachedObj.name.Replace("(Clone)", "");
                Debug.Log(string.Format("CREATED {0} GameObject in cache for hexes", cachedObj.name));

                sharedMeshPrefabs.Add(i++, mFilter.sharedMesh);
            }
        }
    }

    highlightColors = new Dictionary<HighlightState, Color>();
    highlightColors.Add(HighlightState.None, Color.black);      //all additive to none.
    highlightColors.Add(HighlightState.AOE, Color.yellow);
    highlightColors.Add(HighlightState.MovementSelected, new Color(0f,1f,0f));
    highlightColors.Add(HighlightState.MovementRange, new Color(0f, 0.8f, 1f));
    highlightColors.Add(HighlightState.AttackRange, Color.yellow);
    highlightColors.Add(HighlightState.MovePath, new Color(0f, 0.3f, 1f));
    highlightColors.Add(HighlightState.CloseEnemy, Color.red);
    highlightColors.Add(HighlightState.RangedEnemy, Color.red);
    highlightColors.Add(HighlightState.SelectedEnemy, Color.red);
    highlightColors.Add(HighlightState.Ally, new Color(0f, 1f, 0.3f));
    highlightColors.Add(HighlightState.SelectedAlly, new Color(0f, 1f, 0.8f));
}

public GameObject GetHexCopy(string prefabName)
{
    GameObject retObject = null;
    HexScript script = null;

    //Debug.Log("GET HEX COPY");

    foreach (GameObject obj in HexMeshCache)
    {
        if (obj.name == prefabName)
            retObject = (GameObject)GameObject.Instantiate(obj);
    }

    if (retObject != null && script != null)
    {
        //Get Texture page id from the hex itself.
        Material[] defaultMats = GetHexSharedMaterialsByPageIndex(script.MaterialPageIndex);
        if (ByHumans.Safe(defaultMats))
        {
            retObject.renderer.sharedMaterials = defaultMats;
        }
    }

    return retObject;
}

private Material[] SetPrimarySharedHexMaterial(Material newMaterial)
{
    Material[] retList = null;

    if (Application.loadedLevelName == "SuperManagement")
    {
        retList = new Material[1];
        retList[BASE_MATERIAL] = newMaterial;  //Base texture of this hex
    }
    else
    {
        retList = new Material[3];
        retList[BASE_MATERIAL] = newMaterial;   //Base texture of this hex
        retList[TEAM_MATERIAL] = HexTeamColor; //Base texture for team as second draw
        retList[HIGHLIGHT_MATERIAL] = HexSelectionHighlight;    //Base Highlight when needed.
        //Debug.Log(string.Format("Initialize Material page idx : {0} to {1}", script.MaterialPageIndex, retObject.name));
    }
    return retList;
}

//Return specific Hex Mesh (for hex texture selection)
public Mesh GetHexSharedMesh(int prefabIndex)
{
	Mesh returnMesh = null;

    sharedMeshPrefabs.TryGetValue(prefabIndex, out returnMesh);

    return returnMesh;
}

public int HexMeshCacheCount
{
    get 
    {
        return HexMeshCache.Length;
    }
}

public int MaterialCacheCount
{
    get
    {
        return HexMaterialCache.Length;
    }
}

public Material[] GetHexSharedMaterialsByName(string materialName)
{
    Material retObject = null;

    foreach (KeyValuePair<int, Material> matKv in sharedPrimaryMaterials)
    {
        if (matKv.Value.name == materialName)
            retObject = matKv.Value;
    }

    return SetPrimarySharedHexMaterial(retObject);
}

public Material[] GetHexSharedMaterialsByPageIndex(int pageIndex)
{
    Material retObject = null;

    if (pageIndex <= sharedPrimaryMaterials.Count - 1)
    {
        retObject = sharedPrimaryMaterials[pageIndex];
    }

    return SetPrimarySharedHexMaterial(retObject);
}

//Pass this as sharedMaterial
public void SetSharedTeamMaterial(string playerGUID, HexScript hexScript)
{
    if (playerGUID == null)
    {
        //No team on this hex, shutdown color by reseting original team color material (all black)
        //Because its a copy sent by unity, resend its modifications see : http://answers.unity3d.com/questions/32250/meshrenderermaterials.html
        Material[] mats = hexScript.renderer.materials;
        mats[TEAM_MATERIAL] = HexTeamColor; //original team with no color material
        hexScript.renderer.sharedMaterials = mats;
    }
    else
    {
        Material retSharedMaterialRef = null;
        Color c = CombatTeamColor.GetColor(playerGUID);

        if (!sharedTeamColorMaterials.TryGetValue(c, out retSharedMaterialRef))
        {
            Material newColorMaterial = new Material(HexTeamColor);
            newColorMaterial.SetColor("_TintColor", c);
            sharedTeamColorMaterials.Add(c, newColorMaterial);
            retSharedMaterialRef = newColorMaterial;
        }

        //Because its a copy sent by unity, resend its modifications see : http://answers.unity3d.com/questions/32250/meshrenderermaterials.html
        Material[] mats = hexScript.renderer.materials;
        mats[TEAM_MATERIAL] = retSharedMaterialRef;
        hexScript.renderer.sharedMaterials = mats;
    }
}

//Pass this as sharedMaterial
public void SetSharedHighlightMaterial(HighlightState materialState, HexScript hexScript)
{
    Material retSharedMaterialRef = null;

    //Debug.Log(string.Format("SET SHARED MATERIAL TO {0} for hex {1}", materialState, hexScript.name));

    //TRY TO FETCH ALREADY CACHED SELECTION MATERIAL.
    if (!sharedSelectionMaterials.TryGetValue(materialState, out retSharedMaterialRef))
    {
        //CREATE A NEW MATERIAL BASED ON A SELECTION STATE.
        Material newColorMaterial = new Material(HexSelectionHighlight);
        newColorMaterial.SetColor("_TintColor", highlightColors[materialState]);
        sharedSelectionMaterials.Add(materialState, newColorMaterial);
        retSharedMaterialRef = newColorMaterial;
    }

    Material[] mats = hexScript.renderer.materials;
    mats[HIGHLIGHT_MATERIAL] = retSharedMaterialRef;
    hexScript.renderer.sharedMaterials = mats;
}

}

So with this code in mind, we create our hexes in the map dynamically. We have 4 Material(texture) page to put on the hexes. Hexes are created dynamically of course. The problem we have is that we do not have 14 VBO objects only when we dezoom (we use occlusion, static & dynamic batching enabled) we have something like 40 VBO for 14x14 hexes (196 hexes) we do have a high batched draw call (around 200). Can someone explain this since we try to use sharedMesh instances without scaling them, just change the positions. We are planning to send this to android and we want to know what can be done to optimize further, and explain this behaviour.

EDIT : I have switched all of my 4 texture to a simple “Texture only shader” taken here : http://wiki.unity3d.com/index.php/Texture_Only from my diffuse/unlit, and it saved me about a 100 MB of texture, according to the documentation here : “Objects with lightmaps have additional (hidden) material parameter: offset/scale in lightmap, so lightmapped objects won’t be batched (unless they point to same portions of lightmap)”

The VBO count has been reduced to 40 for opaque, but the main stat windows shows 74…

Lâche pas Christian :slight_smile: