Viewing and Camera Control in Opengl
Viewing and Camera Control in Opengl
Whenever we work in OpenGL, we have access to its API that denes functions for basic transformations. We use the following functions to create a transform matrix and push it onto the current transform stack1 : glScalef(x, y, z) glTranslatef(x, y, z) glRotatef(x, y, z, angle); - scale object by x,y,z scalars along the current axes - move an object by x,y,z along the current axes. - rotate an object by angle around vector [x,y,z]
Using techniques from linear algebra, any transform can be decomposed into a combination of these simple transforms. Luckily, we do not have to resort to always using only these transforms - OpenGL denes an interface to intuitively work with the cameras orientation and perspective, or we can dene arbitrary transforms and load this into OpenGL.
We think of viewing in OpenGL as specifying properties about a hypothetical camera inside our scene. To do this, we will be specifying transformations that must be applied to our world during the rendering process. OpenGL supports this by storing transformations in a user-controllable stack. OpenGL has 3 dierent transformation stacks which are applied at dierent times of the rendering pipeline. We are concerned with two of these stacks: the Modelview stack and the Projection stack.2 We will explore the relation between our hypothetical camera and these transformations stacks. Each of these OpenGL transformation stacks specify the following information about the camera: GL MODELVIEW - The position and orientation of the camera and the world. GL PROJECTION - How the camera sees the world
Projection transformation
The projection transform denes how a point (or vertex) is transformed from world space to the 2D plane of the screen (screen space). This is part of what we studied when we discussed perspective transforms. OpenGL gives you functions to dene an orthographic or a perspective projection relative to the camera: glFustrum (left, right, bottom, top, near, far); gluPerspective(fovy, aspect, near, far); glOrtho(left, right, bottom, top, near, far); The following sketches should explain each of these functions:
Much 1 See
of this handout was adapted from Stu Pomerantz at the Pittsburgh Supercomputing Center. http://www.psc.edu/ section 2 2 There is also a Texture transform stack.
(a) glOrtho given left, right, bottom, top, near and far
(b) glFustrum give left, right, bottom, top, near and far
Modelview transformation
The Modelview transformation species both the position and orientation of the camera and the objects in the world. Why dont we have a model transform and a view transform? Because translating the world and translating the camera has exactly the same eect - thus we combine it into one Modelview transform. We tend to specify a viewing transform to place the camera, followed by transformations on the objects. Note that placing the camera using the viewing transform is exactly the same as applying the rotations and translations to place your camera using OpenGLs transformation functions. Define the viewing transform: void gluLookAt(eyeX,eyeY,eyeZ, centerX,centerY,centerZ, upX,upY,upZ) PARAMETERS eyeX,eyeY, eyeZ - Specifies the position of the eye point. centerX, centerY, centerZ - Specifies the position of the reference point. upX, upY, upZ - Specifies the direction of the up vector. Since the default transformation on any stack is the identity, this translates into a default camera with eye at (0, 0, 1) and center at (0, 0, 0) with an up direction of (0, 1, 0) along the y axis. In other words, gluLookAt(0,0,1,0,0,0,0,1,0) is the default view.
Todays graphics programs demand complex scenes with many objects, each rendered under its own transform. To facilitate this, as mentioned, OpenGL stores transformations in a stack. We control this stack through the following four methods: glMatrixMode(STACK ) - Selects the stack to aect. glLoadIdentity() - Resets the selected stack to the Identity transform. glPushMatrix() - Duplicates the top transform of the current stack. glPopMatrix() - Deletes the top transform of the current stack. We tend to initialize the ModelView stack to the identity, then apply our viewing transform (positioning the camera). Once this has occurred, for each object we want to render, we push a duplicate matrix onto the modelview stack, apply the transformations for the given object, draw the object, and pop the top matrix o the stack, thereby returning to the original view transform, ready to draw the next object.
If you specify your viewport and projection on initialization, you need to specify only your modelview transformation every time you render. OpenGLs stack-based approach then allows you to apply dierent transformations to each object. Thus, a possible Reshape callback function (also called on initialization) would look like the following: 3
Listing 1: Specifying the view void r e s h a p e ( int w, int h ) { // S e t t h e v i e w p o r t g l V i e w p o r t ( 0 , 0 ,w, h ) ; // S e t t h e p r o j e c t i o n t r a n s f o r m glMatrixMode (GL PROJECTION ) ; glLoadIdentity ( ) ; gluPerspective (45 ,1 ,5 ,100); } And a possible display function would look like the following: Listing 2: Rendering the scene f l o a t zoom , rotx , roty , tx , ty ; void d i s p l a y ( ) { g l C l e a r (GL COLOR BUFFER BIT | GL DEPTH BUFFER BIT ) ; // C l e a r ZB u f f e r // S e t t h e camera o r i e n t a t i o n : glMatrixMode (GL MODELVIEW) ; glLoadIdentity ( ) ; gluLookAt (0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 ) ; // R o t a t e and zoom t h e camera . This o r d e r g i v e s you mayal i k e c o n t r o l . g l T r a n s l a t e f (0 ,0 , zoom ) ; g l T r a n s l a t e f ( tx , ty , 0 ) ; g l R o t a t e f ( rotx , 1 , 0 , 0 ) ; g l R o t a t e f ( roty , 0 , 1 , 0 ) ; //FOR EACH OBJECT: glPushMatrix ( ) ; // Save t h e c u r r e n t v i e w m a t r i x . //HERE YOU CAN APPLY TRANSFORMATIONS TO EACH OBJECT BEFORE DRAWING IT //AND HERE YOU CAN DRAW IT ! glPopMatrix ( ) ; // R e s t o r e t o t h e v i e w m a t r i x glutSwapBuffers ( ) ; } For more information, consult either the Red Book, or the following sites: http://www.newcyber3d.com/selfstudy/tips/camera_analogy.htm http://www.robthebloke.org/opengl_programming.html#4 http://www.morrowland.com/apron/tut_gl.php
We highly recommend a copy of the so-called Blue3 and Red4 books for all graphics programmers. OpenGL is your friend. The cake is not a lie. NOTE: We suggest googling for the MAN pages of the functions we mention here!
7.1
Drawing Objects
You already know how to do this, but as a brief overview, objects are drawn by issuing glVertex() and glNormal() calls between glBegin(TYPE) and glEnd() commands. For the normals to matter, you also want to enable lighting and dene both material properties and lights, as covered in section 7.6. If you scale your objects, you need to have OpenGL renormalize your normals. You can enable this behavior with glEnable(GL_NORMALIZE).
7.2
Shading
OpenGL supports at and smooth (gouraud) shading as part of the hardware pipeline. This is toggled between with glShadeModel(GL_SMOOTH) and glShadeModel(GL_FLAT). The shading model uses colors at vertices which are either computed by the lighting model or, if lighting is not enabled, specied directly with glColor().
7.3
Wireframes
OpenGL can draw polygons in one of several modes, controlled through the glPolygonMode() function. You can switch between lled polygons and outlined polygons with glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) and glPolygonMode(GL_FRONT_AND_BACK, GL_LINE). Keep in mind that lighting and shading occurs in the same fashion for both of these, so it is often wise to disable lighting when drawing in GL_LINE mode, which you can do with glDisable(GL_LIGHTING).
7.4
OpenGL implements a Z-Buering algorithm to calculate the visibility of polygons. The Z-Buer is initialized by passing the GLUT_DEPTH ag to glutInitDisplayMode(), and enabled with glEnable(GL_DEPTH_TEST). Once the Z-Buer has been enabled, you can clear it by calling glClear(GL_DEPTH_BUFFER_BIT), something you normally want to do at the start of each frames rendering cycle. If you want greater control of how the depth test is performed, you can use glDepthFunc(FUNC). By default, a pixel is compared with the current Z-Buer value using GL_LESS. 7.4.1 Hidden Line Removal
We can use a trick to draw wireframes but remove hidden lines by employing a two-pass scheme. Render the scene with lled polygons without populating your color buer (call glColorMask() with all GL_FALSE) to calculate the depth buer values. Then rerender the scene with wireframed polygons without clearing the depth buer, but switching the depth test from GL_LESS to GL_LEQUAL.
7.5
Display Lists
Display lists provide an easy way to speed things up, by letting OpenGL remember a list of rendering instructions. It can optimize the instructions for rendering, and store them so you dont constantly need to send them to the card yourself. See http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=12
3 http://www.opengl.org/documentation/blue 4 http://www.opengl.org/documentation/red
book/ book/
7.6
Lighting Example
Listing 3: Lighting Example
void i n i t L i g h t s ( ) { g l E n a b l e (GL LIGHTING ) ; g l L i g h t M o d e l i (GL LIGHT MODEL TWO SIDE, GL TRUE ) ; GLfloat global ambient [ ] = { . 1 f , . 1 f , . 1 f } ; g l L i g h t M o d e l f v (GL LIGHT MODEL AMBIENT, g l o b a l a m b i e n t ) ; // d e f i n e and e n a b l e l i g h t 0 . You have 8 l i g h t s i n t o t a l . G L f l o a t ambient [ ] = { . 1 f , . 1 f , . 1 f } ; GLfloat d i f f u s e [ ] = { . 6 f , . 5 f , . 5 f } ; GLfloat s p e c u l a r [ ] = { 0 . 0 , 0 . 0 , 0 . 0 , 1 . 0 } ; G L f l o a t pos [ ] = { 3, 0 , 2 , 1 } ; g l L i g h t f v (GL LIGHT0 , GL AMBIENT, ambient ) ; g l L i g h t f v (GL LIGHT0 , GL DIFFUSE , d i f f u s e ) ; g l L i g h t f v (GL LIGHT0 , GL SPECULAR, s p e c u l a r ) ; g l L i g h t f v (GL LIGHT0 , GL POSITION , pos ) ; g l E n a b l e (GL LIGHT0 ) ; // d e f i n e m a t e r i a l p r o p e r t i e s : //You p r o b a b l y don t want t o use t h e e m i s s i o n term f o r t h i s c l a s s . GLfloat mat specular [ ] = { 1 . 0 , 0 . 0 , 0 . 0 , 1 . 0 } ; GLfloat m a t d i f f u s e [ ] = { 0 . 0 , 1 . 0 , 0 . 0 , 1 . 0 } ; G L f l o a t mat ambient [ ] = { 0 . 0 , 0 . 1 , 0 . 1 , 1 . 0 } ; GLfloat mat emission [ ] = { 0 . 2 , 0 . 0 , 0 . 0 , 1 . 0 } ; GLfloat m a t s h i n i n e s s ={10.0}; g l M a t e r i a l f v (GL FRONT AND BACK, GL SPECULAR, m a t s p e c u l a r ) ; g l M a t e r i a l f v (GL FRONT AND BACK, GL AMBIENT, mat ambient ) ; g l M a t e r i a l f v (GL FRONT AND BACK, GL DIFFUSE , m a t d i f f u s e ) ; g l M a t e r i a l f v (GL FRONT AND BACK, GL EMISSION , m a t e m i s s i o n ) ; g l M a t e r i a l f (GL FRONT AND BACK, GL SHININESS , m a t s h i n i n e s s ) ; glShadeModel (GL SMOOTH) ; // GL FLAT g i v e s f l a t s h a d i n g // A l l o w s you t o s c a l e o b j e c t s a t t h e c o s t o f some performance . g l E n a b l e (GL NORMALIZE ) ; } int main ( int argc , char argv ) { g l u t I n i t (& argc , argv ) ; g l u t I n i t D i s p l a y M o d e (GLUT DOUBLE|GLUT RGBA|GLUT DEPTH ) ; glutInitWindowSize (640 ,480); glutCreateWindow ( L i g h t i n g Test ) ; ... initLights (); g l E n a b l e (GL DEPTH TEST ) ; glutMainLoop ( ) ; }