ARUNAN RABINDRAN - CS594 - FINAL EXAM - 05/02/2006

TUTORIAL  -  A billboard using vertex/fragment shaders in GLSL


Step 1 - Daylight imagery

              - A quad with the artwork is setup in the application and the shader code is applied.

a.  Start with simple texturing GLSL vertex/fragment code.

[Vertex_Shader]

    void main()
   {
    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_Position = ftransform();
   }

[Fragment_Shader]

     uniform sampler2D bbMap;

     void main (void)
    {
     gl_FragColor = texture2D( bbMap, gl_TexCoord[0].st);
    }

 

   - The vertex shader just uses built-in varying variable gl_TexCoord[0] and vertex attribute gl_MultiTexCoord0 to setup the texture lookup with openGL fixed functionality.

   - The fragment shader just looks up into the billboard texture map and passes on the color at each texel.

b. Add lighting calculation to the shader programs.

[Vertex_Shader]

  //With Lighting

  varying float diffuse;
  varying float spec;
  uniform vec3 lightpos;
  varying vec2 TexCoord;

  void main()
  {
   // eye position, normal, light vector, reflection vector and view vectors are
   // calculated for finding the specularity, which is the dot product of the
   // reflection vector and the view vector clamped between 0 and 1, raised
   // to the power of 16 and diffuse values.

  vec3 eyeVec = vec3 (gl_ModelViewMatrix * gl_Vertex);
  vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
  vec3 lightVec = normalize(lightpos - eyeVec);
  vec3 reflectVec = normalize(reflect(-lightVec, normal));
  vec3 viewVec = normalize(-eyeVec);

  diffuse = max(dot(lightVec, normal), 0.0);
  spec = max(dot(reflectVec, viewVec), 0.0);
  spec = pow(spec, 2.0);

  TexCoord = gl_MultiTexCoord0.st;
  gl_Position = ftransform();
  }

[Fragment_Shader]

  uniform sampler2D bbMap;
  varying float diffuse;
  varying vec2 TexCoord;
  varying float spec;

  void main (void)
  {
   float diff = diffuse * 0.95;  // constant values for the contribution are used.
   float sp = spec * 0.05;
   vec3 lightcolor = vec3(texture2D(bbMap, TexCoord).rgb * (diff + sp));
   gl_FragColor = vec4(lightcolor , 1.0);
   }

The shaders calculate per-vertex light intensity and apply it to the texture lookup per-fragment.

Fig 1.1  The billboard with texture applied.                    Fig 1.2  Same billboard with another texture applied.

- The diffuse, specularity values are varying variables that are calculated in the vertex shader and passed on to the fragment shader.

- The terms that are used to calculate them are the eye vector, light vector, normal, reflection vector (calculated quite easily using the 'reflect' built-in function) and view vector.

- The fragment shader just picks the texels and adds diffuse / specular contribution to them. (constant values are used for diffuse and specular contributions, they can also be passed on as uniform variables)


Step 2 - Night Lighting

       - For night lighting the title of the movie "Rang De Basanti" on the billboard is cycled between two textures

GLSL vertex/fragment code for controlling the cycling of the two textures

- Two texture maps are created one without glow and the other with the title glowing

- They are passed on to the fragment shader and each fragment lookup is stored as a color value.

- A control variable is also passed. It is setup in the application as below:

 [Vertex_Shader]

   varying vec2 TexCoord0, TexCoord1;

   void main()
   {
    TexCoord0 = gl_MultiTexCoord0.st;     // Pass in the two textures to the fragment shader
    TexCoord1 = gl_MultiTexCoord1.st;     // as varying variables.
    gl_Position = ftransform();
   }

[Fragment_Shader]

     varying vec2 TexCoord0, TexCoord1; 
     uniform sampler2D bbMap0, bbMap1;   // uniforms for textures
     uniform float cvar;                 // uniform control variable

     void main()
    {
     float cv = max(sin(cvar), 0.0);     // a '+' sine function of the control variable is stored.

     vec3 lightcolor0 = vec3(texture2D(bbMap0, TexCoord0));  //lookup the textures
     vec3 lightcolor1 = vec3(texture2D(bbMap1, TexCoord1));

     //the color is varied over the control variable for each of the textures  
     vec3 color = lightcolor0 * cv + lightcolor1 * (1 - cv);  // multiply with ctrl variable
     gl_FragColor = vec4(color, 1.0);
    }

      In Application:

       glUseProgramObjectARB(ntProg);

       if(controlvar>=360)
          controlvar=0;
          controlvar+=0.05;

       glActiveTextureARB(GL_TEXTURE1);
       glBindTexture(GL_TEXTURE_2D, billboard_texture);
       glActiveTextureARB(GL_TEXTURE0);
       glBindTexture(GL_TEXTURE_2D, billboardmod_texture);

       glUniform1iARB(getUniLoc(ntProg, "bbMap0"), 0);
       glUniform1iARB(getUniLoc(ntProg, "bbMap1"), 1);
       glUniform1fARB(getUniLoc(ntProg, "cvar"), controlvar);
       glUniform3fARB(getUniLoc(ntProg, "lightpos"), light1pos[0], light1pos[1], light1pos[2]);


- The quad is multitextured with the two billboard textures.

- A sine function is used to govern the rate at which the sign glows.

Fig 2.1  The billboard with sign not glowing.                    Fig 2.2  Same billboard with sign glowing over time.

The two textures that were used to generate the effect

 

Fig 2.3  1024 X 768 - JPEG file (loaded with DevIL)             Fig 2.4  1024 X 768 - modified with GLOW

 

Step 3 - Lighting calculation for a point light

             This step just calculates light for the point light that is setup in the program for night lighting. The only difference from the previous lighting calculation is that, the normal, light and eye directions are passed as varying variables to the fragment shader which then calculates the view/reflect vectors and diffuse/specular values.

 

[Vertex_Shader]

    varying vec2 TexCoord0, TexCoord1;
    varying vec3 eyeVec, normal, lightVec;
    uniform vec3 lightpos;

    void main()
    {
     // eye position, normal, light vector, reflection vector and view vectors are
     // calculated for finding the specularity which is the dot product of the
     // reflection vector and the view vector clamped between 0 and 1 and raised
     // to the power of 16.

     eyeVec = normalize(vec3(gl_ModelViewMatrix * gl_Vertex));
     normal = normalize(gl_NormalMatrix * gl_Normal);
     lightVec = normalize(lightpos - eyeVec);

     TexCoord0 = gl_MultiTexCoord0.st;
     TexCoord1 = gl_MultiTexCoord1.st;
     gl_Position = ftransform();
    }

[Fragment_Shader]

     varying vec2 TexCoord0, TexCoord1;
     varying vec3 eyeVec, normal, lightVec;
     uniform sampler2D bbMap0, bbMap1;
     uniform float cvar;
     float diffuse;
     float spec;

     void main()
     {
      float cv = max(sin(cvar), 0.0); 
      diffuse = 0.0;
      spec = 0.0;

      float lterm = dot(lightVec, normal);   // Lambertian Term

        if(lterm > 0.0)
       {
        vec3 reflectVec = normalize(reflect(-lightVec, normal));
        vec3 viewVec = normalize(-eyeVec);

        diffuse = max(dot(lightVec, normal), 0.0);
        spec = max(dot(reflectVec, viewVec), 0.0);
        spec = pow(spec, 2.0);
       }

      float diff = diffuse * 0.95;   // Constant diffuse,specular contributions
      float sp = spec * 0.05;

      vec3 lightcolor0 = vec3(texture2D(bbMap0, TexCoord0).rgb * (diff+sp));
      vec3 lightcolor1 = vec3(texture2D(bbMap1, TexCoord1).rgb * (diff+sp));

      vec3 color = lightcolor0 * cv + lightcolor1 * (1 - cv);
      gl_FragColor = vec4(color, 1.0);
     }

     It is a combination of shaders from step 1 and 2 with slight modifications. If the lighting is not extended to the glowing sign shader then even when the light is moved away from the scene, while other objects are dark the billboard is always lit up.


Step 4 - Adding a neon light border to the billboard

- 4 more quads are setup in the application as the borders to the existing billboard and a  " 128 x 64 " texture is bound.

   Fig 4.1  128 x 64 texture used to texture the borders of the billboard

[Vertex_Shader]

    void main()
   {
     gl_TexCoord[0] = gl_MultiTexCoord0;
     gl_Position = ftransform();
   }

[Fragment_Shader]

   uniform sampler2D bbMap;
   uniform float brightness, cvar;

   void main (void)
   {

    vec4 col = texture2D(bbMap, gl_TexCoord[0].st);
    col += col * brightness * cvar ;   //brightness calculated using control variable.

    vec4 neon = vec4(0.1, 0.7, 0.1, 1.0); // mixed with a green shade to match the texture
    gl_FragColor = mix(col, neon, 0.4);
   }

- A control variable and a brightness value are passed in as uniforms to the fragment shader along with the texture.

     In Application:

     glUseProgramObjectARB(nnProg);

      if(controlvar1>=3)
      controlvar1=0;
      controlvar1+=0.03;

     glUniform1iARB(getUniLoc(nnProg, "bbMap"), 0);
     glUniform1fARB(getUniLoc(nnProg, "brightness"), 2.0);
     glUniform1fARB(getUniLoc(nnProg, "cvar"), controlvar1);

- Tweaking the value of the increment for controlvar1 and the brightness value until we get a desired effect helps.

Fig 4.2  The neon light borders during the day                  Fig 4.3  Glowing during the night

 

Code - The final code

       Platform: Windows XP Professional.

       Program: VC++ 8 (Visual Studio 2005) using GLUT, DevIL and GLEW libraries.

       Shaders: Shader code has been provided at each stage in a gray box and the final shaders are in billboard.zip .

       Links    : Zip file with code and data -> billboard.zip     C Source Code