Anisotropic Shader For Hair

0

best_hair

Introduction

This document will explain how to build a material with anisotropic specular highlights as seen in hair and other directionally grooved surfaces. The term anisotropic simply means having different values when measured in different directions. It is a relatively general term, and has more than one use in the world of computer graphics (including anisotropic filtering, which involves blending texture pixels in different amounts along different axes depending on the orientation of a polygon in relation to the camera.) Here we are using the term anisotropic in relation to the specular highlight of a surface. The goal is to stretch the highlight in a particular direction.

versus_results

This is useful for surfaces like hair, brushed metal, coiled wire, and anything else where surface elements smaller than a pixel each have their own individual specular highlights. In hair, for example, each strand is cylindrical, and therefore reflects the light along their own curvature, regardless of the curvature of the character’s head. Theoretically this could be achieved with a regular specular if the normal map and screen resolution were ridiculously high, with each strand having it’s own calculation. For practical purposes, however, this is out of the question. Instead, we must fake it with a material. Here is the material that we will be creating.

anisotropic_mat

Concepts

The basic idea of this material is that instead of using the Specular input to create a standard highlight, we recreate the calculations that result in specularity ourselves using vector math, and add this computed highlight onto the diffuse. By isolating the specular in this way, we have the freedom to manipulate it however we want, in this case warping it over the surface and using its brightness values as coordinates upon which to map a custom painted highlight gradient.

Before we can actually construct this material, it is necessary to understand some fundamental concepts regarding specularity and the vectors that control it. Think of these vectors as imaginary lines that head from a point on an object’s surface towards the camera, the light, and straight up from the surface (the normal). The diffuse lighting of a surface depends solely upon the position of the light in regards to the surface. Specular, however, is essentially scattered reflection, and is therefore reliant upon the position of the camera as well as the light and the surface normal. In order to evaluate this relationship, we use what is called the Half-Angle Vector, which is a vector that is halfway between the Camera Vector and the Light Vector.

vector_diagram_basic

When there is a significant difference between the Half-Angle Vector and the Surface Normal, the result is dark, with dim or no highlight at that point.

vector_diagram_low_spec

When the Half-Angle Vector approaches the Surface normal, the specular highlight increases.

vector_diagram_high_spec

Creating Specularity

The core of this material is a group of expressions that find the Half-Angle Vector, and determine its proximity to the surface normal in order to determine if any given point should be highlighted with specularity or not. These expressions are shown below.

anisotropic_mat_spec_core

In order to find the Half-Angle Vector, we add together a Camera Vector and a Light Vector. Adding two vectors together creates a third vector that is halfway between the two. In much the same way that one finds the average of two numbers by adding them and dividing by two, we do the same thing here with three dimensional vectors, except that we have not bothered to divide by 2, because for the moment we are concerned only with direction, not magnitude. Adding vectors is regularly dependent on their magnitude as well as their direction, but in UnrealEd the Camera Vector and Light Vector are both normalized, which means that their size is always considered to be 1, regardless of how close or far the camera or light is from the surface. Because of this, we can be certain that adding these two vectors will always produce a true Half-Angle Vector.

Now that we have the Half-Angle Vector, we need to distinguish between pixels where it is near the Surface Normal, and where it is not. For this we use a Dot Product of the two vectors. A Dot Product returns a value of 1 when two vectors are exactly identical, and darkens as the angle between them increase. When the two vectors are 90 degrees from each other, the result of the Dot Product is zero. At 180 degrees, the value is -1. The specific mathematical relationship is that a Dot Product is the cosine of the angle between the two vectors multiplied by the size of each vector. This means that the size of the vectors matters as well as the direction, but only in regards to the brightness of the result. The dot product of two normalized vectors (meaning with a magnitude of 1) will vary between -1 and 1 depending on the angle, but if each vector had a magnitude of 2, the dot product would vary between -4 and 4. Regardless of their magnitudes, however, the zero point remains the same. For this reason, there is no need to normalize the vectors, because we can simply control the brightness of the result afterwards using a Multiply expression and a constant.

The surface normal used for the Dot Product can be taken from a normal map, or in the absence of a normal map, from a Constant 3 Vector, with the value of 1 in the Blue Channel, and zero in the others (which is what Normal Map Blue becomes in Unreal). You will notice that in our example, a different value is used. The reason for this will be addressed below. If we were to use Normal Map Blue for the moment, and connect the result to the diffuse, there would be a specular highlight so wide that it is almost indistinguishable from diffuse lighting. The image below shows this to the left. If we were then to apply a Power expression using a large exponent, we could attenuate it down to a tighter highlight, and Multiply up the result like what is shown below to the left. In this way we can simulate regular specularity without ever touching the actual specular input of the material.

isolated_spec_result

Creating Texture Coordinate Data from the Highlight

Our goal, however, is not to simulate regular specularity, but to stretch the highlight across the surface perpendicular to the direction of the hairs, as if each hair was shining individually. The first step of this is to use a different value in place of the surface normal. If we increase the green this will tilt the interpreted normal upwards along the UVs, and the highlight will follow. The image below shows the progression as the Green channel of the Constant 3 Vector is increased.

constant3_change

The last example has been given merely to show an exaggerated result of what this causes. For our purposes we will only use R=0, G=1, B=1. The reason for tilting the normal is so that it warps the specular highlight to get a gradation that stretches all the way across the surface, perpendicular to the direction of the hairs. We will soon be using the gradation as texture coordinate information with which to map our own custom highlight. Different normals used here would orient this gradation in different directions. While the important fact here is that the normal is tilted along the direction of the hairs, it should be noted that there are two different directions which follow the hairs, up and down. These will give different results. Ultimately we will be creating a ring around the regular specular highlight, and bending it around the surface, but there will still always be curvature of some kind, and it will never entirely match up with the proper placement of the highlight, just close enough to seem correct to the eye. Choosing one direction for the normal map versus its opposite will decide which side of the regular highlight the final result curves away from. All other factors controlling falloff and placement will be dependent on the constants in the material and details of the hand-painted gradient. The choice between opposite directions should be made simply based on what looks best.

direction_comparison

You will notice that a normal map texture has been left unconnected right above the Constant 3 Vector, which can be swapped into the Dot Product instead. Most of this texture is filled with the same color as the Constant 3 Vector, but it fades off to standard Normal Map Blue at the top and bottom. This causes the warping effect to fade off at the top and bottom of the texture sheet, which in this case corresponds to the poles of the sphere. The greater implication of this is that one can custom paint a normal map with different directions for different hairs, and fade the effect off by blending to standard normal map blue. This freedom allows us to use the anisotropic effect on hair that is messy and wild as well as straight and homogenous, regardless of the orientation of their UVs. More information on this is detailed below, in the Practical Usage section.

While changing the normal here results in something that no longer is a specular highlight, the important thing is that it is still governed by the proper vector relationships. The goal here is to use the gradient as texture coordinate data for a highlight that we paint ourselves. We are only concerned here with U coordinates, and can disregard the V. As such, we can now manipulate these values as we would any other texture coordinate values. This means Multiplying it by a constant to stretch or squeeze the texture space, and Adding a constant to slide it around. The values of these constants will be found through trial and error once we’ve applied our hand-painted gradient highlight.

Applying the Custom Anisotropic Highlight

Because of the non-linear nature of the warped highlight’s falloff, the gradient that we paint in Photoshop should not actually have it’s high point at the middle, but really something closer to 70% across. If the gradient tool is used to create this, the falloff will also need to be made harsher using a Levels or Curves adjustment. While these changes to the gradient could theoretically all be done with math in the material itself, it’s just a lot simpler to do it in the pixels themselves with Photoshop. Here is the highlight gradient we have used in this example.

ramp_blond

This gradient never actually reaches black, because we will be using it as a base color for the diffuse as well, but in other circumstances where the specular is multiplied on top of separate diffuse information, the specular gradient may need to go down to black. Notice that its height is negligible. We will be using our grayscale coordinate data solely as U input, with no V at all, so the sideways difference is all that matters. In Unreal’s Texture Properties window, make sure that the gradient’s AddressX is set to TA_Clamp rather than TA_Wrap. This will ensure that the texture doesn’t repeat when the coordinates exceed the 0 to 1 range. Connect the expression tree that we have built so far to the Coord input of this gradient’s texture sample, as shown below.

anisotropic_mat_gradient

The gradient texture should now stretch from the top to the bottom of the surface, with the highlight appearing somewhere in the middle. Adjustments to the constants that Multiply and Add to the coordinate data may be necessary, as well as tweaks to the gradient itself with Photoshop, until the highlight is in the correct position in regard to the light.

Completing the Material

We have now achieved anisotropic specularity. The rest of the material is relatively simple by comparison.

anisotropic_mat_fore

Here are the additional textures it calls. One is used to modulate the light and darks of the hair, and the other is a normal map derived from it.

line_textures

We create a Multiply expression between our highlight’s output and a constant to modulate the brightness of the hair, then Multiply it again with our lines texture to add shadows between the strands of hair. This is finally connected to the Diffuse input of the material. Since the specularity is all done in the diffuse itself, we can put a constant of zero in the Specular input, although for extra shine in some situations it might help to connect all of this to the Specular as well as the Diffuse, in which case the Specular Power should be defined by a constant of its own. The normal map of the lines is, of course, connected to the Normal input.

The remaining expressions in this material merely stretch and tile these two line textures. Because of the nature of hair, it suits us to elongate these textures. We stretch them by splitting a Texture Coordinate expression into its separate channels using Component Masks, and Multiply the U component (red channel) by 5, then recombine the result using an AppendVector. The altered coordinate data is then connected to the line texture and the normal map. Here is the final result.

anisotropic_result


Practical Usage

Here we will put these techniques to practical use, giving anisotropic hair to a work-in-progress of the character Lady Love, modeled and textured by Liquid Development for Damnation.

ladylove_comparison

Here is the material for her hair.

ladylove_mat

These are the textures responsible for the anisotropic effect.

ladylove_textures

This material is very similar to the one shown above, with a few complications and refinements.

The first and simplest difference is that here we only want the anisotropic effect to apply to the hair, which shares a texture sheet with the rest of her head. Because of this, we use the mask LadyLove_Anis_LIN_MAS to multiply the effect down to zero for her other parts. Likewise, her regular specular texture has been blackened on the hair parts, because we don’t want it to interfere with the anisotropic specular.

We have added a Power expression in order to attenuate the falloff numerically, which can be useful for visual tweaking. Also the falloff texture itself is centered this time, because with a shape such as hair the positioning of the highlight is not nearly as important as the look of the falloff. This gradient is slightly blue in the lighter parts, fading off to dark brown in the shadows, so that the color is changed subtly by this effect as well as the brightness.

Another difference is that we are using this in combination with an existing diffuse. We still want to use the brights and darks of this image to modulate the specular effect, creating shadows from the individual hairs, so we still multiply it by the image as we did in the last example, but first we Add a Constant of 1. This means that the lowest brightness, which had been zero, is now one, so that when we multiply by the diffuse texture, everything that was masked out is multiplied by one, and therefore does not change from its value in the texture.

The biggest difference here is that not all of the hair runs straight up and down the texture sheet. There is some hair that runs horizontally. Because of this, we must use a custom-painted normal map with values that follow the direction of the hair. The image below can be used as a palette upon which to base color choices. If the hair runs downward, use the bottom color, which is R = 0, G =1, B=1. If the hair runs to the right, use the color on the right, which is R=1, G=0, B=1. Diagonal direction can be chosen in the same way.

sphere_nor_palette

The choice of which opposite color to use (up versus down, or left versus right) should be based entirely upon what looks best. It should be noted, however, that for symmetry on mirrored UVs, the direction should be flipped. That is why the LadyLove_Anis_NOR image has one side of her head painted from the color on the far left of this palette, and the other painted from the far right of the palette. While it is not necessary to invert any colors on a normal map that actually connects to the Normal input, here we are simply using it as a computational element for the diffuse channel, so we must make these adjustments by hand.

A separate version of this normal map, called LadyLove_Anis_Noisy_NOR can be swapped in its place. As you can see in the textures shown above, this version has other diagonal colors painted in tufts, rather than a solid color. This adds some more natural variation to the distribution of the highlight, giving the appearance of less tidy hair. Both versions have their benefits. The perfectly straight hair looks more glamorous, but the untidy hair seems more natural.

ladylove_noisy_comparison



No Comment