/// LSU EE 7700-2 (Sp 08), Graphics Processors // /// Fragment Shader / Phong Shading Demonstration // $Id:$ /// Purpose // // Show the simple use of a fragment shader, to implement phong shading. /// More Information // // OpenGL documentation. // http://www.ece.lsu.edu/koppel/gp/refs/glspec21.pdf // // OpenGL Shading Language // http://www.ece.lsu.edu/koppel/gp/refs/GLSLangSpec.Full.1.20.8.pdf /// What Code Does // This program displays the undulating tube and lonely triangle from // previous examples, with the option of using a fragment shader for // the tube and triangle (f key). With the fragment shader off // primitives are colored using Gouraud shading, in which lighting // is computed only at the vertices of a primitive and then interpolated // for use in fragments. The fragment shader implements Phong shading, // in which lighting is computed at each fragment. // The difference can be best seen by moving the light (arrow keys // and PageUp / PageDown) near the triangle, but away from any vertex. // With Phong shading there will be a bright spot near the light, with // Gouraud shading the brightest part of the triangle will be at the // vertex closest to the light. /// Fragment Shader OpenGL Code /// Initialization // OpenGL 2.1 2.15 // As with a vertex shader, fragment shader code must be loaded, // compiled, and linked. This is done using the pShader class // in shader.h. // The code in Tube::init instantiates three such objects, vs_lighting // which includes only a vertex shader implementing a simple lighting // model, vs_phong which implements the Phong shader, and vs_fixed // which is used to select OpenGL fixed functionality. // The code in pShader::init loads the shader code from a file, // demo-5-shader.cc, adds on a "main" routine, and then uses OpenGL // functions to compile and link the shaders. The shaders are // compiled into microcode for the GPU shaders, the linked microcode // is likely copied to GPU memory to await its use. /// Use of Shader Code // The fragment shader is turned on by the use member function of // pShader, which itself calls glUseProgram. This loads the GPU // micro code memories with the microcode. Subsequent vertices will // be processed by the shader code. Calling glUseProgram with a zero // argument switches back to fixed-function processing. This is most // likely implemented by loading manufacturer-provided microcode in // the micro code memories, to implement the fixed functionality as // defined by OpenGL. /// Shader Code // // OpenGL 2.1 2.15.4 Vertex Shader Functionality. // OGSL 1.2 7 Built In Shader Variables // The shader code is in demo-5-shader.cc, that code is used for // both vs_lighting (which doesn't perform fragment shading) and // vs_phong (which does fragment shading). // The vertex shader is responsible for performing a set of // calculations normally provided in fixed-functionality // operation. This includes transforming coordinates and performing // lighting calculations, see OpenGL 2.1 2.15.4 for a complete // list. The vertex shader must perform all of the calculations (or // else loose the functionality). For example, though the goal of // vs_lighting was a lighting tweak it must still perform vertex // transformations. Texture coordinate generation is not performed // by vs_lighting and so texturing would not work (things are // different in demo-6). /// vs_lighting: Simple Lighting Model. // // The entry point is vs_main_lighting, that calls vs_ff_vertex for // vertex transformation and then generic_lighting for lighting. // See OGSL 1.2 7 for a list of built-in variables available to // shaders. /// vs_phong: Phong Shading // vs_phong installs both a vertex shader (vs_main_phong) and // fragment shader (fs_main_phong), they work together. // The vertex shader, vs_phong, performs the coordinate // transformations to clip space, but also writes eye-space // coordinates to a new varying, var_vertex_e. The surface normal is // also written to a declared varying but the material color // (unlighted) is written to predefined varyings: gl_BackColor and // gl_FrontColor. // The fragment shader, fs_main_phong, uses the interpolated // eye-space vertex, normal, and color for a lighting // computation. Notice that the same lighting routine, // generic_lighting, is used by the fragment shader and the // vs_lighting vertex shader. The difference is that the fragment // shader is calling it at every fragment and so the surface normals // and distances will be correct. // Note that the generic_lighting routine performs a substantial // amount of calculation, which is why Phong shading is not even // an option in OpenGL. For modern GPUs Phong shading is not a // great burden. #include <stdio.h> #include <strings.h> #include <stdlib.h> #include <deque> #define GL_GLEXT_PROTOTYPES #define GLX_GLXEXT_PROTOTYPES #include <GL/gl.h> #include <GL/glext.h> #include <GL/glx.h> #include <GL/glxext.h> #include <GL/glu.h> #include <GL/freeglut.h> #include "util.h" #include "coord.h" #include "shader.h" // Display a tetrahedron, used to indicate light position. // void insert_tetrahedron(pCoor& loc, float size) { pCoor v0(loc.x,loc.y,loc.z); pCoor v1(loc.x,loc.y-size,loc.z+size); pCoor v2(loc.x-.866*size,loc.y-size,loc.z-0.5*size); pCoor v3(loc.x+.866*size,loc.y-size,loc.z-0.5*size); static pColor c1(0xffffff); static pColor c2(0xff00); glDisable(GL_LIGHTING); #define TRI(va,vb,vc) \ { \ pVect n = cross(va,vb,vc); \ glNormal3fv(n); \ glColor3fv(c1); glVertex3fv(va); \ glColor3fv(c2); glVertex3fv(vb); \ glVertex3fv(vc); \ } glBegin(GL_TRIANGLES); TRI(v0,v1,v2); TRI(v0,v2,v3); TRI(v0,v3,v1); glEnd(); # undef TRI glEnable(GL_LIGHTING); } // Class for re-using sine and cosine values. // class MTrig { public: MTrig():size(0),storage(NULL){} void init(int sizep){ size = sizep; if ( storage ) delete storage; storage = new float[size]; idx = 0; full = false; } float sin(float theta){ return trig(theta,::sin); } float cos(float theta){ return trig(theta,::cos); } private: float trig(float theta,double (*func)(double)) { if ( !full ) { storage[idx] = func(theta); full = idx == size - 1; } if ( idx == size ) idx = 0; return storage[idx++]; } int size; float* storage; int idx; bool full; }; // /// Tube Object // class Tube { public: Tube(pOpenGL_Helper &fb):ogl_helper(fb){ init(); } static void render_w(void *moi){ ((Tube*)moi)->render(); } void init(); void render(); private: pOpenGL_Helper &ogl_helper; pVariable_Control variable_control; pFrame_Timer frame_timer; pShader *vs_fixed; // Fixed functionality. pShader *vs_lighting; // Lighting tweak. pShader *vs_phong; // Phong shading. pVect to_eye_vector; float r0; float x_shift; float pattern_pitch_z; float opt_pattern_levels; float opt_pattern_width; float opt_light_intensity; pCoor opt_light_location; double time_last_frame; double time_app; // Time in simulated world in seconds. Starts at zero. pCoor* coor_buffer; pVect* norm_buffer; int num_coor_alloc; MTrig tarray; bool opt_pause; bool opt_fshader; }; void Tube::init() { time_app = 0; time_last_frame = time_wall_fp(); // Tell frame timer that work unit is "MB/s" and how should be scaled. // frame_timer.work_unit_set("MB/s",1e-6); r0 = 2; // Tube radius. x_shift = 0.4; // Tube x offset. pattern_pitch_z = 0.25; // Triangle size (z axis). opt_pattern_levels = 50; // Tube depth (z direction.) opt_pattern_width = 30; // Number of triangles along circumference. opt_light_intensity = 2; opt_light_location.set(( r0 - 0.1 ), 0, -3 ); to_eye_vector.set(-1,-0.5,-3); // Arrange that variables below can be modified from the keyboard. // variable_control.insert(opt_light_intensity,"Light Intensity"); variable_control.insert(opt_pattern_levels,"Pattern Levels"); variable_control.insert(opt_pattern_width,"Pattern Width"); variable_control.insert(to_eye_vector.x,"Viewer X"); coor_buffer = NULL; norm_buffer = NULL; num_coor_alloc = 0; glEnable(GL_NORMALIZE); // Declared like a programmable shader, but used for fixed-functionality. // vs_fixed = new pShader(); // Prepare a vertex shader implementing a simple lighting model. // vs_lighting = new pShader("demo-5-shader.cc","vs_main_lighting();"); // Prepare a vertex shader and fragment shader, implementing a Phong shader. // vs_phong = new pShader ("demo-5-shader.cc","vs_main_phong();","fs_main_phong();"); opt_pause = false; opt_fshader = false; } void Tube::render() { frame_timer.frame_start(); /// /// Reset Frame and Z Buffers /// glClearColor(0,0,0.0,0.5); glClearDepth(1.0); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // Have frame timer provide timing information for top of image. // ogl_helper.fbprintf("%s\n",frame_timer.frame_rate_text_get()); /// /// Transformation Matrices Setup /// glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(to_eye_vector.x,to_eye_vector.y,to_eye_vector.z); const int win_width = ogl_helper.get_width(); const int win_height = ogl_helper.get_height(); const float aspect = float(win_width) / win_height; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-0.8,+0.8,-0.8/aspect,0.8/aspect,1,5000); glViewport(0, 0, win_width, win_height); pError_Check(); /// /// Light Location and Lighting Options /// // Adjust options based on user input. // switch ( ogl_helper.keyboard_key ) { case FB_KEY_LEFT: opt_light_location.x -= 0.1; break; case FB_KEY_RIGHT: opt_light_location.x += 0.1; break; case FB_KEY_UP: opt_light_location.y += 0.1; break; case FB_KEY_DOWN: opt_light_location.y -= 0.1; break; case FB_KEY_PAGE_DOWN: opt_light_location.z += 0.2; break; case FB_KEY_PAGE_UP: opt_light_location.z -= 0.2; break; case 'p': case 'P': opt_pause = !opt_pause; break; case 'f': case 'F': opt_fshader = !opt_fshader; break; case 9: variable_control.switch_var_right(); break; case '-':case '_': variable_control.adjust_lower(); break; case '+':case '=': variable_control.adjust_higher(); break; default: break; } glLightfv(GL_LIGHT0, GL_POSITION, opt_light_location); const float light_intensity[4] = {opt_light_intensity, opt_light_intensity, opt_light_intensity, 1.0}; const float light_off[4] = {0,0,0,0}; const float light_dim[4] = {0.1,0.1,0.1,1}; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, &light_dim[0]); glLightfv(GL_LIGHT0, GL_DIFFUSE, &light_intensity[0]); glLightfv(GL_LIGHT0, GL_AMBIENT, &light_off[0]); glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0); glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1); glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.25); glEnable(GL_LIGHT0); glEnable(GL_LIGHTING); glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); ogl_helper.fbprintf("FRAGMENT SHADER: %s (f TO CHANGE)\n", opt_fshader ? "on" : "off"); if ( !vs_lighting->pobject ) ogl_helper.fbprintf ("PROGRAMMABLE GPU API: %savailable. GPU CODE: %s\n", ptr_glCreateShader ? "" : "not", vs_lighting->pobject ? "okay" : "problem"); ogl_helper.fbprintf ("ANIMATION %s (p TO TOGGLE)\n", opt_pause ? "paused" : "running"); pVariable_Control_Elt* const cvar = variable_control.current; ogl_helper.fbprintf ("VAR %s = %.3f (tab, +/- TO ADJUST)\n", cvar->name,cvar->var[0]); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); // Insert marker (green tetrahedron) to show light location. // vs_fixed->use(); insert_tetrahedron(opt_light_location,0.05); /// /// Insert a tessellated tube in the vertex array. /// float z = -1; pColor color_purple(0x580da6); // LSU Spirit Purple pColor color_gold(0xf9b237); // LSU Spirit Gold // // Compute Tube Specifications // const int pattern_width = 3 * int( opt_pattern_width * 0.33333333 ); const int pattern_levels = int( opt_pattern_levels + 0.5 ); // Number of vertices passed to OpenGL. const int vertices_per_ring = 3 * 2 * pattern_width; const int num_coor = pattern_levels * vertices_per_ring; const double cycles_per_second = 0.2; const double now = time_wall_fp(); const double delta_t = now - time_last_frame; time_last_frame = now; if ( !opt_pause ) time_app += delta_t; const double phase_n = time_app * cycles_per_second; const double phase = phase_n * 2.0 * M_PI; const float wavelength_z = 2.8; const float radians_per_z = 2.0 * M_PI / wavelength_z; int num_bytes = 0; const float ampl = 0.4; glColor3fv( color_gold ); // If number of vertices has changed re-allocate our storage // (coor_buffer, norm_buffer) and MTrig object and also remember // that gpu's buffer needs to be updated. if ( num_coor_alloc != num_coor ) { if ( coor_buffer ) { delete coor_buffer; delete norm_buffer; } coor_buffer = new pCoor[num_coor]; norm_buffer = new pVect[num_coor]; tarray.init( vertices_per_ring * 4 ); num_coor_alloc = num_coor; } // Outer Loop: z axis (down axis of tube). // { pCoor* cptr = coor_buffer; pVect* nptr = norm_buffer; const float delta_theta = M_PI / pattern_width; for ( int i = 0; i < pattern_levels; i++ ) { const float next_z = z - pattern_pitch_z; const float last_z = z + pattern_pitch_z; float theta = i & 1 ? delta_theta : 0; const float angle_z = phase + radians_per_z * z; const float angle_nz = phase + radians_per_z * next_z; const float angle_lz = phase + radians_per_z * last_z; const float r = r0 * ( 1 + ampl * sin( angle_z ) ); const float rnz = r0 * ( 1 + ampl * sin( angle_nz ) ); const float rlz = r0 * ( 1 + ampl * sin( angle_lz ) ); const float cos_z = cos(angle_z); const float cos_lz = cos(angle_lz); const float cos_nz = cos(angle_nz); pCoor* const cptr_stop = cptr + vertices_per_ring; // Inner Loop: around circumference of tube. // while ( cptr < cptr_stop ) { // This loop iterates for two trips around the ring, // on the first trip first_round is true and triangles point // away from the viewer, on the second round they point towards // the viewer. const bool first_round = theta < 2 * M_PI; const float z1 = first_round ? next_z : last_z; const float rz1 = first_round ? rnz : rlz; const float cos_z1 = first_round ? cos_nz : cos_lz; float cos_theta = tarray.cos(theta); // Reassigned float sin_theta = tarray.sin(theta); // Reassigned // First vertex of triangle. // *cptr++ = pCoor(x_shift + r * cos_theta, r * sin_theta, z); *nptr++ = pVect(-cos_theta,-sin_theta,cos_z); theta += delta_theta; cos_theta = tarray.cos(theta); sin_theta = tarray.sin(theta); // Second vertex of triangle. // *cptr++ = pCoor(x_shift + rz1 * cos_theta, rz1 * sin_theta, z1); *nptr++ = pVect(-cos_theta,-sin_theta,cos_z1); theta += delta_theta; cos_theta = tarray.cos(theta); sin_theta = tarray.sin(theta); // Third vertex of triangle. // *cptr++ = pCoor(x_shift + r * cos_theta, r * sin_theta, z); *nptr++ = pVect(-cos_theta,-sin_theta,cos_z); } z = next_z; } } // // Send CPU-Computed Vertices to GPU // if ( opt_fshader ) vs_phong->use(); else vs_lighting->use(); glNormalPointer(GL_FLOAT,0,norm_buffer); glVertexPointer(3,GL_FLOAT,sizeof(pCoor),coor_buffer); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_TRIANGLES,0,num_coor); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); num_bytes += sizeof(float) * 6 * num_coor; pError_Check(); frame_timer.work_amt_set(num_bytes); // Insert additional triangle. // { pCoor v0( 1.5, 0, -3.2 ); pCoor v1( 0, 5, -5 ); pCoor v2( 9, 6, -9 ); pVect normal(cross(v0,v1,v2)); glColor3fv( color_purple ); glBegin(GL_TRIANGLES); glNormal3fv(normal); glVertex3fv(v0); glNormal3fv(normal); glVertex3fv(v1); glNormal3fv(normal); glVertex3fv(v2); glEnd(); } vs_fixed->use(); glColor3f(0,1,0); // This sets the text color. Don't know why. pError_Check(); frame_timer.frame_end(); glutSwapBuffers(); } int main(int argc, char **argv) { pOpenGL_Helper popengl_helper(argc,argv); Tube tube(popengl_helper); popengl_helper.rate_set(30); popengl_helper.display_cb_set(tube.render_w,&tube); return 0; }