/// LSU EE 7700-1 (Sp 2009), Graphics Processors // /// Texturing Demonstration // $Id:$ /// Purpose // // Demonstrate texturing techniques. /// More Information // // OpenGL documentation. // http://www.ece.lsu.edu/koppel/gp/refs/glspec21.pdf /// What Code Does // The code shows an undulating tube (use p to pause) pierced by a // purple quadrilateral. After turning on texturing (use m) the course // syllabus will appear on the quadrilateral and tube. // Relevant User Interface: // m: Change texture application modes. Initially texturing off. // a: Change texture magnification filtering options. // i: Change texture minification filtering options. // b: Turn blending on and off. Affects appearance of syllabus on rectangle. // p: Pause/resume. /// Texturing and this Code /// Initialization // Textures loaded from image files by the procedure pTexture_From_PNM. // The procedure sets basic options for the texture, loads the data, // possibly modifies it, and passes it to OpenGL. It returns // a texture id that is used to refer to the texture. // Two textures are loaded, one for the tube and one for the // quadrilateral. (As of this writing they are the same image with // different options.) /// Texture Coordinates // Regardless of its size the coordinate space for a texture is from // (0,0), the upper left, to (1,1), the lower right. The symbol s is // used for the horizontal axis (akin to x) and t is used for the // vertical axis (akin to y). // The pTexture_From_PNM procedure sets texturing so that // coordinates outside the valid range wrap, so that both (0.2,0.2) // and (0.2,1.2) refer to the same texture location. // Texture coordinates are one of the attributes specified with a // vertex. The Tube::render routine now collects texture coordinates // for the tube and individually specifies them for the // quadrilateral (which was a triangle in earlier versions). The // texture coordinates are passed as an array using // glTexCoordPointer or individually using commands such as // glTexCoord2f. // For the tube the texture s coordinate is a scaled version of theta // and the t coordinate is a scaled version of z. /// Texture Filtering // OpenGL 2.1 3.8.8 // The user interface can be used to select minification filtering // (the i key) and magnification filtering (the a key). The chosen // filter options are passed to OpenGL using a glTexParameterf call // placed before vertices are sent to OpenGL. /// Texture Mode (Application Options) // OpenGL 2.1 3.8.13 // There are many useful ways of combining a texel with the lighted // color (or other texture's texels). These are selected in part by // specifying a /mode/ in a call to glTexEnvi. Search for // GL_TEXTURE_ENV_MODE and try user interface key m. /// Texture Use // Commands such as glTexEnvi operate on the /bound texture/, that // is, the texture specified in the last call to // glBindTexture. (Search for this.) // Texturing (2D) is turned on using glEnable(GL_TEXTURE_2D) and // turned off with a correspondence glDisable. /// Blending // The quadrilateral is displayed with blending turned on, meaning, // its pixel values will be blended with any pixels already in the // frame buffer. This blending improves anti-aliased text appearance // by allowing the gray edges of text to blend with colors in back // of the text. // Blending is turned on using glEnable(GL_BLEND) and is set to // blend using the alpha channel of the "source" meaning the textured // quadrilateral. #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 "image.h" /// /// Create and Initialize a Texture Object from a File /// enum { PT_Invert = 1, PT_To_Alpha = 2 }; GLuint pTexture_From_Image (char *file_name, int option = 0, int transp = 256 ) { P_Image_Read image(file_name,transp); if ( !image.image_loaded ) return 0; if ( option & PT_To_Alpha ) image.gray_to_alpha(); if ( option & PT_Invert ) image.color_invert(); GLuint texture_id; // Generate a texture id (name). (This doesn't create a texture object.) // glGenTextures(1,&texture_id); // Create a texture object for texture_id (since one doesn't yet exist). // glBindTexture(GL_TEXTURE_2D,texture_id); // Set some parameters for our new texture object. // glTexParameteri(GL_TEXTURE_2D,GL_GENERATE_MIPMAP,1); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); // Load data into the texture object. // glTexImage2D (GL_TEXTURE_2D, 0, // Level of Detail (0 is base). GL_RGBA, // Internal format to be used for texture. image.width, image.height, 0, // Border image.gl_fmt, // GL_BGRA: Format of data read by this call. image.gl_type, // GL_UNSIGNED_BYTE: Size of component. (void*)image.data); pError_Check(); return texture_id; } // 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; }; #define ENUM_LABEL(c) { #c, c } struct pEnum_Label { const char *label; int value; }; pEnum_Label texture_env_modes[] = { { "Texturing Off", 0 }, ENUM_LABEL(GL_REPLACE), ENUM_LABEL(GL_MODULATE), ENUM_LABEL(GL_DECAL), // Blend using alpha value of texture. ENUM_LABEL(GL_BLEND), // Blend using separate alpha. ENUM_LABEL(GL_ADD), // Sum of colors, product of alphas. ENUM_LABEL(GL_COMBINE), // Use separate combine function. {NULL,0} }; pEnum_Label texture_min_filters[] = { ENUM_LABEL( GL_NEAREST_MIPMAP_NEAREST ), ENUM_LABEL( GL_LINEAR_MIPMAP_NEAREST ), ENUM_LABEL( GL_NEAREST_MIPMAP_LINEAR ), ENUM_LABEL( GL_LINEAR_MIPMAP_LINEAR ), {NULL,0} }; pEnum_Label texture_mag_filters[] = { ENUM_LABEL( GL_NEAREST ), ENUM_LABEL( GL_LINEAR ), {NULL,0} }; /// /// 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 modelview_update(); void render(); private: pOpenGL_Helper &ogl_helper; pVariable_Control variable_control; pFrame_Timer frame_timer; pCoor eye_location; pVect eye_direction; pMatrix modelview; bool opt_move_light; 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; bool opt_pause; bool opt_blend; double time_last_frame; double time_app; pCoor* coor_buffer; pVect* norm_buffer; float* text_buffer; int num_coor_alloc; MTrig tarray; int opt_texture_env_mode; int opt_texture_min_filter; int opt_texture_mag_filter; GLuint texture_id_syllabus; GLuint texture_id_image; }; 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 = 50; // Triangle size (circumferential). opt_light_intensity = 2; opt_light_location.set(( r0 - 0.1 ), 0, -3 ); eye_location.set(0,0,2.5); eye_direction.set(0,0,-1); modelview_update(); // Arrange that variables below can be modified from the keyboard. // variable_control.insert(opt_light_intensity,"Light Intensity"); coor_buffer = NULL; norm_buffer = NULL; num_coor_alloc = 0; texture_id_syllabus = pTexture_From_Image("gp.png",PT_Invert | PT_To_Alpha,true); // texture_id_image = pTexture_From_Image("ee_bdg_to_ur_ed.ppm",false,-1); texture_id_image = pTexture_From_Image("gp.png"); opt_texture_env_mode = 0; opt_texture_min_filter = 0; opt_texture_mag_filter = 0; opt_blend = true; opt_pause = false; } void Tube::modelview_update() { pMatrix_Translate center_eye(-eye_location); pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1)); modelview = rotate_eye * center_eye; } void Tube::render() { frame_timer.frame_start(); 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 Matrix Setup /// glMatrixMode(GL_MODELVIEW); glLoadTransposeMatrixf(modelview); 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(); /// /// Adjust options based on user input. /// pVect adjustment(0,0,0); pVect user_rot_axis(0,0,0); switch ( ogl_helper.keyboard_key ) { case FB_KEY_LEFT: adjustment.x = -0.1; break; case FB_KEY_RIGHT: adjustment.x = 0.1; break; case FB_KEY_UP: adjustment.z = -0.1; break; case FB_KEY_DOWN: adjustment.z = 0.1; break; case FB_KEY_PAGE_DOWN: adjustment.y = -0.1; break; case FB_KEY_PAGE_UP: adjustment.y = 0.1; break; case FB_KEY_DELETE: user_rot_axis.y = 1; break; case FB_KEY_INSERT: user_rot_axis.y = -1; break; case FB_KEY_HOME: user_rot_axis.x = 1; break; case FB_KEY_END: user_rot_axis.x = -1; break; case 'b': case 'B': opt_blend = !opt_blend; break; case 'l': case 'L': opt_move_light = true; break; case 'e': case 'E': opt_move_light = false; break; case 'p': case 'P': opt_pause = !opt_pause; break; case 'm': opt_texture_env_mode++; if ( !texture_env_modes[opt_texture_env_mode].label ) opt_texture_env_mode = 0; break; case 'i': opt_texture_min_filter++; if ( !texture_min_filters[opt_texture_min_filter].label ) opt_texture_min_filter = 0; break; case 'a': opt_texture_mag_filter++; if ( !texture_mag_filters[opt_texture_mag_filter].label ) opt_texture_mag_filter = 0; 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; } // Update eye_direction based on keyboard command. // if ( user_rot_axis.x || user_rot_axis.y ) { pMatrix_Rotation rotall(pVect(0,0,-1),eye_direction); user_rot_axis *= rotall; eye_direction *= pMatrix_Rotation(user_rot_axis, M_PI * 0.03); modelview_update(); } // Update eye_location based on keyboard command. // if ( adjustment.x || adjustment.y || adjustment.z ) { const double angle = fabs(eye_direction.y) > 0.99 ? 0 : atan2(eye_direction.x,-eye_direction.z); pMatrix_Rotation rotall(pVect(0,1,0),-angle); adjustment *= rotall; if ( opt_move_light ) opt_light_location += adjustment; else eye_location += adjustment; modelview_update(); } 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); pVariable_Control_Elt* const cvar = variable_control.current; ogl_helper.fbprintf("VAR %s = %.3f (+/- to adjust)\n", cvar->name,cvar->var[0]); ogl_helper.fbprintf("Texture Mode: %s (m to change)\n", texture_env_modes[opt_texture_env_mode].label); ogl_helper.fbprintf("Min Filter: %s (i to change)\n", texture_min_filters[opt_texture_min_filter].label); ogl_helper.fbprintf("Mag Filter: %s (a to change)\n", texture_mag_filters[opt_texture_mag_filter].label); ogl_helper.fbprintf("Blending %s\n", opt_blend ? "ON" : "OFF"); if ( opt_pause ) ogl_helper.fbprintf("Animation PAUSED (p to resume)"); else ogl_helper.fbprintf("Animation RUNNING (p to pause)"); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); // Insert marker (green tetrahedron) to show light location. // insert_tetrahedron(opt_light_location,0.05); // // Insert a tessellated tube in the vertex list. // 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.2; glColor3fv( color_gold ); // If number of vertices has changed re-allocate our storage // (coor_buffer, norm_buffer, text_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; delete text_buffer; } coor_buffer = new pCoor[num_coor]; norm_buffer = new pVect[num_coor]; text_buffer = new float[num_coor*2]; 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; float* tptr = text_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); const float z_scale = -0.125; const float th_scale = -1.0 / ( 2 * M_PI ); // Inner Loop: around circumference of tube. // while ( theta < 4 * M_PI - delta_theta ) { 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 pCoor v0(x_shift + r * cos_theta, r * sin_theta, z); pVect v0_normal(-cos_theta,-sin_theta,cos_z); *tptr++ = theta * th_scale; *tptr++ = z * z_scale; theta += delta_theta; cos_theta = tarray.cos(theta); sin_theta = tarray.sin(theta); pCoor v1(x_shift + rz1 * cos_theta, rz1 * sin_theta, z1); pVect v1_normal(-cos_theta,-sin_theta,cos_z1); *tptr++ = theta * th_scale; *tptr++ = z1 * z_scale; theta += delta_theta; cos_theta = tarray.cos(theta); sin_theta = tarray.sin(theta); pCoor v2(x_shift + r * cos_theta, r * sin_theta, z); pVect v2_normal(-cos_theta,-sin_theta,cos_z); *tptr++ = theta * th_scale; *tptr++ = z * z_scale; *nptr++ = v0_normal; *nptr++ = v1_normal; *nptr++ = v2_normal; *cptr++ = v0; *cptr++ = v1; *cptr++ = v2; } z = next_z; } } if ( opt_texture_env_mode ) { // Make the Image texture the current 2D texture. // glBindTexture(GL_TEXTURE_2D,texture_id_image); // Set parameters in texture object (texture_id_image). // glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texture_min_filters[opt_texture_min_filter].value); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texture_mag_filters[opt_texture_mag_filter].value); // Set parameter for texture unit. // glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texture_env_modes[opt_texture_env_mode].value); glEnable(GL_TEXTURE_2D); } // Render Tube // // Note that now texture coordinates are also sent. // glNormalPointer(GL_FLOAT,0,norm_buffer); glVertexPointer(3,GL_FLOAT,sizeof(pCoor),coor_buffer); glTexCoordPointer(2,GL_FLOAT,0,text_buffer); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glDrawArrays(GL_TRIANGLES,0,num_coor); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); num_bytes += sizeof(float) * 6 * num_coor; frame_timer.work_amt_set(num_bytes); // Insert additional quadrilateral. // { // Position of vertices: // // v3 v2 // // v0 v1 // pCoor v0( -1, -1, -5 ); pCoor v1( 5, -1, -3 ); pCoor v2( 5, 6, -1 ); pCoor v3( -1, 6, -3 ); pVect normal(cross(v2,v1,v0)); glColor3fv( color_purple ); // Make primitive more sensitive to light because purple is a dark color. glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.2); glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.05); if ( opt_texture_env_mode ) { // Make the Syllabus texture the current 2D texture. // glBindTexture(GL_TEXTURE_2D,texture_id_syllabus); // Enable Blending // // Blending helps with anti-aliasing the syllabus text. // (Blending is not a texture-specific feature.) // When blending is off an alpha test is used to make the syllabus // "paper" transparent. // if ( opt_blend ) glEnable(GL_BLEND); else glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER,0.1); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); } glBegin(GL_QUADS); // Note: Texture coordinate (0,0) is upper-left. // glTexCoord2f(0,1); glNormal3fv(normal); glVertex3fv(v0); glTexCoord2f(1,1); glNormal3fv(normal); glVertex3fv(v1); glTexCoord2f(1,0); glNormal3fv(normal); glVertex3fv(v2); glTexCoord2f(0,0); glNormal3fv(normal); glVertex3fv(v3); glEnd(); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); } 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; }