/// LSU EE 7700-1 (Sp 2009), Graphics Processors // /// CPU-Only Demo 5: Textures // $Id:$ /// Purpose // // Demonstrate texture techniques. /// To compile and run: // // make // demo-5-textures /// More Information // // File coord.h on coordinate and matrix objects and operations. /// Keyboard Commands /// Texturing // // t: Cycle through different textures. // u: Turn tube texture on and off. // p: Turn perspective-correct texture coordinate interpolation on and off. /// Eye and Light Location // Arrows, Page Up, Page Down // Move either the light or the eye. // After pressing 'l' the keys move the light, after pressing 'e' // they move the eye (viewer location). The eye and light location // coordinates are displayed in the upper left. /// Eye Direction // Home, End, Delete, Insert // Turn the eye direction (after Problem 1 solved). // Home should rotate eye direction up, End should rotate eye // down, Delete should rotate eye left, Insert should rotate eye // right. The eye direction vector is displayed in the upper left. /// Lighting Options // d, a, n, +, - // d: Toggle use of distance in computing vertex lighting. // a: Toggle use of normal in computing vertex lighting. // n: Switch between using triangle normal and tube normal. // +,-: Change intensity of light. /// Screenshot // F12 // Pressing F12 will write a png image. The file name base will // match the executable name, for example, "demo-4-lighting.png". #include <stdio.h> #include <strings.h> #include <stdlib.h> #include <deque> #include "frame_buffer.h" #include "coord.h" class pTexture { public: pTexture(const char *path, int transp = 0): image(path),image_loaded(false),data(NULL),syn_lg(0) { using namespace Magick; width = image.columns(); height = image.rows(); size = width * height; if ( !width || !height ) return; if ( transp == 255 ) image.transparent(Color("White")); pp = image.getPixels(0,0,width,height); gl_fmt = GL_BGRA; for ( int i = 0; i < size; i++ ) pp[i].opacity = MaxRGB - pp[i].opacity; gl_type = sizeof(PixelPacket) == 8 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; data = (unsigned char*) pp; image_loaded = true; }; pTexture(int syn_lgp):data(NULL),syn_lg(syn_lgp) { width = 1 << syn_lg; height = 1 << syn_lg; syn_mask = ( width - 1 ) >> 4; syn_cover = syn_mask >> 2; } void color_invert() { using namespace Magick; for ( int i = 0; i < size; i++ ) { PixelPacket& p = pp[i]; const int sum = p.red + p.blue + p.green; p.opacity = (typeof p.opacity)( MaxRGB - sum * 0.3333333 ); p.red = p.blue = p.green = MaxRGB; } } Magick::PixelPacket& get(int x, int y) { if ( syn_lg ) { using namespace Magick; const bool xstripe = ( x & syn_mask ) < syn_cover; const bool ystripe = ( y & syn_mask ) < syn_cover; static PixelPacket p; p.red = xstripe ? MaxRGB : 0x88; p.green = ystripe ? MaxRGB : 0x88; p.blue = 0x88; return p; } return pp[x + y * width]; } Magick::Image image; Magick::PixelPacket *pp; bool image_loaded; int width, height, maxval, size; unsigned char *data; int gl_fmt; int gl_type; int syn_lg; int syn_mask; int syn_cover; private: }; class pGraphics_State { public: pGraphics_State():texture(NULL){} pTexture *texture; }; pGraphics_State graphics_state; pTexture texture_eebldg("computer-room-tree-600px.jpeg"); pTexture texture_syllabus("gp.png"); pTexture texture_grid(10); /// Vertex Object // // Holds coordinates, color, and normal. // class pVertex : public pCoor { public: pVertex(float xp, float yp, float zp):pCoor(xp,yp,zp){copy_ctx();} pVertex(float xp, float yp, float zp, uint32_t color): pCoor(xp,yp,zp){copy_ctx();set_color(color);}; pVertex(pCoor c, pVect n, uint32_t color) :pCoor(c),normal(n){copy_ctx(); set_color(color);} pVertex():pCoor(){}; pVertex(pVertex *v){ *this = *v; } void copy_ctx() { texture = graphics_state.texture; } void set_color(uint32_t colorp) { color = colorp; red = float( 0xff & ( color >> 16 ) ); green = float( 0xff & ( color >> 8 ) ); blue = float( 0xff & color ); } void set_texture(float sp, float tp) { s = sp; t = tp; } float red, green, blue; float s, t; // Texture coordinates. pTexture *texture; uint32_t color; pVect normal; }; /// Vertex List // // Declare vertex list types so that many vertices can easily be // operated on. // typedef std::deque<pVertex*> pVertex_List; typedef pVertex_List::iterator pVertex_Iterator; /// Vertex Sort // // Sort three vertices at vertex list iterator position. // class pSortVertices { public: pSortVertices(pVertex_Iterator& ci) { rv_idx = 0; for ( int i=0; i<3; i++ ) v[i] = ci[i]; swap(0,1); swap(0,2); swap(1,2); } operator pVertex& () { return *v[rv_idx++]; } private: void swap(int a, int b) { if ( v[a]->y <= v[b]->y ) return; pVertex* const t = v[a]; v[a] = v[b]; v[b] = t; } pVertex* v[3]; int rv_idx; }; int clampi(float valp, int min, int max) { const int val = (int) valp; if ( val < min ) return min; if ( val > max ) return max; return val; } float wrapu(float u) { float i; const float f = modff(u,&i); return f < 0 ? 1 + f : f; } bool opt_ps = false; /// Interpolation Object // // Return x and y values on line connecting two points. Skips // out-of-range values. // // Can be instantiated to advance in +x direction or +y direction. // class pInterpolate { public: pInterpolate(pVertex& v0, pVertex& v1, int ymin, int ymax) { set(v0, v1, ymin, ymax); } # define INIT_ITEM3(v0,v1,item) \ item##_0 = v0.item; item##_01 = v1.item - v0.item; #define INIT_BOGUS(item) \ item##_0 = 0; item##_01 = 0; d_##item = 0; item = 0; void set(pVertex& v0, pVertex& v1, int ymin, int ymax) { const float y_range_inv = 1.0 / ( v1.y - v0.y ); const int v1yi = int(v1.y); yi_last = ymax < v1yi ? ymax : v1yi; const float pre_y = float(ymin) - v0.y; const bool scissor = pre_y > 0.0; yi = scissor ? ymin : int(v0.y); const int y_range_part_i = v1yi - yi; const float y_range_part_inv = 1.0 / y_range_part_i; texture = v0.texture; #define INIT_ITEM(item) INIT_ITEM3(v0,v1,item) #define DELTA3(v0item,v1item,item) \ const float dtrue_##item = (v1item - v0item) * y_range_inv; \ item = v0item + ( scissor ? pre_y * dtrue_##item : 0.0 ); \ d_##item = (v1item - item) * y_range_part_inv; #define DELTA(item) DELTA3(v0.item,v1.item,item) DELTA(red); DELTA(green); DELTA(blue); DELTA(x); DELTA(z); if ( !texture ) { v0o = v1o = delta_vo_inv = 0; INIT_BOGUS(s); INIT_BOGUS(t); winv = 0; d_winv = 0; return; } const float v0winv = 1.0 / v0.w; const float v1winv = 1.0 / v1.w; v0o = truncf(v0.y) / v0winv; v1o = truncf(v1.y) / v1winv; delta_vo_inv = 1.0 / ( v1o - v0o ); INIT_ITEM(s); INIT_ITEM(t); DELTA(s); DELTA(t); DELTA3(v0winv,v1winv,winv); #undef INIT_ITEM #undef DELTA #undef DELTA3 } pInterpolate(pInterpolate& v0, pInterpolate& v1, int xmin, int xmax) { pInterpolate& vmin = v0.x < v1.x ? v0 : v1; pInterpolate& vmax = v0.x < v1.x ? v1 : v0; const float x_range_inv = 1.0 / ( vmax.x - vmin.x ); xi_last = xmax < int(vmax.x) ? xmax : int(vmax.x); const float pre_x = float(xmin) - vmin.x; const bool scissor = pre_x > 0.0; xi = scissor ? xmin : int(vmin.x); const float x_range_part_inv = 1.0 / ( int(vmax.x) - xi ); texture = v0.texture; #define DELTA3(vminitem,vmaxitem,item) \ const float dtrue_##item = (vmaxitem - vminitem) * x_range_inv; \ item = vminitem + ( scissor ? pre_x * dtrue_##item : 0.0 ); \ d_##item = (vmaxitem - item) * x_range_part_inv; #define DELTA(item) DELTA3(vmin.item,vmax.item,item) DELTA(red); DELTA(green); DELTA(blue); DELTA(z); if ( !texture ) { v0o = v1o = delta_vo_inv = 0; INIT_BOGUS(s); INIT_BOGUS(t); winv = 0; d_winv = 0; return; } v0o = truncf(vmin.x) / vmin.winv; v1o = truncf(vmax.x) / vmax.winv; delta_vo_inv = 1.0 / ( v1o - v0o ); # define INIT_ITEM(item) INIT_ITEM3(vmin,vmax,item) INIT_ITEM(s); INIT_ITEM(t); DELTA(winv); DELTA(s); DELTA(t); # undef INIT_ITEM #undef DELTA #undef DELTA3 #undef INIT_ITEM3 } bool keep_going_y() { return yi <= yi_last; } bool keep_going_x() { return xi <= xi_last; } void advance_y() { advance_y_linear(); advance_tex(yi); } void advance_x() { advance_x_linear(); advance_tex(xi); } void advance_y_linear() { advance_common(); x += d_x; yi++; } void advance_x_linear() { advance_common(); xi++; } void advance_common() { red += d_red; green += d_green; blue += d_blue; z += d_z; } void advance_tex(int i) { if ( !texture ) return; if ( opt_ps ) { winv += d_winv; const float frac = ( i / winv - v0o ) * delta_vo_inv; s = s_0 + frac * s_01; t = t_0 + frac * t_01; } else { s += d_s; t += d_t; } } # define DECLPS(v) DECL(v); float v##_0, v##_01; # define DECL(v) float v, d_##v; DECL(red); DECL(green); DECL(blue); DECL(z); DECL(x); DECL(winv); DECLPS(s); DECLPS(t); # undef DECL float v0o, v1o, delta_vo_inv, frac; uint32_t color() { return ( ( clampi(red,0,255) << 0 ) | ( clampi(green,0,255) << 8 ) | ( clampi(blue,0,255) << 16 ) ); } int xi, xi_last, yi, yi_last; private: bool texture; }; // Add an unlighted tetrahedron to VTX_LIST at LOC of size SIZE. // void insert_tetrahedron(pVertex_List& vtx_list, 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); const int32_t c1 = 0x1ffffff, c2 = 0x100ff00; pVect n; # define TRI(va,vb,vc) \ n = cross(va,vb,vc); \ vtx_list.push_back( new pVertex(va,n,c1) ); \ vtx_list.push_back( new pVertex(vb,n,c2) ); \ vtx_list.push_back( new pVertex(vc,n,c2) ); TRI(v0,v1,v2); TRI(v0,v2,v3); TRI(v0,v3,v1); # undef TRI } void insert_triangle_or_rectangle (pVertex_List& vtx_list, pCoor v, pVect w, pVect h, uint32_t c, bool triangle, float s, float t, float width, float height) { pVect n(w,h); pVertex *vtx = new pVertex(v,n,c); vtx->s = s; vtx->t = t; vtx_list.push_back(vtx); vtx = new pVertex(v+h,n,c); vtx->s = s; vtx->t = t+height; vtx_list.push_back(vtx); vtx = new pVertex(v+w,n,c); vtx->s = s+width; vtx->t = t; vtx_list.push_back(vtx); if ( triangle ) return; vtx = new pVertex(v+w,n,c); vtx->s = s+width; vtx->t = t; vtx_list.push_back(vtx); vtx = new pVertex(v+h,n,c); vtx->s = s; vtx->t = t+height; vtx_list.push_back(vtx); vtx = new pVertex(v+h+w,n,c); vtx->s = s+width; vtx->t = t+height; vtx_list.push_back(vtx); } void insert_triangle (pVertex_List& vtx_list, pCoor v, pVect w, pVect h, uint32_t c) {insert_triangle_or_rectangle(vtx_list,v,w,h,c,true,0,0,0,0);} void insert_rectangle (pVertex_List& vtx_list, pCoor v, pVect w, pVect h, uint32_t c, float s, float t, float width, float height) {insert_triangle_or_rectangle(vtx_list,v,w,h,c,false,s,t,width,height);} void render_light(pFrame_Buffer &frame_buffer) { // This routine will be called automatically each time the frame // buffer needs to be painted. /// /// Differences with Demo 4 (lighting) // // Each vertex now carries a texture id and texture coordinates. // If the texture id is non-null a texture image will be applied // to the primitive. /// /// User and Light Locations /// static pCoor eye_location(1,0.5,3); static pVect eye_direction(0,0,-1); static pCoor light_location(0.5, 1.5, -1.5 ); static bool opt_move_light = true; /// /// Light Location and Lighting Options /// static bool opt_attenuation = true; static bool opt_v_to_light = true; static bool opt_triangle_normal = false; static float opt_light_intensity = 2; static bool opt_tube_texture = false; static int opt_texture = 0; pTexture* const textures[] = { &texture_grid, &texture_syllabus, &texture_eebldg, NULL }; const int texture_count = sizeof(textures)/sizeof(textures[0]); /// /// Adjust options based on user input. /// pVect adjustment(0,0,0); pVect user_rot_axis(0,0,0); switch ( frame_buffer.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 '-':case '_': opt_light_intensity *= 0.9; break; case '+':case '=': opt_light_intensity *= 1.1; break; case 'd': case 'D': opt_attenuation = !opt_attenuation; break; case 'a': case 'A': opt_v_to_light = !opt_v_to_light; break; case 'l': case 'L': opt_move_light = true; break; case 'n': case 'N': opt_triangle_normal = !opt_triangle_normal; break; case 'e': case 'E': opt_move_light = false; break; case 'p': case 'P': opt_ps = !opt_ps; break; case 't': case 'T': opt_texture++; break; case 'u': case 'U': opt_tube_texture = !opt_tube_texture; case '0': case '1': case '2': case '3': opt_texture = frame_buffer.keyboard_key - '0'; break; default: break; } if ( opt_texture >= texture_count ) opt_texture = 0; // 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); } pMatrix_Rotation rotall(eye_direction,pVect(0,0,-1)); // 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 ) light_location += adjustment; else eye_location += adjustment; } // // User Messages (Magically inserted into frame buffer.) // frame_buffer.fbprintf ("Lighting : distance - %s, angle - %s, normals - %s " "('d', 'a', 'n', '+', '-' to change)\n", opt_attenuation ? "ON" : "OFF", opt_v_to_light ? "ON" : "OFF", opt_triangle_normal ? "TRIANGLE" : "VERTEX"); frame_buffer.fbprintf ("Eye location: [%.1f, %.1f, %.1f] " "(%suse arrow and page keys to move).\n", eye_location.x, eye_location.y, eye_location.z, opt_move_light ? "press 'e' then " : "" ); frame_buffer.fbprintf ("Light location: [%.1f, %.1f, %.1f] " "(%suse arrow and page keys to move).\n", light_location.x, light_location.y, light_location.z, opt_move_light ? "" : "press 'l' then "); frame_buffer.fbprintf ("Eye direction: [%.2f, %.2f, %.2f] " "(use 'Home', 'End', 'Del', 'Insert' keys to turn).\n", eye_direction.x, eye_direction.y, eye_direction.z); frame_buffer.fbprintf ("Tube Texturing %s ('u') Perspective-Correct %s ('p') " "Texture Num %d ('t')\n", opt_tube_texture ? "ON" : "OFF", opt_ps ? "ON" : "OFF", opt_texture); // Instantiate list of vertices. // pVertex_List vtx_list; const uint32_t color_gold = 0xf9b237; // LSU Spirit Gold const uint32_t color_purple = 0x580da6; // LSU Spirit Purple // Specify texture to be used by all succeeding vertices. // graphics_state.texture = textures[opt_texture]; // Insert large rectangles. // // One consists of two large triangles, the other many small ones. // // The difference between linear- and perspective-correct texture // coordinate interpolation will be obvious on the large one. // const float z_inc = -0.5; for ( float z=0; z>-10; z+= z_inc ) insert_rectangle ( vtx_list, pCoor(-1,-0.2,z), pVect(1,0,0), pVect(0,0,z_inc), color_gold, 1, -z/5*2.5, -1,0.25); insert_rectangle ( vtx_list, pCoor(0,-0.2,0), pVect(1,0,0), pVect(0,0,-10), color_gold, 1,0,-1,5); // Specify that succeeding primitives will not carry a texture. // graphics_state.texture = NULL; // Insert big purple triangle into the vertex list. // { pVertex* const v0 = new pVertex( 1.5, 0, -3.2, color_purple ); pVertex* const v1 = new pVertex( 0, 5, -5, color_purple ); pVertex* const v2 = new pVertex( 9, 6, -9, color_purple ); v0->normal = v1->normal = v2->normal = cross(*v0,*v1,*v2); vtx_list.push_back( v0 ); vtx_list.push_back( v1 ); vtx_list.push_back( v2 ); } // Optionally apply texture to tube. // if ( opt_tube_texture ) graphics_state.texture = textures[opt_texture]; // // Insert a tessellated tube into the vertex list. // const float r = 2; // Tube radius. const float x_shift = 0.4; // Tube x offset. const int pattern_levels = 50; // Tube depth (z direction.) const float pattern_width = 20; // Triangle size (circumferential). const float pattern_pitch_z = 0.25; // Triangle size (z axis). float z = -1; // Outer Loop: z axis (down axis of tube). // for ( int i = 0; i < pattern_levels; i++ ) { const float next_z = z - pattern_pitch_z; const float last_z = z + pattern_pitch_z; const float delta_theta = M_PI / pattern_width; float theta = i & 1 ? delta_theta : 0; const uint32_t marker_color[] = {0xaa, 0xaa0000, 0x111111, 0xaa00,}; // Port Zenith Nadir Starboard // Left Up Down Right // Red Blue Gray Green float marker_target = i & 1 ? M_PI_2 - delta_theta - 0.00001 : 10000; int marker_idx = 0; // Inner Loop: around circumference of tube. // while ( theta < 4 * M_PI ) { const float z1 = theta < 2 * M_PI ? next_z : last_z; uint32_t color = color_gold; if ( theta >= marker_target && marker_idx < 4 ) { color = marker_color[marker_idx++]; marker_target += M_PI_2; } pVertex* const v0 = new pVertex( x_shift + r * cos(theta), r * sin(theta), z, color ); if ( !opt_triangle_normal ) v0->normal = pVect(-cos(theta),-sin(theta),0); // Add texture coordinates (called s and t) to vertex. // v0->s = theta / M_PI; v0->t = z / 2; theta += delta_theta; pVertex* const v1 = new pVertex( x_shift + r * cos(theta), r * sin(theta), z1, color); if ( !opt_triangle_normal ) v1->normal = pVect(-cos(theta),-sin(theta),0); // Add texture coordinates (called s and t) to vertex. // v1->s = theta / M_PI; v1->t = z1 / 2; theta += delta_theta; pVertex* const v2 = new pVertex( x_shift + r * cos(theta), r * sin(theta), z, color ); if ( !opt_triangle_normal ) v2->normal = pVect(-cos(theta),-sin(theta),0); // Add texture coordinates (called s and t) to vertex. // v2->s = theta / M_PI; v2->t = z / 2; if ( opt_triangle_normal ) v0->normal = v1->normal = v2->normal = cross(*v0,*v1,*v2); vtx_list.push_back( v0 ); vtx_list.push_back( v1 ); vtx_list.push_back( v2 ); } z = next_z; } // Insert light position marker (green tetrahedron) into vertex list. // insert_tetrahedron(vtx_list,light_location,0.05); /// /// Rendering Pipeline Starts Here /// // Indicate to frame buffer simulator that for purposes of showing // timing, code above is part of application (included in frame time // but not render time) and that code below is part of rendering // pipeline. // frame_buffer.render_timing_start(); const int win_width = frame_buffer.get_width(); const int win_height = frame_buffer.get_height(); const int fb_size = win_width * win_height; int32_t* const f_buffer = frame_buffer.get_buffer(); // Allocate and initialize a z buffer. // (Note: Allocation only needs be performed when size changes.) // float* const z_buffer = (float*) malloc( fb_size * sizeof(*z_buffer) ); for ( int i=0; i<fb_size; i++ ) z_buffer[i] = 1; /// /// Compute Coordinate Transformations /// // Compute transformation from object space to eye space. // pMatrix_Translate center_eye(-eye_location.x,-eye_location.y,-eye_location.z); pMatrix object_to_eye = rotall * center_eye; // Compute transformation from eye space to window space. // const float aspect = float(win_width) / win_height; pMatrix_Frustum frustum(1.6,1.6/aspect,1,5000); pMatrix_Translate center_window(1,1,0); pMatrix_Scale scale(win_width/2,win_height/2); pMatrix eye_to_window = scale * center_window * frustum; // Compute matrix needed to transform normals. // pMatrix normal_to_eye(object_to_eye); normal_to_eye.transpose(); normal_to_eye.invert3x3(); /// /// Transform Coordinates and Normals from Object Space to Eye Space /// for ( pVertex_Iterator ci = vtx_list.begin(); ci < vtx_list.end(); ci++ ) { pVertex& v = **ci; v *= object_to_eye; v.normal *= normal_to_eye; v.normal.normalize(); v.homogenize(); } // Convert light location to eye space. // pCoor light_location_e = object_to_eye * light_location; /// /// Apply Lighting to Vertices /// for ( pVertex_Iterator ci = vtx_list.begin(); ci < vtx_list.end(); ci++ ) { pVertex& v = **ci; const bool vertex_no_lighting = v.color & 0x1000000; if ( vertex_no_lighting ) continue; // Compute vectors from vertex to light and to viewer. // pVect v_to_light(v,light_location_e); pVect v_to_viewer(v,pCoor(0,0,0)); // Distance from vertex to light. // const float length = v_to_light.normalize(); // Lighting coefficients and attenuation with distance. // const float k0 = 0.9; const float k1 = 0.0; const float k2 = 0.3; const float attenuation = !opt_attenuation ? 1.0 : 1.0 / ( k0 + k1 * length + k2 * length * length ); // Projections: // 1: vertex (normal) is facing light (or viewer). // 0: vertex (normal) is orthogonal (90 degrees) from light. // -1: vertex (normal) is facing opposite direction of light. // const float dot_v_to_light = dot(v.normal,v_to_light); const float dot_v_to_viewer = dot(v.normal,v_to_viewer); // Assume back side (-normal direction) is same color as front. // const float v_to_light_scale = dot_v_to_viewer < 0 ? -dot_v_to_light : dot_v_to_light; // Combine effect of distance (attenuation) and surface normal // (v_to_light_scale). // const float scale = opt_light_intensity * attenuation * ( !opt_v_to_light ? 1.0 : v_to_light_scale ); // Convert material property color to lighted color. // v.red *= scale; v.green *= scale; v.blue *= scale; } /// /// Transform Coordinates from Eye Space to Window Space /// for ( pVertex_Iterator ci = vtx_list.begin(); ci < vtx_list.end(); ci++ ) { pVertex& v = **ci; v *= eye_to_window; v.homogenize_keep_w(); } /// /// Rasterize Primitives /// for ( pVertex_Iterator ci = vtx_list.begin(); ci < vtx_list.end(); ci += 3 ) { pSortVertices sort(ci); // Sort next 3 items in list. pVertex& c0w = sort; // Coordinate with smallest y. pVertex& c1w = sort; pVertex& c2w = sort; // Coordinate with largest y. // Reject primitive if at least one vertex behind eye. // (It would have been better to clip them earlier.) // if ( c0w.w <= 0 || c1w.w <= 0 || c2w.w <= 0 ) continue; // Instantiate interpolation objects. // // Each object instantiated with two vertices and a valid // range of y values. The object will compute x and y along the // line connecting the vertices, skipping y values < 0 or // >= win_width. // // Interpolation objects also interpolate z and color components. // pInterpolate interp_02(c0w,c2w,0,win_height-1); pInterpolate interp_012(c0w,c1w,0,win_height-1); // Compute position (index) in frame buffer of first row to be written. // int fb_line_idx = interp_02.yi * win_width; // Outer Loop: Iterate from smallest y to largest y. // while ( interp_02.keep_going_y() ) { // If point c1w reached then switch interp_012 to line // connecting c1w and c2w. // if ( ! interp_012.keep_going_y() ) interp_012.set(c1w,c2w,0,win_height-1); // Instantiate x-axis interpolation object using the two // y-axis interpolation objects, interp_02 and interp_012. // The new object will compute points on the line connecting // the current position of interp_02 and interp_012. // pInterpolate interp_line(interp_02,interp_012,0,win_width-1); // Inner Loop: Iterate along x axis. // while ( interp_line.keep_going_x() ) { const int fb_idx = fb_line_idx + interp_line.xi; int32_t color; if ( c0w.texture ) { // Determine texture's s (x) and t (y) coordinates. // const int tex_x = (wrapu(interp_line.s)-0.0001) * c0w.texture->width; const int tex_y = (wrapu(interp_line.t)-0.0001) * c0w.texture->height; // Read texel from image. // // In this simple implementation there are no MIPMAP // levels. // Magick::PixelPacket& p = c0w.texture->get(tex_x,tex_y); // Combine texel colors with lighted colors from fragment. // This kind of combination is called MODULATION in // OpenGL. // const int red = clampi(int(interp_line.red * p.red) >> 16,0,255); const int green = clampi(int(interp_line.green * p.green) >> 16,0,255); const int blue = clampi(int(interp_line.blue * p.blue) >> 16,0,255); // Assemble the components back into a color. // color = red | ( green << 8 ) | ( blue << 16 ); } else color = interp_line.color(); // If z value to be written is smaller (in front of) z value // already there then go ahead and write frame buffer. // if ( interp_line.z < z_buffer[ fb_idx ] ) { f_buffer[ fb_idx ] = color; z_buffer[ fb_idx ] = interp_line.z; } // Tell interpolation object to advance in x direction. // interp_line.advance_x(); } // Tell interpolation objects to advance in y direction. // interp_02.advance_y(); interp_012.advance_y(); // Advance the frame buffer index. // fb_line_idx += win_width; } } // A paint routine is no place for a memory leak! // (And excessive dynamic memory allocation, but this is only a demo.) // for ( pVertex_Iterator ci = vtx_list.begin(); ci < vtx_list.end(); ci++ ) delete *ci; free(z_buffer); } int main(int argc, char **argv) { pFrame_Buffer frame_buffer(argc,argv); // Frame buffer object will call render_light routine, where // most of our work is done, whenever the window needs to be updated, // including after keyboard key presses. // frame_buffer.show(render_light); return 0; }