(For more resources related to this topic, see here.)
The specularity of an object surface simply describes how shiny it is. These types of effects are often referred to as view-dependent effects in the Shader world. This is because in order to achieve a realistic Specular effect in your Shaders, you need to include the direction the camera or user is facing the object's surface. Although Specular requires one more component to achieve its visual believability, which is the light direction. By combining these two directions or vectors, we end up with a hotspot or highlight on the surface of the object, half way between the view direction and the light direction. This half-way direction is called the half vector and is something new we are going to explore in this article, along with customizing our Specular effects to simulate metallic and cloth Specular surfaces.
Utilizing Unity3D's built-in Specular type
Unity has already provided us with a Specular function we can use for our Shaders. It is called the BlinnPhong Specular lighting model. It is one of the more basic and efficient forms of Specular, which you can find used in a lot of games even today. Since it is already built into the Unity Surface Shader language, we thought it is best to start with that first and build on it. You can also find an example in the Unity reference manual, but we will go into a bit more depth with it and explain where the data is coming from and why it is working the way it is. This will help you to get a nice grounding in setting up Specular, so that we can build on that knowledge in the future recipes in this article.
Let's start by carrying out the following:
- Create a new Shader and give it a name.
- Create a new Material, give it a name, and assign the new Shader to its shaper property.
- Then create a sphere object and place it roughly at world center.
- Finally, let's create a directional light to cast some light onto our object.
When your assets have been set up in Unity, you should have a scene that resembles the following screenshot:
How to do it…
- Begin by adding the following properties to the Shader's Properties block:
- We then need to make sure we add the variables to the CGPROGRAM block, so that we can use the data in our new properties inside our Shader's CGPROGRAM block. Notice that we don't need to declare the _SpecColor property as a variable. This is because Unity has already created this variable for us in the built-in Specular model. All we need to do is declare it in our Properties block and it will pass the data along to the surf() function.
- Our Shader now needs to be told which lighting model we want to use to light our model with. You have seen the Lambert lighting model and how to make your own lighting model, but we haven't seen the BlinnPhong lighting model yet. So, let's add BlinnPhong to our #pragma statement like so:
- We then need to modify our surf() function to look like the following:
How it works…
This basic Specular is a great starting point when you are prototyping your Shaders, as you can get a lot accomplished in terms of writing the core functionality of the Shader, while not having to worry about the basic lighting functions.
Unity has provided us with a lighting model that has already taken the task of creating your Specular lighting for you. If you look into the UnityCG.cginc file found in your Unity's install directory under the Data folder, you will notice that you have Lambert and BlinnPhong lighting models available for you to use. The moment you compile your Shader with the #pragma surface surf BlinnPhong, you are telling the Shader to utilize the BlinnPhong lighting function in the UnityCG.cginc file, so that we don't have to write that code over and over again.
With your Shader compiled and no errors present, you should see a result similar to the following screenshot:
Creating a Phong Specular type
The most basic and performance-friendly Specular type is the Phong Specular effect. It is the calculation of the light direction reflecting off of the surface compared to the user's view direction. It is a very common Specular model used in many applications, from games to movies. While it isn't the most realistic in terms of accurately modeling the reflected Specular, it gives a great approximation that performs well in most situations. Plus, if your object is further away from the camera and the need for a very accurate Specular isn't needed, this is a great way to provide a Specular effect on your Shaders.
In this article, we will be covering how to implement the per vertex version of the and also see how to implement the per pixel version using some new parameters in the surface Shader's Input struct. We will see the difference and discuss when and why to use these two different implementations for different situations.
- Create a new Shader, Material, and object, and give them appropriate names so that you can find them later.
- Finally, attach the Shader to the Material and the Material to the object. To finish off your new scene, create a new directional light so that we can see our Specular effect as we code it.
How to do it…
- You might be seeing a pattern at this point, but we always like to start out with our most basic part of the Shader writing process: the creation of properties. So, let's add the following properties to the Shader:
- We then have to make sure to add the corresponding variables to our CGPROGRAM block inside our SubShader block.
- Now we have to add our custom lighting model so that we can compute our own Phong Specular. Add the following code to the Shader's SubShader() function. Don't worry if it doesn't make sense at this point; we will cover each line of code in the next section:
- Finally, we have to tell the CGPROGRAM block that it needs to use our custom lighting function instead of one of the built-in ones. We do this by changing the #pragma statement to the following:
The following screenshot demonstrates the result of our custom Phong lighting model using our own custom reflection vector:
How it works…
Let's break down the lighting function by itself, as the rest of the Shader should be pretty familiar to you at this point.
We simply start by using the lighting function that gives us the view direction. Remember that Unity has given you a set of lighting functions that you can use, but in order to use them correctly you have to have the same arguments they provide. Refer to the following table, or go to http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderLighting.html:
Not view Dependent
half4 Lighting Name You choose (SurfaceOutput s, half3 lightDir, half atten);
half4 Lighting Name You choose (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten);
In our case, we are doing a Specular Shader, so we need to have the view-dependent lighting function structure. So, we have to write:
This will tell the Shader that we want to create our own view-dependent Shader. Always make sure that your lighting function name is the same in your lighting function declaration and the #pragma statement, or Unity will not be able to find your lighting model.
The lighting function then begins by declaring the usual Diffuse component by dotting the vertex normal with the light direction or vector. This will give us a value of 1 when a normal on the model is facing towards the light, and a value of -1 when facing away from the light direction.
We then calculate the reflection vector taking the vertex normal, scaling it by 2.0 and by the diff value, then subtracting the light direction from it. This has the effect of bending the normal towards the light; so as a vertex normal is pointing away from the light, it is forced to look at the light. Refer to the following screenshot for a more visual representation. The script that produces this debug effect is included at the book's support page at www.packtpub.com/support.
Then all we have left to do is to create the final spec's value and color. To do this, we dot the reflection vector with the view direction and take it to a power of _SpecPower. Finally, we just multiply the _SpecularColor.rgb value over the spec value to get our final Specular highlight.
The following screenshot displays the final result of our Phong Specular calculation isolated out in the Shader:
Creating a BlinnPhong Specular type
Blinn is another more efficient way of calculating and estimating specularity. It is done by getting the half vector from the view direction and the light direction. It was brought into the world of Cg by a man named Jim Blinn. He found that it was much more efficient to just get the half vector instead of calculating our own reflection vectors. It cut down on both code and processing time. If you actually look at the built-in BlinnPhong lighting model included in the UnityCG.cginc file, you will notice that it is using the half vector as well, hence the reason why it is named BlinnPhong. It is just a simpler version of the full Phong calculation.
- This time, instead of creating a whole new scene, let's just use the objects and scene we have, and create a new Shader and Material and name them BlinnPhong.
- Once you have a new Shader, double-click on it to launch MonoDevelop, so that we can start to edit our Shader.
How to do it…
- First, we need to add our own properties to the Properties block, so that we can control the look of the Specular highlight.
- Then, we need to make sure that we have created the corresponding variables inside our CGPROGRAM block, so that we can access the data from our Properties block, inside of our subshader.
- Now it's time to create our custom lighting model that will process our Diffuse and Specular calculations.
- To complete our Shader, we will need to tell our CGPROGRAM block to use our custom lighting model rather than a built-in one, by modifying the #pragma statement with the following code:
The following screenshot demonstrates the results of our BlinnPhong lighting model:
How it works…
The BlinnPhong Specular is almost exactly like the Phong Specular, except that it is more efficient because it uses less code to achieve almost the same effect. You will find this approach nine times out of ten in today's modern Shaders, as it is easier to code and lighter on the Shader performance.
Instead of calculating our own reflection vector, we are simply going to get the vector half way between the view direction and the light direction, basically simulating the reflection vector. It has actually been found that this approach is more physically accurate than the last approach, but we thought it is necessary to show you all the possibilities.
So to get the half vector, we simply need to add the view direction and the light direction together, as shown in the following code snippet:
Then, we simply need to dot the vertex normal with that new half vector to get our main Specular value. After that, we just take it to a power of _SpecPower and multiply it by the Specular color variable. It's much lighter on the code and much lighter on the math, but still gives us a nice Specular highlight that will work for a lot of real-time situations.
Masking Specular with textures
Now that we have taken a look at how to create a Specular effect for our Shaders, let's start to take a look into the ways in which we can start to modify our Specular and give more artistic control over its final visual quality. In this next recipe, we will look at how we can use textures to drive our Specular and Specular power attributes.
The technique of using Specular textures is seen in most modern game development pipelines because it allows the 3D artists to control the final visual effect on a per-pixel basis. This provides us with a way in which we can have a mat-type surface and a shiny surface all in one Shader; or, we can drive the width of the Specular or the Specular power with another texture, to have one surface with a broad Specular highlight and another surface with a very sharp, tiny highlight.
There are many effects one can achieve by mixing his/her Shader calculations with textures, and giving artists the ability to control their Shader's final visual effect is key to an efficient pipeline. Let's see how we can use textures to drive our Specular lighting models. This article will introduce you to some new concepts, such as creating your own Input struct, and learning how the data is being passed around from the output struct, to the lighting function, to the Input struct, and to the surf() function. Understanding the flow of data between these core Surface Shader elements is core to a successful Shader pipeline.
- We will need a new Shader, Material, and another object to apply our Shader and Material on to.
- With the Shader and Material connected and assigned to your object in your scene, double-click the Shader to bring it up in MonoDevelop.
- We will also need a Specular texture to use. Any texture will do as long as it has some nice variation in colors and patterns. The following screenshot shows the textures we are using for this recipe:
How to do it…
- First, let's populate our Properties block with some new properties. Add the following code to your Shader's Properties block:
- We then need to add the corresponding variables to the subshader, so that we can access the data from the properties in our Properties block. Add the following code, just after the #pragma statement:
- Now we have to add our own custom Output struct. This will allow us to store more data for use between our surf function and our lighting model. Don't worry if this doesn't make sense just yet. We will cover the finer details of this Output struct in the next section of the article. Place the following code just after the variables in the SubShader block:
- Just after the Output struct we just entered, we need to add our custom lighting model. In this case, we have a custom lighting model called LightingCustomPhong. Enter the following code just after the Output struct we just created:
- In order for our custom lighting model to work, we have to tell the SubShader block which lighting model we want to use. Enter the following code to the #pragma statement so that it loads our custom lighting model:
- Since we are going to be using a texture to modify the values of our base Specular calculation, we need to store another set of UVs for that texture specifically. This is done inside the Input struct by placing the word uv in front of the variable's name that is holding the texture. Enter the following code just after your custom lighting model:
- To finish off the Shader, we just need to modify our surf() function with the following code. This will let us pass the texture information to our lighting model function, so that we can use the pixel values of the texture to modify our Specular values in the lighting model function:
The following screenshot shows the result of masking our Specular calculations with a color texture and its channel information. We now have a nice variation in Specular over the entire surface, instead of just a global value for the Specular:
How it works…
This Shader is basically the same as Phong calculations, except that we are now going to modify our Specular with a per-pixel texture, giving our Specular much more visual interest and depth.
To do this, we need to be able to pass information from our surface function to our lighting functions. The reason is that we can't get the UVs of a surface within the lighting function. You can procedurally generate UVs in the lighting function but if you want to unpack a texture and get its pixel information, you have to use the Input struct, and the only way to access the data from the Input struct is to use the surf() function.
So to set up this data relationship, we have to create our own SurfaceCustomOutput struct. This struct is the container for all the final data in a Surface Shader and luckily for us, the lighting function and the surf() function can both access the data from it. So if we create our own, we can add more data to it. The following code is our SurfaceCustomOutput struct in our Shader:
So, we add this to our Shader and we need to tell the surf() function and the lighting function that they should use this struct instead of the built-in one. This is done by the following code:
Notice how the surf() function and the lighting function now have the struct SurfaceCustomOutput for one of their arguments. We have also added a new entry into our SurfaceOutput struct called SpecularColor. This will allow us to store the per-pixel information from our Specular color texture and use it in our lighting function, instead of just multiplying a single global color over our whole Specular value.
We simply use the tex2D() function to get our texture information, and then pass that into our SurfaceCustomOutput struct by assigning o.SpecularColor the return value of the tex2D() function. Once that is done, you can now access the texture information in the lighting function.
This technique is crucial for creating custom effects in your Shaders. Now you know how to access textures from the surf() function and use it in your lighting function. This allows you to create very high-quality, per-pixel effects in your Shader.
Metallic versus soft Specular
In this section, we are going to explore a way to create a Shader that gives us the versatility to have a soft Specular as well as a hard Specular. You will find in most productions that you will need to create a nice set of Shaders to perform many tasks. As managing too many Shaders can become overwhelming, it is common for Shader programmers to create a set of Shaders that can both be used for cloth and for metal in one Shader file. It's all about how the end user sets the properties on their model. Our goal in this article is to achieve this modularity with Specular, so that an end user can get a soft, shiny material and then using the same Shader, achieve a very hard metallic Shader.
To accomplish this flexibility, we are going to create a similar Specular lighting model to the Cook Torrance Shader, but we will give it our own touch so that it is a bit friendlier for the artist or end user using this Shader.
- Create a fresh Unity scene and set up a simple sphere, plane, and directional light in the new scene. Make sure to save the scene with a name of your choice.
- Create a new Shader and Material, and give them names that you decide on.
- Finally, attach the Shader to the Material and attach the Material to the sphere object in your new scene.
- We are also going to need to get some textures together that will allow an artist to refine the roughness of the Specular by defining how blurry and how sharp the Specular should be. Refer to the following screenshot for examples on how these textures look.
The following screenshot visually shows examples of different roughness textures used in this recipe:
How to do it…
- First and foremost, we need to set up the properties we will need for our Shader. Enter the following code into the Properties block of your Shader:
- We then need to make sure that the property data is available to our SubShader block. Enter the following code just after the #pragma statement in your Shader:
- We now need to declare our new lighting model and tell the #pragma statement to look for it:
- At this point we are ready to fill in our custom lighting model function with our lighting calculations. We are first going to want to generate all of our diffuse and view dependent vectors, as this lighting model is going to make use of them all.
- The next section of code in the Shader takes care of producing the roughness values for our Specular, by using a texture to define the Specular shape and to procedurally simulate micro bumps in the surface of the object. Enter the following code:
- The last element we need for our Specular calculation is a Fresnel term. This will help us mask off the Specular when your view becomes very glancing to the object's surface.
- Now that we have all the components ready for our Specular, we just need to combine them together to generate our final Specular value.
- To complete the lighting model, we simply need to add our Diffuse terms and our Specular terms together:
With all the code entered into your Shader, return to the Unity editor to let the Shader compile. If no errors were reported, you should have a result similar to the following screenshot:
How it works…
Alright…that might seem like a lot of stuff going on there, but actually it is all pretty simple to understand. You can even debug each step of the Shader's code by assigning c.rgb with a float3 value. Once you do that, you will see, in the editor view, that the Shader is now displaying the step of whichever calculation you are feeding it; always a good tip to keep in mind when debugging Shaders.
If we actually debug the first block of code, where we are calculating all of our Diffuse and view dependent vectors, you would see something very similar to the following screenshot:
Once we have all of this data we need to start to work with it, almost like layers in Photoshop. We start this process by generating a procedural value that simulates small micro bumps in the surface of the object, to fake the effect of light bouncing around and distributing the light.
One of the more key aspects of this lighting model is the fact that we control the width of the Specular, or its roughness, by looking up a texture that has a baked-in Specular function. This will allow us to procedurally generate some UVs and pick a spot on the texture to use for our Specular. For this we want to use the NdotH or the dot product of the half vector and the vertex normal, and feed that into a float2() variable for the tex2D() function. This float2() variable will become our UV that we use to look up our texture. The second value is a property that we exposed in the Inspector tab. This allows the user to expand or contract the Specular highlight.
We then need to create our Fresnel effect, so that when we look in the opposite direction the light is pointing we get an increase in Specular intensity.
With all of these components completed, we simply want to multiply them together to achieve our final Specular value. In this case, we have also multiplied another property called _SpecPower to give one more level of intensity control over the final Specular value.
The last step is to combine our Specular with the Diffuse component and return the final color to the Surface Shader. Hopefully, you can see the level of modifications you can make to a simple system just by using other types of vectors and textures.
- To find out more about the Cook Torrance Specular model, refer to the following links:
Creating an Anisotropic Specular type
Anisotropic is a type of Specular or reflection that simulates the directionality of grooves in a surface, and modifies/stretches the Specular in the perpendicular direction. It is very useful when you want to simulate brushed metals, not a metal with a clear, smooth, polished surface. Imagine the Specular you see when you look at the data side of a CD or DVD, or the way Specular is shaped at the bottom of a pot or pan. You will notice that if you carefully examine the surface, you will see that there is a direction to the grooves in the surface, usually the way the metal was brushed. When you apply a Specular to that surface, you get a Specular stretched in the perpendicular direction.
This article will introduce you to the concept of augmenting your Specular highlights to achieve different types of brushed surfaces. In future recipes, we will look at ways in which we can use the concepts of this recipe to achieve other effects, such as stretched reflections and hair, but here we are going to learn the fundamentals of the technique first. We will be using this Shader as our reference for our own custom Anisotropic Shader:
The following screenshot shows examples of different types of Specular effects one can achieve by using Anisotropic Shaders in Unity:
- Create a new scene with some objects and lights, so that we can visually debug our Shader.
- Then, create a new Shader and Material, and hook them up to our objects.
- Lastly, we will need some sort of normal map that will indicate the directionality of our Anisotropic Specular highlight.
The following screenshot shows the Anisotropic normal map we will be using for this article. It is available from the book's support page at www.packtpub.com/support:
How to do it…
- We first need to add the properties we are going to need for our Shader. These will allow a lot of artistic control over the final appearance of the surface:
- We then need to make the connection between our Properties block and our SubShader block, so that we can use the data being provided by the Properties block:
- Now we can create our lighting function that will produce the correct Anisotropic effect on our surface:
- In order to use this new lighting function, we need to tell the subshader's #pragma statement to look for it instead of using one of the built-in lighting functions. We are also telling the Shader to target Shader model 3.0, so that we can have more space for textures in our program:
- We have also given the Anisotropic normal map its own UVs by declaring the following code in the Input struct. This isn't entirely necessary as we could just use the UVs from the main texture, but this gives us independent control over the tiling of our brushed metal effect, so that we can scale it to any size we want.
- Finally, we need to use the surf() function to pass the correct data to our lighting function. So we get the per-pixel information from our Anisotropic normal map and set our Specular parameters.
The following screenshot demonstrates the result of our Anisotropic Shader. The Anisotropic normal map allows us to give the surface direction and helps us disperse the Specular highlight around the surface:
How it works…
Let's break this Shader down into its core components and explain why we are getting the effect we are getting. We will mostly be covering the custom lighting function here, as the rest of the Shader should be pretty self-explanatory at this point.
We first start by declaring our own SurfaceCustomOutput struct. We need to do this in order to get the per-pixel information from the Anisotropic normal map, and the only way we can do that in a Surface Shader, is to use a tex2D() function inside the surf() function.
We can use the SurfaceOutput struct as a way of interacting between the lighting function and the surface function. In our case, we are storing the per-pixel texture information in the variable called anisoTex in our surf() function, and then passing that data to the SurfaceAnisoOutput struct by storing it in the AnisoDirection variable. Once we have that, we can use that per-pixel information in the lighting function, by using s.AnisoDirection.
With that data connection set up, we can move on to our actual lighting calculations. This begins by getting the usual out of the way, the half vector, so that we don't have to do the full reflection calculation and the diffuse lighting, which is the vertex normal dotted with the light vector or direction.
Then we start the actual modification to the Specular to get the right look. We first dot the normalized sum of the vertex normal and the per-pixel vectors from our Anisotropic normal map with the halfVector calculated in the previous step. This gives us a float value that gives a value of 1 as the surface normal, modified by the Anisotropic normal map, as it becomes parallel with the halfVector and 0 as it is perpendicular. Finally, we modify this value with a sin() function so that we can basically get a darker middle highlight and ultimately a ring effect based off of the halfVector.
Finally, we scale the effect of the aniso value by taking it to a power of s.Gloss, and then globally decrease its strength by multiplying it by s.Specular.
This effect is great for creating more advanced metal type surfaces, especially ones that are brushed and seem to have directionality to them. It also works well for hair or any sort of soft surface with directionality to it. The following screenshot shows the result of just displaying the final Anisotropic lighting calculation:
From this article, we learned how to apply Specular effects to create masked specular, metallic specular, and a technique for creating anisotropic specular.
Resources for Article:
- Unity 3-0 Enter the Third Dimension [Article]
- Introduction to Game Development Using Unity 3D [Article]
- Microsoft Enterprise Library: Security Application Block [Article]