870 likes | 1.05k Views
OpenGL 3 Angel: Chapter 2 OpenGL Programming and Reference Guides, other sources. ppt from Angel, AW, etc. CSCI 6360/4360. Questions from last week?. Overview…. Review of event driven architecture – GLUT, callbacks, etc. Other interactive program techniques and elements Text
E N D
OpenGL 3Angel: Chapter 2OpenGL Programming and Reference Guides, other sources.ppt from Angel, AW, etc. CSCI 6360/4360
Overview… • Review of event driven architecture – GLUT, callbacks, etc. • Other interactive program techniques and elements • Text • Bitmap and stroke • Display Lists • Retained mode graphics • Picking • Select objects from the display • Bitwise manipulation, e.g., XOR for non-erasure • Rubberbanding • Design of interactive programs • Preview of the OpenGL pipeline transformations … next few weeks
Review • Window system and graphics system • Event driven control flow • … hopefully, this will be boring
Software Org. – Window & Graphics Systems application program OpenGL Motif widget or similar GLUT GLX, AGLor WGL GLU GL X, Win32, Mac O/S software and/or hardware • Window system controls/manages window creation, appearance, messages (event queue), etc. • Has its own API for programming, with and without accessing OpenGL • Graphics system controls graphics display hardware • Here, OpenGL – GL and its utilities, GLU graphics system window system
Software Org. – Window & Graphics Systems application program OpenGL Motif widget or similar GLUT GLX, AGLor WGL GLU GL X, Win32, Mac O/S software and/or hardware • Window system controls/manages window creation, appearance, messages (event queue), etc. • Has its own API for programming, with and without accessing OpenGL • Graphics system controls graphics display hardware • Here, OpenGL – GL and its utilities, GLU graphics system window system
Software Org. – Window & Graphics Systems application program OpenGL Motif widget or similar GLUT GLX, AGLor WGL GLU GL X, Win32, Mac O/S software and/or hardware • GLUT - layer for access to both window and graphics systems • GLUT uses GLU and GL for graphics • Controls operating and window systems primarily through GLX, AGL, WGL • Callback functions are means GLUT uses to “shield programmer from intricacies of direct event loop programming” graphics system window system
Software Org. – Window & Graphics Systems application program OpenGL Motif widget or similar GLUT, Qt GLX, AGLor WGL GLU GL X, Win32, Mac O/S software and/or hardware • Qt also provides access to both window and graphics system • Widely used and robust, e.g., Google Earth • Many more widgets • Tradeoff: • Flexibility (not all window system calls available) graphics system window system
Window Manager Distributes Events to Appropriate Event Queues • E.g., mouse move across multiple programs • Information about input event distributed to all programs, e.g., mouse x, y • Every program has an event queue Program 1 Program 2 Program 3 Event queue 1 Event queue 2 Event queue 3 Wndproc 1 Wndproc 2 Wndproc 3
GLUT Callback FunctionsCalled when various event occur – “encapsulates” messages WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) // front message of event queue { switch (iMsg) // note switch structure – handles and returns { // several “raw” messages result in glut callback being executed case WM_LBUTTONDOWN : // myMouse (button, state, x, y); - one function several “raw” messages case WM_LBUTTONUP : // named in glutMouseFunc case WM_RBUTTONDOWN : . : x = LOWORD (lParam); y = HIWORD (lParam); TextOut (hdc, 20, 200, szBuffer, nLength); // write x and y locations (BTW, later see GLUT) return 0 ; case WM_CREATE : // message when program starts : case WM_SIZE : // message when window size changed case WM_MOUSEMOVE : x = LOWORD (lParam); y = HIWORD (lParam); // “decode” lParam to get x and y locations : return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; // default handling of events, if not here }
GLUT Callback FunctionsCalled when various event occur – “encapsulates” messages WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) // front message of event queue { switch (iMsg) // note switch structure – handles and returns { // several “raw” messages result in glut callback being executed case WM_LBUTTONDOWN : // myMouse (button, state, x, y); - one function several “raw” messages case WM_LBUTTONUP : // named in glutMouseFunc case WM_RBUTTONDOWN : . : x = LOWORD (lParam); y = HIWORD (lParam); TextOut (hdc, 20, 200, szBuffer, nLength); // write x and y locations return 0 ; case WM_CREATE : // named in glutDisplayFunc // message when program starts : case WM_SIZE : // named in glutReshapeFunc // message when window size changed case WM_MOUSEMOVE : // named in glutMotionFunc x = LOWORD (lParam); y = HIWORD (lParam); // “decode” lParam to get x and y locations : return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; // default handling of events, if not here }
Summary - Events • Events – from input devices (usually not result in redraw – call to display) • Most systems have more than one input device, each of which can be triggered at an arbitrary time by a user • Mouse: click one or more buttons • Motion: move mouse • Keyboard: press or release a key • Events – from system • Window: resize, expose, iconify (usually do result in redraw – call to display) • Idle: nonevent • Define what should be done if no other event is in queue • Events – from your program • postRedisplay – new! • Each event is put in an event queue • OpenGL uses callback functions to handle events in event queue
Control Structure Review • Homework as Example • Recall, all drawing is done in one function so can handle “redraw” (or refresh) at any time • Vs., e.g., doing partial drawings at various times • At first exposure to event driven programming, this is new – at least degree to which it is done • Requires, then, some signal (message) to redraw • System produces signals that result in call to display • E.g., window moved over • Postredisplay() is that signal • Your program sends a message to the message queue that results in the program call the display function • In essence “redraw the screen” • In practice “something has changed (or time elapsed), so redraw the screen” • And can have Postredisplay be called within your program to signal “redraw yourself”
Review – Glut Event Loop #include <GL/glut.h> void mydisplay(){ glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_POLYGON); glVertex2f(-0.5, -0.5); glVertex2f(-0.5, 0.5); glVertex2f(0.5, 0.5); glVertex2f(0.5, -0.5); glEnd(); glFlush(); } int main(int argc, char** argv){ glutCreateWindow("simple"); glutDisplayFunc(mydisplay); glutMainLoop(); } • Program defines a display callback function • Here, named mydisplay • Now familiar • Every glut program must have a display callback • Executed whenever display must be refreshed, • E.g., when window opened • The main function ends with program entering an event loop
Paradigm Control Structure • Something happens - an “event” 1. System may cause redraw, i.e., call “myDisplay” • E.g., move window off • GLUT display function called 2. GLUT callback may handle, if defined • E.g., glutMouseFunc - myMouse • Update any signals (values) that creating display depends on Event – user, system, program 1 2 Program’s GLUT callback may change state of program Draw Display (myDisplay)
Paradigm Control Structure • Something happens - an “event” 1. System may cause redraw, i.e., call “myDisplay” • E.g., move window off • GLUT display function called 2. GLUT callback may handle, if defined • E.g., glutMouseFunc - myMouse • Update any signals (values) that creating display depends on • Often, display needs to be redrawn • Programmer decides and signals this • Program indicates by postRedisplay () • Which actually causes system to indicate call to GLUT display function • Draw display – because of postRedisplay • GLUT display function Event – user, system, program 1 2 Program’s GLUT callback may change state of program requiring redraw PostRedisplay() Draw Display (myDisplay)
Text in GLUT • When using OpenGL, all the API calls of the window system/manager are available: • E.g., for MS Windows TextOut (… • So, could use system native commands, but at cost of portability • GLUT provides portable and reasonably easy to use text • Bitmapped • Each character is a fixed size in a matrix of pixels • Not pretty, when scaled • Written directly to frame buffer (raster) • Eases challenges of scale, e.g., when writing into scene • Does not provide for rotating, etc. (applying transformations) to text • Stroke • Essentially, rules for generating the characters • Handles scaling • Transformations applied • Have their own challenges, e.g., orientation to the cop
Using Bitmap Fonts : void *font = GLUT_BITMAP_TIMES_ROMAN_24; : GLUT_BITMAP_9_BY_15, GLUT_BITMAP_TIMES_ROMAN_10, GLUT_BITMAP_TIMES_ROMAN_24 : char defaultMessage[] = "GLUT means OpenGL."; char *message = defaultMessage; void output (int x, int y, char *string) { int len, i; glRasterPos2f(x, y); len = (int) strlen(string); for (i = 0; i < len; i++) { glutBitmapCharacter(font, string[i]); } } void display(void) { glClear(GL_COLOR_BUFFER_BIT); output(0, 24, "This is written in a GLUT …"); output(100, 100, message); output(50, 145, "(positioned in pixels with ….)"); glutSwapBuffers(); } • Pretty easy! • GlutBitmapCharacter (GLUT_BITMAP_8_BY_13, int character) • Also, advances raster position width of character • “raster position” is part of OpenGL state • Where next raster primitive will be placed • Set with, e.g., glRasterPos2f() • Use to specify where bitmap to be drawn • http://www.opengl.org/resources/code/samples/glut_examples/examples/examples.html
Window Management • Something else pretty straightforward • Can in fact create as many windows as like • id = glutCreateWindow(<title>) • idPlanets = glutCreateWindow (“Rotating Planets”) • idGalaxy = glutCreateWindow (“Milky Way Galaxy”) • Invoke glutInitDisplay prior to create to set properties different • Use window id to select window as current window into which objects rendered • glutSetWindow (id) • Can have different display functions, etc. • Communication is easy, as all in same name space • Vs. messages, etc. • Can transfer control among windows by having others select as current
Display Lists • Conceptually similar to a graphics file • Define (name, create), add contents, close • In client-server environment, display list is placed on server • Can be redisplayed without sending primitives over network each time • Can also be used in program to not need to re-execute • OpenGL immediate and retained mode graphics • Immediate mode graphics • In standard OpenGL program, once an object is rendered there is no memory of it and to redisplay it, we must re-execute the code for it to be displayed • Can be especially slow if objects complex and sent over a network • Retained mode graphics • Alternative is define objects and keep them in some form that can be redisplayed easily • Accomplished in OpenGL via display lists
Display List Functions and State • GLuint id; • void init() • { • id = glGenLists ( 1 ); • glNewList (id, GL_COMPILE ); • // OpenGL routines, polygon, etc. • glEndList (); • } • Creating a display list: ex -> • Call a created list: • Most OpenGL functions can be put in display lists • State changes made inside display list persist after list executed • Efficient to save • Can avoid unexpected results by using glPushAttrib and glPushMatrix upon entering a display list, and glPopAttrib and glPopMatrix before exiting • More later, again … • void display () • { • glCallList ( id ); • }
Hierarchy and Display Lists • Consider model of a car, or robot arm, or anything • Objects are hierarchical in that each of several components is part of car object • Might, e.g., • Create display list for chassis • Create display list for wheel • To “move” car in scene, • translate (more next wk) each element: • glNewList( CAR, GL_COMPILE ); • glCallList( CHASSIS ); • glTranslatef( … ); • glCallList( WHEEL ); • glTranslatef( … ); • glCallList( WHEEL ); • … • glEndList(); • Will later consider scene graphs, which encapsulate object information in this way • Foundation of much practical cg, e.g., “in hardware”
“Picking” or “Hit-testing” or … • “Pick” or hit test • Identify an object in the display • Fundamental action in user interface • Recall, six types of logical input: • Locator: return a position • Pick: return ID of an object • Keyboard: return strings of characters • Stroke: return array of positions • Valuator: return floating point number • Choice: return one of n items • In principle, should be simple because mouse gives the x and y position, and should be able to determine to which object(s) a position corresponds to
“Picking” or “Hit-testing” or … • In practice harder to do • Practical difficulties: • OpenGL Pipeline architecture is feed forward, hard to go from screen back to world • Object in world, create view from position (transform), clip, map to viewport • Complicated by screen being 2D, world is 3D • Other details • E.g., How close does user have to come to object to say selected it? • Three approaches: • Create bounding boxes and programmer checks (detail follows) • “hit list” • Exploits/use graphics system - “selection mode” • Use a system buffer to store object ids as objects are rendered
OpenGL Rendering for Picking • Briefest of overviews • OpenGL can render in one of three modes selected by glRenderMode(mode) • GL_RENDER: normal rendering to the frame buffer (default) • GL_SELECTION: Each primitive in the view volume generates a hit record that is placed in a name stack which can be examined later • GL_FEEDBACK: provides list of primitives rendered but no output to the frame buffer
Selection Mode - briefly • First, series of ids set by application program to identify objects • Functions: • glSelectBuffer(): specifies name buffer, glInitNames(): initializes name buffer • glPushName(id): push id on name buffer, glPopName(): pop top of name buffer • glLoadName(id): replace top name on buffer • Using selection mode – straightforward in principle, but efficiency • Initialize name buffer, enter selection mode (using mouse) • Render scene with user-defined ids • Reenter normal render mode (returns number of hits) • Examine contents of name buffer (hit records, id and depth information) • Changes viewing parameters so that only those primitives near cursor are in altered view volume • Use gluPickMatrix (see text for details)
Using 2nd Buffer and Colors for Picking • Call it a trick … (OK, technique) • Besides an object’s “light color”, assign an arbitrary unique color to each object that will be used as an identifier – doesn’t matter what looks like • e.g., (1, 0, 0) = polygon on left front of house 5; (1, 0, 0) = polygon on right front of house 5; … • Render scene as usual to front buffer (which is displayed, as usual) • Render scene again to a (color) buffer other than front buffer so results of rendering are not visible! • Could even use back buffer if not need double buffering • But there are many buffers available to OpenGL • Then, get mouse position and use glReadPixels() to read color in second (non-visible) buffer – • Which gives identifier (color) for object (all its polygons) • Which is identifier for object! • … programming as if resources don’t matter
Bounding Boxes • Extent of an object • Smallest rectangle, aligned with coordinate axes, that contains object • Angel program – at class web site • For 2d, easy to determine rectangle in screen coordinates that corresponds to a rectangle point in object or world coordinates • For 3d, bounding box is a right parallelepid (box) • Program can associate objects with bounding boxes • Hit testing (picking) is process of checking if mouse is within bounding box of objects
Testing 2d ExtentsChecking if mouse is within bounding box of objects • Again, full Angel program on class web site • Test if mouse (pointer) is in region • Recall, • myMouse (button, state, x, y) • button • GLUT_LEFT_BUTTON • GLUT_MID_BUTTON • GLUT_RIGHT_BUTTON • state • GLUT_UP • GLUT_DOWN • x, y • current position • Will need data structure to hold extents
Testing 2d ExtentsChecking if mouse is within bounding box of objects • Check to see if mouse is “in” the region • Yes, it’s this easy … and crude Void myMouse (btn, state, x, y) { y = wh – y; if (btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN && ( x > 10 && x < 50 ) && ( y > 10 && y < 30 ) ) { // if hit, do things: bHit = TRUE; // probably, set some “signal” postReDisplay(); // probably, cause display to be redrawn } // using information of “signal” }
Testing Multiple ExtentsSearching an array of extents • Check if mouse in any of several regions • Array of extents is searched rectObjs[0].left = 10; rectObjs[0].right = 50; rectObjs[0].top = 20; rectObjs[0].bottom = 30; rectObjs[1].left = 10; rectObjs[1].right = 50; rectObjs[1].top = 30; rectObjs[1].bottom = 50; // other assignments nObjects = 3; void myMouse (btn, state, x, y) { y = wh – y; : for (i=0; i<nObjects; i++) { if (btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN && ( x > rectOjs[i].left && x < rectOjs[i].right ) && ( y > rectOjs[i].top && y < rectOjs[i].bottom ) ) { bHit = TRUE; // probably, set some “signals” nObjectPicked=i; postReDisplay(); // probably, cause display to be redrawn break; // using information of “signal” } } }
Angel’s polygon.c – Pick Polygon • Polygon pick, …, add, delete • Examples of bounding box pick, data structures, etc. // Angel’s polygon struct // More than you will need for homework typedef struct polygon { int color; /* color index */ bool used; /* TRUE if polygon exists */ int xmin, xmax, ymin, ymax; /* bounding box */ float xc, yc; /* center of polygon */ int nvertices; /* number of vertices */ int x[MAX_VERTICES]; /* vertices */ int y[MAX_VERTICES]; } polygon; int pick_polygon(int x, int y) { // find first polygon in which we are in bounding box int i; for(i=0; i<MAX_POLYGONS; i++) { if(polygons[i].used) if((x>=polygons[i].xmin)&& (x<=polygons[i].xmax)&& (y>=polygons[i].ymin)&& (y<polygons[i].ymax)) { in_polygon = i; moving = TRUE; return(i); } printf("not in a polygon\n"); return(-1); } }
polygon.c – Polygon follows Cursorfyi – using glutMotionFunc void myMotion(int x, int y) // name defined in glutMotionFunc { float dx, dy; int i,j; if (moving) // global flag for action to move polygon – set by user { y = wh - y; // invert screen coordinates to world coordinates j = pick_polygon(x, y); // just saw - returns index of polygon (if any) at pointer if (j<0) { printf("not in a polygon\n"); return; } // if not in polygon, j<0, return .. better variable name // if inside polygon, then move polygon - x, y are mouse loc, poly has x, y – so dx,y is distance to move dx = x - polygons[j].xc; dy = y - polygons[j].yc; for(i = 0; i< polygons[j].nvertices; i++) // assign polygon new locations { polygons[j].x[i] += dx; polygons[j].y[i] += dy; } // update bounding box polygons[j].xc += dx; polygons[j].yc += dy; if(dx>0) polygons[j].xmax += dx; else polygons[j].xmin += dx; if(dy>0) polygons[j].ymax += dy; else polygons[j].ymin += dy; glutPostRedisplay(); // cause display redraw with new polygon location } • Polygin
Placing and Aiming OGL Camera • Have used default camera location, viewing direction, etc., so far • That’s no fun
Placing and Aiming OGL Camera • Natural to position camera in world space as if real camera 1. Identify the eye point where the camera is located 2. Identify the look-at point that we wish to appear in center of view 3. Identify an up-vector vector oriented upwards in final image • Specify camera configuration with: • gluLookAt(ex, ey, ez, ax, ay, az, ux, uy, uz) • 3 camera vectors: lookFrom (ex, ey, ez), lookAt (ax, ay, az), vUp (ux, uy, uz) • Or, gluLookAt (lookFromx,y,z, lookAtx,y,z, vUpx,y,z)
OpenGL: gluLookAt(a really handy little function) • Specify camera configuration with • gluLookAt(ex, ey, ez, ax, ay, az, ux, uy, uz) • gluLookAt (lookFromx,y,z, lookAtx,y,z, vUpx,y,z) • Note: a utility – glu, not gl • Three camera vectors • lookFrom(ex, ey, ez) • lookAt(ax, ay, az) • vUp(ux, uy, uz) • Typical Transformation Setup (more later): glMatrixMode(GL_PROJECTION); // which matrix glLoadIdentity(); // initialize – recall, make have no affect gluPerspective(fovy, aspect, zNear, zFar); // will be perspective (more later) glMatrixMode(GL_MODELVIEW); // which matrix glLoadIdentity(); // initialize gluLookat(ex, ey, ez, ax, ay, az, 0, 1, 0); // easy!
gluLookAt Function(from red book) • From Red Book …. • Function gluLookAt forms required modelview matrix through a simple interface • Still need to initialize • Can concatenate with modeling transformations • Example: isometric view of cube aligned with axes glMatrixMode(GL_MODELVIEW): glLoadIdentity(); //gluLookAt(ex, ey, ez, ax, ay, az, ux, uy, uz) gluLookAt(1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0., 1.0. 0.0);
Angel Fly Around Example • “Fly around” – easy with gluLookAt • Angel example also allows mouse to rotate cube (at class site) or mesh • Honolulu data • … or objects • Angel, polygon mesh • “Fly around”: • Moves camera • Keeps “look at” same (origin) • Keeps “vUp” same • No tilting, etc. of camera • Keys change location of camera • static GLdouble viewer[]= {0.0, 0.0, 5.0}; • void display(void) • { • glClear(GL_COLOR_BUFFER_BIT | • GL_DEPTH_BUFFER_BIT); • glLoadIdentity(); • // gluLookAt (lookFromx,y,z, lookAtx,y,z, vUpx,y,z) • gluLookAt(viewer[0],viewer[1],viewer[2], • 0.0, 0.0, 0.0, • 0.0, 1.0, 0.0); • // draw scene • glFlush(); • glutSwapBuffers(); • }
Angel Fly Around Examplefunctions keys and main keys(unsigned char key, int x, int y) { // ‘x’, ‘X’, ‘y’, ‘Y’, ‘z’, and ‘Z’ keys // change global array viewer[3] // - used to store viewpoint // // Then, after key input call postredisplay // - signal redraw necessary, // and, hence, display called if(key == 'x') viewer[0]-= 1.0; if(key == 'X') viewer[0]+= 1.0; if(key == 'y') viewer[1]-= 1.0; if(key == 'Y') viewer[1]+= 1.0; if(key == 'z') viewer[2]-= 1.0; if(key == 'Z') viewer[2]+= 1.0; display(); } main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow(“fly"); glutReshapeFunc(myReshape); glutDisplayFunc(display); glutKeyboardFunc(keys); glEnable(GL_DEPTH_TEST); glutMainLoop(); }
Camera Fly-around • http://www.youtube.com/watch?v=kki1kbmJ8x8 • Is the camera moving, or is the globe rotating? • Can’t tell! • Recall, “illusion” of computer graphics • Either can be implemented • Just saw fly-around -- Movement of camera in world coordinates • And, have also seen rotation of objects • Change of object’s orientation in, again, world coordinates • A kind of “duality” of display • Will see much more • Transformations
Preview … • Next week will talk about the series of transformations that are the elements of the OpenGL pipeline • Following, is the “big picture” • Which we will see again • And again • And again • And again • And again
Recall, Polygon Mesh Representation 42, 64, 91 47, 69, 90 • Interactive computer graphics uses polygon representation of objects for efficiency • Wireframe shows only the polygons • Vertex position for every polygon • Shading of polygons used to add “realism” • Photorealisism, as well environment mapping bump mapping flat shading smooth shading
Polygon Mesh Representation 42, 64, 91 47, 69, 90 • Each polygon represented by a set of points • Operations on these vertices are at core of interactive cg techniques • And, it is the vertices upon which most of the processing in the pipeline is performed • Also, E.g., Gouraud, interpolated, shading simply takes illumination values at vertices as inputs • Vertex lists … more later
Recall, Coordinate Systems in ViewingTransformations of these coordinate systems at core of graphics pipeline! • Coordinate Systems in the Graphics Pipeline • OCS – object coordinate system • WCS – world coordinate system • VCS – viewing coordinate system • CCS – clipping coordinate system • NDCS - normalized device coordinate system • DCS – device coordinate system • And images are formed on the image plane
Viewing • Thinking about viewing …. • In what is displayed on view plane • Consider moving the viewpoint and consider moving the object (scene) • Object position and/or orientation changed – • “Are you or is it moving?!” • BTW, frustum • Transformations used to create image on screen when an object is viewed • Also, OGL viewing based on specification of transformation matrices • Specify a world • Possibly, transform the world • Specify a projection • Clip (exclude from image) part of world • Specify a viewport into the world
OpenGLThe big picture … in small print • ModelView Matrix • Transformation of elements for projection on view plane • Projection matrix • Clipping • Viewport • It is points / lines that are sent down the pipeline • Rasterization is done at latest possible time • For efficiency
Transformations and Coordinate Systems… to Eye/View Coordinate System • Coordinate Systems in the Graphics Pipeline • OCS – object coordinate system • WCS – world coordinate system • VCS – viewing coordinate system • CCS – clipping coordinate system • NDCS - normalized device coordinate system • DCS – device coordinate system • Series of “viewing transformations” • transforms a point (its coordinates) from world space to eye space • Set these transformation matrices as part of OpenGL programming • Each transformation operates on different spaces and coordinates • Model – View – Projection – Perspective Divide - Viewport
OpenGL Transformations • Viewing process has 2 parts: • Use model-view matrix to switch vertex reps from object frame in which objects defined to their representation in eye/camera frame – eye at origin • Allows use of canonical viewing procedures • Type of projection (parallel or perspective) and part of world to image (clipping or view volume) • Normalization lets clip against simple cube regardless of projection • Delay final projection until end • Important for hidden-surface removal to retain depth information as long as possible