Geometry Shader: Generating Fur
By dieter tack
Introduction
In this paper I will try to explain the process of generating fur on a model. We do not render each individual hair on its own because this would be way too intensive for a real time application. Instead I will be using the shells and fins technique.
To begin we will just render our base mesh, like we would with a normal material. But we set the diffuse color to be slightly darker, because when there is fur on a surface, it is logical that the surface will be more in the shadow, thus darker.
Then on top of that we start generating the shells. The shells are the most noticeable part of the fur shader, because this is what actually fakes the hair strands. To visualize these shells, we render our layer n times (n being the amount of layers we want) on top of each other.
And then for each of these layers we sample the basic diffuse texture, but we also sample a noise map for the alpha, where white is visible thus fur, and black becomes transparent which will be the area between the furs.
We could do the shell step in 2 ways, either we generate them on the GPU with a geometry pass, or we actually render the shell pass multiple times and pass a new layer as parameter.
Although after benchmarking these 2 ways and noticing significant better results with the geometry approach, I still chose to go with the way of rendering it multiple times in engine.
This because the result can look visually way more appealing this way, the reason being we can render as much layers as we want, while with the geometry approach we are limited to our hardware with a limited number of vertices we can generate in one pass.
As a final step we render the fins. We need this step because sometimes the gaps between the different layers of the shells can become visible on the sides of our mesh. Therefore we extrude a quad on the sides of our mesh and apply the alpha values of a texture
representing the side of some hair.
States used
These are pretty much self-explanatory.
Structs used
The structs used are pretty self-explanatory as well.
We obviously have the position of our vertices, then the normal to do light calculations and the standard texture coordinate for sampling our diffuse texture. The only noticeable thing really is the TexCoord1 in our GS_OUTPUT struct, this will be The texture coordinates we use to sample our alpha for the fin textures.
Base Mesh Pass
pass normalPass {
SetDepthStencilState(EnableDepthWriting, 0);
SetRasterizerState(BackCulling);
SetVertexShader(CompileShader(vs_4_0, MainVS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_4_0, MainPS()));
}
In this pass we just do the basic stuff, something to notice is that we specifically set our DepthStencilState to enable depth, because in our last pass we will disable depth, so here we need to reset it.
In the vertex shader we transform our position to world space, rotate our normal with the world matrix and simply pass along the texture coordinates.
In the pixel shader we sample our diffuse texture. We calculate the diffuse intensity, and we use the Blinn-Phong technique to calculate our specular lighting. We add these things all together, make it a bit darker, and return the result;
Shell Pass
pass shellPass {
SetDepthStencilState(EnableDepthWriting, 0);
SetRasterizerState(NoCulling);
SetVertexShader(CompileShader(vs_4_0, ShellVS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_4_0, ShellPS()));
}
Just to be safe we make sure to enable depthwriting again, and we put our rasterizerstate to no culling, because there is a slim chance we might still see some hairs popping out of the back between our shells, it’s not really necessary but I prefer to be safe.
Vertex Shader
In the vertex shader of the shell pass we almost do the same as we did in the last pass, except for the position there is a big difference, we extrude our vertices for a certain distance along the normal depending on the current layer we are rendering. In the engine we make sure that our CurrentLayer is always a number between 0 and 1 so that the number of layers has no effect on the hair length.
To add gravity we use an extra downward pointing vector, we adjust this with the sine of elapsed time to give some kind of wind feeling making the hair go from left to right. Then we multiply this gravity vector with a factor k, this decides how much the hair should be hanging, the further outward we are the lower we want the hair to be hanging, that’s why we use an exponential function. Then we simply add this gravity vector to our world position.
Pixel Shader
In the Pixel shader we do pretty standard stuff again.
The main difference here is that we sample a second texture, being some sort of noise, to determine our alpha value. This way we can decide where there should be hair and where not.
Also we have a shadow factor, this is to mimic inter fur shading. Because in the real world these strains of hair also cast shadows, so the lower parts of the hair are much darker than the top parts. To calculate this we simply lerp our current layer between a minimum darkness value and 1. We lerp this because we don’t want the bottom parts to be completely black, as there is still some light falling on those.
Also a small notice is that when our current layer we are rendering is 0, we set our alpha value to 0, this just to make sure that we don’t render over our base render again to avoid possible artifacts or z-fighting.
Fin Pass
pass finPass {
SetBlendState(AlphaBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF);
SetDepthStencilState(DisableDepthWriting, 0);
SetRasterizerState(BackCulling);
SetVertexShader(CompileShader(vs_4_0, FinVS()));
SetGeometryShader(CompileShader(gs_4_0, FinGS()));
SetPixelShader(CompileShader(ps_4_0, FinPS()));
}
Here it is important to set our DepthStencilState to disable depth writing or reading, we just want to render the fins on top of our already rendered geometry, if not we would not see the fins because they would be covered by some parts of the shells we rendered before after the depth test. For this pass we also use a geometry shader.
Vertex Shader
In our fin vertex shader, as mostly expected when we use a geometry shader, we don’t do anything with our data we receive, we just pass it along to the geometry shader.
Geometry Shader
As you can see, we take a line as input, so it is important in the engine implementation that we change our primitive topology before we start this pass. We put our maxvertexcount at 4 because we only want to generate 1 quad each time the geometry shader gets called.
The fins should be generated on the side of our mesh, but then we need to know first which lines are on the edge and which are not. To determine this we take the average between our 2 positions and subtract the position of our camera which we get out of our view inverse matrix. We then calculate the average normal between the 2 points, and calculate
the dot product. If the dot product is somewhere around 0 we know that we are on the edge, so we can proceed by extruding these vertices.
As you can see we then call our CreateFinVertex function 4 times, so that we generate a new quad on the edge. We start by adding the points we got as input to our trianglestream, afterward we calculate the upper points of this quad, and we simply do this by adding our normal times our fur length.
The CreateFinVertex method is similar to the vertex shader from the shell pass using gravity and such to calculate our world position, without the extrusion of course. One thing to notice is that here we pass a texCoord1 as last parameter, as mentioned before, this is to sample the alpha texture for our fins.
Pixel Shader
In our fin pixel shader, we do more of the same that we did in other pixel shaders. With some additions. We sample the alpha from a different texture that represents the side of hair. To make sure that the fins are not super noticeable I added a gradient to our alpha, so that the closer we are to our mesh the more transparent the quad is, this is so that you don’t notice any ugly edges from the quad “cutting” into our hairs. For the rest we just do our standard diffuse and specular calculations again.
This is what the shells would look like on their own with this technique:
Engine Remark
As I mentioned in the beginning I chose to render the shells in multiple times, because of the ability to generate more layers, at the cost of some performance.
I approached it like this:
The UpdateMaterials is just a method that updates all the needed variables, so that we can change them real time, as parameter we pass the current layer we are rendering, for the basic and fin pass it is not important which number we use, because these passes don’t use the current layer variable.
As you can see, when we are rendering pass 1 (which is the shell pass) we loop our draw over and over again as much times as the desired amount of layers.
Also as mentioned before, it is important that when we go into the fin pass (pass number 2) we set our primitive topology to linelist.
Result
If we add all these passes together we get to a pretty decent looking result, looking something like this.
References
http://developer.download.nvidia.com/SDK/10/direct3d/Source/Fur/doc/FurShellsA ndFins.pdf
http://www.xbdev.net/directx3dx/specialX/Fur/
http://www.catalinzima.com/xna/tutorials/fur-rendering/
http://research.microsoft.com/en-us/um/people/hoppe/fur.pdf