From 741d341332b8a2fdb0d8b0fa6759b3553fdbcab0 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Thu, 5 Feb 2015 11:43:04 -0700 Subject: [PATCH] Dirtmapping (ambient occlusion) from q3map2 --- include/common/mathlib.h | 21 +++ include/light/entities.h | 11 ++ include/light/light.h | 20 +++ light/entities.c | 68 ++++++++ light/light.c | 67 ++++++++ light/ltface.c | 332 ++++++++++++++++++++++++++++++++++++++- man/light.1 | 45 ++++++ 7 files changed, 559 insertions(+), 5 deletions(-) diff --git a/include/common/mathlib.h b/include/common/mathlib.h index 19cdfe33..2768ca7b 100644 --- a/include/common/mathlib.h +++ b/include/common/mathlib.h @@ -39,6 +39,8 @@ typedef vec_t vec3_t[3]; #define Q_PI 3.14159265358979323846 +#define DEG2RAD( a ) ( ( a ) * ( ( 2 * Q_PI ) / 360.0 ) ) + extern const vec3_t vec3_origin; #define EQUAL_EPSILON 0.001 @@ -91,12 +93,31 @@ VectorInverse(vec3_t v) v[2] = -v[2]; } +static inline void +VectorSet(vec3_t out, vec_t x, vec_t y, vec_t z) +{ + out[0] = x; + out[1] = y; + out[2] = z; +} + static inline vec_t Q_rint(vec_t in) { return (vec_t)(floor(in + 0.5)); } +/* + Random() + returns a pseudorandom number between 0 and 1 + */ + +static inline vec_t +Random( void ) +{ + return (vec_t) rand() / RAND_MAX; +} + static inline void VectorMA(const vec3_t va, vec_t scale, const vec3_t vb, vec3_t vc) { diff --git a/include/light/entities.h b/include/light/entities.h index 2f1b1bae..f7006b6a 100644 --- a/include/light/entities.h +++ b/include/light/entities.h @@ -68,6 +68,17 @@ typedef struct entity_s { float anglescale; int style; + /* worldspawn only */ + vec_t dirtdepth; + int dirtmode; + int sunlight_dirt; + int minlight_dirt; + + /* worldspawn, light entities */ + vec_t dirtscale; + vec_t dirtgain; + int dirt; + char target[MAX_ENT_VALUE]; char targetname[MAX_ENT_VALUE]; struct epair_s *epairs; diff --git a/include/light/light.h b/include/light/light.h index 3aff73b0..732837b9 100644 --- a/include/light/light.h +++ b/include/light/light.h @@ -104,6 +104,24 @@ extern lightsample_t minlight; extern lightsample_t sunlight; extern vec3_t sunvec; +/* dirt */ + +extern qboolean dirty; // should any dirtmapping take place? +extern qboolean dirtDebug; +extern int dirtMode; +extern float dirtDepth; +extern float dirtScale; +extern float dirtGain; + +extern qboolean globalDirt; // apply dirt to all lights (unless they override it)? +extern qboolean sunlightDirt; // apply dirt to sunlight? +extern qboolean minlightDirt; // apply dirt to minlight? + +extern qboolean dirtModeSetOnCmdline; +extern qboolean dirtDepthSetOnCmdline; +extern qboolean dirtScaleSetOnCmdline; +extern qboolean dirtGainSetOnCmdline; + /* * Return space for the lightmap and colourmap at the same time so it can * be done in a thread-safe manner. @@ -116,4 +134,6 @@ extern byte *lit_filebase; extern int oversample; extern qboolean write_litfile; +void SetupDirt(); + #endif /* __LIGHT_LIGHT_H__ */ diff --git a/light/entities.c b/light/entities.c index a6032059..ff35464f 100644 --- a/light/entities.c +++ b/light/entities.c @@ -417,6 +417,26 @@ LoadEntities(const bsp2_t *bsp) normalize_color_format(minlight.color); } else if (!strcmp(key, "_anglesense") || !strcmp(key, "_anglescale")) entity->anglescale = atof(com_token); + else if (!strcmp(key, "_dirtdepth")) + entity->dirtdepth = atof(com_token); + else if (!strcmp(key, "_dirtmode")) + entity->dirtmode = atoi(com_token); + else if (!strcmp(key, "_sunlight_dirt")) + entity->sunlight_dirt = atoi(com_token); + else if (!strcmp(key, "_minlight_dirt")) + entity->minlight_dirt = atoi(com_token); + else if (!strcmp(key, "_dirtscale")) + entity->dirtscale = atof(com_token); + else if (!strcmp(key, "_dirtgain")) + entity->dirtgain = atof(com_token); + else if (!strcmp(key, "_dirt")) { + entity->dirt = atoi(com_token); + if (entity->dirt == 1 && !dirty) { + logprint("entity with \"_dirt\" \"1\" detected, enabling " + "dirtmapping.\n"); + dirty = true; + } + } } /* @@ -445,6 +465,54 @@ LoadEntities(const bsp2_t *bsp) } if (entity->anglescale >= 0 && entity->anglescale <= 1.0) sun_anglescale = entity->anglescale; + + if (entity->dirtdepth && !dirtDepthSetOnCmdline) { + dirtDepth = entity->dirtdepth; + logprint("Using dirtdepth value %f from worldspawn.\n", + dirtDepth); + } + if (entity->dirtmode && !dirtModeSetOnCmdline) { + dirtMode = entity->dirtmode; + logprint("Using dirtmode value %i from worldspawn.\n", + dirtMode); + } + if (entity->dirtscale && !dirtScaleSetOnCmdline) { + dirtScale = entity->dirtscale; + logprint("Using dirtscale value %f from worldspawn.\n", + dirtScale); + } + if (entity->dirtgain && !dirtGainSetOnCmdline) { + dirtGain = entity->dirtgain; + logprint("Using dirtgain value %f from worldspawn.\n", + dirtGain); + } + if (entity->dirt == 1) { + globalDirt = true; + dirty = true; + logprint("Global dirtmapping enabled in worldspawn.\n"); + } + + if (entity->sunlight_dirt == 1) { + sunlightDirt = true; + dirty = true; + logprint("Sunlight dirtmapping enabled in worldspawn.\n"); + } else if (entity->sunlight_dirt == -1) { + sunlightDirt = false; + logprint("Sunlight dirtmapping disabled in worldspawn.\n"); + } else { + sunlightDirt = globalDirt; + } + + if (entity->minlight_dirt == 1) { + minlightDirt = true; + dirty = true; + logprint("Minlight dirtmapping enabled in worldspawn.\n"); + } else if (entity->minlight_dirt == -1) { + minlightDirt = false; + logprint("Minlight dirtmapping disabled in worldspawn.\n"); + } else { + minlightDirt = globalDirt; + } } } diff --git a/light/light.c b/light/light.c index 2d250d07..425525c2 100644 --- a/light/light.c +++ b/light/light.c @@ -35,6 +35,24 @@ lightsample_t minlight = { 0, { 255, 255, 255 } }; lightsample_t sunlight = { 0, { 255, 255, 255 } }; vec3_t sunvec = { 0, 0, 16384 }; /* defaults to straight down */ +/* dirt */ +qboolean dirty = false; +qboolean dirtDebug = false; +int dirtMode = 0; +float dirtDepth = 128.0f; +float dirtScale = 1.0f; +float dirtGain = 1.0f; + +qboolean globalDirt = false; +qboolean sunlightDirt = false; +qboolean minlightDirt = false; + +qboolean dirtSetOnCmdline = false; +qboolean dirtModeSetOnCmdline = false; +qboolean dirtDepthSetOnCmdline = false; +qboolean dirtScaleSetOnCmdline = false; +qboolean dirtGainSetOnCmdline = false; + byte *filebase; // start of lightmap data static byte *file_p; // start of free space after data static byte *file_end; // end of free space for lightmap data @@ -257,6 +275,50 @@ main(int argc, const char **argv) anglescale = sun_anglescale = atoi(argv[++i]); else Error("-anglesense requires a numeric argument (0.0 - 1.0)"); + } else if ( !strcmp( argv[ i ], "-dirty" ) ) { + dirty = true; + globalDirt = true; + sunlightDirt = true; + minlightDirt = true; + logprint( "Dirtmapping enabled globally\n" ); + } else if ( !strcmp( argv[ i ], "-dirtdebug" ) || !strcmp( argv[ i ], "-debugdirt" ) ) { + dirty = true; + globalDirt = true; + dirtDebug = true; + logprint( "Dirtmap debugging enabled\n" ); + } else if ( !strcmp( argv[ i ], "-dirtmode" ) ) { + dirtModeSetOnCmdline = true; + dirtMode = atoi( argv[ ++i ] ); + if ( dirtMode != 0 && dirtMode != 1 ) { + dirtMode = 0; + } + if ( dirtMode == 1 ) { + logprint( "Enabling randomized dirtmapping\n" ); + } + else{ + logprint( "Enabling ordered dirtmapping\n" ); + } + } else if ( !strcmp( argv[ i ], "-dirtdepth" ) ) { + dirtDepthSetOnCmdline = true; + dirtDepth = atof( argv[ ++i ] ); + if ( dirtDepth <= 0.0f ) { + dirtDepth = 128.0f; + } + logprint( "Dirtmapping depth set to %.1f\n", dirtDepth ); + } else if ( !strcmp( argv[ i ], "-dirtscale" ) ) { + dirtScaleSetOnCmdline = true; + dirtScale = atof( argv[ ++i ] ); + if ( dirtScale <= 0.0f ) { + dirtScale = 1.0f; + } + logprint( "Dirtmapping scale set to %.1f\n", dirtScale ); + } else if ( !strcmp( argv[ i ], "-dirtgain" ) ) { + dirtGainSetOnCmdline = true; + dirtGain = atof( argv[ ++i ] ); + if ( dirtGain <= 0.0f ) { + dirtGain = 1.0f; + } + logprint( "Dirtmapping gain set to %.1f\n", dirtGain ); } else if (argv[i][0] == '-') Error("Unknown option \"%s\"", argv[i]); else @@ -267,6 +329,7 @@ main(int argc, const char **argv) printf("usage: light [-threads num] [-extra|-extra4]\n" " [-light num] [-addmin] [-anglescale|-anglesense]\n" " [-dist n] [-range n] [-gate n] [-lit]\n" + " [-dirty] [-dirtdebug] [-dirtmode n] [-dirtdepth n] [-dirtscale n] [-dirtgain n]\n" " [-soft [n]] bspfile\n"); exit(1); } @@ -301,6 +364,10 @@ main(int argc, const char **argv) ConvertBSPFormat(BSP2VERSION, &bspdata); LoadEntities(bsp); + + if (dirty) + SetupDirt(); + MakeTnodes(bsp); modelinfo = malloc(bsp->nummodels * sizeof(*modelinfo)); FindModelInfo(bsp); diff --git a/light/ltface.c b/light/ltface.c index e6c93097..6720069b 100644 --- a/light/ltface.c +++ b/light/ltface.c @@ -158,6 +158,12 @@ typedef struct { int numpoints; vec3_t points[SINGLEMAP]; + + /* + raw ambient occlusion amount per sample point, 0-1, where 1 is + fully occluded. dirtgain/dirtscale are not applied yet + */ + vec_t occlusion[SINGLEMAP]; } lightsurf_t; typedef struct { @@ -705,6 +711,69 @@ Light_ClampMin(lightsample_t *sample, const vec_t light, const vec3_t color) } } +/* + * ============ + * Dirt_GetScaleFactor + * + * returns scale factor for dirt/ambient occlusion + * ============ + */ +static inline vec_t +Dirt_GetScaleFactor(vec_t occlusion, const entity_t *entity) +{ + vec_t light_dirtgain = dirtGain; + vec_t light_dirtscale = dirtScale; + vec_t outDirt; + qboolean usedirt; + + /* is dirt processing disabled entirely? */ + if (!dirty) + return 1.0f; + + /* should this light be affected by dirt? */ + if (entity && entity->dirt == -1) { + usedirt = false; + } else if (entity && entity->dirt == 1) { + usedirt = true; + } else { + usedirt = globalDirt; + } + + /* if not, quit */ + if (!usedirt) + return 1.0; + + /* override the global scale and gain values with the light-specific + values, if present */ + if (entity) { + if (entity->dirtgain) + light_dirtgain = entity->dirtgain; + if (entity->dirtscale) + light_dirtscale = entity->dirtscale; + } + + /* early out */ + if ( occlusion <= 0.0f ) { + return 1.0f; + } + + /* apply gain (does this even do much? heh) */ + outDirt = pow( occlusion, light_dirtgain ); + if ( outDirt > 1.0f ) { + outDirt = 1.0f; + } + + /* apply scale */ + outDirt *= light_dirtscale; + if ( outDirt > 1.0f ) { + outDirt = 1.0f; + } + + /* return to sender */ + return 1.0f - outDirt; +} + + /* * ================ * LightFace_Entity @@ -773,6 +842,8 @@ LightFace_Entity(const entity_t *entity, const lightsample_t *light, angle = (1.0 - entity->anglescale) + entity->anglescale * angle; add = GetLightValue(light, entity, dist) * angle * spotscale; + add *= Dirt_GetScaleFactor(lightsurf->occlusion[i], entity); + Light_Add(sample, add, light->color); /* Check if we really hit, ignore tiny lights */ @@ -822,9 +893,13 @@ LightFace_Sky(const lightsample_t *light, const vec3_t vector, sample = lightmap->samples; surfpoint = lightsurf->points[0]; for (i = 0; i < lightsurf->numpoints; i++, sample++, surfpoint += 3) { + vec_t value; if (!TestSky(surfpoint, vector, shadowself)) continue; - Light_Add(sample, angle * light->light, light->color); + value = angle * light->light; + if (sunlightDirt) + value *= Dirt_GetScaleFactor(lightsurf->occlusion[i], NULL); + Light_Add(sample, value, light->color); if (!hit && sample->light >= 1) hit = true; } @@ -857,10 +932,13 @@ LightFace_Min(const lightsample_t *light, hit = false; sample = lightmap->samples; for (i = 0; i < lightsurf->numpoints; i++, sample++) { + vec_t value = light->light; + if (minlightDirt) + value *= Dirt_GetScaleFactor(lightsurf->occlusion[i], NULL); if (addminlight) - Light_Add(sample, light->light, light->color); + Light_Add(sample, value, light->color); else - Light_ClampMin(sample, light->light, light->color); + Light_ClampMin(sample, value, light->color); if (!hit && sample->light >= 1) hit = true; } @@ -875,13 +953,15 @@ LightFace_Min(const lightsample_t *light, surfpoint = lightsurf->points[0]; for (j = 0; j < lightsurf->numpoints; j++, sample++, surfpoint += 3) { if (addminlight || sample->light < entity->light.light) { + vec_t value = entity->light.light; trace = TestLight(entity->origin, surfpoint, shadowself); if (!trace) continue; + value *= Dirt_GetScaleFactor(lightsurf->occlusion[j], entity); if (addminlight) - Light_Add(sample, entity->light.light, entity->light.color); + Light_Add(sample, value, entity->light.color); else - Light_ClampMin(sample, entity->light.light, entity->light.color); + Light_ClampMin(sample, value, entity->light.color); } if (!hit && sample->light >= 1) hit = true; @@ -892,6 +972,240 @@ LightFace_Min(const lightsample_t *light, Lightmap_Save(lightmaps, lightsurf, lightmap, 0); } +/* + * ============= + * LightFace_DirtDebug + * ============= + */ +static void +LightFace_DirtDebug(const lightsurf_t *lightsurf, lightmap_t *lightmaps) +{ + int i; + lightsample_t *sample; + lightmap_t *lightmap; + + /* use a style 0 light map */ + lightmap = Lightmap_ForStyle(lightmaps, 0); + + /* Overwrite each point with the dirt value for that sample... */ + sample = lightmap->samples; + for (i = 0; i < lightsurf->numpoints; i++, sample++) { + sample->light = 255 * Dirt_GetScaleFactor(lightsurf->occlusion[i], NULL); + VectorSet(sample->color, sample->light, sample->light, sample->light); + } + + Lightmap_Save(lightmaps, lightsurf, lightmap, 0); +} + +/* Dirtmapping borrowed from q3map2, originally by RaP7oR */ + +#define DIRT_CONE_ANGLE 88 /* degrees */ +#define DIRT_NUM_ANGLE_STEPS 16 +#define DIRT_NUM_ELEVATION_STEPS 3 +#define DIRT_NUM_VECTORS ( DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS ) + +static vec3_t dirtVectors[ DIRT_NUM_VECTORS ]; +static int numDirtVectors = 0; + +/* + * ============ + * SetupDirt + * + * sets up dirtmap (ambient occlusion) + * ============ + */ +void SetupDirt( void ) { + int i, j; + float angle, elevation, angleStep, elevationStep; + + /* note it */ + logprint("--- SetupDirt ---\n" ); + + /* calculate angular steps */ + angleStep = DEG2RAD( 360.0f / DIRT_NUM_ANGLE_STEPS ); + elevationStep = DEG2RAD( DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS ); + + /* iterate angle */ + angle = 0.0f; + for ( i = 0, angle = 0.0f; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep ) { + /* iterate elevation */ + for ( j = 0, elevation = elevationStep * 0.5f; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) { + dirtVectors[ numDirtVectors ][ 0 ] = sin( elevation ) * cos( angle ); + dirtVectors[ numDirtVectors ][ 1 ] = sin( elevation ) * sin( angle ); + dirtVectors[ numDirtVectors ][ 2 ] = cos( elevation ); + numDirtVectors++; + } + } + + /* emit some statistics */ + logprint("%9d dirtmap vectors\n", numDirtVectors ); +} + +/* + * ============ + * DirtTrace + * + * returns true if the trace from start to stop hits something solid, + * or if it started in the void. + * ============ + */ +qboolean +DirtTrace(const vec3_t start, const vec3_t stop, const dmodel_t *self, vec3_t hitpoint_out) +{ + const dmodel_t *const *model; + const int traceflags = TRACE_HIT_SOLID | TRACE_HIT_SKY; + int result = TRACE_HIT_NONE; + tracepoint_t hitpoint; + + if (self) { + result = TraceLine(self, traceflags, start, stop, &hitpoint); + if (result == -TRACE_HIT_SOLID) { + /* We started in the void, which ideally wouldn't happen, + but does (say on e1m1). Return the start point as the hitpoint, + which will make fully black dirt. + */ + VectorCopy(start, hitpoint_out); + return true; + } else if (result == TRACE_HIT_SOLID) { + VectorCopy(hitpoint.point, hitpoint_out); + return true; + } + } + + /* Check against the list of global shadow casters */ + for (model = tracelist; *model; model++) { + result = TraceLine(*model, traceflags, start, stop, &hitpoint); + if (result == -TRACE_HIT_SOLID) { + VectorCopy(start, hitpoint_out); + return true; + } else if (result == TRACE_HIT_SOLID) { + VectorCopy(hitpoint.point, hitpoint_out); + return true; + } + } + + return false; +} + +/* + * ============ + * DirtForSample + * ============ + */ +static vec_t +DirtForSample(const dmodel_t *model, const vec3_t origin, const vec3_t normal){ + int i; + float gatherDirt, angle, elevation, ooDepth; + vec3_t worldUp, myUp, myRt, temp, direction, displacement; + vec3_t traceEnd, traceHitpoint; + + /* dummy check */ + if ( !dirty ) { + return 1.0f; + } + + /* setup */ + gatherDirt = 0.0f; + ooDepth = 1.0f / dirtDepth; + + /* check if the normal is aligned to the world-up */ + if ( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f ) { + if ( normal[ 2 ] == 1.0f ) { + VectorSet( myRt, 1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } else if ( normal[ 2 ] == -1.0f ) { + VectorSet( myRt, -1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + } else { + VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); + CrossProduct( normal, worldUp, myRt ); + VectorNormalize( myRt ); + CrossProduct( myRt, normal, myUp ); + VectorNormalize( myUp ); + } + + /* 1 = random mode, 0 (well everything else) = non-random mode */ + if ( dirtMode == 1 ) { + /* iterate */ + for ( i = 0; i < numDirtVectors; i++ ) { + /* get random vector */ + angle = Random() * DEG2RAD( 360.0f ); + elevation = Random() * DEG2RAD( DIRT_CONE_ANGLE ); + temp[ 0 ] = cos( angle ) * sin( elevation ); + temp[ 1 ] = sin( angle ) * sin( elevation ); + temp[ 2 ] = cos( elevation ); + + /* transform into tangent space */ + direction[ 0 ] = myRt[ 0 ] * temp[ 0 ] + myUp[ 0 ] * temp[ 1 ] + normal[ 0 ] * temp[ 2 ]; + direction[ 1 ] = myRt[ 1 ] * temp[ 0 ] + myUp[ 1 ] * temp[ 1 ] + normal[ 1 ] * temp[ 2 ]; + direction[ 2 ] = myRt[ 2 ] * temp[ 0 ] + myUp[ 2 ] * temp[ 1 ] + normal[ 2 ] * temp[ 2 ]; + + /* set endpoint */ + VectorMA( origin, dirtDepth, direction, traceEnd ); + + /* trace */ + if (DirtTrace(origin, traceEnd, model, traceHitpoint)) { + VectorSubtract( traceHitpoint, origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + } + } else { + /* iterate through ordered vectors */ + for ( i = 0; i < numDirtVectors; i++ ) { + /* transform vector into tangent space */ + direction[ 0 ] = myRt[ 0 ] * dirtVectors[ i ][ 0 ] + myUp[ 0 ] * dirtVectors[ i ][ 1 ] + normal[ 0 ] * dirtVectors[ i ][ 2 ]; + direction[ 1 ] = myRt[ 1 ] * dirtVectors[ i ][ 0 ] + myUp[ 1 ] * dirtVectors[ i ][ 1 ] + normal[ 1 ] * dirtVectors[ i ][ 2 ]; + direction[ 2 ] = myRt[ 2 ] * dirtVectors[ i ][ 0 ] + myUp[ 2 ] * dirtVectors[ i ][ 1 ] + normal[ 2 ] * dirtVectors[ i ][ 2 ]; + + /* set endpoint */ + VectorMA( origin, dirtDepth, direction, traceEnd ); + + /* trace */ + if (DirtTrace(origin, traceEnd, model, traceHitpoint)) { + VectorSubtract( traceHitpoint, origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + } + } + + /* direct ray */ + VectorMA( origin, dirtDepth, normal, traceEnd ); + + /* trace */ + if (DirtTrace(origin, traceEnd, model, traceHitpoint)) { + VectorSubtract( traceHitpoint, origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + + /* save gatherDirt, the rest of the scaling of the dirt value is done + per-light */ + + return gatherDirt / ( numDirtVectors + 1 ); +} + + +/* + * ============ + * LightFace_CalculateDirt + * ============ + */ +static void +LightFace_CalculateDirt(lightsurf_t *lightsurf) +{ + const modelinfo_t *modelinfo = lightsurf->modelinfo; + const plane_t *plane = &lightsurf->plane; + const vec_t *surfpoint; + int i; + + /* Check each point... */ + surfpoint = lightsurf->points[0]; + for (i = 0; i < lightsurf->numpoints; i++, surfpoint += 3) { + lightsurf->occlusion[i] = DirtForSample(modelinfo->model, surfpoint, plane->normal); + } +} + + static void WriteLightmaps(bsp2_dface_t *face, const lightsurf_t *lightsurf, const lightmap_t *lightmaps) @@ -992,6 +1306,10 @@ LightFace(bsp2_dface_t *face, const modelinfo_t *modelinfo, Lightsurf_Init(modelinfo, face, bsp, &lightsurf); Lightmaps_Init(lightmaps, MAXLIGHTMAPS + 1); + /* calculate dirt (ambient occlusion) but don't use it yet */ + if (dirty) + LightFace_CalculateDirt(&lightsurf); + /* * The lighting procedure is: cast all positive lights, fix * minlight levels, then cast all negative lights. Finally, we @@ -1024,6 +1342,10 @@ LightFace(bsp2_dface_t *face, const modelinfo_t *modelinfo, if (sunlight.light < 0) LightFace_Sky(&sunlight, sunvec, &lightsurf, lightmaps); + /* replace lightmaps with AO for debugging */ + if (dirtDebug) + LightFace_DirtDebug(&lightsurf, lightmaps); + /* Fix any negative values */ for (i = 0; i < MAXLIGHTMAPS; i++) { if (lightmaps[i].style == 255) diff --git a/man/light.1 b/man/light.1 index 1493ee17..8c67e277 100644 --- a/man/light.1 +++ b/man/light.1 @@ -62,6 +62,15 @@ surface (more detailed explanation in the "_anglescale" key below). Force generation of a .lit file, even if your map does not have any coloured lights. By default, light will automatically generate the .lit file when needed. +.IP "\fB\-dirty\fP" +Globally enables dirtmapping (ambient occlusion), overriding the "_dirt" worldspawn +key. See "_dirt" for more details. +.IP "\fB\-dirtdebug\fP" +Implies "-dirty", and renders just the dirtmap against a fullbright background, +ignoring all lights in the map. Useful for previewing and turning the dirt settings. +.IP "\fB\-dirtmode n\fP | \fB\-dirtdepth n\fP | \fB\-dirtscale n\fP | \fB\-dirtgain n\fP" +Fine-tune the dirtmapping, overriding the corresponding worldspawn keys. See the +worldspawn keys below. .SH "MODEL ENTITY KEYS" @@ -93,6 +102,33 @@ sunlight. RGB component values are between 0 and 255 (between 0 and 1 is also accepted). Default is white light ("255 255 255"). +.IP "\fB""_dirt"" ""n""\fP" +1 enables dirtmapping (ambient occlusion) on all lights, borrowed from q3map2. This adds shadows +to corners and crevices. You can override the global setting for specific lights with the +"_dirt" light entitiy key or "_sunlight_nodirt" and "_minlight_nodirt" worldspawn keys. +Default is no dirtmapping (-1). + +.IP "\fB""_sunlight_dirt"" ""n""\fP" +1 enables dirtmapping (ambient occlusion) on sunlight, -1 to disable (making it illuminate the dirtmapping shadows). Default is to use the value of "_dirt". + +.IP "\fB""_minlight_dirt"" ""n""\fP" +1 enables dirtmapping (ambient occlusion) on minlight, -1 to disable. Default is to use the value of "_dirt". + +.IP "\fB""_dirtmode"" ""n""\fP" +Choose between ordered (0, default) and randomized (1) dirtmapping. + +.IP "\fB""_dirtdepth"" ""n""\fP" +Maximum depth of occlusion checking for dirtmapping, default 128. + +.IP "\fB""_dirtscale"" ""n""\fP" +Scale factor used in dirt calculations, default 1. Lower values (e.g. 0.5) make +the dirt fainter, 2.0 would create much darker shadows. + +.IP "\fB""_dirtgain"" ""n""\fP" +Exponent used in dirt calculation, default 1. Lower values (e.g. 0.5) make the +shadows darker and stretch further away from corners. + + .SS "Model Entity Keys" .PP @@ -188,6 +224,15 @@ on a surface has on the brightness of the surface. \fIn\fP must be between 0.0 and 1.0. Smaller values mean less attenuation, with zero meaning that angle of incidence has no effect at all on the brightness. Default 0.5. +.IP "\fB""_dirtscale"" ""n""\fP | \fB""_dirtgain"" ""n""\fP" +Override the global "_dirtscale" or "_dirtgain" settings to change how this +light is affected by dirtmapping (ambient occlusion). See descriptions of these +keys in the worldspawn section. + +.IP "\fB""_dirt"" ""n""\fP" +Overrides the worldspawn setting of "_dirt" for this particular light. -1 to disable dirtmapping (ambient occlusion) for this light, making it illuminate the dirtmapping shadows. 1 to enable ambient occlusion for this light. Default is to defer to the worldspawn setting. + + .SH AUTHOR Written by Kevin Shanahan (aka Tyrann) .br