Billboard Shader

A billboard shader using GLSL, C and OpenGL. This project was part of the GPU Programming course at UIC and we had to write a tutorial explaining how we created the shader. Original tutorial link: here.

There are two different shaders one for controlling the glow of the billboard texture and the other for controlling the glow intensity of the neon light bordering the billboard.
C
OpenGL
GLSL
GLUT
GLEW
DevIL

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.
Billboard - Image - One

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);
}



Billboard - Image - Two

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

- 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

Billboard Shader - Night Texture

- 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.

Billboard Shader - Night Texture 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.

Billboard - neon texture
The 128 x 64 texture used for texturing 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.


Day
Billboard - neon light - 1

Night
Billboard - neon light - 2