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 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. struct entity_s *targetent; // CSL - epca@powerup.com.au // Store the colour of the light int lightcolour[3]; // CSL In Entities.c in LoadEntities After : else if (!strcmp(key, "angle")) { entity->angle = atof(com_token); } // 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 In ltface.c in the definition of lightinfo_t After : dface_t *face; // CSL - epca@powerup.com.au vec3_t lightmapcolours[MAXLIGHTMAPS][SINGLEMAP]; // CSL
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 } }
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; } } }
In the common directory at the base of the utils package, you will find
bspfile.h #define BSPVERSION 29 // CSL - epca@powerup.com.au #define BSPVERSION 30 // CSL
After : #define BSPVERSION 29 // CSL - epca@powerup.com.au #define CSLBSPVERSION 30 // CSL After : cvar_t temp1 = {"temp1","0"}; // CSL - epca@powerup.com.au // Use bspextensions to determine changes in format to bsp files int bspextensions = 0; // CSL extern double realtime; // not bounded in any way, changed at // start of every frame, never reset // CSL - epca@powerup.com.au extern int bspextensions; // CSL if (i != BSPVERSION) Sys_Error ("Mod_LoadBrushModel: %s has wrong version number (%i should be %i)", mod->name, i, BSPVERSION); // 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
In gl_rsurf.c in R_BuildLightMap 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 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 =========== 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); 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" } 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" } ========== 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.
|