How Do I Make One Mesh Act As Multiple Objects?

I’m hearing a lot of bits and pieces in the forums about a cool strategy for keeping draw calls low that I was hoping someone could maybe spell out more explicitly.

It seems to go something like this …

  1. Start with a single plane mesh with a single texture applied to it, in your 3D editor,

  2. Break it into multiple plane polygons made of two-tri’s each,

  3. Bone each polygon,

  4. Import this Franken-mesh into Unity,

  5. Manipulating each boned polygon separately via bone animation or scripting.

The net effect is that you get multiple, differently-textured, independently-moving bits of (flat) geometry all under one draw call (per Franken-mesh).

Am I getting it right?

And, if so, does anyone have a simple demo of this technique in action that they can share or point me to?

TIA!

there are simpler solutions, the most obvious one is the one a few threads further down, the sprite manager

Yeah, but isn’t the sprite manager just for 2D stuff?

What if I want the “sprites” to be in a 3D world?

Pretty close, except possibly for the “differently-textured” bit, if by that you mean using multiple textures. Since you can only use one texture per mesh (if you want to keep draw calls to one). But you can use a texture atlas and map different UVs on the quads, which usually amounts to much the same thing.

Also, you don’t necessarily need to just use quads, unless you’re interested in making 2D sprites. All the separate-but-combined bits can be any shape you want really.

For more flexibility, you can make the skinned mesh by code. A while ago, I was doing some comparisons of skinned mesh sprites vs. manually updated combined mesh sprites, and did this for making boned quads:

var numberOfObjects = 50;
var mat : Material;

function Start () {
	var smRenderer : SkinnedMeshRenderer = gameObject.AddComponent(SkinnedMeshRenderer);
	renderer.material = mat;

	// Build basic mesh
	var aMesh : Mesh = new Mesh ();
	
	var newVertices = new Vector3[4 * numberOfObjects];
	var newUV = new Vector2[4 * numberOfObjects];
	var newTris = new int[6 * numberOfObjects];
	
	var index = 0;
	var triIndex = 0;
	var offset = 0;
	for (i = 0; i < numberOfObjects; i++) {
		newUV[index] = Vector2(0.0, 0.0);
		newVertices[index++] = Vector3(-.5, -.5, 0.0);
		newUV[index] = Vector2(1.0, 0.0);
		newVertices[index++] = Vector3(.5, -.5, 0.0);
		newUV[index] = Vector2(0.0, 1.0);
		newVertices[index++] = Vector3(-.5, .5, 0.0);
		newUV[index] = Vector2(1.0, 1.0);
		newVertices[index++] = Vector3(.5, .5, 0.0);

		newTris[triIndex++] = 0+offset;
		newTris[triIndex++] = 1+offset;
		newTris[triIndex++] = 2+offset;
		newTris[triIndex++] = 1+offset;
		newTris[triIndex++] = 3+offset;
		newTris[triIndex++] = 2+offset;
		offset += 4;
	}
			
	aMesh.vertices = newVertices;
	aMesh.uv = newUV;
	aMesh.triangles = newTris;
	aMesh.RecalculateNormals();
	
	// Assign bone weights to mesh
	var weights = new BoneWeight[4 * numberOfObjects];
	
	index = 0;
	for (i = 0; i < numberOfObjects; i++) {
		weights[index].weight0 = 1;
		weights[index++].boneIndex0 = i;
		weights[index].weight0 = 1;
		weights[index++].boneIndex0 = i;
		weights[index].weight0 = 1;
		weights[index++].boneIndex0 = i;
		weights[index].weight0 = 1;
		weights[index++].boneIndex0 = i;		
	}

	aMesh.boneWeights = weights;
	
	// Make bones
	var aBones = new Transform[numberOfObjects];
	var bindPoses = new Matrix4x4[numberOfObjects];
	
	for (i = 0; i < numberOfObjects; i++) {
		aBones[i] = new GameObject("Bone", Rigidbody).transform;
		aBones[i].eulerAngles = Vector3(0.0, 0.0, Random.Range(0.0, 360.0));
		aBones[i].rigidbody.AddTorque(Vector3(0.0, 0.0, Random.Range(0.0, 360.0)) * Random.Range(1.0, 20.0));
		aBones[i].rigidbody.AddRelativeForce(Vector3.up * Random.Range(20, 25));
		aBones[i].rigidbody.useGravity = false;
		bindPoses[i] = Matrix4x4.identity;
	}
	
	// Assign bones and bind poses and mesh
	smRenderer.bones = aBones;
	aMesh.bindposes = bindPoses;
	smRenderer.sharedMesh = aMesh;
}

The results, by the way, were that the skinned mesh technique was about 20% faster, but that’s just on my computer…haven’t tested on the iPhone.

–Eric

As the sprite manager does the same as yours just without bones it can theoretically do everything yours does. it just might require a bit of modification to work in 3D and with 3D objects.

I know I’m definitely not up to modifying the sprite manager. :wink:

As for your skinned mesh sample code, Eric, thanks mucho! And yes, I do know that you need to use a single texture. :wink: Thing is, I’m not even at the understanding-sample-code stage on this technique yet. Any chance you (or someone) could post a little sample project? :slight_smile:

I think the above code might suffice to start with. :slight_smile: 1) Make new empty scene. 2) Put code on camera. 3) Press play. You can also drag a material onto the slot in the script and maybe add a light so you can see things better.

Since the bones are just objects like any other, once they’re instantiated, you can move them around as usual. Of course you’d want to take out the rigidbody stuff normally; that was just to get something moving. The only thing you’d need additional code for is setting up UVs for texture atlases, and UV animation if desired.

–Eric

Amazing, Eric!

Works perfectly. And what a nice, simple, clear example!

This should be enough to get me started.

Thanks a million!

Eric,

In your neat-o code, where exactly can I specify the dimensions of the resulting poly pieces or will they always be the size of the image in the material?

TIA!

That’s defined by the position of the vertices. To make it more dynamic, make a public variable like

var squareSize = .5;

Then change all the “.5” references in the vertices to “squareSize”, like

newVertices[index++] = Vector3(-squareSize, -squareSize, 0.0);

Then you can make the size whatever you want by changing the value of squareSize in the inspector. The actual dimensions would be squareSize*2 in world units.

–Eric

Heya, Eric!

If I wanted to change the material being textures to one of the mesh pieces made with your code, but only one of them, how would I do that?

Sorry, I didn’t quite get that…rephrase please? :slight_smile:

–Eric

In your demo, you create a mesh and cut it into pieces, each of which uses the same render material.

Thus, they all look the same.

What I was wondering is if I could either use a different material on the individual pieces.

Or, if they have to use the same material, could I map different parts of its texture to different pieces?

The goal either way is to get the individual mesh pieces to appear differntly-textured.

TIA!

You can use submeshes to have different materials, but I haven’t used those so I don’t know off the top of my head how it’s done, but that increases draw calls anyway.

Yep, you can change how the UVs are set for each piece. Right now they are all mapped from 0,0 to 1,1 with the “newUV[index] = Vector2(0.0, 0.0);” etc. code. So probably you’d take those out and set the UVs separately in another loop.

–Eric

I’ve used the method that marty described in his original post (the franken-mesh) for generating dozens of on screen elements under a single draw call, and its very effective and efficient. Personally, I find the ability to create my polygonal ‘billboards’ in a 3d app and set their UV’s there to be a much more intuitive way to go then generating polygons and adjusting their UV’s strictly through code. You would have a hard time scripting a non-square poly that has its UV’s set to slice out a specific shape from the middle of a large texture that includes many different elements.

Anyone has tested on the iphone if this method is faster than sprite manager?

@PirateNinjaAlliance: I like the idea to map the 3d billboard and bones in the 3d application but how can you display more sprite of the same type? do you instantiate the same bone n times at startup?

So, for - say - a field of clumps of grass with three different varieties of grass, you’re hand-creating and rigging every clump? That seems like a method with lots of potential for user-error. Maybe it’s the programmer in me… :slight_smile:

It’s not different from rigging a mesh.

Simply duplicating a bone from a skinned mesh will not duplicate the piece of mesh that is bound to it, so no, there’s no quick and easy way to clone a billboard made in this way in unity (and if you did, it would be another draw call anyway, defeating the purpose).

What I did was to simply clone my billboards in my 3d app, and make myself a decent sized pool of them to draw from. In Zombieville for instance, I have a few dozen individual billboards for bullet casings, blood and guts hidden off screen that I cycle through (if you’re not familiar with my game, you can see a trailer at our website www.zombievilleusa.com ) I just had to plan ahead how many I would need to see (maximum) on screen at one time. Our entire UI and all of its animating parts are also a single skinned mesh.

Maybe I’m crazy, but to me this is a very clean way of working, because I can think in terms of unity’s normal workflow - there is no difference between scripting a bunch of bones vs. scripting a bunch of other game objects - everything you would want to do with your individual billboards - moving/rotating/scaling them, scripting them individually, giving them tags or finding them by name… it all “just works.”

I can’t comment on whether its faster than sprite manager, as I have not used both, but I plan to use the technique for future projects.

As for the potential for user error, I suppose that’s always a risk, but if you’re just dealing with individual billboards the process is basically automatic in maya (just select all of your bones, your mesh, and simply bind to closest joint - done)

And if you’re just using a bunch of rectangular billboard objects, instead of say the more complicated zombie-bits (i.e. legs, arms, torso, zombieheads) that PNA is using, the all-code solution works very well too.

In fact, I’m using the all-code solution with some slightly non-rectangular pieces and just using alpha-bits in the images to achieve non-rectangularity.

Either method works, potentially saves you many draw calls and makes the planet a better place for all of us to live.

Thanks again to Eric and PNA for helping out on this thread!