Tutorial *99*
Once I had implemented Coloured Dynamic Lights in GLQuake (http://qsg.telefragged.com/tut/gfx/lights.shtml) I was happy with the effect I achieved, but it was still missing something. I figured it must be that all lights around the coloured lights were plain, old boring white, and figured I might as well do something about it.

Before following this tutorial, make sure you have followed the Coloured Dynamic Lights tutorial (http://qsg.telefragged.com/tut/gfx/lights.shtml), as this code makes use of some changes made by it. Also remember to run the program with -lm_4 to enable RGBA lightmaps, as GLQuake will just render normal lightmaps otherwise.

To implement the lights, I had to change the way lightmaps are calculated by light.exe, so you will need the source code for light.exe available in the file qutils.zip from your favourite idsoftware mirror.

Here are the changes I had to make to implement Coloured Static Lights.

Part A: Light.exe Changes
Part B: GLQuake.exe Changes
Part C: Creating Maps
Conclusion

Light.exe Changes
=================

Light.exe is the program which creates that lightmaps that light the Quake world. To create coloured lighting, we must change the way lightmaps are stored in the bsp file.

Currently, they are stored as a single value, that tells the engine how light or dark the area is. We must change this to store 4 values, the red component, the blue component, the green component and the alpha component.

First, we must create a way to save the colour of each light, found in the bsp file.
In Entities.h in the definition of entity_t After :



	struct entity_s *targetent;

Add :


	// CSL - epca@powerup.com.au

        // Store the colour of the light

        int     lightcolour[3];

        // CSL

Then we must create a way to read the colour of the light found, into our new array.
In Entities.c in LoadEntities After :


	    else if (!strcmp(key, "angle"))

            {

                entity->angle = atof(com_token);

            }

Add :


	    // CSL - epca@powerup.com.au

            else if (!strcmp(key, "_color") || !strcmp(key, "color"))

            {

                // scan into doubles, then assign

                // which makes it vec_t size independent

                if (sscanf(com_token, "%lf %lf %lf",

                        &vec[0], &vec[1], &vec[2]) != 3)

                    Error ("LoadEntities: not 3 values for colour");

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

                    entity->lightcolour[i] = vec[i];

            }

            // CSL

Now that we can read in and store the colour of a light, we must create a way of storing the colours for each face.
In ltface.c in the definition of lightinfo_t After :


	    dface_t *face;

Add :


	    // CSL - epca@powerup.com.au

            vec3_t  lightmapcolours[MAXLIGHTMAPS][SINGLEMAP];

            // CSL

Now we can store the colour of light present at each point on a face, we must add a way of calculating how much each light will effect each point of the face, and then store it for later writing out to the lightmap. SingleLightFace, calculates the amount of effect a light has on a face, and is called for each face in the bsp file.

In ltface.c replace SingleLightFace, with the following version



void SingleLightFace (entity_t *light, lightinfo_t *l)

{

    vec_t   dist;

    vec3_t  incoming;

    vec_t   angle;

    vec_t   add;

    vec_t   *surf;

    qboolean    hit;

    int     mapnum;

    int     size;

    int     c, i;

    vec3_t  rel;

    vec3_t  spotvec;

    vec_t   falloff;

    vec_t   *lightsamp;

    // CSL - epca@powerup.com.au

    vec3_t  *lightcoloursamp;

    // CSL



    VectorSubtract (light->origin, bsp_origin, rel);

    dist = scaledist * (DotProduct (rel, l->facenormal) - l->facedist);



// don't bother with lights behind the surface

    if (dist <= 0)

        return;



// don't bother with light too far away

    if (dist > light->light)

    {

        c_culldistplane++;

        return;

    }



    if (light->targetent)

    {

        VectorSubtract (light->targetent->origin, light->origin, spotvec);

        VectorNormalize (spotvec);

        if (!light->angle)

            falloff = -cos(20*Q_PI/180);

        else

            falloff = -cos(light->angle/2*Q_PI/180);

    }

    else

        falloff = 0;    // shut up compiler warnings



    mapnum = 0;

    for (mapnum=0 ; mapnum<l->numlightstyles ; mapnum++)

        if (l->lightstyles[mapnum] == light->style)

            break;

    lightsamp = l->lightmaps[mapnum];

    // CSL - epca@powerup.com.au

    lightcoloursamp = l->lightmapcolours[mapnum];

    // CSL

    if (mapnum == l->numlightstyles)

    {   // init a new light map

        if (mapnum == MAXLIGHTMAPS)

        {

            printf ("WARNING: Too many light styles on a face\n");

            return;

        }

        size = (l->texsize[1]+1)*(l->texsize[0]+1);

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

        {

            // CSL - epca@powerup.com.au

            lightcoloursamp[i][0] = lightcoloursamp[i][1] = lightcoloursamp[i][2] = 0;

            // CSL

            lightsamp[i] = 0;

        }

    }



//

// check it for real

//

    hit = false;

    c_proper++;



    surf = l->surfpt[0];

    for (c=0 ; c<l->numsurfpt ; c++, surf+=3)

    {

        dist = CastRay(light->origin, surf)*scaledist;

        if (dist < 0)

            continue;   // light doesn't reach



        VectorSubtract (light->origin, surf, incoming);

        VectorNormalize (incoming);

        angle = DotProduct (incoming, l->facenormal);

        if (light->targetent)

        {   // spotlight cutoff

            if (DotProduct (spotvec, incoming) > falloff)

                continue;

        }



        angle = (1.0-scalecos) + scalecos*angle;

        add = light->light - dist;

        add *= angle;

        if (add < 0)

            continue;

        lightsamp[c] += add;

        // CSL - epca@powerup.com.au

        if (!light->lightcolour[0] && !light->lightcolour[1] && !light->lightcolour[2])

        {

            // No colour

            lightcoloursamp[c][0] += add;

            lightcoloursamp[c][1] += add;

            lightcoloursamp[c][2] += add;

        }

        else

        {

            lightcoloursamp[c][0] += (add * light->lightcolour[0]) /255;

            lightcoloursamp[c][1] += (add * light->lightcolour[1]) /255;

            lightcoloursamp[c][2] += (add * light->lightcolour[2]) /255;

        }

        // CSL

        if (lightsamp[c] > 1)       // ignore real tiny lights

            hit = true;

    }



    if (mapnum == l->numlightstyles && hit)

    {

        l->lightstyles[mapnum] = light->style;

        l->numlightstyles++;    // the style has some real data now

    }

}

Now we can calculate how much each light effects each face and store it, we must write out the extra information into the lightmap. LightFace, is called for each face in the bsp file. It traverses through all entities in the bsp file, and for those which are lights calls SingleLightFace. It then writes the lightmap out for that face.

In ltface.c replace LightFace, with the following version



void LightFace (int surfnum)

{

    dface_t *f;

    lightinfo_t l;

    int     s, t;

    int     i,j,c;

    vec_t   total;

    int     size;

    int     lightmapwidth, lightmapsize;

    byte    *out;

    vec_t   *light;

    // CSL - epca@powerup.com.au

    vec3_t  *lightcolour;

    vec3_t  totalcolours;

    // CSL

    int     w, h;



    f = dfaces + surfnum;



//

// some surfaces don't need lightmaps

//

    f->lightofs = -1;

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

        f->styles[j] = 255;



    if ( texinfo[f->texinfo].flags & TEX_SPECIAL)

    {   // non-lit texture

        return;

    }



    memset (&l, 0, sizeof(l));

    l.surfnum = surfnum;

    l.face = f;



//

// rotate plane

//

    VectorCopy (dplanes[f->planenum].normal, l.facenormal);

    l.facedist = dplanes[f->planenum].dist;

    if (f->side)

    {

        VectorSubtract (vec3_origin, l.facenormal, l.facenormal);

        l.facedist = -l.facedist;

    }







    CalcFaceVectors (&l);

    CalcFaceExtents (&l);

    CalcPoints (&l);



    lightmapwidth = l.texsize[0]+1;



    size = lightmapwidth*(l.texsize[1]+1);

    if (size > SINGLEMAP)

        Error ("Bad lightmap size");



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

        l.lightstyles[i] = 255;



//

// cast all lights

//

    l.numlightstyles = 0;

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

    {

        if (entities[i].light)

            SingleLightFace (&entities[i], &l);

    }



    FixMinlight (&l);



    if (!l.numlightstyles)

    {   // no light hitting it

        return;

    }



//

// save out the values

//

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

        f->styles[i] = l.lightstyles[i];



    // CSL - epca@powerup.com.au

    file://lightmapsize = size*l.numlightstyles;

    // Now that we are storing RGBA, we need extra room

    lightmapsize = size*l.numlightstyles*4;

    // CSL



    out = GetFileSpace (lightmapsize);

    f->lightofs = out - filebase;



// extra filtering

    h = (l.texsize[1]+1)*2;

    w = (l.texsize[0]+1)*2;



    for (i=0 ; i< l.numlightstyles ; i++)

    {

        if (l.lightstyles[i] == 0xff)

            Error ("Wrote empty lightmap");

        light = l.lightmaps[i];

        // CSL - epca@powerup.com.au

        lightcolour = l.lightmapcolours[i];

        // CSL

        c = 0;

        for (t=0 ; t<=l.texsize[1] ; t++)

            for (s=0 ; s<=l.texsize[0] ; s++, c++)

            {

                if (extrasamples)

                {   // filtered sample

                    total = light[t*2*w+s*2] + light[t*2*w+s*2+1]

                    + light[(t*2+1)*w+s*2] + light[(t*2+1)*w+s*2+1];

                    total *= 0.25;



                    // CSL - epca@powerup.com.au

                    // Calculate the colour

                    totalcolours[0] = lightcolour[t*2*w+s*2][0] + lightcolour[t*2*w+s*2+1][0]

                    + lightcolour[(t*2+1)*w+s*2][0] + lightcolour[(t*2+1)*w+s*2+1][0];

                    totalcolours[0] *= 0.25;

                    totalcolours[1] = lightcolour[t*2*w+s*2][1] + lightcolour[t*2*w+s*2+1][1]

                    + lightcolour[(t*2+1)*w+s*2][1] + lightcolour[(t*2+1)*w+s*2+1][1];

                    totalcolours[1] *= 0.25;

                    totalcolours[2] = lightcolour[t*2*w+s*2][2] + lightcolour[t*2*w+s*2+1][2]

                    + lightcolour[(t*2+1)*w+s*2][2] + lightcolour[(t*2+1)*w+s*2+1][2];

                    totalcolours[2] *= 0.25;

                    // CSL

                }

                else

                {

                    total = light[c];



                    // CSL - epca@powerup.com.au

                    // Calculate the colour

                    VectorCopy (lightcolour[c], totalcolours);

                    // CSL

                }

                total *= rangescale;    // scale before clamping



                // CSL - epca@powerup.com.au

                VectorScale (totalcolours, rangescale, totalcolours);

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

                {

                    if (totalcolours[j] > 255)

                        totalcolours[j] = 255;

                    if (totalcolours[j] < 0)

                        Error ("colour %i < 0", j);

                }

                // CSL



                if (total > 255)

                    total = 255;

                if (total < 0)

                    Error ("light < 0");



                // CSL - epca@powerup.com.au

                // Write out the lightmap in RGBA format

                *out++ = totalcolours[0];

                *out++ = totalcolours[1];

                *out++ = totalcolours[2];

                // CSL



                *out++ = total;

            }

    }

}

To distinguish between bsp files with coloured lighting, and those without, we must change the version number of the files. Without the changed engine, these files will not run.

In the common directory at the base of the utils package, you will find bspfile.h
Replace :



	        #define BSPVERSION  29

 
With :


		// CSL - epca@powerup.com.au

                #define BSPVERSION  30

                // CSL

Since we have changed the version of the bsp file, I would recommend recompiling qbsp.exe and vis.exe so that they will write the new version numbers to the bsp file as well. Otherwise, you will have to use light.exe last so that it will write the correct version number even though qbsp.exe and vis.exe won't.


GLQuake.exe Changes
===================

Since we have changed the version of the bsp files bein written, we must let GLQuake now that there is another version of bsp file that is acceptable. In bspfile.h

After :



	    #define BSPVERSION  29

Add :


	    // CSL - epca@powerup.com.au

            #define CSLBSPVERSION 30

            // CSL

Then to distinguish between the different maps, we create a global variable, that when true will signify a newer version of the bsp file. in host.c

After :



	    cvar_t  temp1 = {"temp1","0"};

Add :


	    // CSL - epca@powerup.com.au

            // Use bspextensions to determine changes in format to bsp files

            int bspextensions = 0;

            // CSL

in quakedef.h After :


	    extern  double      realtime;           // not bounded in any way, changed at

                                                    // start of every frame, never reset

Add :


	    // CSL - epca@powerup.com.au

            extern int bspextensions;

            // CSL

Now that we have the variable, we must assign it somewhere. In gl_model.c in Mod_LoadBrushModel Replace :


		if (i != BSPVERSION)

                    Sys_Error ("Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, i, BSPVERSION);

With :


		// CSL - epca@powerup.com.au

                file://if (i != BSPVERSION)

                if ((i != BSPVERSION) && (i != CSLBSPVERSION))

                    Sys_Error ("Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, i, CSLBSPVERSION);



                bspextensions = i - BSPVERSION;

                // CSL

Since you have already completed the Coloured Dynamic Lights tutorial, there is very little that needs to be done to get the engine to render the coloured lights. In fact, only the for loop that reads the lightmap needs to be changed, everything else is taken care of by the Coloured Dynamic Lights code.

In gl_rsurf.c in R_BuildLightMap
Replace :



		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

With :


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

                {

                    // CDL - epca@powerup.com.au

                    // CSL - epca@powerup.com.au

                    if (bspextensions)

                    {

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

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

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

                    }

                    else

                    {

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

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

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

                    }

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

                    // CSL

                    // CDL

                }

                // CSL - epca@powerup.com.au

                if (bspextensions)

                    lightmap += size * 4;

                else

                    lightmap += size;   // skip to next lightmap

                // CSL

Q.S.G Note:
===========
Since SaRcaZm originally wrote this tutorial, it has came to light that several video cards, with various drivers, exibit the "white" effect. This has been determined to be caused by a few lines of code that iD forgot to impliment in it's RGBA code.

To fix this, find "R_BlendLightmaps" in GL_RSurf.c, and add the code shown in blue, to the top part of this function:



  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} else {
  glBlendFunc (GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
}

if (!r_lightmap.value)
{

Now scroll down to the end and add in THESE lines [as highlighted in blue]:

  glColor4f (1,1,1,1);
} else {
 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

 glDepthMask (1); // back to normal Z buffering
Fin :)

Creating Maps
=============

To add coloured lights to your map, all you need to do is add a "_color" or "color" tag to the light entity

e.g.



     {

        "classname" "light"

        "origin" "-128 128 -64"

        "_color" "0 255 0"

     }

will result in a green light at -128, 128, -64. The "_color" tag is preferrable to the "color" tag as Quake won't give a 'color' is not a field error for "_color". This could be fixed, but I don't want to do everything for you. If you really want a solution to this, tell me and I will look into it. If the colour field is left out, it defaults to a standard white light that will act exactly as the quake ones did.

Here is a simple map, that demonstrates the coloured lighting, as well as the limits of my mapping ability. If anyone creates a cool level using this code, I would love to see it, as with my abilities, I will never get to see the full extent of this effect.


{

"classname" "worldspawn"

"message" "Coloured Static Lights Example\nby SaRcaZm"

"wad" "Quake.wad"

{

( -192 192 64 ) ( 256 192 64 ) ( 256 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -192 192 64 ) ( -192 -320 64 ) ( -192 -320 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 192 -192 ) ( 256 -320 -192 ) ( 256 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 192 64 ) ( -192 192 64 ) ( -192 192 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 -320 -192 ) ( -192 -320 -192 ) ( -192 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 224 -288 32 ) ( 224 160 32 ) ( -160 160 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

}

{

( -192 -320 -192 ) ( 256 -320 -192 ) ( 256 192 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -192 192 64 ) ( -192 -320 64 ) ( -192 -320 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 192 -192 ) ( 256 -320 -192 ) ( 256 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 192 64 ) ( -192 192 64 ) ( -192 192 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 -320 -192 ) ( -192 -320 -192 ) ( -192 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 224 160 -160 ) ( 224 -288 -160 ) ( -160 -288 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

}

{

( -192 192 64 ) ( -192 -320 64 ) ( -192 -320 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 192 64 ) ( -192 192 64 ) ( -192 192 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 -320 -192 ) ( -192 -320 -192 ) ( -192 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 160 32 ) ( 224 160 32 ) ( 224 -288 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 -288 -160 ) ( 224 -288 -160 ) ( 224 160 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 -288 -160 ) ( -160 -288 32 ) ( -160 160 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

}

{

( 256 192 -192 ) ( 256 -320 -192 ) ( 256 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 192 64 ) ( -192 192 64 ) ( -192 192 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 256 -320 -192 ) ( -192 -320 -192 ) ( -192 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 160 32 ) ( 224 160 32 ) ( 224 -288 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 -288 -160 ) ( 224 -288 -160 ) ( 224 160 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 224 -288 32 ) ( 224 -288 -160 ) ( 224 160 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

}

{

( 256 192 64 ) ( -192 192 64 ) ( -192 192 -192 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 160 32 ) ( 224 160 32 ) ( 224 -288 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 -288 -160 ) ( 224 -288 -160 ) ( 224 160 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 160 32 ) ( -160 -288 32 ) ( -160 -288 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 224 160 -160 ) ( 224 -288 -160 ) ( 224 -288 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 160 -160 ) ( -160 160 32 ) ( 224 160 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

}

{

( 256 -320 -192 ) ( -192 -320 -192 ) ( -192 -320 64 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 160 32 ) ( 224 160 32 ) ( 224 -288 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 -288 -160 ) ( 224 -288 -160 ) ( 224 160 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 160 32 ) ( -160 -288 32 ) ( -160 -288 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( 224 160 -160 ) ( 224 -288 -160 ) ( 224 -288 32 ) AFLOOR1_3 0 0 0 1.000000 1.000000

( -160 -288 32 ) ( -160 -288 -160 ) ( 224 -288 -160 ) AFLOOR1_3 0 0 0 1.000000 1.000000

}

}

{

"classname" "info_player_start"

"origin" "64 -64 -64"

}

{

"classname" "light"

"origin" "-128 -256 -64"

"_color" "255 0 0"

}

{

"classname" "light"

"origin" "192 -256 -64"

"_color" "0 0 255"

}

{

"classname" "light"

"origin" "-128 128 -64"

"_color" "0 255 0"

}

{

"classname" "light"

"origin" "192 128 -64"

}

Conclusion
==========

Notes :
1. Values less than 200 or so don't seem to make that much of a difference. Not sure why, you can tweak this by changing the 255 constants in lines 108, 109 and 110 of SingleLightFace.

2. You can overbright colours, meaning you can use values of greater than 255.

3. Green doesn't come out very well. May have to overbright it if you want it to show.


Now you will be able to have your Quake worlds lit with a variety of colours, which will give you map makers out there more freedom to create better looking maps. Any questions, comments, corrections? I would love to hear them.
Contact me at epca@powerup.com.au



 
Sign up
Login:
Passwd:
[Remember Me]