Friday 15 April 2016

Diffuse shader: vertex / fragment shader

In this example, I will show yo how we can write a shader to achieve the same effect as the previous shader we wrote (light diffuse), only this time with a vertex/fragment shader.

Here, we will have to compute light manually and color each pixel properly according to the light detected.

Please bear in mind that this particular type of shader will only work with a single, directional light, it will not react to multiple lights, ambient light or even single lights which are not of type directional. We will see later, in other posts, how to add multiple lights and ambient light.

This is the shader code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
Shader "Custom/DiffuseSingleLight"
{
 Properties
 {
 _Color("Color",Color) = (1,1,1,1)
 }

 Subshader
 {
  Tags{"LightMode" = "ForwardBase"}

   Pass
   {

    CGPROGRAM

    #pragma vertex vert
    #pragma fragment frag

    struct input
     {

     float4 ver : POSITION;
     float3 norm : NORMAL;

     };
    struct v2f
     {

     float4 pos : POSITION;
     float3 norm : NORMAL;

     };

    float4 _Color;
    float3 _LightColor0; //is a built in variable but must be declared!

    v2f vert(input i)
     {
     v2f o;

     o.pos = mul(UNITY_MATRIX_MVP,i.ver);
     o.norm = mul(float4(i.norm,0.0),_World2Object).xyz; //_World2Object is float4
     return o;

     }

    float4 frag(v2f v) : COLOR
     {

     float3 normDirection = normalize(v.norm);
     float3 lightDirection = normalize(_WorldSpaceLightPos0);

     float3 light = max(0.0,dot(normDirection,lightDirection)) * _LightColor0.rgb;

     return float4(light,1) * _Color;

     }

    ENDCG

   }

  }



}

I assume you are now familiar with how to begin a shader program, so I will skip the first few lines of code.

On line 10 we add the tag "ForwardBase". This is necessary as it tells Unity that we are in forward rendering and dealing with main directional light.

In the input structure we have one additional parameter, called norm with the semantic NORMAL. This will take the vertex normal vector from the object.

On line 36 we declare a variable called _LightColor0. This is a built in Unity variable that represents the main directional light color property. If you have more directional lights while using this shader, one will override the other one, according to rotation and intensity, the lights will not blend together.

On line 43, we convert the normal vector from the input to a "world position" vector, which is then normalized in the fragment shader on line 52. On the next line, we normalize the direction vector of the directional light, using another built in Unity variable, _WorldSpaceLightPos0.

With these 2 normalized vectors we can calculate the light attenuation, which is what happens on line 55. We first perform a dot product between the 2 vectors.

A dot product of normalized vectors return a value between -1 and 1, depending on the direction they are pointing to: if they point to the same direction, the value returned is 1, if one points to the complete opposite direction of the other one, the value is -1.This value is used to represent the intensity of the light intensity.

Then, we clamp the value obtained between the value itself (which is maximum 1) and 0, using the max method you see being used on the same line. This is done because we don't want any negative contribution, in other words, the light intensity simply cannot be a negative value.

Finally, we multiply this intensity value for the color of the light, represented by the variable _LightColor0.

At the end of the method we return the light variable, "casted" to a float 4 as we want to returna  color. The value of 1 added is the alpha value. This is then multiplied by the _Color public parameter, which is the used defined color, to give a tint to the object.

This is the result:


As you may have noticed, writing vertex/fragment shaders require a lot more code comparing to the surface shader, however, we are allowed much greater flexibility and we can create more complex effects.

No comments:

Post a Comment