This page was last updated: December 4, 2023
The goal of this project is to create an animated ellipse pattern by using an OpenGL fragment shader. The center of the ellipse will be animated using the KeyTime method you already know. The S-radius and T-radius will be animated using the Time variable and some functions that you invent. Light the object using per-fragment lighting.
Note: the reason this pattern looks more teardrop-shaped than ellipse-shaped is that the nature of spheres is to distort patterns near the poles. That Mercator-projection map you always had in the front of the classroom in middle and high school showed this effect in how greatly it distorted the size of Greenland.
The circle equation is explained here:
The ellipse equation is explained here:
When you are done with this assignment, you will understand how to create a GPU-program, known as a shader. Your shader will let you dynamically alter a color pattern on an arbitrary object. Today's games and movies employ hundreds of shader programs to get the variety of special effects they need. This assignment will also set you up to succeed in CS 457/557, our shaders course which you will be able to take in the Winter Quarter.
'k' | Toggle the keytime-based animation on and off |
't' | Toggle the Time-based animation on and off |
The glslprogram.h and glslprogram.cpp files are already in your Sample folder. You just need to un-comment their #include's.
See the class Shader notes for how to use them.
To help you get started, here are some head starts to work from:
You need to do per-fragment lighting.
Here is what needs to be added to the vertex shader code to enable per-fragment lighting:
// make this 120 for the mac: #version 330 compatability // out variables to be interpolated in the rasterizer and sent to each fragment shader: out vec3 vN; // normal vector out vec3 vL; // vector from point to light out vec3 vE; // vector from point to eye out vec2 vST; // (s,t) texture coordinates // where the light is: const vec3 LightPosition = vec3( 0., 5., 5. ); void main( ) { vST = gl_MultiTexCoord0.st; vec4 ECposition = gl_ModelViewMatrix * gl_Vertex; vN = normalize( gl_NormalMatrix * gl_Normal ); // normal vector vL = LightPosition - ECposition.xyz; // vector from the point // to the light position vE = vec3( 0., 0., 0. ) - ECposition.xyz; // vector from the point // to the eye position gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; }
Here is what needs to be added to the fragment shader code to enable per-fragment lighting:
// make this 120 for the mac: #version 330 compatability // lighting uniform variables -- these can be set once and left alone: uniform float uKa, uKd, uKs; // coefficients of each type of lighting -- make sum to 1.0 uniform vec3 uColor; // object color uniform vec3 uSpecularColor; // light color uniform float uShininess; // specular exponent // ellipse-equation uniform variables -- these should be set every time Display( ) is called: uniform float uSc, uTc; uniform float uRs, uRt; // in variables from the vertex shader and interpolated in the rasterizer: in vec3 vN; // normal vector in vec3 vL; // vector from point to light in vec3 vE; // vector from point to eye in vec2 vST; // (s,t) texture coordinates void main( ) { vec3 Normal = normalize(vN); vec3 Light = normalize(vL); vec3 Eye = normalize(vE); float s = vST.s; float t = vST.t; // determine the color using the ellipse equation: vec3 myColor = uColor; if( ??? ) { myColor = ???; } // apply the per-fragmewnt lighting to myColor: vec3 ambient = uKa * myColor; float d = max( dot(Normal,Light), 0. ); // only do diffuse if the light can see the point vec3 diffuse = uKd * d * myColor; float s = 0.; if( dot(Normal,Light) > 0. ) // only do specular if the light can see the point { vec3 ref = normalize( reflect( -Light, Normal ) ); s = pow( max( dot(Eye,ref),0. ), uShininess ); } vec3 specular = uKs * s * uSpecularColor; gl_FragColor = vec4( ambient + diffuse + specular, 1. ); }
So, the first half of the fragment shader determines the color to use based on if that fragment is inside the pattern or not. The second half of the fragment shader code uses that color in the per-fragment lighting. The final value assigned to gl_FragColor will be an RGBA that has both the pattern color and the lighting intensity.
I recommend that you pass in your pattern parameters as uniform variables and just control when you give them new values. Thus, in Display( ), you might say:
Pattern.Use( ); if( KeytimePatternOn ) { float sc = Sc.GetValue( Time ); float tc = Tc.GetValue( Time ); } if( TimePatternOn ) { float rs = << some function of the Time variable >> float rt = << some function of the Time variable >> } Pattern.SetUniformVariable( "uSc", sc ); Pattern.SetUniformVariable( "uTc", tc ); Pattern.SetUniformVariable( "uRs", rs ); Pattern.SetUniformVariable( "uRt", rt ); << do the drawing >> Pattern.UnUse( ); // Pattern.Use(0); also works
Use the Teach system to turn in your:
Item | Points |
Ellipse pattern is drawn | 25 |
Ellipse center is keytime-animated | 25 |
Ellipse radii are Time-equation-animated | 25 |
Per-fragment lighting works | 25 |
Potential Total | 100 |