»

Unity Draw Call Batching

I was having problems with Unity draw call batching. I’ve started on a small 2d game (Earth Guardian) and had made a small mockup and was already having problems with the number of draw calls 🙁 It looked like I was getting an ekstra draw call for each sprite I created. So I didn’t know what I was doing..! So I had to find out what I was doing wrong.

Asking Google I found three possible things, that could be the problem.

  1. When creating my mesh I set mainTextureOffset and mainTextureScale.
  2. My sprites use the build in transparent shader, which apperently could also kill the dynamic batching.
  3. I set the material for all meshes to my spriteatlas texture. Not using shared materials.

The Code is as follows.

public static GameObject CreateSprite(float x, float y, float width, float height, float scale, Material material)
    {
        y = materialHeight - y;
        float uvWidthUnit = 1f / materialWidth;
        float uvHeightUnit = 1f / materialHeight;

        float vertHeightScale = scale * (height / width);

        Mesh mesh = new Mesh();

        Vector3[] verts = new Vector3[4];
        Vector2[] uvs = new Vector2[4];
        int[] tris = new int[6] { 0, 1, 2, 2, 1, 3 };

        verts[0] = -Vector3.right * scale + Vector3.up * vertHeightScale;
        verts[1] = Vector3.right * scale + Vector3.up * vertHeightScale;
        verts[2] = -Vector3.right * scale - Vector3.up * vertHeightScale;
        verts[3] = Vector3.right * scale - Vector3.up * vertHeightScale;

        uvs[0] = new Vector2(0.0f, 1.0f);
        uvs[1] = new Vector2(1.0f, 1.0f);
        uvs[2] = new Vector2(0.0f, 0.0f);
        uvs[3] = new Vector2(1.0f, 0.0f);

        mesh.vertices = verts;
        mesh.triangles = tris;
        mesh.uv = uvs;

        mesh.RecalculateNormals();

        GameObject newMeshObject = new GameObject();
        newMeshObject.AddComponent<MeshFilter>().mesh = mesh;
        newMeshObject.AddComponent<MeshRenderer>();
        newMeshObject.renderer.material = material;
        newMeshObject.renderer.material.mainTextureOffset = new Vector2(uvWidthUnit * x, uvHeightUnit * y);
        newMeshObject.renderer.material.mainTextureScale = new Vector2(uvWidthUnit * width, uvWidthUnit * height);
        newMeshObject.AddComponent<BoxCollider>();
        Vector3 boxColliderSize = newMeshObject.GetComponent<BoxCollider>().size;
        boxColliderSize.z = 0.1f;
        newMeshObject.GetComponent<BoxCollider>().size = boxColliderSize;
        newMeshObject.AddComponent<Rigidbody>();
        Rigidbody rigidbody = newMeshObject.GetComponent<Rigidbody>();
        rigidbody.constraints = RigidbodyConstraints.FreezeAll;
        newMeshObject.isStatic = true;
        return newMeshObject;
    }

So first I tried setting UV for the vertices instead of changing mainTextureOffset and mainTextureScale. For the first test I just removed the two lines, to see if the number of draw calls decreased.

The number of draw calls, on my main game screen, went down from 42 to 9 (I have some GUI taking up some calls).. 🙂 And when I started shooting, a lot, the number of draw calls stayed solid at 9. So a real improvement. 🙂 Of cause now I had to test if changing the UV coordinates for each frame changes anything…. It didn’t effect the number of draw calls 🙂 This post from the Unity Forum says it all. So modifying mainTextureOffset and mainTextureScale is not the way to do it!  So no more testing, the UV way of doing things must be the way to go.

[ExecuteInEditMode]
public class UVSetter : MonoBehaviour
{

    public float pixX;
    public float pixY;
    public float pixW;
    public float pixH;
    public float matrialSizeX;
    public float matrialSizeY;
    private Mesh theMesh;
    public bool updatable;
    // Use this for initialization
    void Start()
    {
        theMesh = transform.GetComponent<MeshFilter>().mesh;

        float uvx = pixX / matrialSizeX;
        float uvy = 1f - (pixY / matrialSizeY);
        float uvw = pixW / matrialSizeX;
        float uvh = pixH / matrialSizeY;

        Vector2[] newUVs = new Vector2[4];
        newUVs[0] = new Vector2(uvx, uvy);
        newUVs[3] = new Vector2(uvx, uvy + uvh);
        newUVs[2] = new Vector2(uvx + uvw, uvy);
        newUVs[1] = new Vector2(uvx + uvw, uvy + uvh);
        theMesh.uv = newUVs;
    }

    // Update is called once per frame
    void Update()
    {
        if (!updatable)
        {
            return; 
        }
        float uvx = pixX / matrialSizeX;
        float uvy = 1f - (pixY / matrialSizeY);
        float uvw = pixW / matrialSizeX;
        float uvh = pixH / matrialSizeY;

        Vector2[] newUVs = new Vector2[4];
        newUVs[0] = new Vector2(uvx, uvy);
        newUVs[3] = new Vector2(uvx, uvy + uvh);
        newUVs[2] = new Vector2(uvx + uvw, uvy);
        newUVs[1] = new Vector2(uvx + uvw, uvy + uvh);
        theMesh.uv = newUVs;
    }
}

On a final note I just want to mention that, to get the editor to show the correct UV in the editor, I just added [ExecuteInEditMode] decoration to my UVSetter test class..

Now, go make games! 😉

– Henning

Leave a Reply

Your email address will not be published. Required fields are marked *