In this step, we will add the ambient and specular light to our script as well as create our custom lighting models.
The custom lighting model is basically the function that will be used to calculate our surface shader, which is the output of (surf()
function) interaction with the lights.
surf()
function is the function that will take any UVs or data we need as input, and fill in the output structure SurfaceOutput
(the predefined structure, such as Albedo, Normal, Emission, Specular, Gloss, and Alpha).
MyShader.shader
file, and go to the Properties
section and add the highlighted script as follows:Properties { _MainTex ("Texture", 2D) = "white" {} _BumpMap ("Bumpmap", 2D) = "bump" {} _AmbientColor ("Ambient Color", Color) = (0.1, 0.1, 0.1, 1.0) _SpecularColor ("Specular Color", Color) = (0.12, 0.31, 0.47, 1.0) _Glossiness ("Gloss", Range(1.0,512.0)) = 80.0 }
SubShader
section, modify, and add the following highlighted code:SubShader { Tags { "RenderType"="Opaque" } LOD 400 CGPROGRAM // Custom lighting function that uses a texture ramp based on angle between light direction and normal #pragma surface surf RampSpecular sampler2D _MainTex; sampler2D _BumpMap; fixed4 _AmbientColor; fixed4 _SpecularColor; half _Glossiness; struct Input { float2 uv_MainTex; float2 uv_BumpMap; };
LOD
to 400
, and set #pragma surface surf
to RampSpecular
instead of Lambert
, and get the other three properties for Ambient and Specular light. Now, we will need the custom lighting models function. Let's add the following highlighted code under the surf()
function:void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); } inline fixed4 LightingRampSpecular (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten) { //Ambient Light fixed3 ambient = s.Albedo * _AmbientColor.rgb; //Diffuse fixed NdotL = saturate(dot (s.Normal, lightDir)); //Get the direction of the light source related to the normal of character fixed3 diffuse = s.Albedo * _LightColor0.rgb * NdotL; //Specular - Gloss fixed3 h = normalize (lightDir + viewDir); // Get the Normalize of the lighting direction and view direction float nh = saturate(dot (s.Normal, h)); //Make sure that the return number isn't lower than 0 or greater than 1 float specPower = pow (nh, _Glossiness); fixed3 specular = _LightColor0.rgb * specPower * _SpecularColor.rgb; //Result fixed4 c; c.rgb = (ambient + diffuse + specular) * (atten * 2); c.a = s.Alpha + (_LightColor0.a * _SpecularColor.a * specPower * atten); return c; } ENDCG }
We have finished this step. We can now go back to Unity, and click Play to see our result with the specular reflection, as shown in the following screenshot:
In this step, we first added the new properties _AmbientColor
, e
, and _Glossiness
, which will be used to calculate in our custom lighting models function to get the specular reflection.
Next, we increased the LOD
to 400
because we wanted to increase the Level of Detail for our custom lighting model that will calculate the specular lighting. Then we changed #pragma surface surf
from Lambert
to RampSpecular
, which means that we changed our lighting calculated from the Lambert
built-in to RampSpecular
(our custom lighting function, LightingRampSpecular
).
In the surf()
function, we have changed the first line from half4 c = tex2D (_MainTex, IN.uv_MainTex);
to fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
to increase the performance of our shader. Also, since the return value from the tex2D()
function is the color value (R,G,B,A), which has a range from 0 to 1, it will be expensive to use half
or float
.
What are half
and fixed
parameters for? When we are writing a shader in Cg/HLSL, there are three types of the parameter that we can use, which are fixed
, half
, and float
. These parameters determine the precision of computations. The parameter fixed
is low precision (11 bits, the range of -2.0 to +2.0 and 1/256th precision), half
is medium precision (16 bits, the range of -60000 to +60000 and 3.3 decimal digits of precision), and float
is high precision (32 bits, similar to the float in regular programming language).
Reference from:
http://unity3d.com/support/documentation/Components/SL-ShaderPerformance.html
However, it follows a trend wherein the more precision we have, the more calculation we need. If we use all float
for our shader, it will cause the game to slow down. So, if we want to improve the performance of our game, we should use the lowest precision as possible.
Then, we created our custom lighting function, which is inline half4 LightingRampSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
. This function passes four parameters, SurfaceOutput
, light Direction
, view direction
, and light attenuation
that we will use to calculate the output for our shader.
Why is the name of this function not RampSpecular
? First, we call this function by using #pragma surface surf
RampSpecular
, but to have this function working properly, we need to add Lighting
in front of the name of our custom lighting function, so that Unity will know that this function is a custom lighting function. This is the way that the surface shaders are set up in Unity. You can find out more details from the following website:
http://unity3d.com/support/documentation/Components/SL-SurfaceShaderLighting.html
In this function, we first get the ambient color value by getting s.Albedo
, which is the parameter from the surf()
function
o.Albedo
, and then multiply the s.Albedo
by _AmbientColor.rgb
, where _AmbientColor
is the color information from the Properties section at the beginning of our code.
The fixed
, half
, and float
parameters in Cg/HLSL can contain one, two, three, or four values of floating number such as 1.0, (1.0, 1.0), (1.0, 1.0, 1.0), or (1.0, 1.0, 1.0, 1.0) by calling it fixed
, fixed2
, fixed3
, fixed4
, half
, half2
, half3
, half4
, float
, float2
, float3
, float4
. We can also access the value in these parameters by using (x, y, z, w) or (r, g, b, a). For example, if you have fixed4 color = (1.0, 0.5, 0.3, 0.8);
and you want to create another parameter, which will contain only three values (1.0, 0.5, 0.3)
from the fixed4 color
, you can do it like this: fixed3 newColor = color.rgb;
. However, if we want the newColor
value equal to (0.5, 1.0, 0.3)
, you can do it like this: fixed3 newColor = color.grb;
.
Then, we calculate the diffuse color by getting the dot product of the surface normal of the object (s.Normal
) that we pass out from the surf()
function (o.Normal
), and the light direction (fixed NdotL = dot (s.Normal, lightDir);
). Then, we use that value to multiply with the object diffuse texture (s.Albedo
) and light color (_LightColor0.rgb
), which is similar to the Lambert
model.
Next, we calculate the specular color by first getting the normalize vector of light direction and view direction (fixed3 h = normalize (lightDir + viewDir);
). In float nh = saturate(dot (s.Normal, h));
, we calculate the dot product of the surface normal and normalize vector, and make sure that the return number isn't greater than 1 or lower than 0 by using saturate()
. Then, we use nh
to calculate the specular power by powering it with the _Glossiness
properties (float specPower = pow (nh, _Glossiness);)
, and we get the specular color from multiplying the light color, specular power, and the specular color properties (_LightColor0.rgb * specPower * _SpecularColor.rgb;
), which is similar to the Blinn-Phong model.
In the last step, we add ambient, diffuse, and specular together, and multiply the lighting attenuation value doubled, to get the smooth specular effect (c.rgb = (ambient + diffuse + specular) * (atten * 2);
).
A major part of the code is in the Cg/HLSL language, so you might not be familiar with it. However, you can still get an idea of how it works by trying to see more examples and taking a look at the Cg/HLSL language:
http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html
We can also see an example of the custom lighting model from the following Unity website:
http://unity3d.com/support/documentation/Components/SL-SurfaceShaderExamples.html
How exactly do the surface shaders work?
First, we get the parameters from the Input
struct
, and these parameters will get passed to the SurfaceOutput
struct
inside the surf
function. Then, the return of the SurfaceOutput
struct
will go to the lighting model function to calculate both the vertex and pixel (fragment) shader. Lastly, the result from the lighting model function will be passed to the frame buffer, as shown in the following diagram:
18.225.235.144