This is the first run that I came up with so far. Now I have
something to work with. Here we have kinda funny textured
buildings. At the bottom we have a fog. What we'll do for
now is just use multi-texturing and animate it by modifying the texture
coordinates over time. The letters on the sign are an actual .obj
model and not a texture.
So the first thing we'll do is having something that is kinda cool and
useable during the day time.
Daytime GLSL Standard Stuff
How about some basic stuff? Directonal lighting with texture
mapping? This is a basic ability and is covered all over the
web. We'll do per-fragment lighting, and in this case we will
just do simple diffuse lighting.
For lighting we need to send from the vertex shader to the fragment
shader:
- Transformed vertex
- Light Direction with respect to the eye
- Diffuse and ambient colors after combining the diffuse and
ambient contributions of the materials and light
For texture mapping we need:
- The application to send which texture unit the color map is bound
to
- The vertex shader to pass the texture coordinates to the fragment
shader
Below is what our vertex shader looks like for the basic set of
things. As you can see, there are a lot of things that come in
for free (i.e. don't have to be setup as special uniforms), such as the
light source position, diffuse and ambient colors. This is
assuming you've setup the proper attributes in the application as you
would do with a standard OpenGL application without using shaders.
varying vec3 tnorm; // our transformed normal
varying vec3 lightdir; // the light direction vector
varying vec4 diffuse;
varying vec4 ambient;
void main(void)
{
// get the vertex in modelview space
vec4 tvec = gl_ModelViewMatrix * gl_Vertex;
// light direction in eye space
lightdir = normalize( vec3( gl_LightSource[0].position - tvec ) );
// our fully transformed vertex
tvec = gl_ProjectionMatrix * tvec;
// our normalized, transformed normal
tnorm = normalize( gl_NormalMatrix * gl_Normal );
// calculate what our diffuse and ambient colors would be
// if the light where dead on
// Kd = Ld * Md
// Ka = (La * Ma) + (Ga * Ma)
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient;
// pass our texture coordinates gained from glTexCoord2f calls
gl_TexCoord[0] = gl_MultiTexCoord0;
// finish vertex shader
gl_Position = tvec;
}
Once we get that necessary data, and we have setup a uniform for our
sampler2D that we will use to get the color map, we can finally fill in
the color of our object.
Now in my scene I have some things that don't use textures, such as the
billboard itself. I also have parts where I don't want lighting
applied and just want the texture applied as normal, such as the skybox
and night skybox. I'll need to add integer flags to indicate if
some calculations should occur. Here is the fragment shader to do
so, with diffuse lighting calculated and textures used, if the flags
are turned on.
Note: You
can use glUniform1i to assign a value of 0 or 1 to a uniform boolean.
uniform bool dolighting; // do we do lighting calculations?
uniform bool usetexture; // do we use the texture color?
uniform sampler2D colormap; // our color map texture
varying vec3 tnorm
varying vec3 lightdir;
varying vec4 diffuse;
varying vec4 ambient;
void main(void)
{
// make sure we have a normalized normal
vec3 n = normalize(t norm);
// assign a color of white in case we don't do lighting
vec4 lightColor = vec4( 1.0, 1.0, 1.0, 1.0 );
if( dolighting )
{
lightColor = ambient;
// perform standard diffuse lighting calculations
//using dot product
float ndotl = max( dot( n, lightdir), 0.0 );
if( ndotl > 0.0 )
{
lightColor += diffuse * ndotl;
}
}
// assign a color of white in case we don't use color map
vec4 texColor = vec4( 1.0, 1.0, 1.0, 1.0 );
if( usetexture )
texColor = texture2D( colormap, gl_TexCoord[0].st );
// combine the colors to produce our final color
gl_FragColor = texColor * lightColor;
}
So how are we using it in the daytime scene? Here is psuedocode
to describe how we're using it for drawing the billboard, buildings and
sky/night box.
- Bind the shader
- Set "usetexture" to 0 and "dolighting" to 1
- Draw our billboard related objects (text, base, poles)
- Set "usetexture" and "dolighting" to 1
- Draw our buildings
- Set "usetexture" to 1 and "dolighting" to 0
- Draw our sky/night box and draw our semi-transparent cloud plane
What a useful shader!!! Here's a screen shot where you can see
the psuedocode in action. Looks about the same as fix
functionality, minus specular highlights since we aren't calculating
it.... Yet.
Daytime GLSL Pulsating Beam
So let's add some unusual spice to our scene for the day time.
How about
making the poles that are holding up the billboard look sci-fi
like. What we'll do is have pulsating colors shooting out from
the thruster looking things at the base of the billboard. Let's
pick the color green. Why? Because nothing else in the
scene is green,
and that's the color of Luc's lightsaber in Return of the Jedi.
So our goal is to have spurts of light or color shooting down, and have
the pole be transparent in between the bursts. Cool!
How are we going about doing this? Well we can use a sine
function to be a periodic function that would describe how much light
we would have at a spot. The nice thing is that we'll have some
smoothness to this compared to using a step function that is either on
or off.
So where are we going to do most of our work? Since we're
modifying colors and discarding fragments, we should be doing most of
our work in the fragment shader. What we'll do first is figure
out how our fragment shader should look like. This will then tell
us what our vertex shader needs to send in as a "varying" data and what
our application code should send in as "uniforms" or "attributes."
First, we need to be able to know where we are vertically, so we'll
send in a y-position from the vertex shader. Also, we'll need
something to let us know what time it is, and that will have to be a
uniform coming in.
So far this is what our fragment shader looks like.
varying float y;
uniform float time;
void main(void)
{
// store the result of our moving sine function,
// and remove negative values from the picture
float intensity;
intensity = sin( y + time );
// draw our color as a non transparent shade of green
gl_FragColor = vec4( 0.0, intensity, 0.0, 1.0);
}
So what does our vertex shader look like right now? Simple
enough, just get the y-position from the incoming gl_Vertex and pass it
on. The value will get interpolated later on.
varying float y;
void main(void)
{
// get our y position
y = gl_Vertex.y;
// transform our vertex
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
Here's the results
Notice the black parts. That's something we should get rid
of. Here's where we add the discard call. All we have to do
is check if our intensity value is less than or equal to some
value. What shall our value be? If we use zero then we will
still have the somewhat dark parts of green. Light can't be dark
so let's use the value of 0.5. Our new fragment shader looks like
this.
varying float y;
uniform float time;
void main(void)
{
// store the result of our moving sine function,
// and remove negative values from the picture
float intensity;
intensity = sin( y + time );
// draw our color as a non transparent shade of green
// and get rid of black
if( intensity <= 0.5 )
discard;
else
gl_FragColor = vec4( 0.0, intensity, 0.0, 1.0);
}
Below is what we get. It's an improvement, but notice something
funny? You can see a curving on the bottom green bursts.
That's because I've enabled back face culling. If I get disable
that it will help, but here's a fundamental problem with this
effect. Can you guess? If we look straight down the y-axis
we won't see anything because there is no face being filled in that is
above the fog!!!