Article: Fur Rendering

by Catalin Zima
In this article, you will see how you can render fur in your XNA games. At first, we will look over the technique use to render the fur, then we will look at several improvements that can be done, and in the end, we will apply it to a model.
Rendering Fur
When thinking about rendering fur, there are a few possible ways you could think of. The most obvious is to model each individual strand of hair by one or two polygons. This way, you could animate each strand individually, but the cost of modeling and rendering just a small patch of hair would be awfully large. Another way would be to create some polygons which are rendered with textures containing some strand of hair on them, with a transparent background (one polygon contains a vertical slice of a hair patch). This is also fairly easy to animate, but it yields some problems when trying to use alpha blending (because you need to sort the polygons) and it looses it's furriness when viewed from above. The method that will be implemented in this sample is called shell rendering, and creates a patch of fur by rendering several horizontal slices through the patch. As opposed to the previous method, the problem with this approach appears when viewing the patch of hair from low angles (looking at it from the side). However, when applying this to a model, instead of a flat surface, this artifact is not so visible, and we can get away with it. Another possibility would be to combine the shell rendering with the vertical slices, method called shells and fins. But this will be left as a possible future improvement for the reader. For the scope of this tutorial, we will only focus on the shells.
Rendering with shells
The main idea of rendering with shells can be illustrated through the following image.

As you can see, there are several horizontal slices through the patch of hair. Each slice by itself doesn't to a good job of representing fur: it is just a polygon with lots of points on it. But when stacking enough slices one over the other, the impression of fur soon emerges. It's just like drawing a line by drawing lots of points close to each other. To render fur like this, there are three main issues that have to be considered:
- Information about the positions of the hair strands. When rendering each slice, we need to know which pixels will be visible (belonging to a strand of hair), and which pixels will be transparent. We will store this information in a texture. A opaque pixel in this texture will represent a hair strand. When creating this texture, we will randomly place opaque pixels on it. The number of pixels we will place will depend on a value which will indicate the density of the hair.
- Position of the slices. For a simple demonstration on a planar horizontal patch, the positions of the slices would simply be growing on the Up axis. However, we want to make this easily applicable on any model, so the best way to do this is to displace each slice based on the base polygon's normal. In fact, to create the slices, we will just render each polygon several times, and at each rendering, we will displace the polygons with a small distance, based on their normals.
- Color of each hair strand. Initially, we will simply read the color from the density map, but afterwards, we will read the color from a separate map, in order to achieve nicer looking fur.
Next, we will go through rendering a patch of fur, step by step, starting with generating the fur texture.
Generation of the fur map
As seen previously, the fur map will be used to render each slice of fur. It will contains transparent pixels for non-fur areas, and opaque pixels for hair strands. We compute the number of these pixels using the density, and then randomly place them on the surface of the texture. The complete function that does this can be seen below
/// <summary>
/// This functions prepares a texture to be used for fur rendering
/// </summary>
/// <param name="furTexture">This will contain the final texture</param>
/// <param name="density">Hair density in [0..1] range </param>
private void FillFurTexture(Texture2D furTexture, float density)
{
//read the width and height of the texture
int width = furTexture.Width;
int height = furTexture.Height;
int totalPixels = width * height;
//an array to hold our pixels
Color[] colors;
colors = new Color[totalPixels];
//random number generator
Random rand = new Random();
//initialize all pixels to transparent black
for (int i = 0; i < totalPixels; i++)
colors[i] = Color.TransparentBlack;
//compute the number of opaque pixels = nr of hair strands
int nrStrands = (int)(density * totalPixels);
//fill texture with opaque pixels
for (int i = 0; i < nrStrands; i++)
{
int x, y;
//random position on the texture
x = rand.Next(height);
y = rand.Next(width);
//put color (which has an alpha value of 255, i.e. opaque)
colors[x * width + y] = Color.Gold;
}
//set the pixels on the texture.
furTexture.SetData<Color>(colors);
}
After having the fur texture, we will first try to render fur on a single polygon. For simplicity, we will simply use an array of vertices, which will be drawn using DrawUserPrimitives. Later in the tutorial, we will use the Model class.
Generating the geometry
The function that generates geometry simply creates two triangles, sitting on the XY plane, with the -Z axis as a normal, and corresponding texture coordinates.
VertexPositionNormalTexture[] vertices;
private void GenerateGeometry()
{
vertices = new VertexPositionNormalTexture[6];
vertices[0] = new VertexPositionNormalTexture(
new Vector3(-10,0,0),
-Vector3.UnitZ,
new Vector2(0,0));
vertices[1] = new VertexPositionNormalTexture(
new Vector3(10,20,0),
-Vector3.UnitZ,
new Vector2(1,1));
vertices[2] = new VertexPositionNormalTexture(
new Vector3(-10, 20, 0),
-Vector3.UnitZ,
new Vector2(0, 1));
vertices[3] = vertices[0];
vertices[4] = new VertexPositionNormalTexture(
new Vector3(10, 0, 0),
-Vector3.UnitZ,
new Vector2(1, 0));
vertices[5] = vertices[1];
}
Rendering the shells
We saw earlier the we need to draw a certain number of slices in order to achieve the wanted effect. The slices have to each be displaced by a small amount from the previous slice. Generating all these displacements on the CPU is a time consuming work, and since we have access to vertex shaders, it would be a pity to waste all those CPU cycles for something the GPU can do easily. To achieve all layers, we will send the same geometry several times to be rendered. We will also tell the shader which slice we are currently rendering, and what is the maximum displacement from the base geometry. Using this information, the vertex shader will slightly modify the position of each vertex along the normal, based on the current layer.
Now that you have the fur texture, and the geometry, the next step is to write the shader that will handle the rendering of fur. To do this, create a new shader in the Content project. For the shader, we will need several parameters. The World, View and Projection matrices are well-known, but we also need a parameter that controls the hair length, let's name it MaxHairLength, and one that tells us which horizontal slice (i.e. layer) is currently being processed. To achieve some independence from the actual numbers of layers that are actually used, we will consider that the CurrentLayer parameter belong to the [0..1] range, 0 meaning that the current layer is the closest to the real surface, and 1 that it is the farthest from it. The intermediate layers will map to intermediate values. This value, multiplied with the maximum hair length will yield the displacement of the layer being processed at the respective moment. Lastly, we need to read data from the fur texture, so we need a parameter and a sampler for it.
float4x4 World;
float4x4 View;
float4x4 Projection;
float CurrentLayer; //value between 0 and 1
float MaxHairLength; //maximum hair length
texture FurTexture;
sampler FurSampler = sampler_state
{
Texture = (FurTexture);
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
AddressU = Wrap;
AddressV = Wrap;
};
Each vertex needs to have data related to position, normal and texture coordinates. This data is send by the application, so we need to declare a vertex input structure that can receive it. The output of the vertex shader will contain the final position, and the texture coordinates. Later in the tutorial, when you will also add lighting to the fur, the normals will also be passed as an output, but for now, we leave that out. The two vertex shader structures can be seen below.
struct VertexShaderInput
{
float3 Position : POSITION0;
float3 Normal : NORMAL0;
float2 TexCoord : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
Now, to move onwards to the main part of the effect file, the shaders. In the vertex shader, you need to generate the new position of a vertex, as explained previously. To obtain the new position, we compute the current layer's displacement, and move the vertex along its normal with that value. The line of code that does that is:
float3 pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;
This results in the final position of the vertex. The rest of the vertex shader implements the operations commonly found in vertex shaders: transform the position using the three matrices, and pass along the texture coordinates to the pixel shader. The final vertex shader can be seen below.
VertexShaderOutput FurVertexShader(VertexShaderInput input)
{
VertexShaderOutput output;
float3 pos;
pos = input.Position + input.Normal * MaxHairLength * CurrentLayer;
float4 worldPosition = mul(float4(pos,1), World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.TexCoord = input.TexCoord;
return output;
}
The pixel shader simply reads from the fur texture, and output the color. Since we convened that the fur shader has alpha 0 for areas where there is no hair, we will use alpha blending to make those areas transparent. The pixels which belong to the hair strands will remain opaque. Specifying the alpha blending can be done either through code in the application, or inside the declaration of techniques and passes in the effect files. For this tutorial, we chose the latter.
float4 FurPixelShader(VertexShaderOutput input) : COLOR0
{
return tex2D(FurSampler, input.TexCoord);
}
technique Fur
{
pass Pass1
{
AlphaBlendEnable = true;
SrcBlend = SRCALPHA;
DestBlend = INVSRCALPHA;
CullMode = None;
VertexShader = compile vs_2_0 FurVertexShader();
PixelShader = compile ps_2_0 FurPixelShader();
}
}
Putting it together: drawing the geometry
For clearer code, let's create a function that draws the geometry we defined earlier. This function needs to set the vertex declaration on the device, and make a call to GraphicsDevice.DrawUserPrimitives.
private void DrawGeometry()
{
using (VertexDeclaration vdecl = new VertexDeclaration(
GraphicsDevice,
VertexPositionNormalTexture.VertexElements))
{
GraphicsDevice.VertexDeclaration = vdecl;
GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, vertices, 0, 2);
}
}
Now we need to write some code in the Draw function, code that actually draws our object and the fur layers. Before this, we need to add some members, and initialize them in the LoadContent() function. Also add the file Camera.cs to your project, and initialize a camera in the game's Initialize function.
[...]
using XNASimpleCamera; //namespace of Camera.cs
[...]
public class Game1 : Microsoft.Xna.Framework.Game
{
[...]
//simple camera for use in the game
Camera camera;
//texture containing fur data
Texture2D furTexture;
//effect for fur shaders
Effect furEffect;
//number of layers of fur
int nrOfLayers = 40;
//total length of the hair
float maxHairLength = 2.0f;
//density of hair
float density = 0.2f;
protected override void Initialize()
{
//initialize the camera
camera = new Camera(this);
Components.Add(camera);
base.Initialize();
}
protected override void LoadContent()
{
[...]
//generate the geometry
GenerateGeometry();
//load the effect
furEffect = Content.Load<Effect>("FurEffect");
//create the texture
furTexture = new Texture2D(GraphicsDevice,
256, 256, 1,
TextureUsage.None,
SurfaceFormat.Color);
//fill the texture
FillFurTexture(furTexture, density);
}
}
You can use some of the parameters to change the way the fur looks and behaves. The numberOfLayers controls both the quality and the performance. A large number of layers will result in better quality, but may result in some loss of performance. You can easily reach a compromise depending on your application. The maxHairLength and density easily modify the looks of the fur.
Now let's go to the Draw() function. Here we need to set up the parameters for the effect. After that, we draw the mode nrOfLayers times, and set the CurrentLayer shader parameter before each drawing.
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
furEffect.Parameters["World"].SetValue(Matrix.CreateTranslation(0,-10,0));
furEffect.Parameters["View"].SetValue(camera.View);
furEffect.Parameters["Projection"].SetValue(camera.Projection);
furEffect.Parameters["MaxHairLength"].SetValue(maxHairLength);
furEffect.Parameters["FurTexture"].SetValue(furTexture);
furEffect.Begin();
for (int i = 0; i < nrOfLayers; i++)
{
furEffect.Parameters["CurrentLayer"].SetValue((float)i/nrOfLayers);
furEffect.CommitChanges();
furEffect.CurrentTechnique.Passes[0].Begin();
DrawGeometry();
furEffect.CurrentTechnique.Passes[0].End();
}
furEffect.End();
}
Running the game after this should show something similar to the following picture.

As you can see, the strands of fur show up. However, there are two things that could be done better. The first one is drawing an opaque polygon at the base of the fur, so we don't see through to the other side, and the second is applying colors from a texture to the fur.
Download the following image (courtesy of http://www.emanueleferonato.com ), and add it to your Content project.

Now add a member to the Game class to hold this texture, and load it inside the LoadContent() function.
Texture2D furColorTexture;
protected override void LoadContent()
{
[...]
furColorTexture = Content.Load<Texture2D>("bigtiger");
}
Next, we need to modify the shader. We will add a new parameter and sampler for the color texture. Inside the pixel shader (the only place we need to make modifications), we will read the color information and combine it with the alpha of the fur texture. (Note: we could also just put the transparency information in the color texture, but separating them let's us easily change the color texture, and leaves us with potential space for parameters that might be written into the RGB channels of the fur texture. We will talk about this later)
In order to make the base layer opaque, we will compare the CurrentLayer variable with 0. If it is 0, we set the result's alpha to 1 (opaque), otherwise, we set it to the value read from the fur texture.
texture Texture;
sampler FurColorSampler = sampler_state
{
Texture = (Texture);
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Wrap;
AddressV = Wrap;
};
[...]
float4 FurPixelShader(VertexShaderOutput input) : COLOR0
{
float4 furData = tex2D(FurSampler, input.TexCoord);
float4 furColor = tex2D(FurColorSampler, input.TexCoord);
//make opaque if lowest layer, otherwise use alpha channel of furData
furColor.a = (CurrentLayer == 0) ? 1 : furData.a;
return furColor;
}
Then add the following line in your Draw() function.
furEffect.Parameters["Texture"].SetValue(furColorTexture);
The result can be seen below

January 30th, 2010 - 23:07
AMAZING !
This is a VERY useful tutorial, it removes the mystery from the cool looking effects we see in games
April 15th, 2010 - 23:24
Catalin, your tutorial is nicely done.
Have you tried adding the Banks/Kay-Kajiya hair lighting model? I found that to be the key to getting good looking fur, especially as the light moves.
See http://jedwork.com/jed/papers/LPFH-fur-2001.pdf for more details.