Creating hair cards with Blender and xNormal

In a previous post I mentioned how I was stuck in the process of creating hair in Blender for real-time usage in Unreal Engine, and trying to wrap my head around the entire process. I am happy to report that I’ve been able to crack it after spending the weekend on this.

Hair cards are critical for real-time hair rendering, mainly videogames. You can afford to endure thousands of polygons for a CGI sequence in a film, but not so much for a game, because everything is rendered in real time, so performance is a huge consideration. The geometry budget is basically “as little as possible”. Hair cards are a fantastic workaround for this issue. I spent a lot of time reading about the methods and watching tutorials trying to reverse engineer how it’s done, and in particular how to do it in Blender. There were some roadblocks that was able to overcome and I intend to cover in this article. Mainly:

1- How get the new hair system into a usable mesh in Blender

2- How to separate single strands of hair into ID groups in order to fake different specular values later in Unreal (and do all other kinds of manipulations)

3- How to bake them in xNormal

Hair Modelling in Blender

First of all, the new hair system. There are several tutorials for this so I will skip over the basics of this and get to what we need in order to get the hair into a mesh.

The steps are as follows:

1- Create the hair with the new hair system. This is my example setup, with a plane in the back and a plane on the side working as the emitter. BTW, it is meant to be an example. In a real project you want to spend much more time figuring out what strands of hair you need, what kind of character you want to make, looking at references, etc. For now I’ll just use an example with 2 strands to show the process. The starter blender project file for this can be downloaded here.

Two strands of hair from top view. Each comes from a dedicated plane working as emitter that is perpendicular to the background plane.
This is what your outliner should look like.

By the way, hair can be easily thickened with this geometry node setup:

Geometry node setup for thickening hair. You can adjust the thickness with that integer, and the random offset in the random value “Max” parameters.

2- In the outliner, select the hair curve, go to the modifiers tab and apply the “Surface Deform” modifier to the hair. Do this for all your hair chunks.

Applying the modifiers

3- In object mode, go to the Object menu, then convert, then select particle system (a.k.a. the old hair system). Do this for all your hair chunks.

Converting the new hair system to the old system

4- Select the emitter for the particle system. Then go to the modifier tab, under the Particle System, select “Convert to mesh”. This will produce a set of edges, not a mesh, so we are not done. As always, do this for every hair segment. It will show up as “Mesh” on your outliner. Rename as appropiate.

Converting the particles into a mesh

5- Hide the emitters and the hair curves. Now select the new meshes (I have renamed them Segment1 and Segment2 here) then in the object menu convert them to curve. Then go to “Geometry” section in the curve tab, and add depth (0.005 tends to work but experiment according to your needs). Check the “Fill Caps” box.

Converting to Curve
Adding Depth

6- Select the curve, convert it back to mesh. Now our strands of hair are finished.

Converting back to mesh to finish our hair chunks

8- Select and join all hair meshes (Ctrl+J or Cmd+J)

9- Now you need to select and group vertices into vertex groups. There are several ways to do this but this is my preferred method (if anybody has a better way I’m happy to hear it!).

Enter edit mode, and in vertex selection mode (shortcut “1” on the keyboard) start selecting vertices at random with all linked vertices by hovering over the mesh with your mouse cursor and striking the “L” key on your keyboard. This will select an entire strand. If you keep moving your mouse at random over the vertices and hitting L (no mouse clicks necessary, just hover while hitting “L”), you’ll eventually get a good number of strands selected. Assign them to a vertex group.

After assigning them, deselect everything and do it again. Before assigning the new selection to a new vertex group, make sure to deselect the elements of the previous groups, because there will be overlap. Do this 4-5 times, each time deselecting all the previous groups before assigning the new one. For the final group, select everything, then deselect all the previous groups and assign all the previously unassigned strands to that last group. You should end up with 4-5 discreet groups of strands, each in a vertex group.

10- Deselect everything and select the elements of the first vertex group. Hit “P” on your keyboard and separate the elements by selection. Do this for each vertex group. The final result will be a set of 4-5 meshes which, combined, form your entire hair card mesh.

Separating hair groups
Hair mesh separated into 4 groups

11- Duplicate the background plane and put it on top of your meshes, it will be used as a cage for our baking process in xNormal. You should duplicate it with Shift+D, then press the Z key to move it on the Z axis and position it on top of our hair chunks.

Cage plane position

Preliminaries in GIMP

Next step is some preliminaries for xNormal. You can use any photo editing software (Photoshop, etc), I use GIMP. We have to create textures for baking. You need a full white texture, and a grayscale progression all the way to black according to the number of strand groups you created. So, as an example, if you had 4 like I do for this example, you need a fully black texture, a fully white texture, and then a 33% and 66% grey textures. These will be used for our height map. Also create a vertical gradient to use for modifying our hair color at the root of the chunks. This will allow us to hide the seams at the beginning of our hair cards. These texture do not need to be high res. For convenience here’s a set:

xNormal

From xNormal we need a set of different textures. We need an alpha map, a height map, a root map, and an ID map. I am using xNormal proper, but I know a Blender add-on exists that simplifies this process. I haven’t tried it, though, so I can’t say how well it works.

1- Export from Blender each mesh we’ve created. The 2 planes, and the 4 (or 5 or whatever you had) groups of hair strands.

2- Add all your strand groups as your high resolution meshes. Add your bottom plane as your low res mesh, and right click on it to add a cage mesh. Point to the top plane for this.

High Poly Meshes
Cage setup

3- Select all the high res meshes, right click, select the “base texture to bake” option. To get started, select the fully white texture. Then bake a base texture in the baking options. This will be our alpha. Make sure your resolution is 2 or 4k.

Alpha Baking

4- We’re going to repeat that same procedure with a slight differences. Change the output filename and change the map to bake from the base texture to the height map. A window will pop up to adjust the result. This might take several different tries and maybe even some adjusting in GIMP or Photoshop. It’s a process of experimentation. I guess one could also bake several different ones with different settings and layer them or layer blend them. I just did one for this example. The main thing is to make sure you get enough depth information, particularly in your more dense strands.

5- Change the base texture of the high res meshes to the gradient. Bake the base texture again with a different name. This will be our hair root map. We will use this ramp in unreal to hide the roots of the hair during implementation. You might have to rotate the gradient depending on your original orientation in Blender. Roots should be black, tips of the hair should be white.

5- Now change the texture of each strand to a different tier of the grayscale progression we made. Black, 33%, 66%, White. Then bake a base texture again under a different name. This will be our ID map.

ID base texture setup

You can also bake Ambient Occlusion and a Normal Map if you want to. But I want to keep this tutorial focused. So, for now, we’re done with xNormal, and these are our textures.

It’s time to go back to Blender.

Back to Blender

Create a plane, or better yet, go back to your previous project, hide all your meshes and keep the background plane. Change your renderer to Cycles (we need the transparency). Go to the shading tab for the plane and create a material using this node setup:

You should be seeing something like this on render view (I unchecked “Use scene world” and “Use scene lights” to simplify the scene)

Our results so far…

Now subdivide your plane into 6-8 segments to be able to model each card and split your mesh into cards, one for each strand of hair. Make sure to limit the amount of empty space for each card, so try to conform the shape of the card to the actual texture as much as possible. The game engine will need to compute the alpha and that is expensive, so you want your cards to be as optimized as possible. Make sure to leave some padding, though.

And you’re done! Now you can use your hair cards to model real-time hair on your character.

This was A LOT of stuff to reverse engineer, so there might be more efficient or better ways to do this (happy to hear from you if you know of any!). Notably, right now there is an app called FiberShop, it that creates all of these maps without so much hassle. It’s not really expensive if you’re going to be doing a lot of hair, and probably well worth the investment. Having said that, as an indy gamedev buying each tool that makes your life easier… that adds up. This process is free, and while FiberShop seems really convenient, I think there’s value in really understanding the steps and what’s going on and doing it like this at least once.

Back to the grind to try and finish a high quality 3rd person character. Making some great progress. Until next time!