Tutorial *94*
One thing that I've always felt was missing from Quake, was the ambience that coloured lighting provides. This effect was in Quake2 and I feel that it went a long way to adding to the atmosphere of the game. This tutorial goes part way to creating that effect with the GLQuake engine. It will only change the colour of the dynamic lights and does not affect the static lights yet.

Aside: The GL_FLASHBLEND cvar controls how dynamic lights are rendered in GLQuake.

GL_FLASHBLEND 1 - The default. This creates a sphere around the light

                  that does not interact with the environment and doesn't

                  look very good in my opinion.

GL_FLASHBLEND 0 - This renders the lights like in normal Quake. The dynamic

                  light lights up the surfaces around it. Much better if

                  you ask me.

Part 1: Controlling Coloured Lighting
Part 2: GL_FLASHBLEND 1
Part 3: GL_FLASHBLEND 0
Conclusion

Part 1 is a discussion on how to control the coloured lights, whilst Parts 2 & 3 start to implement the changes. If you just want to see GLQuake with blue explosions skip Part 1.

Part 1: Controlling Coloured Lighting Before doing the actual coding, we must know how to implement the change of colour within the engine. Here are 2 possibilities -



    1. From the engine itself, setting a constant colour for each type of

       dynamic light.

       e.g. in cl_tent.c in CL_ParseTEnt:

            after dl->decay = 300; under case TE_EXPLOSION:

            you could add something like

            dl->colour[0] = 0.8; dl->colour[1] = 0.2; dl->colour[2] = 0;

            This way every TE_EXPLOSION in the game would be red.



    2. From the QuakeC code, setting the colour of the next dynamic light,

       before spawning it. Here is a possible way of doing this -



            Write the colour of the explosion like you write the coordinates

            just after you create the explosion

            e.g. in shalrath.qc in void() ShalMissileTouch:



                 after :

                 WriteCoord (MSG_BROADCAST, self.origin_x);

                 WriteCoord (MSG_BROADCAST, self.origin_y);

                 WriteCoord (MSG_BROADCAST, self.origin_z);

                 add :

                 // Write the colour of the explosion

                 WriteCoord (MSG_BROADCAST, 0.2);

                 WriteCoord (MSG_BROADCAST, 0.2);

                 WriteCoord (MSG_BROADCAST, 0.8);



                 Then in cl_tent.c in CL_ParseTEnt:

                 after dl->decay = 300; under case TE_EXPLOSION:

                 you would add

                 dl->colour[0] = MSG_ReadCoord ();

                 dl->colour[1] = MSG_ReadCoord ();

                 dl->colour[2] = MSG_ReadCoord ();



Both of these ways have their disadvantages, setting the colour from the engine, stops people from defining their own custom colour explosions without the source code for GLQuake and a way of compiling it. Whilst the way I have described of changing it from QuakeC will break old progs.dat and demo files because the engine will be expecting a colour of the explosion that the older files just will not provide. Anyone with a better idea of how to control these things should contact me at epca@powerup.com.au

For the purposes of this tutorial, I will use the first method for simplicity. It is up to you how you implement it in your engine, although a standard, like for the fog would be good so that all engines will be compatible. Now on to the coding.

Part 2: GL_FLASHBLEND 1 The first thing that needs to be done is to add a way for each dynamic light to track what colour it currently is. In OpenGL, colours are defined by an array of floats. So we need to add that array to the declaration of dlight_t in client.h

After :



	    #ifdef QUAKE2

                qboolean    dark;           // subtracts light instead of adding

            #endif

Add :


	    // CDL - epca@powerup.com.au

            // Add a colour to the dynamic lights

            float colour[3];

            // CDL

Note how I have commented where I have begun and completed making changes to the code, so that if I make a mistake, I can easily remove all code relating to the mistake and start again.

Now that we have a way to track the colour of the lights, we must do something about changing the colour. So in cl_tent.c in CL_ParseTEnt

After :



	    case TE_EXPLOSION:          // rocket explosion

            pos[0] = MSG_ReadCoord ();

            pos[1] = MSG_ReadCoord ();

            pos[2] = MSG_ReadCoord ();

            R_ParticleExplosion (pos);

            dl = CL_AllocDlight (0);

            VectorCopy (pos, dl->origin);

            dl->radius = 350;

            dl->die = cl.time + 0.5;

            dl->decay = 300;

Add :


	    // CDL - epca@powerup.com.au

            // Make the dynamic light purple

            dl->colour[0] = 0.2; dl->colour[1] = 0.2; dl->colour[2] = 0.8;

            // CDL

Now that we have a coloured light, we must render it in the right colour. This is simple, when you realise that dynamic lights are rendered from R_RenderDLight in gl_rlight.c. So in R_RenderDLight in gl_rlight.c

Change :



	     glColor3f (0.2,0.1,0.0);

To :


	     // CDL - epca@powerup.com.au

             if (light->colour[0] || light->colour[1] || light->colour[2])

                 glColor3f (light->colour[0], light->colour[1], light->colour[2]);

             else

                 glColor3f (0.2,0.1,0.0);

             // CDL

Compile and run it. You will now see a purple light surround all of your explosions. Notice however, that if you change GL_FLASHBLEND to 0, that you lose the coloured dynamic light that you just created. To fix this, move on to the next part in the tutorial.

Part 3: GL_FLASHBLEND 0 In this section of the tutorial, we will be modifying lightmaps. Lightmaps tell the engine how much light is in a specified area, and dynamic lights modify these lightmaps to interact with the environment. There is a problem with the Quake implementation of lightmaps, in that they are only luminense lightmaps, and only tell the engine how light or dark an area is, and not in what colours. Luckily for us, the people at ID gave us the option to use RGBA (coloured) lightmaps, by add -lm_4 to the command line. However, I don't think they tested this mode, because if you run GLQuake with this parameter, the level is just fullbright. To fix this, go into gl_rsurf.c in After :



		case GL_RGBA:

                stride -= (smax<<2);

                bl = blocklights;

                for (i=0 ; i>= 7;

                        if (t > 255)

                            t = 255;

Add :


	    // epca@powerup.com.au

            // Fix bug in quake where this mode is just fullbright

            dest[0] = dest[1] = dest[2] = 255-t;

            // End of fix

This will return the lightmap back to its intended form whilst using RGBA. Back to coloured lighting. In gl_rsurf.c in the declarations at the top

After :



unsigned        blocklights[18*18];

Add :


	    // CDL

            unsigned        blocklightcolours[3][18*18];

            // CDL

Now we have a way to store the changed colours, we must do something about changing them. Dynamic lights are added to the lightmap in the function R_AddDynamicLights in gl_rsurf.c. Compare the following to that function and once you see what is going on, replace it.



void R_AddDynamicLights (msurface_t *surf)

{

 int         lnum;

 int         sd, td;

 float       dist, rad, minlight;

 vec3_t      impact, local;

 int         s, t;

 int         i;

 int         smax, tmax;

 mtexinfo_t  *tex;

 // CDL - epca@powerup.com.au

 dlight_t *dl;

 // CDL



 smax = (surf->extents[0]>>4)+1;

 tmax = (surf->extents[1]>>4)+1;

 tex = surf->texinfo;



 for (lnum=0 ; lnum<MAX_DLIGHTS ; lnum++)

 {

  if ( !(surf->dlightbits & (1<<lnum) ) )

   continue;       // not lit by this light



  rad = cl_dlights[lnum].radius;

  dist = DotProduct (cl_dlights[lnum].origin, surf->plane->normal) - surf->plane->dist;

  rad -= fabs(dist);

  minlight = cl_dlights[lnum].minlight;

  if (rad < minlight)

   continue;

  minlight = rad - minlight;



  for (i=0 ; i<3 ; i++) {

   impact[i] = cl_dlights[lnum].origin[i] - surf->plane->normal[i]*dist;

  }



  local[0] = DotProduct (impact, tex->vecs[0]) + tex->vecs[0][3];

  local[1] = DotProduct (impact, tex->vecs[1]) + tex->vecs[1][3];



  local[0] -= surf->texturemins[0];

  local[1] -= surf->texturemins[1];



  for (t = 0 ; t<tmax ; t++) {

   td = local[1] - t*16;

   if (td < 0)

    td = -td;

   for (s=0 ; s<smax ; s++) {

    sd = local[0] - s*16;

    if (sd < 0)

     sd = -sd;

    if (sd > td)

     dist = sd + (td>>1);

    else

     dist = td + (sd>>1);

    if (dist < minlight) {

     blocklights[t*smax + s] += (rad - dist)*256;

     // CDL - epca@powerup.com.au

     dl = &cl_dlights[lnum];

     if (!dl->colour[0] && !dl->colour[1] && !dl->colour[2]) {

      blocklightcolours[0][t*smax + s] += (rad - dist)*(0.2*256);

      blocklightcolours[1][t*smax + s] += (rad - dist)*(0.1*256);

      blocklightcolours[2][t*smax + s] += (rad - dist)*(0.0*256);

     } else {

      blocklightcolours[0][t*smax + s] += (rad - dist)*(dl->colour[0]*256);

      blocklightcolours[1][t*smax + s] += (rad - dist)*(dl->colour[1]*256);

      blocklightcolours[2][t*smax + s] += (rad - dist)*(dl->colour[2]*256);

     }

    // CDL

    }

   }

  }

 }

}

If you look at the sections that I have changed, you will see that I am simply adding the value of the colour of the light onto the colours that will be used later. The reason that the colours are multiplied by 256 is that I have stored each colour value between 0 and 1, and the GLQuake engine adds the colours between 0 and 256.

If you compile and run at this stage, you will notice that the coloured lighting still isn't working, that is because all we have done is calculate the values to be used and not used them. To use them, we must modify R_BuildLightMaps in gl_rsurf.c. Compare that version to the version below and then replace it.



void R_BuildLightMap (msurface_t *surf, byte *dest, int stride)

{

    int smax, tmax;

    int t, r, s, q;

    int i, j, size;

    byte *lightmap;

    unsigned scale;

    int maps;

    int lightadj[4];

    unsigned *bl;

    // CDL - epca@powerup.com.au

    unsigned *blcr, *blcg, *blcb;

    // CDL



    surf->cached_dlight = (surf->dlightframe == r_framecount);

    smax = (surf->extents[0]>>4)+1;

    tmax = (surf->extents[1]>>4)+1;

    size = smax*tmax;

    lightmap = surf->samples;



    // set to full bright if no light data

    if (r_fullbright.value || !cl.worldmodel->lightdata)

    {

        for (i=0 ; i<size ; i++)

        {

            // CDL - epca@powerup.com.au

            blocklightcolours[0][i] =

            blocklightcolours[1][i] =

            blocklightcolours[2][i] =

            // CDL

            blocklights[i] = 255*256;

        }

        goto store;

    }



    // clear to no light

    for (i=0 ; i<size ; i++)

    {

        // CDL - epca@powerup.com.au

        blocklightcolours[0][i] =

        blocklightcolours[1][i] =

        blocklightcolours[2][i] =

        // CDL

        blocklights[i] = 0;

    }



    // add all the lightmaps

    if (lightmap)

        for (maps = 0 ; maps < MAXLIGHTMAPS && surf->styles[maps] != 255 ; maps++)

        {

            scale = d_lightstylevalue[surf->styles[maps]];

            surf->cached_light[maps] = scale; // 8.8 fraction

            for (i=0 ; i<size ; i++)

            {

                // CDL - epca@powerup.com.au

                blocklightcolours[0][i] += lightmap[i] * scale;

                blocklightcolours[1][i] += lightmap[i] * scale;

                blocklightcolours[2][i] += lightmap[i] * scale;

                // CDL

                blocklights[i] += lightmap[i] * scale;

            }

            lightmap += size; // skip to next lightmap

        }



    // add all the dynamic lights

    if (surf->dlightframe == r_framecount)

    R_AddDynamicLights (surf);



    // bound, invert, and shift

store:

    switch (gl_lightmap_format)

    {

    case GL_RGBA:

        stride -= (smax<<2);

        bl = blocklights;

        // CDL - epca@powerup.com.au

        blcr = blocklightcolours[0];

        blcg = blocklightcolours[1];

        blcb = blocklightcolours[2];

        // CDL

        for (i=0 ; i<tmax ; i++, dest += stride)

        {

            for (j=0 ; j<smax ; j++)

            {

                // CDL - epca@powerup.com.au

                q = *blcr++; q >>= 7;

                if (q > 255) q = 255;

                r = *blcg++; r >>= 7;

                if (r > 255) r = 255;

                s = *blcb++; s >>= 7;

                if (s > 255) s = 255;

                dest[0] = 255-q;

                dest[1] = 255-r;

                dest[2] = 255-s;

                // CDL

                t = *bl++;

                t >>= 7;

                if (t > 255)

                t = 255;

                // epca@powerup.com.au

                // Fix bug in quake where this mode is just fullbright

                //dest[0] = dest[1] = dest[2] = 255-t;

                // End of fix

                dest[3] = 255-t;

                dest += 4;

            }

        }

        break;



    case GL_ALPHA:

    case GL_LUMINANCE:

    case GL_INTENSITY:

        bl = blocklights;

        for (i=0 ; i<tmax ; i++, dest += stride)

        {

            for (j=0 ; j<smax ; j++)

            {

                t = *bl++;

                t >>= 7;

                if (t > 255)

                    t = 255;

                dest[j] = 255-t;

            }

        }

        break;



    default:

        Sys_Error ("Bad lightmap format");

    }

}

Compile and run, and you will be looking at blue explosions!

Conclusion I hope that this tutorial has helped you understand a bit more about the GLQuake Engine, and I hope to see all sorts of coloured lighting in the future. Any questions, comments, corrections? I would love to hear them. Contact me at epca@powerup.com.au



 
Sign up
Login:
Passwd:
[Remember Me]