opengl fundamentals.doc

  • 2,349 views
Uploaded on

 

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
2,349
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
122
Comments
1
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com OpenGL Fundamentals OpenGL Fundamentals..................................................................................................1 Introduction................................................................................................................1 2D-Drawing with OpenGL.........................................................................................2 3D-Drawing and Animation with OpenGL..............................................................11 Text Output with OpenGL.......................................................................................13 Fullscreen versus Windowed Mode.........................................................................14 Lighting and Materials.............................................................................................14 Distant Light.........................................................................................................16 Spot Light.............................................................................................................17 Textures....................................................................................................................18 Transparent Surfaces................................................................................................21 Further Reading........................................................................................................22 Introduction If you want to use advanced graphics output, you have to decide, whether you prefer OpenGL or DirectX. Each of them has its specific advantages and disadvantages. While OpenGL exists on all major platforms (Linux, Windows, Macintosh,…) DirectX is limited to Windows, but on the other hand DirectX supports multimedia, which OpenGL is just starting to support (the so called OpenML). Due to the platform independence of OpenGL, I will use OpenGL to show how advanced graphics can be implemented. OpenGL is a software interface that allows you to access the graphics hardware without taking care of the hardware details or which graphics adapter is in the system. To become more independent of the platform and programming language, OpenGL provides it’s own data types. In the following list you can find a summary of the most important data types and their equivalent in standard C: OpenGL Data Type Internal Representation Equivalent C Type C Literal Suffix GLbyte 8-bit integer signed char b GLshort 16-bit integer short s GLint, GLsizei 32-bit integer long l GLfloat 32-bit floating point float f GLdouble 64-bit floating point double d GLubyte 8-bit unsigned integer unsigned char ub GLushort 16-bit unsigned integer unsigned short us GLuint, GLenum, 32-bit unsigned integer unsigned long ui GLbitfield Another advantage of OpenGL is the logical structure of its commands. Lets take the glcolor3f command as an example (this command is used to set the color of vertices). copyright 2003 by Heinz Seyringer 1
  • 2. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com a c tu a l ty p e o f th e com m and a rg u m e n ts { g lc o lo r3 f { lib ra ry num ber of p re fix a rg u m e n ts The first two letters are the library prefix. “gl” means, that the command is part of the gl library, which is used most (nevertheless there are also other libraries like the glut, which are very usefull). “color” is the actual command, “3” gives the number of arguments and “f” tells us, that the arguments are of type “float”. If you select a color, all drawing routines will use this color until you select another color with the glcolor3f command (i.e. OpenGL works as a state machine). In the following we will discuss a basic OpenGL application, that shows how to use the most important commands. Of course this discussion can’t teach you everything about OpenGL, but hopefully it can give you a starting point. 2D-Drawing with OpenGL For this example we will start with windows and use the Borland C++ Builder as programming language. The advantage of this combination is, that windows is for historical reasons probably the most familiar operating system to many readers and Borland Builder has the advantage, that with Kylix, there is a very similar programming environment under Linux. If you don’t have included the gl.h library n the path of your compiler, you should add the corresponding #include. By the way, you shouldn’t use the gl.h library that shipped with your compiler and is typically in the default path of the compiler, because this is usually pretty old. For drawing the scene you need some sort of update loop. You can solve this problem by either using a thread or the OnIdle function. For our applications I prefer the OnIdle function because threads have some overhead (if you think about programming a game things might become different ;-) In the TFormMain procedure we define our update loop function (I call it IdleLoop) as idle function. //--------------------------------------------------------------------------- TFormMain::TFormMain(TComponent* Owner) : TForm(Owner) { Application->OnIdle = IdleLoop; // Define idle function } In our IdleLoop we first request more time from the system (done=false), so that we have enough time to do all the rendering. Then we render the scene and after finishing we put the rendered scene on the screen (SwapBuffers). The reason for drawing into a buffer and then putting the finished picture on the screen is that the user shouldn't see the entire drawing process but only the finished results. //--------------------------------------------------------------------------- void TFormMain::IdleLoop(TObject*, bool& done) copyright 2003 by Heinz Seyringer 2
  • 3. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com { done = false; // request more idle time from the system RenderGLScene(); SwapBuffers(hdc); } In the FormCreate procedure we have to set up the actual OpenGL window. First we need a rendering context, so that OpenGL knows where it can draw. The rendering context can be obtained from the device context by using the wglCreateContext function. Finally we enable face culling and depth test and set the background color to black. The first three values of glClearColor should be between 0.0 and 1.0 and determine the color, that is used to clear the screen, the fourth value represents the alpha channel and should be 1.0 (which means no transparency at all). GlClear does the actual clearing and also initializes the depth buffer. //--------------------------------------------------------------------------- void TFormMain::FormCreate(TObject *Sender) { hdc = GetDC(Handle); // get device context from main window SetPixelFormatDescriptor(); // initialize pixel format hrc = wglCreateContext(hdc); // use device context to create // a rendering context if(hrc == NULL) ShowMessage("Creating Rendering Context failed."); wglMakeCurrent(hdc,hrc); // tell windows to use hdc and rdc if(wglMakeCurrent(hdc, hrc) == false) // show message if MakeCurrent fails ShowMessage("MakeCurrent of rendering context failed."); w = ClientWidth; // width of main windows client area h = ClientHeight; // height of main windows client area glEnable(GL_DEPTH_TEST); // use depth buffering glCullFace(GL_BACK); // select backside of polygons for culling glEnable(GL_CULL_FACE); // cull backside of polygons glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // clear background to black glClearDepth(100.0); // set depth buffer to the most distant value glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } To understand backface culling and the depth buffer a little bit better, we have to take a look at how polygons are defined. In the most primitive case, the polygon is a simple triangle which is determined by it’s three points: glBegin(GL_TRIANGLES); // draw triangle glVertex2f( 0.0f, 0.0f); // 1. point of the triangle glVertex2f( 1.0f, 0.0f); // 2. point of the triangle glVertex2f( 1.0f, 1.0f); // 3. point of the triangle glEnd(); However there is some ambiguity, since we could list the points in clockwise or counterclockwise order. On the other hand, if you list the point counterclockwise and then rotate the triangle by 180°, it looks as if they were listed clockwise. Therefore the order in which you list the points can be used to determine whether we see the front side (counterclockwise listed points) or backside (clockwise listed points) of the triangle. copyright 2003 by Heinz Seyringer 3
  • 4. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com 3 2 1 1 a) 2 b) 3 Fig. 1a) when the points of the triangle are listed in a counterclockwise orientation, the surface normal is directed towards us and we see the frontside of the triangle, b) if the points are listed clockwise, the surface normal is directed away from us and we see the backside of the polygon. In many cases polygons form complex objects, where only the front side of the individual polygons (= object surface) can be seen. In these cases it is not necessary to take care of the backside, and therefore we can speed up the drawing by telling OpenGL to ignore the backside of the Polygon. This is done by the glCullFace(GL_BACK) command and then activated by the glEnable(GL_CULL_FACE) command. Although usually it doesn’t make much sense, you could also ignore the front side of the polygons by using glCullFace(GL_FRONT). The idea behind enabling the depth test is almost the same: we tell OpenGL not to draw polygons, which are behind an other polygon and therefore can’t be seen. To do so, OpenGL uses the so called depth buffer, which works very similar to the color buffer, but while the color buffer has stored for each pixel you see on the screen the appropriate color, the depth buffer stores for each pixel the distance of the object you see on the screen. When OpenGL has to draw a new object, it checks where it should be drawn, whether there is already an object drawn and if yes, the new object is only drawn, if it is nearer. In the later case, the distance of the new object is stored in the depth buffer. copyright 2003 by Heinz Seyringer 4
  • 5. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com tria n g le c irc le sq u a re tria n g le c irc le sq u a re d e p th d is tr ib u tio n d e p th d is tr ib u tio n a) sc re e n o u tp u t b) sc re e n o u tp u t Fig. 2a) if depth testing is enabled, the shapes are drawn according to their actual position, b) if depth testing is disabled, the shapes are drawn one by one and last shape seems to be the “nearest”. To get a better understanding, let’s have a look at the example shown in Fig. 2. When we draw first a rectangle, then a triangle and finally a circle on the screen, where the square is farest away and the triangle is the nearest shape, we would expect, that the final image should look like Fig. 2a) – the shape in front covers the shapes behind. This is indeed the case, if we have enabled the depth testing. However in case that we have disabled it, the result might be a bit surprising: the circle is covering a part of the triangle. Although this result might be a bit unexpected on the first glance, it is actually straight forward: disabling the depth testing means, that all depth information (i.e. the z-coordinate) is ignored and therefore the shapes are drawn one on top of the other and therefore the last shape seems to be the “nearest”, while the first shape seems to be the “farest”. In the function SetPixelFormatDescriptor we tell OpenGL how we want to draw things. //--------------------------------------------------------------------------- void TFormMain::SetPixelFormatDescriptor() { PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of the structure 1, // structure version PFD_DRAW_TO_WINDOW | // draw directly to window // (not to a bitmap file) PFD_SUPPORT_OPENGL | // allow DC to support opengl calls PFD_DOUBLEBUFFER, // use double buffer PFD_TYPE_RGBA, // use RGBA color mode (A = alpha) 24, // use 24 bit color 0,0,0,0,0,0,0,0,0,0,0,0,0, // use default bitplanes 32, // use 32 bit z buffer 0,0, // not (yet) used PFD_MAIN_PLANE, // draw to main plane copyright 2003 by Heinz Seyringer 5
  • 6. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com 0,0,0, // different mask bits }; PixelFormat = ChoosePixelFormat(hdc, &pfd); // choose and set the SetPixelFormat(hdc, PixelFormat, &pfd); // appropriate pixelformat } Now that we have specified the pixelformat we want to use, we can take a look on how we could do the actual output. Basically we have 2 options: orthogonal or perspective view. a) b) Fig. 3 a) orthographic projection, b) perspective projection. Orthographic Projection is needed for CAD-applications, diagrams and similar problems, while perspective projection is more suitable for displaying landscapes and bigger areas, where the effect is more obvious. Since for many of our applications the orthographic projection is more suitable, we will first take a look on how we can define the viewport in this case. The viewport maps the drawing coordinates to window coordinates and therefore defines the region of the scene, that can be seen. copyright 2003 by Heinz Seyringer 6
  • 7. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com ( -ra n g e , r a n g e ,- ra n g e ) ( r a n g e , r a n g e ,- r a n g e ) (-r a n g e , r a n g e ,r a n g e ) y ( r a n g e , ra n g e ,r a n g e ) -z -x x z ( ra n g e , -r a n g e ,- ra n g e ) -y (-ra n g e , -ra n g e ,ra n g e ) ( ra n g e , -r a n g e ,r a n g e ) ( 0 ,0 ) ( W id th ,0 ) x y ( 0 ,H e ig th ) (W id th , H e ig th ) Fig. 4: The viewport maps the drawing coordinates to window coordinates and therefore defines the region of the scene, that can be seen. The OpenGL command used for defining the orthographic projection is void glOrtho(GLdouble xmin, GLdouble xmax, GLdouble ymin, GLdouble ymax, GLdouble zmin, GLdouble zmax) where we define the minimal and maximal value on each of the three scene coordinate axes. The Area of the Screen-Coordinates is defined via the Viewport: void glViewport(GLdouble xmin, GLdouble ymin, GLdouble xmax, GLdouble ymax) If you want to use a perspective projection, you have to use instead of the glOrtho the following command: void gluPerspective(GLdouble fov, GLdouble aspect, GLdouble near, GLdouble far) The parameters of this command need some additional explanation: the fov gives the field-of-view angle in vertical direction, aspect is the ratio of height to width and last but not least near and far give us the distances to the near and far clipping plane. copyright 2003 by Heinz Seyringer 7
  • 8. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com w id th f a r c lip p in g p la n e h e ig h t n e a r c lip p in g p la n e fo v Fig. 5:To use perspective projection, we have to specify the field of view in vertical direction (fov), the aspect ratio and the near and far clipping plane. If the user resizes the window, we have to adjust the viewport and correct the aspect ratio. It is important to take care that h is not equal 0 because otherwise we get some problems when we calculate the new aspect ratio (division by zero). In the variable Range we define the range on the coordinate axes. This parameter can also be used to zoom in and out (zoom in by making Range smaller and zoom out by making Range bigger). //--------------------------------------------------------------------------- void TFormMain::FormResize(TObject *Sender) { GLfloat Range = 100.0; // numerical range on the 3 axes w = ClientWidth; // check new client width of the main window h = ClientHeight; // check new client height of the main window if(h == 0) // prevent a division by zero, by making sure that h = 1; // windows height is at least 1 glViewport(0, 0, w, h); // reset viewport glMatrixMode(GL_PROJECTION); // add perspective to scene glLoadIdentity(); // restore matrix to original state if (w <= h) glOrtho (-Range, Range, -Range*h/w, Range*h/w, -Range, Range); else glOrtho (-Range*w/h, Range*w/h, -Range, Range, -Range, Range); glMatrixMode(GL_MODELVIEW); } After finishing all this preparatory work, we can finally take a look on how we can draw. The entire drawing is done in the RenderGLScene procedure, which con sists only of three commands: first we clear the color and depth buffer with the glClear command, then we call the actuall drawing procedure DrawObjects() and finally we tell OpenGL with the glFlush() command to put everything (all the graphic commands we used so far) on the screen. //--------------------------------------------------------------------------- void TFormMain::RenderGLScene() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); copyright 2003 by Heinz Seyringer 8
  • 9. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com DrawObjects(); glFlush(); } In the DrawObjects function we now can put our actual drawing commands. As an example we will draw a rectangle and a triangle. However instead of using the glQuads command, which usually would be used for a rectangle, we will use the glTriangle_Strip. The reason is, that for most practical purposes it is much easier to use triangles instead of quads since three points always lie in the same plane, while with four points this is – in general – pretty unlikely and may cause a lot of problems. As an additional bonus the hardware architecture of many graphic adapters is optimized to draw triangle based structures much faster than quads. ( 0 .0 ,1 0 0 .0 ) ( 1 0 0 .0 ,1 0 0 .0 ) g reen b lu e ( 1 0 0 .0 ,1 0 0 .0 ) ( 0 .0 ,0 .0 ) ( 1 0 0 .0 ,0 .0 ) b lu e w h ite red ( 0 .0 ,0 .0 ) ( 1 0 0 .0 ,0 .0 ) red g reen Fig. 6:Coordinates and colors of the vertices to be drawn. To draw the objects shown in the figure above we will use the following code: //--------------------------------------------------------------------------- void TFormMain::DrawObjects() { // rendering functions glLoadIdentity(); // initialize drawing coordinates glTranslatef(-80.0,-20.0,0.0); // move 1.quad to upper left sector glBegin(GL_TRIANGLE_STRIP); // draw quad from 2 triangles glColor3f(1.0,1.0,1.0); // set vertex color to white glVertex2f( 0.0, 0.0); glColor3f(1.0,0.0,0.0); // set vertex color to red glVertex2f(100.0, 0.0); glColor3f(0.0,1.0,0.0); // set vertex color to green glVertex2f( 0.0,100.0); glColor3f(0.0,0.0,1.0); // set vertex color to blue glVertex2f(100.0,100.0); glEnd(); // end of triangle strip glTranslatef(50.0,-70.0,0.0); // move to lower left sector glBegin(GL_TRIANGLES); glColor3f(1.0,0.0,0.0); // set vertex color to red glVertex2f( 0.0, 0.0); glColor3f(0.0,1.0,0.0); // set vertex color to green glVertex2f(100.0, 0.0); glColor3f(0.0,0.0,1.0); // set vertex color to blue glVertex2f(100.0,100.0); glEnd(); // end of triangle } First we initialize the transformation matrix by loading the identity matrix with glLoadIdentity. This is needed to make sure, that we start at the coordinate origin. The glTranslatef command is actually not needed, we could simply change the coordinates used in the Vertex commands, but sometimes it is pretty useful to simply move an copyright 2003 by Heinz Seyringer 9
  • 10. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com entire structure. With the first glTranslatef command we move the triangle 80 units to the left and 20 units down (remember the range is –100.0 to +100.0). With the glBegin(GL_TRIANGLE_STRIP) we tell OpenGL that the next vertices should be used to create a triangle strip (which is also very handy to draw all kinds of surfaces). The glColor3f command sets the color of the vertices (the parameters are the red, green and blue component of the color with a range from 0.0 to 1.0). The next glTranslatef command moves the drawing cursor 50 units to the right and 70 units down from the last drawing position – not from the coordinate origin! If the movement should be relative to the coordinate origin, we would have to call the glLoadIdentity() command before. If the Form has to be repainted, we have to redraw your objects and therefore we use the same commands as in the glRenderScene() (of course we could also call the glRenderScene() directly). //--------------------------------------------------------------------------- void TFormMain::FormPaint(TObject *Sender) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); DrawObjects(); glFlush(); } Finally before the program closes, we have to free the memory and delete the rendering context. //--------------------------------------------------------------------------- void TFormMain::FormDestroy(TObject *Sender) { wglMakeCurrent(NULL, NULL); wglDeleteContext(hrc); } Now we have everything in place to draw twodimensional shapes and the output of the program should look like this: Fig. 7: Screenshot of the 2D version of the rendering program. copyright 2003 by Heinz Seyringer 10
  • 11. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com 3D-Drawing and Animation with OpenGL Extending the 2D-drawing program to 3D-drawing is pretty simple: we just have to add the z-coordinate. As an example we will implement a rotating pyramid. y ( 0 ,3 0 ,0 ) red x z ( - 5 0 ,- 5 0 ,- 5 0 ) ( 5 0 ,- 5 0 ,- 5 0 ) w h ite b lu e ( - 5 0 ,- 5 0 ,5 0 ) ( 5 0 ,- 5 0 ,5 0 ) g re en y e llo w Fig. 8: Coordinates and colors of the rotating pyramid. In principle we could draw 4 triangles and a rectangle as bottom, but OpenGL offers much more suitable primitives. If we take a look at the different primitives, we see, that the GL_TRIANGLE_FAN is much more suitable. P 0 P P o in ts P 2 4 P 1 P 3 G L _ P O IN T S P 2 P P 1 P 4 P 1 2 P L in e s 1 P 2 P 7 P P 6 3 P 4 P P P P 0 P 3 0 3 5 P0 G L _ L IN E S G L _ L IN E _ S T R IP G L _ L IN E _ L O O P P 0 P 3 P 5 P 0 T ria n g le s P 2 P P 1 6 P 7 P P P P P 2 5 1 2 4 P 6 P P P P 4 0 1 3 P 8 G L _ T R IA N G L E S G L _ T R IA N G L E _ F A N G L _ T R IA N G L E _ S T R IP P R e c ta n g le s 6 P 0 P 2 P 4 P 3 P 2 P 7 P 0 P 1 P 1 P3 P5 G L_Q U A D S G L _ Q U A D _ S T R IP P 5 P 6 P 4 P o ly g o n P 0 P 1 P 3 P2 G L _PO LY G O N Fig. 9: Basic OpenGL primitives. The numbering of the points shows, in which order the vertices of the primitives must be specified. copyright 2003 by Heinz Seyringer 11
  • 12. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com When we use the GL_TRIANGLE_FAN for the pyramid, it is important to specify the first point again as last point to draw the last triangle and therefore close the triangle fan. Therefore the code for drawing the pyramid would look like this: glBegin(GL_TRIANGLE_FAN); // draw triangle glColor3f(1.0f,0.0f,0.0f); // set color to red glVertex3f( 0.0f, 30.0f, 0.0f); glColor3f(0.0f,1.0f,0.0f); // set color to green glVertex3f(-50.0f, -50.0f, 50.0f); glColor3f(1.0f,1.0f,0.0f); // set color to yellow glVertex3f( 50.0f, -50.0f, 50.0f); glColor3f(0.0f,0.0f,1.0f); // set color to blue glVertex3f( 50.0f, -50.0f, -50.0f); glColor3f(1.0f,1.0f,1.0f); // set color to white glVertex3f( -50.0f, -50.0f, -50.0f); glColor3f(0.0f,1.0f,0.0f); // set color to green glVertex3f(-50.0f, -50.0f, 50.0f); glEnd(); glBegin(GL_QUADS); // draw square glColor3f(0.0f,1.0f,0.0f); // set color to green glVertex3f(-50.0f, -50.0f, 50.0f); glColor3f(1.0f,1.0f,1.0f); // set color to white glVertex3f( -50.0f, -50.0f, -50.0f); glColor3f(0.0f,0.0f,1.0f); // set color to blue glVertex3f( 50.0f, -50.0f, -50.0f); glColor3f(1.0f,1.0f,0.0f); // set color to yellow glVertex3f( 50.0f, -50.0f, 50.0f); glEnd(); To animate the pyramid, we have to do almost nothing: we only use the glRotatef command and change the angle. This command has four parameters, where the first parameter gives the rotation angle, while the next three parameters specify the x-, y- and z-component of the rotation vector. If we want to realize more complex movements, we can combine multiple glRotatef and glTranslatef commands. If we want to rotate the pyramid along the x- and y-axis, the code could look like this: alpha+=0.2; beta+=0.1; glLoadIdentity(); glRotatef(alpha,0.0f,1.0f,0.0f); // rotate around y-axis glRotatef(beta,1.0f,0.0f,0.0f); // rotate around x-axis With these few changes, we now have a rotating pyramid. Fig. 10: Animation of a three dimensional pyramid . copyright 2003 by Heinz Seyringer 12
  • 13. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com Text Output with OpenGL There are a couple of options to print some text on the screen with OpenGL. The simplest (but unfortunately windows specific and therefore not suitable for Linux) is to use the windows support for truetype fonts by using the wglUseFontOutlines function. The basic idea behind using these fonts is to first define all relevant parameters of the font (like font size, name, style,…), create the font, make display lists for each character (these display lists are what OpenGL actually displays when we print text on the screen), delete the font (but keep the display lists) and finally we can print the text by using the glCallList OpenGL command. Lets take a closer look at the structure with the font parameters: LOGFONT logfont; logfont.lfHeight = -12; // setup font characteristics logfont.lfWidth = 0; logfont.lfEscapement = 0; logfont.lfOrientation = 0; logfont.lfWeight = FW_NORMAL; logfont.lfItalic = FALSE; logfont.lfUnderline = TRUE; logfont.lfStrikeOut = FALSE; logfont.lfCharSet = ANSI_CHARSET; logfont.lfOutPrecision = OUT_DEFAULT_PRECIS; logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS; logfont.lfQuality = PROOF_QUALITY; logfont.lfPitchAndFamily = DEFAULT_PITCH || FF_ROMAN; strcpy(logfont.lfFaceName,"Verdana"); The parameters in this structure are pretty self explanatory. Now that we have done all the preparatory work for creating the font, we can finally create it and use it for the creation of the display list: hFont = CreateFontIndirect(&logfont); SelectObject (hdc, hFont); nFontList = glGenLists(128); wglUseFontOutlines(hdc, 0, 128, nFontList, 0.0f, 0.5f, WGL_FONT_POLYGONS, agmf); DeleteObject(hFont); With the display list in place, we can use it to print the text to the screen: glListBase(nFontList); glCallLists (11, GL_UNSIGNED_BYTE, "Text Output"); In glCallLists we first define the number of display lists to be executed (one for each character we want to print on the screen), the next parameter is the type of values in the list, which is in our case GL_UNSIGNED_BYTE, and finally comes the text we want to put on the screen). Since this text is basically a normal OpenGL object, we can use all the OpenGL commands to manipulate it (for example set the color with glColor3f, set the Size with glScalef etc.). Now we have a rotating 3D object with text output. copyright 2003 by Heinz Seyringer 13
  • 14. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com Fig. 11: Three dimensional rotating pyramid with text output. Fullscreen versus Windowed Mode Sometimes it is useful to use fullscreen output instead of windowed mode. It is pretty simple to change to fullscreen: first we have to get rid of the window border which we do by setting the attribute BorderStyle of the main window to bsNone. In the next step we have to increase the size of the window, so that it fills the entire screen. The corresponding property is WindowState, which we set to ws_Maximized. a) b) c) Fig. 12: a) application in normal windowed mode, b) BorderStyle=bsNone, c) WindowState = wsMaximized When we need the windowed mode, we can simply set BorderStyle back to bsSizeAble and WindowState to wsNormal and we are back in windowed mode. Lighting and Materials The light we see all around us actually consists of a couple of different components (see Fig. 13): ambient light: this light scattered so often, that it comes from no particular direction but is uniformly distributed in the environment. If you specify no copyright 2003 by Heinz Seyringer 14
  • 15. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com lighting in OpenGL, the result is the same as if you define only ambient light. diffuse light: this light comes from a certain direction but is reflected homogenously from each point of the surface. Examples for diffuse light are all area lights like fluorescent lights for example. specular light: specular lights are the highlights, that can be seen on the surface of shinny materials. a) b) c) d) Fig. 13: Teapot with different illumination: a) only ambient light, b) only diffuse light, c) only specular light with specular reflections, d) ambient, diffuse and specular light with specular reflections together. Although the diffuse and ambient light is independent of the material, the specular light depends strongly on the material and especially on it’s shininess. For example rubber has a very low shininess, while plastic and ceramics have a high shininess (like in Fig. 13). In OpenGL we can specify the parameters of the light source with the command void glLightfv(GLenum Light, GLenum ParameterName, const GLfloat *ParameterValue) Depending on the variable type you want to use for the ParameterValue, you can alternatively use glLighti (..., …, GLint), glLightf (…, …, GLfloat) or glLightiv (…, …, const GLint). The first parameter of this function is a symbolic name, that identifies the light and is of the form GL_LIGHT0, GL_LIGHT1,… GL_LIGHT7. In some OpenGL implementations you can have more than eight lights, but it is always a good idea to stay on the safe side. Now that we know all the lighting parameters we have to take a look at the materials, since they can have a significant influence on the lighting. The material of an object determines how much of the different light components is reflected, its shininess and even whether the material itself emits light. To set these parameters, we can use the command void glMaterialfv(GLenum face, GLenum ParameterName, const GLfloat *ParameterValue) copyright 2003 by Heinz Seyringer 15
  • 16. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com The face parameter specifies to which side of the polygon the material will be applied. Allowed values are GL_FRONT, GL_BACK and GL_FRONT_AND_BACK. Like before the second parameter is the most interesting one and determines the actual material. Distant Light As a first example of lighting, let’s take a look on how to realize a distant light, as it would be used in environmental scenes to imitate sunlight. Although we won’t get shadows, we can imitate sunlight by reducing the ambient light component to a Minimum like 0.1. The dominant part has to be the diffuse light, so that we get bright areas facing the light source and dark areas in the shadow regions. If we take a tea pot as an object, it makes sense to use a high shininess of the material and a high specular light component. After we have defined the lighting and material we have to activate the defined light and the lighting itself. The entire lighting setup looks therefore as follows: void __fastcall TFormMain::SetupLighting() { GLfloat ambientLight[]={0.1,0.1,0.1,1.0}; // set ambient light parameters glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight); GLfloat diffuseLight[]={0.8,0.8,0.8,1.0}; // set diffuse light parameters glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight); GLfloat specularLight[]={0.5,0.5,0.5,1.0}; // set specular light parameters glLightfv(GL_LIGHT0,GL_SPECULAR,specularLight); GLfloat lightPos[]={0.0,30.0,60.0,0.0}; // set light position glLightfv(GL_LIGHT0,GL_POSITION,lightPos); GLfloat specularReflection[]={1.0,1.0,1.0,1.0}; // set specularity glMaterialfv(GL_FRONT, GL_SPECULAR, specularReflection); glMateriali(GL_FRONT,GL_SHININESS,128); glEnable(GL_LIGHT0); // activate light0 glEnable(GL_LIGHTING); // enable lighting glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight); // set light model glEnable(GL_COLOR_MATERIAL); // activate material glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE); glEnable(GL_NORMALIZE); // normalize normal vectors } A very nice add-on to OpenGL is the glut library which simplifies OpenGL programming significantly. One of the nice commands provided by this library creates a three-dimensional model of the so called Utah tea pot: void glutSolidTeapot(GLfloat TeapotSize); Put everything together the final result should look like the image in Fig. 14. copyright 2003 by Heinz Seyringer 16
  • 17. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com Fig. 14: Utah teapot with a distant light source and a shinny material. Spot Light An entirely different type of light are the spot lights. The biggest difference is, that this time the light source is a distinct point within the scene and the light is conically emitted in one direction. The definition of the spotlight is basically the same as the definition of a distant light. We only have to change the last number in the position from 0.0 to 1.0 to specify that we have a spot light instead of a distant light and the parameters regarding the light cone must be defined: // set light position GLfloat lightPos[]={0.0,0.0,60.0,1.0}; glLightfv(GL_LIGHT0,GL_POSITION,lightPos); // set spot light parameters GLfloat spotDir[]={0.0,1.0,-1.0}; // define spot direction glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir); glLightf(GL_LIGHT0, GL_SPOT_CUTOFF,40.0); // set cutoff angle glLightf(GL_LIGHT0, GL_SPOT_EXPONENT,7.0); // set focusing strength On the first glance this is everything we have to change compared to the above described example with the teapot and if we scene would consist only of the teapot it would work. However if we use for example a height relief the lighting won’t work. The reason for this difference is, that by using the glut command for the teapot, there were automatically surface normals generated. In case of a user defined height relief we have to generate the surface normals by ourselves. Fortunately this is pretty simple by using the vector product. The vector product of two vectors produces always a vector, that is perpendicular to both original vectors (see Fig. 15). C B A Fig. 15: The vector product of the vectors A and B produces a vector C which is normal to the two original vectors and positively oriented. copyright 2003 by Heinz Seyringer 17
  • 18. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com Mathematically the cross product can be calculated component wise:  A   B   A ⋅ B − Az ⋅ B y      x  x  y z  C = A × B =  Ay  ×  B y  =  Az ⋅ B x − Ax ⋅ B z  A  B  A ⋅B − A ⋅B   z  z  x y y x With this formula we can calculate the surface normals: glNormal3f(Cx, Cy, Cz); One problem we still would have is, that the length of the normals would be different for the different normals, what would cause unexpected lighting effects. To avoid this, we have to normalize all of the normal vectors to the same value – usually 1.0. This could either be done by dividing each component by the length of the normal vector or we could leave this task to OpenGL by using the following command: glEnable(GL_NORMALIZE); Putting everything together the spot light could look like in Fig. 16. Fig. 16: Rotating height relief illuminated by a spot light. Textures For the representation of a photorealistic scene one of the most important ingredience are images that are projected on the surfaces of the objects. The advantage of these images – called textures – is that they allow to create very sophisticated looking scenes with simple geometry since many details may be represented by the texture instead of the geometry. copyright 2003 by Heinz Seyringer 18
  • 19. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com The first step in using textures is that we create the bitmap graphics that we want to use as texture. In principle we could use any quadratic bitmap graphic that is not bigger than a certain maximum size that depends on the OpenGL version (for example in version 1.2 this would be 256x256 pixels). This graphic must then be loaded, which could be done with the following commands: bitmap = new Graphics::TBitmap; bitmap->LoadFromFile("TextureName.bmp"); Unfortunately this is not yet in a format that could be handled by the OpenGL commands. To use it as texture we first have to translate it into a more OpenGL compatible format which can be done by converting the bitmap in an array of byte values: GLubyte bits[texturesize][texturesize][4]; for(int i = 0; i < texturesize1; i++) { for(int j = 0; j < texturesize1; j++) { bits[i][j][0]= (GLbyte)GetRValue(bitmap->Canvas->Pixels[i][j]); bits[i][j][1]= (GLbyte)GetGValue(bitmap->Canvas->Pixels[i][j]); bits[i][j][2]= (GLbyte)GetBValue(bitmap->Canvas->Pixels[i][j]); bits[i][j][3]= (GLbyte)255; } } Each quadruple of values represent the red, green, blue and alpha value of a single pixel from the original bitmap. Now that we have the bitmap in a usable form, we must tell OpenGL how to apply the texture to the object. First we specify the alignment requirements for the start of each pixel row in memory (1 means byte- alignment, 2 means that the rows are aligned to even-numbered bytes, 4 means word alignment, and 8 means that the rows start on double-word boundaries). In principle we could now continue to set the texture parameters and then use the texture, but there is a small trick to improve the speed, if more then one texture is used. Normally loading textures slows down the program, however you can avoid this by using texture objects which work similar to display lists and where each object is identified by a single number. To create the texture object you use first the glGenTextures and glBindTexture command and before you terminate the program you have to delete the texture object with the glDeleteTextures command. Additionally we have to tell OpenGL what to do when the texture has to be magnified or minified (i.e. when the object is to big or small for the texture). Usually it is sufficient, if the corresponding filter functions GL_TEXTURE_MAG_FILTER respectively GL_TEXTURE_MIN_FILTER are set to GL_NEAREST, which returns the value of the texture element that is nearest to the center of the pixel being textured. Especially if the texture is smaller then the object we should specify, whether we would like the texture to be repeated (texture wrap set to GL_REPEAT) or clamped to the range [0,1] which is useful for preventing wrapping artifacts when mapping a single image onto an object. Now we only have to define the texture with the glTexImage2D command and then we can use the texture. The code for the texture and parameter definition should look like this: glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); copyright 2003 by Heinz Seyringer 19
  • 20. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texturesize1, texturesize1, 0, GL_RGBA, GL_UNSIGNED_BYTE, bits); If we use one of the predefined objects from the glut library this is everything we need to do: Fig. 17: The texture on the teapot which was taken from the glut library works, but on the rectangle in the background, the texture does not work. As we see in Fig. 17 the defined textures work for the objects from the glut library, but not for arbitrary objects. The reason is, that the predefined objects from the glut library have automatic texture coordinates, while the user created objects need manually defined texture coordinates. When we add them with the glTexCoord2f command to the rectangle code , the texture works also for the rectangle: glBegin(GL_QUADS); // draw square glTexCoord2f(0.0,0.0); glVertex3f(-50.0f,50.0f, -50.0f); glTexCoord2f(1.0,0.0); glVertex3f( -50.0f, -50.0f, -50.0f); glTexCoord2f(1.0,1.0); glVertex3f( 50.0f, -50.0f, -50.0f); glTexCoord2f(0.0,1.0); glVertex3f( 50.0f, 50.0f, -50.0f); glEnd(); With this modification, the texture on the rectangle is also shown: copyright 2003 by Heinz Seyringer 20
  • 21. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com Fig. 18:When we use the glTexCoord2f command for the manually defined objects, the textures work on all objects. The principles shown in this section demonstrate only the absolute basics of how to apply textures. I hope the techniques shown in this section give a starting point for the application of textures, but if you plan to use textures extensively, it could help to take a look at one of the books mentioned at the end of this section to learn more about the details like mipmapping, texture priorities or automatic texture coordinates. Transparent Surfaces The nice thing about transparent surfaces is that they are very easy to implement. We simply have to enable the blending, define the amount of transparency with the alpha value of the color (alpha=0.0 means totally transparent, 1.0 means no transparency at all) and specify which blending function we want to use: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glColor4f(0.0,0.0,1.0,transparency); There are a couple of different modes we could use for the blending function, but for simple transparencies, the most useful is usually the GL_ONE_MINUS_SRC_ALPHA. a) b) c) d) e) f) g) h) Fig. 19:a-d) use GL_ONE_MINUS_SRC_ALPHA with alpha values of a) 0.0, b) 0.25, c) 0.5 and d) 0.75; e-h) use GL_SRC_ALPHA with alpha values of e) 0.0, f) 0.25, g) 0.5 and h) 0.75. In Fig. 19 we see a comparison of GL_ONE_MINUS_SRC_ALPHA with GL_SRC_ALPHA. As we can see, the GL_ONE_MINUS_SRC_ALPHA gives us a behavior as we would expect it from a thin sheet of plastic where we decrease the transparency. With decreasing transparency the color of the plastic sheet becomes more dominant until we see only an opaque plastic sheet. The GL_SRC_ALPHA on the other hand looks pretty counterintuitive however for high alpha values it might come in handy since it allows us to use colors with higher intensity for highly transparent surfaces. When you want to use transparency in more complex scenes, it would be worth to check out the other blending functions and it might also be a good idea, to take a look at some of the books mentioned below to get in depth information on OpenGL. copyright 2003 by Heinz Seyringer 21
  • 22. WWW.NATUREWIZARD.COM mailto:heinz@naturewizard.com Further Reading [1] Mason Woo, Jackie Neider, Tom Davis, Dave Shreiner, OpenGL Architecture Review Board, “OpenGL Programming Guide”, Addison-Wesley Pub Co; 3rd edition (August 6, 1999), ASIN: 0201604582 [2] Richard S. Wright Jr., Michael R. Sweet, “OpenGL SuperBible, Second Edition (2nd Edition)”, Waite Group Press; 2nd Book and CD-ROM edition (December 16, 1999), ISBN: 1571691642 [3] Dave Shreiner, “OpenGL Reference Manual: The Official Reference Document to OpenGL, Version 1.2 (3rd Edition)”, Addison-Wesley Pub Co; 3rd edition (December 17, 1999), ISBN: 0201657651 [4] Dave Astle, Kevin Hawkins, Andre LaMothe, “OpenGL Game Programming w/CD”, Premier Press; Book and CD edition (May 1, 2002), ISBN: 0761533303 [5] Donald Hearn, M. Pauline Baker, “Computer Graphics with OpenGL (3rd Edition”, Prentice Hall; 3rd edition (August 12, 2003), ISBN: 0130153907 [6] Edward Angel, “Interactive Computer Graphics: A Top-Down Approach with OpenGL (3rd Edition)”, Pearson Addison Wesley; 3rd edition (July 16, 2002), ISBN: 0201773430 [7] Edward Angel, “OpenGL 1.2: A Primer”, Pearson Addison Wesley; 1st edition (June 19, 2001), ISBN: 0201741865 [8] Eric Lengyel, “The OpenGL Extensions Guide”, Charles River Media; 1st edition (July 2003), ISBN: 1584502940 [9] Samuel R. Buss, “3D Computer Graphics : A Mathematical Introduction with OpenGL”, Cambridge University Press; (June 2003), ISBN: 0521821037 [10] Chris Seddon, “Opengl Game Development”, Wordware Publishing; (March 2004), ISBN: 1556229895 [11] OpenGL Architecture Review Board, Mason Woo, Jackie Neider, Tom Davis, Dave Shreiner, “OpenGL Library”, Addison-Wesley Pub Co; 1st edition (January 15, 2002), ISBN: 020177576X [12] Francis S. Hill, “Computer Graphics Using Open GL (2nd Edition)”, Prentice Hall; 2nd edition (May 15, 2000), ISBN: 0023548568 Web Links:  http://www.opengl.org  http://msdn.microsoft.com/library/en-us/dnanchor/html/opengl.asp  http://www.naturewizard.com copyright 2003 by Heinz Seyringer 22