Advertisement

Go Beyond Retro Pixel Art With Flat Shaded 3D in Unity

by

In this tutorial, I'll show you how to create flat-shaded 3D graphics for your Unity game, and explain why you'd want to do it in the first place.

Today, there is an abundance of 3D tools and engines that allow anyone to make 3D games. However, the challenges of 3D art are daunting and often require a huge amount of time, effort, and expertise which the solo gamedev or hobbyist doesn't have. Low-polygon models, in combination with flat shading, brings back the style of the early '90s, using techniques that anyone can learn. And it's not just a nostalgic throwback; flat-shading can easily be combined with modern techniques like ambient occlusion maps to give your game a striking, avant-garde look.

Get the example Unity project here

This tutorial assumes you already know how to do 3D modelling, or are able to find free 3D models on the internet. I'll be pulling example models out of thin air; there are tons of tutorials on the internet for how to get started in 3D modelling. If you're just starting out, I can highly recommend Wings 3D and SketchUp. Both have extremely accessible interfaces compared to professional modelling apps, and will export to formats that Blender and Unity can use.


Why Use Flat Shading?

So you've decided to get your hands dirty with Unity and start making a fully 3D game. Maybe you have no prior game art skills, or maybe you've made some art for 2D games; in either case, you will quickly learn that 3D games require a very large set of skills to create. Not only do you need to learn how to create 3D models, you need to unwrap those models to correctly draw a texture on the surface of your model. You also need to painstakingly create the texture, of course.

What if you could just create the model, but have it finished and ready for your game after a few extra steps to color and shade it? You might end up with something like this:

Or this:

Sure, the look is spartan and lo-fi—but that's the point! Whether you're aiming for early '90s nostalgia, a modern abstract look, or just want to save a lot of time making assets for a jam game, flat shading is an excellent choice of style for beginners and 3D experts alike.

If you are just getting started with game art, the simplicity of the workflow lets you concentrate on the fundamentals: learning how to create a 3D model with a nice silhouette and form, and coloring it with a small set of hand-picked colors. Concentrating on these fundamentals teaches you the basic skills of art without distractions.


How Flat Shading Works

Every 3D model is composed of a set of vertices that determine the shape of that model. For every vertex, there's also a vector called a normal—think of this as a small pin sticking out of the vertex.

The rendering engine uses the normal for each vertex and compares its direction to the direction of any incoming light source(s), and the direction in which the camera is facing, in order to determine the amount of light to apply to that vertex.

The renderer slowly fades the amount of light from one vertex to the next, giving a basic 3D model a "pillowy" look. Unless you apply a texture (and often, even if you do), this looks really terrible on anything that isn't actually a curvy, "pillowy" surface:

Flat_shaded_3D_in_Unity_Normals_and_Smooth

With flat shading, the entire surface of a polygon receives a uniform amount of light, calculated from a normal sticking outward from the center of the polygon:

Flat_shaded_3D_in_Unity_Normals_and_Flat

Modern modelling applications use "hard edges" or "smoothing groups" to adjust your model's vertex normals to match the polygon they're adjacent to, thus causing it to be uniformly and flatly shaded. A skilled 3D artist will use these to make sharp edges look sharp and flat surfaces look flat, but we're interested in making the entire model look sharp and flat. Thankfully, Unity makes this very simple.


Basic Technique

If you're sufficiently familiar with your modelling tool you can make your model look flat-shaded by setting all the edges as "hard". However, in Unity you can simply choose to let Unity create hard edges automatically:

Step 1

Click on your model in the Project window to bring up its Import Settings in the Inspector panel.

import_begin

Step 2

Go to the Normals & Tangents section and, in the Normals dropdown, choose Calculate.

import_calculate

Step 3

Set the Smoothing Angle to 0 and click Apply.

import_finished

Unity will make any edge that's a sharper angle than this into a hard edge. We want all our edges to be hard, so naturally setting it to 0 gives this effect.

Feel free to experiment with different smoothing angles, depending on the geometry of your model, it can be an amazingly easy way to give it a completely different look.

import_compare

Step 4

Now that we have our model flat shaded, we need to give it some color! There are many ways to do this, but some are better than others:

  • Select portions of your model and set them to a different material for each color you want to use.
  • Assign vertex colors in your modelling program.
  • Create a "palette texture" containing a bunch of colored squares and texture-map your model using this.

I greatly prefer the last method, which I'll describe to you below. Normally, after creating a 3D model, you must painstakingly unwrap it to match a hand-crafted texture. However, since we're only interested in having a single color for each polygon, it doesn't really matter what your UV unwrapping job looks like—it could just be a random jumble of vertices, so long as it's on top of the correct color!

The first step in coloring is to create the texture. For our example, let's say we want four colors:

palette_fulcrum

Next, load your completed 3D model in your modeller of choice and load up your texture. Select all of the faces of the model you want to be a certain color and automatically UV unwrap them. The result will probably be a confusing jumble of polygons.

Flat_shaded_3D_in_Unity_basic_uvunwrap

As I said, though, that really doesn't matter. Move all of the polygons on top of each other and scale them down into a little blob that will fit into one of the squares on your palette texture. Then, drag them on top of the color of your choice.

Flat_shaded_3D_in_Unity_basic_uvselect

Take a look at your model; the textured sections now have the correct color! Repeat this process for the rest of the faces of your model, again selecting and unwrapping them based on the color you want them to be. If you make a mistake, it's no big deal, just select the faces again, unwrap again if necessary, and put them in the right place on the texture. The same can't be said for "real" UV unwrapping!

Flat_shaded_3D_in_Unity_basic_finished

Voilà‎! You have a colored, flat-shaded model that you can import into your game.

Flat_shaded_3D_in_Unity_basic_imported

What About the Other Methods?

In case you were wondering, here's some reasons to avoid the other two methods:

Multiple Materials

This method creates a lot of materials you'll have to keep track of in Unity, and more importantly, Unity will not dynamically batch your models and use an entire draw call for each material, which is often the smallest bottleneck on modern graphics hardware.

Simply put, you'll get terrible performance if you use this method extensively.

Vertex Colors

This actually works fairly well, and you may in fact want to use this depending on your particular needs or art workflow. However, you'll notice something immediately: when you import your model and use a basic Diffuse shader, your vertex colors don't seem to be showing up!

This is because the basic shaders ignore them; you'll need to use special shaders that actually use the vertex color data. These can be easily found, but things get complicated if you also want other rendering effects from other shaders: these shaders often don't use vertex colors either, and you'll need to modify the shader yourself.

If you don't already know how to write shaders, this can lead to a lot of time and effort that you would rather be spending on the rest of your game. Also, it might just be me, but I find it a lot easier to change multiple colors at once with a texture rather than manually setting them in a modeller or in-game with a script.


Optimizing

Now that you have a texture palette and a basic technique figured out, you can go on to create the rest of the models for your game. As you may suspect, you can just keep adding colors to your palette and use the same texture for the entire game.

There are actually some big performance advantages to doing this: if you stick to using the same material for your entire game as well, Unity will usually draw all of your objects more efficiently by "batching" them. As I mentioned before, "draw calls" are a major bottleneck in computer graphics and it's a good idea to keep them to a minimum.

Unity will automatically try to draw your scene in as few draw calls as possible if you use a single material for everything, though every model needs to be scaled the same way and have less than about 300 vertices. It will inform you how well it's done in the Stats window:

Flat_shaded_3D_in_Unity_optimize_multi
Flat_shaded_3D_in_Unity_optimize_single

Tip: If you'd like to know more about batching, be sure to check out the Unity docs on the subject. This should be required reading for anyone interested in the performance of their game!


Changing Colors on the Fly

You have a set of models that are flat-shaded and colored, and everything seems in place, but what if you want to change colors while the game is being played?

For example, let's say that you have a script that will spawn your airplane, but afterwards you want to color it blue if it's friendly or red if it's an enemy. You can certainly just create separate palettes and separate materials, but it means you lose the performance boost of batching these two types of airplanes.

That's not a big deal if there's only a few of them in view at once. However, take the example of a procedurally-generated terrain made up of tons and tons of tiles or cubes, of many different terrain types, like a certain popular sandbox game. This would mean a ton of draw calls—but fear not, there's a way to keep using just one material!

Flat_shaded_3D_in_Unity_palette_colorizer

Remember how you colored your model by shoving its UV coordinates into colored boxes? Unity lets you modify the vertices of your model on the fly, including the UV coordinates, so for each different terrain type you can just move the UV coordinates into the correct color box via script.

Create a new C# script in Unity called PaletteColorizer and paste this code into it:

using UnityEngine;
using System.Collections;

public class PaletteColorizer : MonoBehaviour
{
	public int colorsPerRow = 4;
	public int colorIndex = 0;
	public bool overrideUVs = false;

	void Awake ()
	{
		if(!enabled)
		{
			return;
		}

		SetUVsToColor(colorIndex);
	}

	void Update()
	{
	}

	public void SetUVsToColor(int colorIndex)
	{
		if(overrideUVs)
		{
			SetUVs(gameObject, GetColorOffset(colorIndex));
		}
		else
		{
			TranslateUVs(gameObject, GetColorOffset(colorIndex));
		}
	}

	Vector2 GetColorOffset(int colorIndex)
	{
		Vector2 offset = new Vector2();
		float row = Mathf.Floor(colorIndex / colorsPerRow);
		float step = 1.0f / colorsPerRow;

		offset.x = (colorIndex - (row * colorsPerRow)) * step;
		offset.y = (1.0f - (row / colorsPerRow));

		return offset;
	}

	static public void TranslateUVs(GameObject obj, Vector2 offset)
	{
		var meshFilter = obj.GetComponent();
		Mesh mesh = meshFilter.mesh;

		var newUVs = new Vector2[mesh.uv.Length];

		for(int i=0; i < mesh.uv.Length; i++)
		{
			newUVs[i] = new Vector2(mesh.uv[i].x + offset.x, mesh.uv[i].y + offset.y);
		}

		mesh.uv = newUVs;
	}

	static public void SetUVs(GameObject obj, Vector2 offset)
	{
		var meshFilter = obj.GetComponent();
		Mesh mesh = meshFilter.mesh;

		var newUVs = new Vector2[mesh.uv.Length];

		for(int i=0; i < mesh.uv.Length; i++)
		{
			newUVs[i] = new Vector2(offset.x + 0.01f, offset.y - 0.01f);
		}

		mesh.uv = newUVs;
	}
}

To use this script, simply add it as a component to any game object with a material using a palette texture, set the number of colors per row, and set the color index (starting with 0 in the upper left-hand corner and counting to the right and down).

If your model's UV coordinates are not set up to fit inside the palette boxes (such as in the case of the "water" quad around the hexes), check the Override UVs checkbox; as you can see above, this will make the script write entirely new coordinates rather than shifting existing ones.

One thing to keep in mind about this method is that, when modifying any mesh, Unity will quietly create a new mesh in memory. If you do this particularly often on a large number of meshes, you might run into memory issues, and you'll want to look into a means of caching and sharing meshes that have been colorized the same way. For a reasonable number of low-poly models, though, you'll probably never need to worry about it.


Baked Ambient Occlusion

Flat_shaded_3D_in_Unity_ao_scene

Flat shading doesn't necessarily need to always be paired with uniform blocks of color. One way you can add some subtle detail and a realistic "dusty" look to any surface is to add ambient occlusion to it. Unity Pro provides ways to add ambient occlusion as a post-processing filter, but I want to show you a way you can add that extra bit of polish without needing the Pro version, and without even needing any special shaders.

ao_compare_haus

All you need to do is unwrap your model and "bake" the ambient occlusion effect onto a texture. This does require you to actually spend some time and effort to correctly unwrap your model, but after that step it's a matter of a few clicks and a bit of fiddling in your paint program to give your model a slightly more real, avant-garde look.

Step 1

First, you will want to boot up Blender with an empty scene: Press A to select all objects in the startup scene, press Delete, then press D to confirm. You don't want any Lamps or Cameras because these will get imported as empty junk objects into Unity.

Step 2

Then, import your model into it. (File > Import > Wavefront (.obj)). For this example, let's start with a boulder.

ao_rock_import

Step 3

Open the UV Editor and create a texture that's able to contain roughly the amount of detail you want. I'm pretty stingy, so I stick to 256x256px for anything about the size of the player (a rock or tree), and only go bigger for very large objects (like the mountain they're walking on).

ao_rock_addtexture

Step 4

Now, unwrap your model. There are many ways to go about this, which are covered in much more detail than I could ever hope to, so please consult the Blender documentation or other online tutorials on how to correctly and efficiently unwrap your model.

Step 5

Now that you have an unwrapped model, you're ready to bake the ambient occlusion onto your texture. Blender will render the ambient occlusion directly onto your texture, respecting the UV coordinates you just unwrapped. Simply follow these steps:

  1. Click the Render button (the camera icon) on the Properties panel on the right side of the screen.
  2. Scroll down to the Bake subsection (at the bottom) and expand it.
  3. Change the Bake Mode from Full Render to Ambient Occlusion.
  4. Click the Bake button directly above that.
ao_blender_bake_options
ao_blender_generate_ao

Step 6

Now you have the baked AO texture and a model that has the correct UV coordinates to use it. Save the texture to an image file and export the model in Blender to a format Unity can use (such as .fbx).

Step 7

Depending on your needs, you may be able to use the texture as-is. Simply create a new Diffuse material that uses your new baked AO texture and set the color to something that looks nice.

Flat_shaded_3D_in_Unity_ao_rock_inscene

However, you'll likely want to have models with multiple colors, such as a tree. You only need to follow a few extra steps. Follow the directions above to create a baked AO texture for your tree model. This time, though, you'll want to keep track of where you put your UV coordinates, because you'll need to color those locations on the texture manually.

It would be wise to group your UV islands by color. In this example, I'm putting the trunk parts at the top of the texture and the leaf parts on the bottom.

Flat_shaded_3D_in_Unity_ao_tree_trunk
Flat_shaded_3D_in_Unity_ao_tree_foliage

Step 8

Open your baked AO texture with your paint program of choice (anything that supports layers will do, such as GIMP). Create a new layer and put it above your AO layer, using the AO layer as a guide to color the new layer according to the colors you want to see in the model.

Use the Lasso tool, or other selection tool of your choice, to select the portions of the texture you want to color, then fill those with the correct color.

Flat_shaded_3D_in_Unity_ao_tree_texture_brown
Flat_shaded_3D_in_Unity_ao_tree_texture_brownandgreen

Step 9

You now have the colors set up, so it's time for the final task: blending the AO on top of the colors. Move the AO layer on top of the colored layer and change the blend mode to Multiply.

Flat_shaded_3D_in_Unity_ao_tree_texture_multiply

Set the layer opacity to whatever looks nice. Use your own judgment, but be sure to write down this percentage, since to get a uniform effect on all of your textures you'll want to use the same blend method and amount.

Flat_shaded_3D_in_Unity_ao_tree_texture_multiply_half

Save this texture to a file and import it in Unity alongside the tree model. Create a material just like you did for the boulder, using the texture you just made.

Flat_shaded_3D_in_Unity_ao_tree_inscene

That's pretty much it! Use this technique on all your models to give the whole scene a more polished, tangible look than you could get with basic flat-shading.

You may notice that your materials are using just basic diffuse shaders. One of the beautiful things about using baked AO blended directly on top of the color texture is that you can use this texture with any other shader that takes a diffuse texture... with no AO-specific code required!


Conclusion

Now you have a handy toolbox of graphics techniques you can use to create an entire flat-shaded game. With enough practice, you can make stunning visuals that are quick to produce and that render efficiently.

Advertisement