Edit 26 October 2011
I am developing an enhanced commercial version, aiming at improving static batching for unity free and Pro. Details here.
Edit 19 May 2011
Un updated version of the Bob script ( v 0.2 ) is available at my blog, http://ippomed.com It handles better user errors.
Edit 17 May 2011
The following workflow also needs to set the blend weights of the bones to one. Unless you do this, you will slow down the program, instead of speeding it up.
Sorry for the inconvenience, I found myself gazing at the results in a new project, until it came up. I modified the steps below to include this step.
Now, to clear things out : The Bob script speeds up the app considerably, once you follow the updated steps
This is a workflow for minimizing the draw calls in a scene. This technique speeds up the rendering considerably.
Suppose you have a scene with 30 gameObjects that move independently. For example’s shake we will create a scene with 30 cubes. If you press now the Stats buttton on the Game view you will see that this scene has 30 drawcalls. Create 3 materials with diffuse shader and 1 texture per material. Assign 1 material per 10 objects. Still 30 drawcalls.
Now the method I suggest will merge those objects in a single mesh using a single material, resulting in 1 draw call.
- Go to the menu. Select Edit → Project Settings → Quality. Set the Blend Weights to one.
- Create a new gameObject, name it root and place all cubes in it.
- Go to the texture import settings and turn the texture’s type to advanced. Check the read/write enabled property.
- Add the following script, 572613–20398–$TexturePacker.cs (6.19 KB) to the root, (created by Phantom and slightly modified by me).
- You will get some error messages. Hit clear in the console. Now, look at the materials of the gameObjects : The textures appear modified. This is because the above script overwriten their texture with a new one, containing their combined textures. Also it modified their uv’s accordingly, so they map each cube’s coordinates to the right texture coordinates. Now remove the script TexturePacker.cs from the root.
- Add the following script, called Bob.js to the root.
//Copyright 2011 by Bournellis Ippokratis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
#pragma strict
import System.Collections.Generic;
@script ExecuteInEditMode()
private var gameObjects:List.<GameObject>;
var _add : boolean; var combine : boolean;
private var partMat: Material;
private var mesh : Mesh;
private var newMesh: Mesh;
var goMat:GameObject;
private var _renderer : SkinnedMeshRenderer;
private var triangles:List.<int>;
private var vertices:List.<Vector3>;
private var uv: List.<Vector2>;
private var weights:List.<BoneWeight>;
private var bones:List.<Transform>;
private var bindposes : List.<Matrix4x4>;
private var meshes:List.<Mesh>;
var transforms:List.<Transform>;
function Start ()
{
gameObjects = new List.<GameObject>();
gameObject.AddComponent.<SkinnedMeshRenderer>();
_renderer = GetComponent.<SkinnedMeshRenderer>();
bones = new List.<Transform>();
mesh = new Mesh ();
_renderer.material = partMat;
_renderer.sharedMesh = mesh;
newMesh = new Mesh();
meshes = new List.<Mesh>();
}
function AddGameObject()
{
partMat = goMat.renderer.sharedMaterial ;
var skin:MeshFilter[] = GetComponentsInChildren.<MeshFilter>();
for (var item : MeshFilter in skin)
{
meshes.Add(item.sharedMesh);
transforms.Add(item.transform);
var myGo :GameObject = new GameObject (item.gameObject.name);
gameObjects.Add(myGo);
item.gameObject.active = false;
}
}
function CombineMeshes()
{
//Create new lists for everything but the bones
triangles = new List.<int>();
vertices = new List.<Vector3>();
uv = new List.<Vector2>();
weights = new List.<BoneWeight>();
bindposes = new List.<Matrix4x4>();
//Add the gameObject's mesh to the mesh list
var vertIdx:int = 0;
var idx:int = 0;
var __weights : BoneWeight[] = new BoneWeight[vertIdx];
//Rebuild the combined mesh from the array of the meshes
newMesh = new Mesh();
var trianglesIdx:int = 0;
var _bonesIdx:int = 0;
var bonesA : Transform[] = new Transform[gameObjects.Count];
var bindPosesA : Matrix4x4[] = new Matrix4x4[gameObjects.Count];
var _meshes : Mesh[];
_meshes = meshes.ToArray();
for (var item:Mesh in _meshes )
{
for(var _item: Vector3 in item.vertices)
{
vertices.Add(_item);
}
for(var _item: Vector2 in item.uv)
{
uv.Add(_item);
}
for(var _item: int in item.triangles)
{
triangles.Add(_item + trianglesIdx);
}
vertIdx = item.vertices.Length;
idx = 0;
__weights = new BoneWeight[vertIdx];
for ( idx = 0; idx<vertIdx; ++idx )
{
__weights[idx].boneIndex0 = _bonesIdx;
__weights[idx].weight0 = 1.0;
weights.Add(__weights[idx]);
}
newMesh.vertices = vertices.ToArray();
newMesh.uv = uv.ToArray();
trianglesIdx = vertices.Count;
//gameObjects[_bonesIdx].transform.localPosition = transforms[_bonesIdx].localPosition;
bonesA[_bonesIdx] = gameObjects[_bonesIdx].transform;
bonesA[_bonesIdx].parent = transform;
bonesA[_bonesIdx].localRotation = Quaternion.identity;
bonesA[_bonesIdx].localPosition = Vector3.zero;
bindPosesA[_bonesIdx] = bonesA[_bonesIdx].worldToLocalMatrix * transform.localToWorldMatrix;
//
gameObjects[_bonesIdx].transform.localPosition = transforms[_bonesIdx].localPosition;
gameObjects[_bonesIdx].transform.localRotation = transforms[_bonesIdx].localRotation;
gameObjects[_bonesIdx].transform.localScale = transforms[_bonesIdx].localScale;
++_bonesIdx;
}
newMesh.triangles = triangles.ToArray();
newMesh.RecalculateNormals();
newMesh.boneWeights = weights.ToArray();
newMesh.bindposes = bindPosesA;
newMesh.RecalculateBounds();
newMesh.Optimize();
mesh.Clear();
mesh = newMesh;
_renderer.bones = bonesA;
_renderer.material = partMat;
_renderer.sharedMesh = mesh;
}
function Update()
{
if(_add)
{
AddGameObject();
_add=false;
}
if(combine)
{
CombineMeshes();
combine=false;
}
}
-
Drag one cube on the goMat slot in the Bob script. Check once Add. It will not appear checked, do not worry for this. Hit once Combine. Now check the draw calls.
-
Remove Bob script from the root. Delete the inactive gameObjects. Now you have 30 cubes to move around independently , costing only one draw call. Enjoy
The above procedure should be followed per shader, some shaders might need further modifications. You should work them out It creates one skinned mesh from the meshes you added to it, with one bone per gameObject. It will not work in its current form with nested hierarchies (eg gameObjects in other gameObjects). It works for my current needs and I do not plan on supporting it, thus I provide the source code for you to play with. Since it is provided with a MIT license, you can modify it, distribute it, sell it at will.
I decided to release this code for free, as a thanks to the unity community, especially the irc channel members who have tolerated my engrish and my strange questions for sooo long. Thanks again Mike, Neil et all