Thursday, December 23, 2010

Lighting equation

In today's games, lighting is one of the most important visual element. It is what makes a scene look realistic, or not. Today we have an extremely high control over the lighting, a lot can be added to a lighting system, it's not uncommon these days to see deferred shading pipelines, light-pre pass rendering, normal-mapping, and a lot more techniques thats alter the lighting in an astonishing, jaw-dropping way. But all of the most complex pipelines imaginable start with that simple equation: 


This is the lighting equation. Ambient, Diffuse, and Specular are vectors representing the color of each terms. Let's look at this more in details.

Ambient

This is the color of an object when there is no light nearby. It is always there lighting the object. In real life, when there is no light the objects are completely dark, but it is rare that there is no light at all. In general there is the moon, the stars, or any other distant light. We use the ambient term to approximate this sort of lighting, that seems to be constant in intensity everywhere in the scene. Example:


vec4 final_color = (gl_FrontLightModelProduct.sceneColor + 
   gl_LightSource[0].ambient) * gl_FrontMaterial.ambient;

Diffuse

This is the color of an object when it is directly lit by a light source. It is computed by doing the dot product between the normal of the surface and the light direction. The more the light direction is parallel with the normal, the more the surface is lit. The 2 vectors must be normalized.

dot(a,b) = ||a|| * ||b|| * cos(theta)

As you can see, the dot product of 2 normalized vector equals cos(theta), where theta is the angle between the 2 vector. This gives us a value comprised within -1 and 1, where the values between -1 and 0 means that the light is lighting the back-face, so we can ignore that part and set it automatically to 0. Example:


diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
diffuse = max(dot(normal, lightDir), 0.0) * diffuse;


Specular

The specular term simulate light reflexion and thus is view-dependent. Here is a visual representation:



Example:


RdotView = clamp(dot(reflect(lightPos, normal), -viewDir), 0.01.0);
specular = gl_LightSource.specular gl_FrontMaterial.specular * 
   pow(RdotView, gl_FrontMaterial.shininess);


We do the dot product between -viewDir and the reflected light vector. The more the 2 vectors are parallel at a specific pixel, the more this pixel will be lit. The power function is used to control the shininess of the specular effect. The greater the power, the shinier the surface will look.

The complete GLSL code:


[Vertex_Shader]
varying vec3 normal, lightPos, viewDir;
void main()
{    
   //Convert normal to eye-space
   normal = gl_NormalMatrix * gl_Normal;

   //Convert vertex to eye-space
   vec3 v = vec3(gl_ModelViewMatrix * gl_Vertex);
   lightPos = vec3(v - gl_LightSource[0].position.xyz);
   viewDir = -v;

   gl_Position = ftransform();
}

[Pixel_Shader]
varying vec3 normal, lightPos, viewDir;
void main (void)
{
   vec4 final_color = (gl_FrontLightModelProduct.sceneColor + 
      gl_LightSource[0].ambient) * gl_FrontMaterial.ambient;
             
   vec3 N = normalize(normal);
   vec3 L = normalize(lightPos);

   float lambertTerm = dot(N,L);

   //Do not calculate Diffuse or specular term if lighting back-face
   if (lambertTerm > 0.0)
   {
      final_color += gl_LightSource[0].diffuse * gl_FrontMaterial.diffuse *  
         lambertTerm; 
   
      vec3 E = normalize(viewDir);
      vec3 R = reflect(L, N);
      float specular = powmax(dot(R, -E), 0.0), gl_FrontMaterial.shininess );
      final_color += gl_LightSource[0].specular * gl_FrontMaterial.specular *  
         specular;  
   }
  
   gl_FragColor = final_color;     
}

No comments:

Post a Comment