Dirtmapping (ambient occlusion) from q3map2

This commit is contained in:
Eric Wasylishen 2015-02-05 11:43:04 -07:00
parent 02196e2efa
commit 741d341332
7 changed files with 559 additions and 5 deletions

View File

@ -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)
{

View File

@ -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;

View File

@ -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__ */

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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