OpenGL:Tutorials:Tutorial Framework:Light and Fog
From GPWiki(Redirected from OpenGL Tutorial Framework:Light and Fog)
The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server. However, the GPWiki forums are still active! Come say hello. This tutorial deals with lighting and fog effects. Lighting adds realistic visual cues to your scene, but it can be costly on the frame rate. Effective use of fog can reduce the polycount by shortening the visible distance of our view without 'pop-up' destroying the realism of the scene.
[edit] Setting Fog ParametersOpenGL supports 'fogging' objects, which means it applies a fog color to stuff that is further away. If your geometry does not fill the whole screen it is advisable to use the same color for fog and for the background, so stuff actually fades away instead of becoming a weird silhouette in the distance. Fog is switched on like many other OpenGL options: glEnable(GL_FOG); Tweaking the parameters of the fog effect is performed using the glFog() function. Again, a multi-flavour function, check your documentation for all the possible options. Here we use glFogfv() to set the fog color using an array of float values: float FogCol[3]={0.8f,0.8f,0.8f}; // Define a nice light grey glFogfv(GL_FOG_COLOR,FogCol); // Set the fog color There are two ways in which OpenGL can apply fog - linear and exponential. For linear fog, you need to do something like the following: glFogi(GL_FOG_MODE, GL_LINEAR); // Note the 'i' after glFog - the GL_LINEAR constant is an integer. glFogf(GL_FOG_START, 10.f); glFogf(GL_FOG_END, 40.f); For the amount of fogginess that a point gets the distance from the camera to that point is used. These parameters create a fog starting at 10 units from the camera (so close objects retain their original color). At 40 units from the camera the fog completely colors objects, making them disappear if the background happens to be the same color as the fog. The default values for these distances are 0 and 1, which is much too close for most scenes. The exact formula for the amound of fog a point gets in linear fog mode is: F = (end - z) / (end - begin), where F is the amount of fog and z is the distance between the point and the camera. For exponential fog use: glFogi(GL_FOG_MODE, GL_EXP); // or glFogi(GL_FOG_MODE, GL_EXP2); This creates a fog that starts right at the camera but progresses in an exponential way. The formulae for these modes are F = e^(-D * z) for GL_EXP and F = e^((-D * z)^2) for GL_EXP2. (to be done: make pretty latex math out of that) The D in there is a constant - the fog density - that defaults to 1 and can be changed like this: glFogf(GL_FOG_DENSITY, 2.f); As mentioned above, the use of fog allows us to reduce the distance to the far clipping plane because faraway stuff will be invisible anyway. The following change to the screen setup brings the far plane a little closer and reduces the work our GPU has to do. gluPerspective(45.f, 800.f / 600.f, 1.f, 60.0f); // Fog allows us to shorten the far clipping plane [edit] Setting Up LightingSo, can you guess how we switch lighting on? glEnable(GL_LIGHTING); We also need to enable the light source: glEnable(GL_LIGHT0); The OpenGL standard specifies that 8 lights ( GL_LIGHT0 - GL_LIGHT7 ) should be supported. However, the actual number varies depending on your implemetation. Using anything more than 2 lights in your scene will kill the frame rate anyway. We set the parameters for the lights in much the same way that we did with the fog: float LightPos[4]={-5.0f,5.0f,10.0f,0.0f}; float Ambient[4]={0.5f,0.5f,0.5f,1.0f}; glLightfv(GL_LIGHT0,GL_POSITION,LightPos); glLightfv(GL_LIGHT0,GL_AMBIENT,Ambient); Here we use glLight() to position the light above, behind and to the left of the observer. We also set the ambient light level to prevent unlit areas from being too dark. The default settings for the light's diffuse and specular values are (1.0,1.0,1.0,1.0), this is fine for our application.
[edit] A Word About NormalsNow we have some light, we can shade our object's faces. However, this is not automatic, the normal vector of a polygon determines the 'facing' of the poly in relation to the light, which in turn affects the shade of the face. We must calculate the normal vectors and pass them to OpenGL using glNormal(). I've placed a (hastily written) function in render.h to calculate normals. // Calculate normal from vertices stVec CalcNormal(stVec v1, stVec v2, stVec v3) { double v1x,v1y,v1z,v2x,v2y,v2z; double nx,ny,nz; double vLen; stVec Result; // Calculate vectors v1x = v1.x - v2.x; v1y = v1.y - v2.y; v1z = v1.z - v2.z; v2x = v2.x - v3.x; v2y = v2.y - v3.y; v2z = v2.z - v3.z; // Get cross product of vectors nx = (v1y * v2z) - (v1z * v2y); ny = (v1z * v2x) - (v1x * v2z); nz = (v1x * v2y) - (v1y * v2x); // Normalise final vector vLen = sqrt( (nx * nx) + (ny * ny) + (nz * nz) ); Result.x = (float)(nx / vLen); Result.y = (float)(ny / vLen); Result.z = (float)(nz / vLen); return Result; } I'm not going to explain normal vectors here, Marijn's already covered them here. Just accept that if you pass in three points of your polygon in counter-clockwise order, this function will return the normal vector.
// Calc the face normal p1.x=-10.0f; p1.y=-2.0f; p1.z= 10.0f; p2.x= 10.0f; p2.y=-2.0f; p2.z= 10.0f; p3.x= 10.0f; p3.y=-2.0f; p3.z=-10.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); // Here's the normal call // Draw floor plane glBegin(GL_QUADS); glTexCoord2f( 0.0f,20.0f); glVertex3f(-50.0f,-3.0f, 50.0f); glTexCoord2f( 0.0f, 0.0f); glVertex3f( 50.0f,-3.0f, 50.0f); glTexCoord2f(20.0f, 0.0f); glVertex3f( 50.0f,-3.0f,-50.0f); glTexCoord2f(20.0f,20.0f); glVertex3f(-50.0f,-3.0f,-50.0f); glEnd(); BTW. I've gone back to quads on this demo as setting normals on the triangle strip was tricky. We only need to specify one normal per face to get flat shading. If we had used a curved surface like a sphere, the normals are specified on a per-vertex basis to achieve the smooth shade effect. [edit] Subdividing(not needed anymore)For lighting and fog to look good it was often a good idea to make sure your geometry does not contain any huge polygons. Light and fog are applied per vertex, and the situation at the corners/vertices may not be a very good representation of the situation in the middle of the polygon. For example, if you have a big wall with a light in the middle, the wall will appear too dark. The corners are far away from the light, and the lightness of the whole wall is interpolated between those corners. The solution to this is to divide big polygons like these into a bunch of smaller polygons. Often simply dividing them into a grid or similar works well. To decide the number of subdivisions you have to weight the improved quality of the render against the decrease in performance caused by the extra polygons. Because of the fact, that performance suffers due to increasing polygoncounts, it's better to use a more modern technology. Gouraud Shading offers a fast way to handle the problem described above, by interpolating colors across triangles. [ glShadeModel(GL_SMOOTH) ] A step into the future is Phong Shading. It interpolates a normalvector from the three given normals across the surface of a triangle for each pixel. You will need a pixelshader for it, like it is included in Rendermonkey and FX-Composer, if you want to create the shader yourself: http://wiki.truevision3d.com/hlsl_phong_sample [edit] Source CodeHere's a screenshot of the demo The source to Render.cpp, compile this demo using the OpenGL Tutorial Framework. #include "Framework.h" #include <math.h> #include "tga.h" // Vertex / Vector struct typedef struct { float x,y,z; }stVec; // Function declarations GLuint LoadTexture(char *TexName); stVec CalcNormal(stVec v1, stVec v2, stVec v3); // Here we go! void Render(void) { GLuint TexID,TexID2; // Handles to our textures float Rotate=0.0f; // Rotation value to be used to spin our polygon float zPos=-5.0f; // The distance to our cube stVec p1,p2,p3,Norm; // Vertices and vector variables // Fog parameters bool FogFlag=true; float FogCol[3]={0.8f,0.8f,0.8f}; // define a nice light grey // Light parameters float LightPos[4]={-5.0f,5.0f,10.0f,0.0f}; float Ambient[4]={0.5f,0.5f,0.5f,1.0f}; // Background color glClearColor(0.8f,0.8f,0.8f,1.0f); // Setup our screen glViewport(0,0,800,600); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f,800.0f/600.0f,1.0f,60.0f); // Fog allows us to shorten the far clipping plane glMatrixMode(GL_MODELVIEW); // Ensure correct display of polygons glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); // Turn On Fog glEnable(GL_FOG); glFogfv(GL_FOG_COLOR,FogCol); // Set the fog color glFogf(GL_FOG_DENSITY,0.1f); // Thin the fog out a little // Turn on Lighting glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0,GL_POSITION,LightPos); glLightfv(GL_LIGHT0,GL_AMBIENT,Ambient); // Load the texture TexID=LoadTexture("Logo.tga"); TexID2=LoadTexture("Check.tga"); // This loop will run until Esc is pressed while(RunLevel) { if(Keys[VK_ESCAPE]) // Esc Key RunLevel=0; if(Keys[VK_UP]) // Up Arrow { zPos-=0.01f; if(zPos < -50.0f) // Limit movement zPos= -50.0f; } if(Keys[VK_DOWN]) // Down Arrow { zPos+=0.01f; if(zPos > -3.0f) // Limit movement zPos= -3.0f; } if(Keys['F']) // Toggle Fog { FogFlag ^= true; Keys[70]=false; } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); // Reset current matrix (Modelview) // Fog On or Off if(FogFlag) glEnable(GL_FOG); else glDisable(GL_FOG); // Enable texturing and select floor texture glColor3f(1.0f,1.0f,1.0f); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,TexID2); // Calc the face normal p1.x=-10.0f; p1.y=-2.0f; p1.z= 10.0f; p2.x= 10.0f; p2.y=-2.0f; p2.z= 10.0f; p3.x= 10.0f; p3.y=-2.0f; p3.z=-10.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); // Draw floor plane, note the use of GL_REPEAT and tex coords outside the 0.0 - 1.0 range glBegin(GL_QUADS); glTexCoord2f( 0.0f,20.0f); glVertex3f(-50.0f,-3.0f, 50.0f); glTexCoord2f( 0.0f, 0.0f); glVertex3f( 50.0f,-3.0f, 50.0f); glTexCoord2f(20.0f, 0.0f); glVertex3f( 50.0f,-3.0f,-50.0f); glTexCoord2f(20.0f,20.0f); glVertex3f(-50.0f,-3.0f,-50.0f); glEnd(); // Do our rotations glTranslatef(0.0f,0.0f,zPos); glRotatef(Rotate,0.0f,0.0f,1.0f); glRotatef(Rotate,1.0f,0.6f,0.0f); // Select cube texture glBindTexture(GL_TEXTURE_2D,TexID); // Calc the top face normal p1.x=-1.0f; p1.y= 1.0f; p1.z= 1.0f; p2.x= 1.0f; p2.y= 1.0f; p2.z= 1.0f; p3.x= 1.0f; p3.y= 1.0f; p3.z=-1.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); // Draw the top face glBegin(GL_QUADS); glTexCoord2f(1.0f,1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f,0.0f); glVertex3f( 1.0f, 1.0f,-1.0f); glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f,-1.0f); // Calc the bottom face normal p1.x=-1.0f; p1.y=-1.0f; p1.z= 1.0f; p2.x=-1.0f; p2.y=-1.0f; p2.z=-1.0f; p3.x= 1.0f; p3.y=-1.0f; p3.z=-1.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); // Draw bottom face glTexCoord2f(1.0f,0.0f); glVertex3f(-1.0f,-1.0f, 1.0f); glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f,-1.0f); glTexCoord2f(0.0f,1.0f); glVertex3f( 1.0f,-1.0f,-1.0f); glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f,-1.0f, 1.0f); // Calc the first side face normal p1.x=-1.0f; p1.y= 1.0f; p1.z= 1.0f; p2.x=-1.0f; p2.y=-1.0f; p2.z= 1.0f; p3.x= 1.0f; p3.y= 1.0f; p3.z= 1.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); glTexCoord2f(1.0f,0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 1.0f); glTexCoord2f(0.0f,1.0f); glVertex3f( 1.0f,-1.0f, 1.0f); glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // Next face p1.x= 1.0f; p1.y= 1.0f; p1.z= 1.0f; p2.x= 1.0f; p2.y=-1.0f; p2.z= 1.0f; p3.x= 1.0f; p3.y= 1.0f; p3.z=-1.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f,1.0f); glVertex3f( 1.0f,-1.0f, 1.0f); glTexCoord2f(0.0f,2.0f); glVertex3f( 1.0f,-1.0f,-1.0f); glTexCoord2f(1.0f,2.0f); glVertex3f( 1.0f, 1.0f,-1.0f); // Next face p1.x= 1.0f; p1.y=-1.0f; p1.z=-1.0f; p2.x=-1.0f; p2.y=-1.0f; p2.z=-1.0f; p3.x=-1.0f; p3.y= 1.0f; p3.z=-1.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); glTexCoord2f(0.0f,2.0f); glVertex3f( 1.0f,-1.0f,-1.0f); glTexCoord2f(0.0f,3.0f); glVertex3f(-1.0f,-1.0f,-1.0f); glTexCoord2f(1.0f,3.0f); glVertex3f(-1.0f, 1.0f,-1.0f); glTexCoord2f(1.0f,2.0f); glVertex3f( 1.0f, 1.0f,-1.0f); // Next face p1.x=-1.0f; p1.y=-1.0f; p1.z=-1.0f; p2.x=-1.0f; p2.y=-1.0f; p2.z= 1.0f; p3.x=-1.0f; p3.y= 1.0f; p3.z= 1.0f; Norm=CalcNormal(p1,p2,p3); glNormal3f(Norm.x,Norm.y,Norm.z); glTexCoord2f(0.0f,3.0f); glVertex3f(-1.0f,-1.0f,-1.0f); glTexCoord2f(1.0f,3.0f); glVertex3f(-1.0f,-1.0f, 1.0f); glTexCoord2f(1.0f,4.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f,4.0f); glVertex3f(-1.0f, 1.0f,-1.0f); glEnd(); // Add to the rotation for next frame Rotate+=0.05f; // Show our cube FlipBuffers(); } // Clean up textures glDeleteTextures(1,&TexID); glDeleteTextures(1,&TexID2); } // Calculate normal from vertices stVec CalcNormal(stVec v1, stVec v2, stVec v3) { double v1x,v1y,v1z,v2x,v2y,v2z; double nx,ny,nz; double vLen; stVec Result; // Calculate vectors v1x = v1.x - v2.x; v1y = v1.y - v2.y; v1z = v1.z - v2.z; v2x = v2.x - v3.x; v2y = v2.y - v3.y; v2z = v2.z - v3.z; // Get cross product of vectors nx = (v1y * v2z) - (v1z * v2y); ny = (v1z * v2x) - (v1x * v2z); nz = (v1x * v2y) - (v1y * v2x); // Normalise final vector vLen = sqrt( (nx * nx) + (ny * ny) + (nz * nz) ); Result.x = (float)(nx / vLen); Result.y = (float)(ny / vLen); Result.z = (float)(nz / vLen); return Result; } // Load a TGA texture GLuint LoadTexture(char *TexName) { TGAImg Img; // Image loader GLuint Texture; // Load our Texture if(Img.Load(TexName)!=IMG_OK) return -1; glGenTextures(1,&Texture); // Allocate space for texture glBindTexture(GL_TEXTURE_2D,Texture); // Set our Tex handle as current // Create the texture if(Img.GetBPP()==24) glTexImage2D(GL_TEXTURE_2D,0,3,Img.GetWidth(),Img.GetHeight(),0, GL_RGB,GL_UNSIGNED_BYTE,Img.GetImg()); else if(Img.GetBPP()==32) glTexImage2D(GL_TEXTURE_2D,0,4,Img.GetWidth(),Img.GetHeight(),0, GL_RGBA,GL_UNSIGNED_BYTE,Img.GetImg()); else return -1; // Specify filtering and edge actions glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); return Texture; } [edit] DownloadsOpenGL_Tut4_(Lighting).zip - A zip including all source code, image files and Win32 exe. |



