Beyond The Screen

Rendering of various phenomena, Article 1: Lens Flares

Greetings!

This is the first in what I hope will be a series of articles explaining how to render various effects in real-time, in a way that might be useful for those interested in creating their own game.

I make the following assumptions:
  • You know C well enough to follow what's going on in a block of code. I comment everything quite heavily, and I try to use constructs that will map well to most other common languages, but the nuts and bolts are all in C.
  • You know, or at least are aware of, OpenGL to some degree (fortunately, the designers of the library saw fit to name every function for what it actually does, so if you don't know it, you'll still be able to follow along).
  • You know how to edit, compile and link a program on your machine.

Additionally, for portability reasons, I rely on the Allegro library for basic things like loading images, interfacing with the windowing system and other OS-specific work. When possible, though, I'll keep to non-Allegro specific code, so people using things like SDL or working directly with their chosen OS' graphics functionality should still find this useful. I've enclosed a Windows binary, but this should compile on any machine.

Disclaimer: using this code or running the binary enclosed could ruin your machine, tarnish your reputation and cause people to make disparaging remarks about your parents, and you should treat this as you would any code found on the internet - however, it's never exhibited this behaviour during coding or testing. Additionally, I don't claim that this is the only way, or even the best way to do a specific task - I'm merely documenting what has worked for me in hopes that others that have the same questions I did when I started will have a base from which to work.

In today's article, I discuss a fairly lightweight method of rendering lens flares and provide a well-commented working implementation. Although the sample program relies on OpenGL to do its dirty work, the method used here would work for any rendering API in which a projection matrix is kept and can be retrieved later, and in which a z-buffer is available.

How it works:

Although the function in the sample program grew to be a 200-line behemoth when I attempted to make both the source and the output prettier, it's really quite simple. For the light source or point you'd like to add a lens flare to:
  1. Find or retrieve the current projection matrix.
  2. Find or retrieve the boundaries of the current window.
  3. Multiply the coordinates of your point by the inverse of the projection matrix obtained in step one.
  4. If the multiplied point is inside the window bounds, check the z-buffer at that location, to see if the point is occluded.
  5. If it's not, render a textured, alpha-blended rectangle at the multiplied point.
  6. ???
  7. Profit!
And that's about it. (I added some secondary flares to simulate the complex optics of a real camera, but, as you'll see, this is trivially done by simply dividing the negative of the distance between where the light source is in the viewport and the centre of the viewport by a constant and placing another textured quad at the resulting point.) For specifics, you'll want to look at the function posted below that actually does all the heavy lifting, and run the program (if your machine allows it) to see the results.
Controls:
  • UP ARROW moves camera up
  • DOWN ARROW moves camera down
  • LEFT ARROW rotates camera left
  • RIGHT ARROW rotates camera right


Bugs:

The trouble with this method, at least as implemented here, is that, because gluProject doesn't take into account the near or far planes of the frustum when multiplying the coordinates of a point passed to it, it's possible something could be inside the double-pyramid the projection matrix describes, causing the lens flare to appear when the camera is facing exactly 180 degrees away from it, too.

It would be possible to test if the light source getting the flare were behind the camera, preventing this case, but I didn't do this as it'd add to the complexity of the demo program. In the common case for most games, though, some sort of ground geometry will usually prevent the shinethrough of, say, the sun from happening by placing something in the z-buffer ahead of the false sun position.

And that's about it. If you have any questions about the source code, feel free to email me at illegalinstr@illegal-instruction.net.



/***************************************************************************************************
* render_lensflare:
*   renders a multi-lens flare
*/
void render_lensflare(GLfloat object_x, GLfloat object_y, GLfloat 
object_z, 
                      GLfloat obj_red,  GLfloat obj_grn,  GLfloat 
obj_blu)
{
 
   /* these are used to store the viewing volume info */
   /* and as inputs to gluProject                     */
   GLdouble modelMatrix[16];
   GLdouble projMatrix[16];
   GLint        viewport[4];
 
   /* this is where the projected object would be     */
   /* on-screen - z is needed so we know if it's      */
   /* occluded or not, as you'll see below            */   
   GLdouble     screen_x;
   GLdouble     screen_y;
   GLdouble     screen_z;
 
   /* this is needed to store the depth buffer value  */
   /* at the point at which we want the light's       */
   /* flare to come from                              */ 
 
   GLfloat temp;
 
   glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); 
   glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
   glGetIntegerv(GL_VIEWPORT, viewport);
 
   /* compute where the location described by the     */
   /* params is in relation to the screen             */ 
   gluProject(object_x, object_y, object_z,
               modelMatrix, projMatrix, viewport,
       &screen_x, &screen_y, &screen_z); 
 
   /* find out if that location is even on screen     */
   if((screen_x < 0) ||
      (screen_x > SCREEN_W) ||
      (screen_y < 0) ||
      (screen_y > SCREEN_H)
     )
   {
 
      /* no, it wouldn't be visible from where the    */
      /* camera is looking                            */ 
      return;
   }
 
   /* is the location occluded by something in the    */
   /* foreground?                                     */
   screen_z = 1.0;
   glReadPixels((int)screen_x, (int)screen_y, 1, 1,GL_DEPTH_COMPONENT, 
GL_FLOAT, &temp);
   if(temp < screen_z)
   {
      /* yes, it's behind something                   */
      return;
   }
 
   setup_2D_mode();
   glDepthMask(GL_FALSE);
   glEnable(GL_BLEND);
   glBlendFunc (GL_SRC_COLOR, GL_ONE); 
 

   /* this is where the glare from the light is     */ 
   /* drawn                                         */
   glColor3f(obj_red,obj_grn,obj_blu);
 
   /* really bright sun texture                     */ 
   glBindTexture(GL_TEXTURE_2D, pri_flare_texture);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
GL_LINEAR_MIPMAP_LINEAR);
 
   glBegin(GL_QUADS);
  glTexCoord2f(0,0);
  glVertex2f(screen_x - (SCREEN_WIDTH / 2.0) - (FLARE_SIZE_1 / 2), 
              screen_y - (SCREEN_HEIGHT / 2.0) - (FLARE_SIZE_1 / 2));
 
  glTexCoord2f(1,0);
  glVertex2f(screen_x - (SCREEN_WIDTH / 2.0) + FLARE_SIZE_1 - 
(FLARE_SIZE_1 / 2), 
              screen_y - (SCREEN_HEIGHT / 2.0) - (FLARE_SIZE_1 / 2));
 
  glTexCoord2f(1,1);
  glVertex2f(screen_x - (SCREEN_WIDTH / 2.0) + FLARE_SIZE_1 - 
(FLARE_SIZE_1 / 2), 
           screen_y - (SCREEN_HEIGHT / 2.0) + FLARE_SIZE_1 - 
(FLARE_SIZE_1 / 2));
 
  glTexCoord2f(0,1);
  glVertex2f(screen_x - (SCREEN_WIDTH / 2.0) - (FLARE_SIZE_1 / 2), 
           screen_y - (SCREEN_HEIGHT / 2.0) + FLARE_SIZE_1 - 
(FLARE_SIZE_1 / 2));
   glEnd();
 
   /* alter the colour to simulate antiglare coating */ 
   glColor3f(obj_red * ANTIGLARE_COATING_R, 
             obj_grn * ANTIGLARE_COATING_G, 
             obj_blu * ANTIGLARE_COATING_B);
 
   /* switch to a texture that simulates internal    */
   /* optics...                                      */          
 
   glBindTexture(GL_TEXTURE_2D, sec_flare_texture);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
GL_LINEAR_MIPMAP_LINEAR);
 
   /* secondary flare                                */ 
   glBegin(GL_QUADS);
  glTexCoord2f(0,0);
  glVertex2f((-screen_x / 1.5) + (SCREEN_WIDTH / 3.0) - (FLARE_SIZE_2 / 
2), 
           (-screen_y / 1.5) + (SCREEN_HEIGHT / 3.0) - (FLARE_SIZE_2 / 
2));
 
  glTexCoord2f(1,0);
  glVertex2f((-screen_x / 1.5) + (SCREEN_WIDTH / 3.0) + FLARE_SIZE_2 - 
(FLARE_SIZE_2 / 2), 
           (-screen_y / 1.5) + (SCREEN_HEIGHT / 3.0) - (FLARE_SIZE_2 / 
2));
 
  glTexCoord2f(1,1);
  glVertex2f((-screen_x / 1.5) + (SCREEN_WIDTH / 3.0) + FLARE_SIZE_2 - 
(FLARE_SIZE_2 / 2), 
           (-screen_y / 1.5) + (SCREEN_HEIGHT / 3.0) + FLARE_SIZE_2 - 
(FLARE_SIZE_2 / 2));
 
  glTexCoord2f(0,1);
  glVertex2f((-screen_x / 1.5) + (SCREEN_WIDTH / 3.0) - (FLARE_SIZE_2/ 
2), 
           (-screen_y / 1.5) + (SCREEN_HEIGHT / 3.0) + FLARE_SIZE_2 - 
(FLARE_SIZE_2 / 2));
 
      /* slightly alter the colour...                   */ 
      glColor3f((obj_red * ANTIGLARE_COATING_R) * .65, 
             (obj_grn * ANTIGLARE_COATING_G) * .65, 
             (obj_blu * ANTIGLARE_COATING_B) * .65);
 
      /* and draw a third flare */ 
  glTexCoord2f(0,0);
  glVertex2f((-screen_x / 3.0) + (SCREEN_WIDTH / 6.0) - (FLARE_SIZE_3 / 
2), 
           (-screen_y / 3.0) + (SCREEN_HEIGHT / 6.0) - (FLARE_SIZE_3 / 
2));
 
  glTexCoord2f(1,0);
  glVertex2f((-screen_x / 3.0) + (SCREEN_WIDTH / 6.0) + FLARE_SIZE_3 - 
(FLARE_SIZE_3 / 2), 
           (-screen_y / 3.0) + (SCREEN_HEIGHT / 6.0) - (FLARE_SIZE_3 / 
2));
 
  glTexCoord2f(1,1);
  glVertex2f((-screen_x / 3.0) + (SCREEN_WIDTH / 6.0) + FLARE_SIZE_3 - 
(FLARE_SIZE_3 / 2), 
           (-screen_y / 3.0) + (SCREEN_HEIGHT / 6.0) + FLARE_SIZE_3 - 
(FLARE_SIZE_3 / 2));
 
  glTexCoord2f(0,1);
  glVertex2f((-screen_x / 3.0) + (SCREEN_WIDTH / 6.0) - (FLARE_SIZE_3/ 
2), 
           (-screen_y / 3.0) + (SCREEN_HEIGHT / 6.0) + FLARE_SIZE_3 - 
(FLARE_SIZE_3 / 2));
 

      /* and a fourth...       */     
   glTexCoord2f(0,0);
  glVertex2f((-screen_x / 4.0) + (SCREEN_WIDTH / 8.0) - (FLARE_SIZE_4 / 
2), 
           (-screen_y / 4.0) + (SCREEN_HEIGHT / 8.0) - (FLARE_SIZE_4 / 
2));
 
  glTexCoord2f(1,0);
  glVertex2f((-screen_x / 4.0) + (SCREEN_WIDTH / 8.0) + FLARE_SIZE_4 - 
(FLARE_SIZE_4 / 2), 
           (-screen_y / 4.0) + (SCREEN_HEIGHT / 8.0) - (FLARE_SIZE_4 / 
2));
 
  glTexCoord2f(1,1);
  glVertex2f((-screen_x / 4.0) + (SCREEN_WIDTH / 8.0) + FLARE_SIZE_4 - 
(FLARE_SIZE_4 / 2), 
           (-screen_y / 4.0) + (SCREEN_HEIGHT / 8.0) + FLARE_SIZE_4 - 
(FLARE_SIZE_4 / 2));
 
  glTexCoord2f(0,1);
  glVertex2f((-screen_x / 4.0) + (SCREEN_WIDTH / 8.0) - (FLARE_SIZE_4 / 
2), 
           (-screen_y / 4.0) + (SCREEN_HEIGHT / 8.0) + FLARE_SIZE_4 - 
(FLARE_SIZE_4 / 2));
   glEnd();
 
   /* switch to another lens artifact texture        */ 
   glBindTexture(GL_TEXTURE_2D, ter_flare_texture);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
GL_LINEAR_MIPMAP_LINEAR);
 
   /* alter the colour to simulate antiglare coating */ 
   glColor3f((obj_red * ANTIGLARE_COATING_R) * .95, 
             (obj_grn * ANTIGLARE_COATING_G) * .95, 
             (obj_blu * ANTIGLARE_COATING_B) * .95);
             
   /* fifth flare - we will centre this one         */
   /* to simulate a common artifact.                */ 
 
   glBegin(GL_QUADS);
  glTexCoord2f(0,0);
  glVertex2f(0 - (FLARE_SIZE_2 / 2), 0 - (FLARE_SIZE_2 / 2));
 
  glTexCoord2f(1,0);
  glVertex2f(FLARE_SIZE_2 - (FLARE_SIZE_2 / 2), 0 - (FLARE_SIZE_2 / 
2));
 
  glTexCoord2f(1,1);
  glVertex2f(FLARE_SIZE_2 - (FLARE_SIZE_2 / 2), FLARE_SIZE_2 - 
(FLARE_SIZE_2 / 2));
 
  glTexCoord2f(0,1);
  glVertex2f(0 - (FLARE_SIZE_2/ 2),  FLARE_SIZE_2 - (FLARE_SIZE_2 / 
2));
   glEnd();
 
   glColor3f(1,1,1);
   glDepthMask(GL_TRUE);
   glDisable(GL_BLEND);
 
   setup_3D_mode();
   return;
}

 

Do you want to write to The Super Novice? Send an email here!

POST MESSAGE
Great Tip by bunny's fan    9.19.05
Thanks for the Lens Flare Tip!

 
 

©Copyright 2001-2002, GameLord.org. All Rights Reserved. Thanks for playing!