Paranoia2/cl_dll/render/gl_slight.cpp

733 lines
20 KiB
C++

/*
gl_slight.cpp - static lighting
Copyright (C) 2019 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "hud.h"
#include <stringlib.h>
#include "cl_util.h"
#include "pm_defs.h"
#include "event_api.h"
#include "gl_local.h"
#include "gl_studio.h"
#include "gl_world.h"
#define NUMVERTEXNORMALS 162
#define AMBIENT_CUBE_SCALE 1.0f
// lightpoint flags
#define LIGHTINFO_HITSKY (1<<0)
#define LIGHTINFO_AVERAGE (1<<1) // compute average color from lightmap
#define LIGHTINFO_STYLES (1<<2)
typedef struct
{
vec3_t diffuse;
vec3_t average;
colorVec lightmap;
vec3_t normal;
msurface_t *surf; // may be NULL
float fraction; // hit fraction
vec3_t origin;
int status;
} lightpoint_t;
static vec_t g_anorms[NUMVERTEXNORMALS][3] =
{
#include "anorms.h"
};
static Vector g_BoxDirections[6] =
{
Vector( 1.0f, 0.0f, 0.0f ),
Vector(-1.0f, 0.0f, 0.0f ),
Vector( 0.0f, 1.0f, 0.0f ),
Vector( 0.0f, -1.0f, 0.0f ),
Vector( 0.0f, 0.0f, 1.0f ),
Vector( 0.0f, 0.0f, -1.0f ),
};
/*
=======================================================================
AMBIENT & DIFFUSE LIGHTING
=======================================================================
*/
/*
=================
R_FindAmbientSkyLight
=================
*/
static mworldlight_t *R_FindAmbientSkyLight( void )
{
// find any ambient lights
for( int i = 0; i < world->numworldlights; i++ )
{
if( world->worldlights[i].emittype == emit_skylight )
return &world->worldlights[i];
}
return NULL;
}
/*
=================
R_LightTraceFilter
=================
*/
static int R_LightTraceFilter( physent_t *pe )
{
if( !pe || !pe->model )
return 1;
return 0;
}
/*
=================
R_GetDirectLightFromSurface
=================
*/
static bool R_GetDirectLightFromSurface( msurface_t *surf, const Vector &point, lightpoint_t *info )
{
mextrasurf_t *es = surf->info;
float ds, dt, s, t;
color24 *lm, *dm;
mtexinfo_t *tex;
int map, size;
tex = surf->texinfo;
if( FBitSet( surf->flags, SURF_DRAWSKY ))
{
info->status |= LIGHTINFO_HITSKY;
return false; // no lightmaps
}
if( FBitSet( surf->flags, SURF_DRAWTILED ))
return false; // no lightmaps
s = DotProduct( point, es->lmvecs[0] ) + es->lmvecs[0][3];
t = DotProduct( point, es->lmvecs[1] ) + es->lmvecs[1][3];
if( s < es->lightmapmins[0] || t < es->lightmapmins[1] )
return false;
ds = s - es->lightmapmins[0];
dt = t - es->lightmapmins[1];
if( ds > es->lightextents[0] || dt > es->lightextents[1] )
return false;
if( !surf->samples )
return true;
int sample_size = Mod_SampleSizeForFace( surf );
int smax = (es->lightextents[0] / sample_size) + 1;
int tmax = (es->lightextents[1] / sample_size) + 1;
ds /= sample_size;
dt /= sample_size;
lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds );
colorVec localDiffuse = { 0, 0, 0, 0 };
size = smax * tmax;
if( es->normals )
{
Vector vec_x, vec_y, vec_z;
matrix3x3 mat;
dm = es->normals + Q_rint( dt ) * smax + Q_rint( ds );
// flat TBN for better results
vec_x = Vector( surf->info->lmvecs[0] );
vec_y = Vector( surf->info->lmvecs[1] );
if( FBitSet( surf->flags, SURF_PLANEBACK ))
vec_z = -surf->plane->normal;
else vec_z = surf->plane->normal;
// create tangent space rotational matrix
mat.SetForward( vec_x.Normalize( ));
mat.SetRight( -vec_y.Normalize( ));
mat.SetUp( vec_z.Normalize( ));
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
{
float f = (1.0f / 128.0f);
Vector normal = Vector(((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f);
uint scale = tr.lightstyle[surf->styles[map]]; // style modifier
mworldlight_t *pSkyLight = NULL;
Vector localDir = g_vecZero;
Vector sky_color = g_vecZero;
// rotate from tangent to model space
localDir = mat.VectorRotate( normal );
// add local dir into main
info->normal += localDir * (float)scale; // direction factor
// compute diffuse color
localDiffuse.r += TEXTURE_TO_TEXGAMMA( lm->r ) * scale;
localDiffuse.g += TEXTURE_TO_TEXGAMMA( lm->g ) * scale;
localDiffuse.b += TEXTURE_TO_TEXGAMMA( lm->b ) * scale;
lm += size; // skip to next lightmap
dm += size; // skip to next deluxemap
}
info->lightmap.r = Q_min(( localDiffuse.r >> 7 ), 255 );
info->lightmap.g = Q_min(( localDiffuse.g >> 7 ), 255 );
info->lightmap.b = Q_min(( localDiffuse.b >> 7 ), 255 );
info->diffuse.x = (float)info->lightmap.r * (1.0f / 255.0f);
info->diffuse.y = (float)info->lightmap.g * (1.0f / 255.0f);
info->diffuse.z = (float)info->lightmap.b * (1.0f / 255.0f);
if( info->normal == g_vecZero ) info->normal = vec_z; // fixup some cases
info->normal = info->normal.Normalize();
}
else
{
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
{
uint scale = tr.lightstyle[surf->styles[map]];
// compute diffuse color
localDiffuse.r += TEXTURE_TO_TEXGAMMA( lm->r ) * scale;
localDiffuse.g += TEXTURE_TO_TEXGAMMA( lm->g ) * scale;
localDiffuse.b += TEXTURE_TO_TEXGAMMA( lm->b ) * scale;
lm += size; // skip to next lightmap
}
info->lightmap.r = Q_min(( localDiffuse.r >> 7 ), 255 );
info->lightmap.g = Q_min(( localDiffuse.g >> 7 ), 255 );
info->lightmap.b = Q_min(( localDiffuse.b >> 7 ), 255 );
info->diffuse.x = (float)info->lightmap.r * (1.0f / 255.0f);
info->diffuse.y = (float)info->lightmap.g * (1.0f / 255.0f);
info->diffuse.z = (float)info->lightmap.b * (1.0f / 255.0f);
}
if(( surf->styles[0] != 0 && surf->styles[0] != LS_SKY ) || surf->styles[1] != 255 )
SetBits( info->status, LIGHTINFO_STYLES );
// also collect the average value
if( FBitSet( info->status, LIGHTINFO_AVERAGE ))
{
Vector localAverage = g_vecZero;
lm = surf->samples;
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
{
float scale = tr.lightstyle[surf->styles[map]];
for( int i = 0; i < size; i++, lm++ )
{
localAverage.x += (float)TEXTURE_TO_TEXGAMMA( lm->r ) * scale;
localAverage.y += (float)TEXTURE_TO_TEXGAMMA( lm->g ) * scale;
localAverage.z += (float)TEXTURE_TO_TEXGAMMA( lm->b ) * scale;
}
localAverage *= (1.0f / (float)size );
}
info->average.x = Q_min( localAverage.x * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
info->average.y = Q_min( localAverage.y * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
info->average.z = Q_min( localAverage.z * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f);
}
info->surf = surf;
return true;
}
/*
=================
R_RecursiveLightPoint
=================
*/
static bool R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, const Vector &start, const Vector &end, lightpoint_t *info )
{
float front, back;
float frac, midf;
int side;
msurface_t *surf;
Vector mid;
// didn't hit anything
if( !node || node->contents < 0 )
return false;
// calculate mid point
front = PlaneDiff( start, node->plane );
back = PlaneDiff( end, node->plane );
side = front < 0.0f;
if(( back < 0.0f ) == side )
return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, start, end, info );
frac = front / ( front - back );
midf = p1f + ( p2f - p1f ) * frac;
VectorLerp( start, frac, end, mid );
// co down front side
if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, start, mid, info ))
return true; // hit something
if(( back < 0.0f ) == side )
return false;// didn't hit anything
// check for impact on this node
surf = model->surfaces + node->firstsurface;
for( int i = 0; i < node->numsurfaces; i++, surf++ )
{
if( R_GetDirectLightFromSurface( surf, mid, info ))
{
info->fraction = midf;
return true;
}
}
// go down back side
return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, mid, end, info );
}
/*
=================
R_LightPoint
=================
*/
static bool R_LightPoint( model_t *model, mnode_t *node, const Vector &start, const Vector &end, mstudiolight_t *light, bool ambient, float *fraction )
{
float factor = (ambient) ? 0.6f : 0.9f;
lightpoint_t info;
memset( &info, 0, sizeof( lightpoint_t ));
info.fraction = *fraction = 1.0f;
info.origin = start;
if( R_RecursiveLightPoint( model, node, 0.0f, 1.0f, start, end, &info ))
{
float total = Q_max( Q_max( info.lightmap.r, info.lightmap.g ), info.lightmap.b );
if( total == 0.0f ) total = 1.0f;
info.normal *= (total * factor);
light->shadelight = info.normal.Length();
light->ambientlight = total - light->shadelight;
if( total > 0.0f )
{
light->diffuse.x = (float)info.lightmap.r * ( 1.0f / total );
light->diffuse.y = (float)info.lightmap.g * ( 1.0f / total );
light->diffuse.z = (float)info.lightmap.b * ( 1.0f / total );
}
else light->diffuse = Vector( 1.0f, 1.0f, 1.0f );
if( light->ambientlight > 128 )
light->ambientlight = 128;
if( light->ambientlight + light->shadelight > 255 )
light->shadelight = 255 - light->ambientlight;
light->normal = info.normal.Normalize();
*fraction = info.fraction;
light->nointerp = FBitSet( info.status, LIGHTINFO_STYLES ) ? true : false;
return true;
}
return false;
}
/*
=================
ComputeLightmapColorFromPoint
=================
*/
static void ComputeLightmapColorFromPoint( lightpoint_t *info, mworldlight_t* pSkylight, float scale, Vector &radcolor, bool average )
{
if( !info->surf && FBitSet( info->status, LIGHTINFO_HITSKY ))
{
if( pSkylight )
{
radcolor += pSkylight->intensity * scale * 0.5f * (1.0f / 255.0f);
}
else if( world->numworldlights <= 0 ) // old bsp format?
{
Vector skyAmbient = tr.sky_ambient * (1.0f / 128.0f) * tr.diffuseFactor;
radcolor += skyAmbient * scale * 0.5f * (1.0f / 255.0f);
}
return;
}
if( info->surf != NULL )
{
Vector reflectivity;
byte rgba[4];
Vector color;
GET_EXTRA_PARAMS( info->surf->texinfo->texture->gl_texturenum, &rgba[0], &rgba[1], &rgba[2], &rgba[3] );
reflectivity.x = TextureToLinear( rgba[0] );
reflectivity.y = TextureToLinear( rgba[1] );
reflectivity.z = TextureToLinear( rgba[2] );
if( average ) color = info->average * scale;
else color = info->diffuse * scale;
#if 0
if( reflectivity != g_vecZero )
color *= reflectivity;
#endif
radcolor += color;
}
}
//-----------------------------------------------------------------------------
// Computes ambient lighting along a specified ray.
// Ray represents a cone, tanTheta is the tan of the inner cone angle
//-----------------------------------------------------------------------------
static void R_CalcRayAmbientLighting( const Vector &vStart, const Vector &vEnd, mworldlight_t *pSkyLight, float tanTheta, Vector &color )
{
cl_entity_t *ent = NULL;
model_t *model = worldmodel;
lightpoint_t info;
pmtrace_t tr0;
memset( &info, 0, sizeof( lightpoint_t ));
SetBits( info.status, LIGHTINFO_AVERAGE );
info.fraction = 1.0f;
info.origin = vStart;
color = g_vecZero;
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
gEngfuncs.pEventAPI->EV_PlayerTraceExt( (float *)&vStart, (float *)&vEnd, PM_WORLD_ONLY, R_LightTraceFilter, &tr0 );
if( tr0.allsolid || tr0.startsolid || tr0.fraction == 1.0f )
return; // doesn't hit anything
if( gEngfuncs.pEventAPI->EV_IndexFromTrace( &tr0 ) != -1 )
ent = GET_ENTITY( gEngfuncs.pEventAPI->EV_IndexFromTrace( &tr0 ));
if( ent ) model = ent->model;
// Now that we've got a ray, see what surface we've hit
R_RecursiveLightPoint( model, &model->nodes[model->hulls[0].firstclipnode], 0.0f, 1.0f, vStart, vEnd, &info );
Vector delta = vEnd - vStart;
// compute the approximate radius of a circle centered around the intersection point
float dist = delta.Length() * tanTheta * info.fraction;
// until 20" we use the point sample, then blend in the average until we're covering 40"
// This is attempting to model the ray as a cone - in the ideal case we'd simply sample all
// luxels in the intersection of the cone with the surface. Since we don't have surface
// neighbor information computed we'll just approximate that sampling with a blend between
// a point sample and the face average.
// This yields results that are similar in that aliasing is reduced at distance while
// point samples provide accuracy for intersections with near geometry
float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f );
// don't have luxel UV, so just use average sample
if( !info.surf ) scaleAvg = 1.0f;
float scaleSample = 1.0f - scaleAvg;
if( scaleAvg != 0.0f )
{
ComputeLightmapColorFromPoint( &info, pSkyLight, scaleAvg, color, true );
}
if( scaleSample != 0.0f )
{
ComputeLightmapColorFromPoint( &info, pSkyLight, scaleSample, color, false );
}
}
/*
=================
R_PointAmbientFromAxisAlignedSamples
fast ambient comptation
=================
*/
static bool R_PointAmbientFromAxisAlignedSamples( const Vector &p1, mstudiolight_t *light )
{
// use lightprobes instead
if( world->numleaflights && world->leafs )
return false;
mworldlight_t *pSkyLight = R_FindAmbientSkyLight();
Vector radcolor[NUMVERTEXNORMALS];
Vector lightBoxColor[6], p2;
lightpoint_t info;
// sample world only along cardinal axes
for( int i = 0; i < 6; i++ )
{
memset( &info, 0, sizeof( lightpoint_t ));
SetBits( info.status, LIGHTINFO_AVERAGE );
info.fraction = 1.0f;
info.origin = p1;
VectorMA( p1, 65536.0f * 1.74f, g_BoxDirections[i], p2 );
// now that we've got a ray, see what surface we've hit
if( !R_RecursiveLightPoint( worldmodel, worldmodel->nodes, 0.0f, 1.0f, p1, p2, &info ))
continue;
ComputeLightmapColorFromPoint( &info, pSkyLight, 1.0f, light->ambient[i], true );
light->ambient[i] *= AMBIENT_CUBE_SCALE;
}
return true;
}
/*
=================
R_PointAmbientFromSphericalSamples
reconstruct the ambient lighting for a leaf
at the given position in worldspace
=================
*/
static bool R_PointAmbientFromSphericalSamples( const Vector &p1, mstudiolight_t *light )
{
// use lightprobes instead
if( world->numleaflights && world->leafs )
return false;
// Figure out the color that rays hit when shot out from this position.
float tanTheta = tan( VERTEXNORMAL_CONE_INNER_ANGLE );
mworldlight_t *pSkylight = R_FindAmbientSkyLight();
Vector radcolor[NUMVERTEXNORMALS];
Vector lightBoxColor[6], p2;
// sample world by casting N rays distributed across a sphere
for( int i = 0; i < NUMVERTEXNORMALS; i++ )
{
// FIXME: a good optimization would be to scale this per leaf
VectorMA( p1, 65536.0f * 1.74f, g_anorms[i], p2 );
R_CalcRayAmbientLighting( p1, p2, pSkylight, tanTheta, radcolor[i] );
}
// accumulate samples into radiant box
for( int j = 0; j < 6; j++ )
{
float t = 0.0f;
VectorClear( light->ambient[j] );
for( int i = 0; i < NUMVERTEXNORMALS; i++ )
{
float c = DotProduct( g_anorms[i], g_BoxDirections[j] );
if( c > 0.0f )
{
VectorMA( light->ambient[j], c, radcolor[i], light->ambient[j] );
t += c;
}
}
VectorScale( light->ambient[j], ( 1.0 / t ) * AMBIENT_CUBE_SCALE, light->ambient[j] );
}
return true;
}
/*
=================
R_PointAmbientFromLeaf
reconstruct the ambient lighting for a leaf
at the given position in worldspace
=================
*/
void R_PointAmbientFromLeaf( const Vector &point, mstudiolight_t *light )
{
memset( light->ambient, 0, sizeof( light->ambient ));
if( r_lighting_extended->value > 1.0f )
{
if( R_PointAmbientFromSphericalSamples( point, light ))
return;
}
else
{
if( R_PointAmbientFromAxisAlignedSamples( point, light ))
return;
}
mleaf_t *leaf = Mod_PointInLeaf( point, worldmodel->nodes );
mextraleaf_t *info = LEAF_INFO( leaf, worldmodel );
mlightprobe_t *pAmbientProbe = info->ambient_light;
float totalFactor = 0.0f;
int i;
if( info->num_lightprobes > 0 )
{
for( i = 0; i < info->num_lightprobes; i++, pAmbientProbe++ )
{
// do an inverse squared distance weighted average of the samples
// to reconstruct the original function
float dist = (pAmbientProbe->origin - point).LengthSqr();
float factor = 1.0f / (dist + 1.0f);
totalFactor += factor;
for( int j = 0; j < 6; j++ )
{
Vector v;
v.x = (float)pAmbientProbe->cube.color[j][0] * (1.0f / 255.0f);
v.y = (float)pAmbientProbe->cube.color[j][1] * (1.0f / 255.0f);
v.z = (float)pAmbientProbe->cube.color[j][2] * (1.0f / 255.0f);
light->ambient[j] += v * factor;
}
}
for( i = 0; i < 6; i++ )
{
light->ambient[i] *= (1.0f / totalFactor) * AMBIENT_CUBE_SCALE;
}
}
}
/*
=================
R_LightIdentity
=================
*/
static void R_LightIdentity( mstudiolight_t *light )
{
// get default values
light->diffuse.x = (float)TEXTURE_TO_TEXGAMMA( 255 ) / 255.0f;
light->diffuse.y = (float)TEXTURE_TO_TEXGAMMA( 255 ) / 255.0f;
light->diffuse.z = (float)TEXTURE_TO_TEXGAMMA( 255 ) / 255.0f;
light->normal = Vector( 0.0f, 0.0f, 0.0f );
}
/*
=================
R_LightMin
=================
*/
static void R_LightMin( mstudiolight_t *light )
{
// get minlight values
light->diffuse.x = (float)TEXTURE_TO_TEXGAMMA( 10 ) / 255.0f;
light->diffuse.y = (float)TEXTURE_TO_TEXGAMMA( 10 ) / 255.0f;
light->diffuse.z = (float)TEXTURE_TO_TEXGAMMA( 10 ) / 255.0f;
light->normal = Vector( 0.0f, 0.0f, 0.0f );
}
/*
=================
R_LightVec
check bspmodels to get light from
=================
*/
void R_LightVec( const Vector &point, mstudiolight_t *light, bool ambient )
{
memset( light, 0 , sizeof( *light ));
if( worldmodel && worldmodel->lightdata )
{
Vector p0 = point + Vector( 0.0f, 0.0f, 8.0f );
Vector p1 = point + Vector( 0.0f, 0.0f, -512.0f );
float last_fraction = 1.0f, fraction;
Vector start = point;
mstudiolight_t probe;
for( int i = 0; i < MAX_PHYSENTS; i++ )
{
physent_t *pe = gEngfuncs.pEventAPI->EV_GetPhysent( i );
Vector offset, start_l, end_l;
matrix3x4 matrix;
mnode_t *pnodes;
if( !pe || !pe->model || pe->model->type != mod_brush )
continue; // skip non-bsp models
pnodes = &pe->model->nodes[pe->model->hulls[0].firstclipnode];
// rotate start and end into the models frame of reference
if( pe->angles != g_vecZero )
{
matrix = matrix3x4( pe->origin, pe->angles );
start_l = matrix.VectorITransform( p0 );
end_l = matrix.VectorITransform( p1 );
}
else
{
start_l = p0 - pe->origin;
end_l = p1 - pe->origin;
}
memset( &probe, 0 , sizeof( probe ));
if( !R_LightPoint( pe->model, pnodes, start_l, end_l, &probe, ambient, &fraction ))
continue; // didn't hit anything
if( fraction < last_fraction )
{
last_fraction = fraction;
*light = probe;
if( light->diffuse != g_vecZero )
return; // we get light now
}
// get light from bmodels too
if( !CVAR_TO_BOOL( r_lighting_extended ))
return; // get light from world and out
}
R_LightMin( light );
}
else
{
R_LightIdentity( light );
}
}
/*
=================
R_LightForSky
=================
*/
void R_LightForSky( const Vector &point, mstudiolight_t *light )
{
if( tr.sun_light_enabled )
{
light->diffuse = tr.sun_diffuse;
}
else
{
light->diffuse = tr.sky_ambient * ( 1.0f / 128.0f ) * tr.diffuseFactor;
}
// FIXME: this is correct?
light->normal = -tr.sky_normal;
light->ambientlight = 128;
light->shadelight = 192;
}
/*
=================
R_LightForStudio
given light for a studiomodel
=================
*/
void R_LightForStudio( const Vector &point, mstudiolight_t *light, bool ambient )
{
R_LightVec( point, light, ambient );
R_PointAmbientFromLeaf( point, light );
}