Started to bring back the Quake 3 lightgrid as irradiance cache

This commit is contained in:
Robert Beckebans 2021-04-13 15:50:46 +02:00
parent 09c9f254c8
commit 6decaa2293
9 changed files with 1099 additions and 99 deletions

View file

@ -26,6 +26,10 @@ SOFTWARE.
#ifndef __MATH_SPHERICAL_HARMONICS_H__
#define __MATH_SPHERICAL_HARMONICS_H__
// RB: there is a very good talk by Yuriy O'Donnell that explains the the functions used in this library
// Precomputed Global Illumination in Frostbite (GDC 2018)
// https://www.gdcvault.com/play/1025214/Precomputed-Global-Illumination-in
// https://graphics.stanford.edu/papers/envmap/envmap.pdf
template <typename T, size_t L>

View file

@ -1787,6 +1787,82 @@ void idRenderBackend::DBG_ShowViewEnvprobes()
}
}
void idRenderBackend::DBG_ShowLightGrid()
{
if( r_showLightGrid.GetInteger() <= 0 || !tr.primaryWorld )
{
return;
}
// all volumes are expressed in world coordinates
renderProgManager.BindShader_Color();
GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_DEPTHMASK );
GL_Color( 1.0f, 1.0f, 1.0f );
idMat3 axis;
axis.Identity();
for( int i = 0; i < tr.primaryWorld->lightGrid.lightGridPoints.Num(); i++ )
{
lightGridPoint_t* gridPoint = &tr.primaryWorld->lightGrid.lightGridPoints[i];
idVec3 distanceToCam = gridPoint->origin - viewDef->renderView.vieworg;
if( distanceToCam.LengthSqr() > ( 1024 * 1024 ) )
{
continue;
}
/*
idVec4 c;
c[0] = idMath::ClampFloat( 0, 1, gridPoint->directed[0] * ( 1.0f / 255.0f ) );
c[1] = idMath::ClampFloat( 0, 1, gridPoint->directed[1] * ( 1.0f / 255.0f ) );
c[2] = idMath::ClampFloat( 0, 1, gridPoint->directed[2] * ( 1.0f / 255.0f ) );
glColor4f( c[0], c[1], c[2], 1 );
float lattitude = DEG2RAD( gridPoint->latLong[1] * ( 360.0f / 255.0f ) );
float longitude = DEG2RAD( gridPoint->latLong[0] * ( 360.0f / 255.0f ) );
idVec3 dir;
dir[0] = idMath::Cos( lattitude ) * idMath::Sin( longitude );
dir[1] = idMath::Sin( lattitude ) * idMath::Sin( longitude );
dir[2] = idMath::Cos( longitude );
idVec3 pos2 = gridPoint->origin - dir * r_showLightGrid.GetFloat();
glBegin( GL_LINES );
glColor4f( c[0], c[1], c[2], 1 );
//glColor4f( 1, 1, 1, 1 );
glVertex3fv( gridPoint->origin.ToFloatPtr() );
glColor4f( 0, 0, 0, 1 );
glVertex3fv( pos2.ToFloatPtr() );
glEnd();
*/
idVec3 color = tr.primaryWorld->lightGrid.GetProbeIndexDebugColor( i );
GL_Color( color );
idRenderMatrix modelRenderMatrix;
idRenderMatrix::CreateFromOriginAxis( gridPoint->origin, axis, modelRenderMatrix );
// calculate the matrix that transforms the unit cube to exactly cover the model in world space
const float size = 3.0f;
idBounds debugBounds( idVec3( -size ), idVec3( size ) );
idRenderMatrix inverseBaseModelProject;
idRenderMatrix::OffsetScaleForBounds( modelRenderMatrix, debugBounds, inverseBaseModelProject );
idRenderMatrix invProjectMVPMatrix;
idRenderMatrix::Multiply( viewDef->worldSpace.mvp, inverseBaseModelProject, invProjectMVPMatrix );
RB_SetMVP( invProjectMVPMatrix );
DrawElementsWithCounters( &zeroOneSphereSurface );
}
}
void idRenderBackend::DBG_ShowShadowMapLODs()
{
if( !r_showShadowMapLODs.GetInteger() )
@ -3125,6 +3201,11 @@ idRenderBackend::DBG_RenderDebugTools
*/
void idRenderBackend::DBG_RenderDebugTools( drawSurf_t** drawSurfs, int numDrawSurfs )
{
if( viewDef->renderView.rdflags & RDF_IRRADIANCE )
{
return;
}
// don't do much if this was a 2D rendering
if( !viewDef->viewEntitys )
{
@ -3158,6 +3239,7 @@ void idRenderBackend::DBG_RenderDebugTools( drawSurf_t** drawSurfs, int numDrawS
DBG_ShowViewEntitys( viewDef->viewEntitys );
DBG_ShowLights();
// RB begin
DBG_ShowLightGrid();
DBG_ShowViewEnvprobes();
DBG_ShowShadowMapLODs();
DBG_ShowShadowMaps();

View file

@ -425,6 +425,7 @@ private:
void DBG_ShowDominantTris( drawSurf_t** drawSurfs, int numDrawSurfs );
void DBG_ShowEdges( drawSurf_t** drawSurfs, int numDrawSurfs );
void DBG_ShowLights();
void DBG_ShowLightGrid(); // RB
void DBG_ShowViewEnvprobes(); // RB
void DBG_ShowShadowMapLODs(); // RB
void DBG_ShowPortals();

View file

@ -501,6 +501,24 @@ struct calcEnvprobeParms_t
halfFloat_t* outBuffer; // HDR R11G11B11F packed atlas
int time; // execution time in milliseconds
};
struct calcLightGridPointParms_t
{
// input
byte* buffers[6]; // HDR R11G11B11F standard OpenGL cubemap sides
int samples;
int outWidth;
int outHeight;
bool printProgress;
idStr filename;
// output
halfFloat_t* outBuffer; // HDR R11G11B11F packed atlas
int time; // execution time in milliseconds
};
// RB end
const int MAX_CLIP_PLANES = 1; // we may expand this to six for some subview issues
@ -940,6 +958,8 @@ public:
unsigned short gammaTable[256]; // brightness / gamma modify this
idMat3 cubeAxis[6]; // RB
srfTriangles_t* unitSquareTriangles;
srfTriangles_t* zeroOneCubeTriangles;
srfTriangles_t* zeroOneSphereTriangles;
@ -956,8 +976,9 @@ public:
idParallelJobList* frontEndJobList;
// RB irradiance and GGX background jobs
idParallelJobList* envprobeJobList;
idList<calcEnvprobeParms_t*> irradianceJobs;
idParallelJobList* envprobeJobList;
idList<calcEnvprobeParms_t*> envprobeJobs;
idList<calcLightGridPointParms_t*> lightGridJobs;
idRenderBackend backend;
@ -1172,6 +1193,7 @@ extern idCVar r_useHierarchicalDepthBuffer;
extern idCVar r_usePBR;
extern idCVar r_pbrDebug;
extern idCVar r_showViewEnvprobes;
extern idCVar r_showLightGrid; // show Quake 3 style light grid points
extern idCVar r_exposure;
// RB end
@ -1349,6 +1371,69 @@ RENDERWORLD_PORTALS
viewEntity_t* R_SetEntityDefViewEntity( idRenderEntityLocal* def );
viewLight_t* R_SetLightDefViewLight( idRenderLightLocal* def );
/*
============================================================
RENDERWORLD_ENVPROBES
============================================================
*/
void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float result[3], float& u, float& v );
idVec2 NormalizedOctCoord( int x, int y, const int probeSideLength );
class CommandlineProgressBar
{
private:
size_t tics = 0;
size_t nextTicCount = 0;
int count = 0;
int expectedCount = 0;
public:
CommandlineProgressBar( int _expectedCount )
{
expectedCount = _expectedCount;
}
void Start()
{
common->Printf( "0%% 10 20 30 40 50 60 70 80 90 100%%\n" );
common->Printf( "|----|----|----|----|----|----|----|----|----|----|\n" );
common->UpdateScreen( false );
}
void Increment()
{
if( ( count + 1 ) >= nextTicCount )
{
size_t ticsNeeded = ( size_t )( ( ( double )( count + 1 ) / expectedCount ) * 50.0 );
do
{
common->Printf( "*" );
}
while( ++tics < ticsNeeded );
nextTicCount = ( size_t )( ( tics / 50.0 ) * expectedCount );
if( count == ( expectedCount - 1 ) )
{
if( tics < 51 )
{
common->Printf( "*" );
}
common->Printf( "\n" );
}
common->UpdateScreen( false );
}
count++;
}
};
/*
====================================================================
@ -1555,7 +1640,6 @@ struct localTrace_t
localTrace_t R_LocalTrace( const idVec3& start, const idVec3& end, const float radius, const srfTriangles_t* tri );
/*
============================================================
@ -1586,6 +1670,9 @@ void RB_DrawBounds( const idBounds& bounds );
void RB_ShutdownDebugTools();
void RB_SetVertexColorParms( stageVertexColor_t svc );
//=============================================
#include "ResolutionScale.h"

View file

@ -300,6 +300,7 @@ idCVar r_useHierarchicalDepthBuffer( "r_useHierarchicalDepthBuffer", "1", CVAR_R
idCVar r_usePBR( "r_usePBR", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use PBR and Image Based Lighting instead of old Quake 4 style ambient lighting" );
idCVar r_pbrDebug( "r_pbrDebug", "0", CVAR_RENDERER | CVAR_INTEGER, "show which materials have PBR support (green = PBR, red = oldschool D3)" );
idCVar r_showViewEnvprobes( "r_showViewEnvprobes", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = displays the bounding boxes of all view environment probes, 2 = show irradiance" );
idCVar r_showLightGrid( "r_showLightGrid", "0", CVAR_RENDERER | CVAR_INTEGER, "show Quake 3 style light grid points" );
idCVar r_exposure( "r_exposure", "0.5", CVAR_ARCHIVE | CVAR_RENDERER | CVAR_FLOAT, "HDR exposure or LDR brightness [0.0 .. 1.0]", 0.0f, 1.0f );
// RB end
@ -1268,11 +1269,6 @@ void R_EnvShot_f( const idCmdArgs& args )
//============================================================================
static idMat3 cubeAxis[6];
void R_TransformCubemap( const char* orgDirection[6], const char* orgDir, const char* destDirection[6], const char* destDir, const char* baseName )
{
idStr fullname;
@ -1691,6 +1687,7 @@ void idRenderSystemLocal::Clear()
guiRecursionLevel = 0;
guiModel = NULL;
memset( gammaTable, 0, sizeof( gammaTable ) );
memset( &cubeAxis, 0, sizeof( cubeAxis ) ); // RB
takingScreenshot = false;
if( unitSquareTriangles != NULL )
@ -1721,7 +1718,8 @@ void idRenderSystemLocal::Clear()
// RB
envprobeJobList = NULL;
irradianceJobs.Clear();
envprobeJobs.Clear();
lightGridJobs.Clear();
}
/*
@ -2037,6 +2035,38 @@ void idRenderSystemLocal::Init()
identitySpace.modelMatrix[1 * 4 + 1] = 1.0f;
identitySpace.modelMatrix[2 * 4 + 2] = 1.0f;
// set cubemap axis for cubemap sampling tools
// +X
cubeAxis[0][0][0] = 1;
cubeAxis[0][1][2] = 1;
cubeAxis[0][2][1] = 1;
// -X
cubeAxis[1][0][0] = -1;
cubeAxis[1][1][2] = -1;
cubeAxis[1][2][1] = 1;
// +Y
cubeAxis[2][0][1] = 1;
cubeAxis[2][1][0] = -1;
cubeAxis[2][2][2] = -1;
// -Y
cubeAxis[3][0][1] = -1;
cubeAxis[3][1][0] = -1;
cubeAxis[3][2][2] = 1;
// +Z
cubeAxis[4][0][2] = 1;
cubeAxis[4][1][0] = -1;
cubeAxis[4][2][1] = 1;
// -Z
cubeAxis[5][0][2] = -1;
cubeAxis[5][1][0] = 1;
cubeAxis[5][2][1] = 1;
// make sure the tr.unitSquareTriangles data is current in the vertex / index cache
if( unitSquareTriangles == NULL )
{

View file

@ -171,7 +171,6 @@ void idRenderWorldLocal::AddAreaViewEnvprobes( int areaNum, const portalStack_t*
R_SampleCubeMapHDR
==================
*/
static idMat3 cubeAxis[6];
static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" };
void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float result[3], float& u, float& v )
@ -208,8 +207,8 @@ void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float re
axis = 5;
}
float fx = ( dir * cubeAxis[axis][1] ) / ( dir * cubeAxis[axis][0] );
float fy = ( dir * cubeAxis[axis][2] ) / ( dir * cubeAxis[axis][0] );
float fx = ( dir * tr.cubeAxis[axis][1] ) / ( dir * tr.cubeAxis[axis][0] );
float fy = ( dir * tr.cubeAxis[axis][2] ) / ( dir * tr.cubeAxis[axis][0] );
fx = -fx;
fy = -fy;
@ -252,56 +251,7 @@ void R_SampleCubeMapHDR( const idVec3& dir, int size, byte* buffers[6], float re
r11g11b10f_to_float3( tmp.i, result );
}
class CommandlineProgressBar
{
private:
size_t tics = 0;
size_t nextTicCount = 0;
int count = 0;
int expectedCount = 0;
public:
CommandlineProgressBar( int _expectedCount )
{
expectedCount = _expectedCount;
}
void Start()
{
common->Printf( "0%% 10 20 30 40 50 60 70 80 90 100%%\n" );
common->Printf( "|----|----|----|----|----|----|----|----|----|----|\n" );
common->UpdateScreen( false );
}
void Increment()
{
if( ( count + 1 ) >= nextTicCount )
{
size_t ticsNeeded = ( size_t )( ( ( double )( count + 1 ) / expectedCount ) * 50.0 );
do
{
common->Printf( "*" );
}
while( ++tics < ticsNeeded );
nextTicCount = ( size_t )( ( tics / 50.0 ) * expectedCount );
if( count == ( expectedCount - 1 ) )
{
if( tics < 51 )
{
common->Printf( "*" );
}
common->Printf( "\n" );
}
common->UpdateScreen( false );
}
count++;
}
};
// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
@ -921,7 +871,7 @@ void R_MakeAmbientMap( const char* baseName, const char* suffix, int outSize, bo
jobParms->outHeight = outSize;
jobParms->outBuffer = ( halfFloat_t* )R_StaticAlloc( idMath::Ceil( outSize * outSize * 3 * sizeof( halfFloat_t ) * 1.5f ), TAG_IMAGE );
tr.irradianceJobs.Append( jobParms );
tr.envprobeJobs.Append( jobParms );
if( useThreads )
{
@ -990,38 +940,6 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL
const viewDef_t primary = *tr.primaryView;
memset( &cubeAxis, 0, sizeof( cubeAxis ) );
// +X
cubeAxis[0][0][0] = 1;
cubeAxis[0][1][2] = 1;
cubeAxis[0][2][1] = 1;
// -X
cubeAxis[1][0][0] = -1;
cubeAxis[1][1][2] = -1;
cubeAxis[1][2][1] = 1;
// +Y
cubeAxis[2][0][1] = 1;
cubeAxis[2][1][0] = -1;
cubeAxis[2][2][2] = -1;
// -Y
cubeAxis[3][0][1] = -1;
cubeAxis[3][1][0] = -1;
cubeAxis[3][2][2] = 1;
// +Z
cubeAxis[4][0][2] = 1;
cubeAxis[4][1][0] = -1;
cubeAxis[4][2][1] = 1;
// -Z
cubeAxis[5][0][2] = -1;
cubeAxis[5][1][0] = 1;
cubeAxis[5][2][1] = 1;
//--------------------------------------------
// CAPTURE SCENE LIGHTING TO CUBEMAPS
//--------------------------------------------
@ -1042,7 +960,7 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL
ref.fov_x = ref.fov_y = 90;
ref.vieworg = def->parms.origin;
ref.viewaxis = cubeAxis[j];
ref.viewaxis = tr.cubeAxis[j];
extension = envDirection[ j ];
fullname.Format( "env/%s/envprobe%i%s", baseName.c_str(), i, extension );
@ -1082,9 +1000,9 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL
tr.envprobeJobList->Wait();
}
for( int j = 0; j < tr.irradianceJobs.Num(); j++ )
for( int j = 0; j < tr.envprobeJobs.Num(); j++ )
{
calcEnvprobeParms_t* job = tr.irradianceJobs[ j ];
calcEnvprobeParms_t* job = tr.envprobeJobs[ j ];
R_WriteEXR( job->filename, ( byte* )job->outBuffer, 3, job->outWidth, job->outHeight, "fs_basepath" );
@ -1103,7 +1021,7 @@ CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL
delete job;
}
tr.irradianceJobs.Clear();
tr.envprobeJobs.Clear();
int end = Sys_Milliseconds();

View file

@ -0,0 +1,828 @@
/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
Copyright (C) 2013-2021 Robert Beckebans
This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
Doom 3 Source Code 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.
Doom 3 Source Code 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.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#include "RenderCommon.h"
// RB: old constant from q3map2
#define MAX_MAP_LIGHTGRID_POINTS 0x100000
void LightGrid::SetupLightGrid( const idBounds& bounds )
{
//idLib::Printf( "----- SetupLightGrid -----\n" );
lightGridSize.Set( 64, 64, 128 );
lightGridPoints.Clear();
idVec3 maxs;
int j = 0;
int numGridPoints = MAX_MAP_LIGHTGRID_POINTS + 1;
while( numGridPoints > MAX_MAP_LIGHTGRID_POINTS )
{
for( int i = 0; i < 3; i++ )
{
lightGridOrigin[i] = lightGridSize[i] * ceil( bounds[0][i] / lightGridSize[i] );
maxs[i] = lightGridSize[i] * floor( bounds[1][i] / lightGridSize[i] );
lightGridBounds[i] = ( maxs[i] - lightGridOrigin[i] ) / lightGridSize[i] + 1;
}
numGridPoints = lightGridBounds[0] * lightGridBounds[1] * lightGridBounds[2];
if( numGridPoints > MAX_MAP_LIGHTGRID_POINTS )
{
lightGridSize[ j++ % 3 ] += 16.0f;
}
}
idLib::Printf( "grid size (%i %i %i)\n", ( int )lightGridSize[0], ( int )lightGridSize[1], ( int )lightGridSize[2] );
idLib::Printf( "grid bounds (%i %i %i)\n", ( int )lightGridBounds[0], ( int )lightGridBounds[1], ( int )lightGridBounds[2] );
idLib::Printf( "%i x %i x %i = %i grid points \n", lightGridBounds[0], lightGridBounds[1], lightGridBounds[2], numGridPoints );
lightGridPoints.SetNum( numGridPoints );
idLib::Printf( "%9u x %" PRIuSIZE " = lightGridSize = (%.2fMB)\n", numGridPoints, sizeof( lightGridPoint_t ), ( float )( lightGridPoints.MemoryUsed() ) / ( 1024.0f * 1024.0f ) );
CalculateLightGridPointPositions();
}
void LightGrid::ProbeIndexToGridIndex( const int probeIndex, int gridIndex[3] )
{
// slow brute force method only for debugging
int gridStep[3];
gridStep[0] = 1;
gridStep[1] = lightGridBounds[0];
gridStep[2] = lightGridBounds[0] * lightGridBounds[1];
gridIndex[0] = 0;
gridIndex[1] = 0;
gridIndex[2] = 0;
int p = 0;
for( int i = 0; i < lightGridBounds[0]; i += 1 )
{
for( int j = 0; j < lightGridBounds[1]; j += 1 )
{
for( int k = 0; k < lightGridBounds[2]; k += 1 )
{
if( probeIndex == p )
{
gridIndex[0] = i;
gridIndex[1] = j;
gridIndex[2] = k;
return;
}
p++;
}
}
}
}
idVec3 LightGrid::GetProbeIndexDebugColor( const int probeIndex )
{
idVec3 color( colorGold.x, colorGold.y, colorGold.z );
int gridIndex[3];
ProbeIndexToGridIndex( probeIndex, gridIndex );
color.x = float( gridIndex[0] & 1 );
color.y = float( gridIndex[1] & 1 );
color.z = float( gridIndex[2] & 1 );
color *= ( 1.0f / Max( color.x + color.y + color.z, 0.01f ) );
color = color * 0.6f + idVec3( 0.2f );
return color;
}
void LightGrid::CalculateLightGridPointPositions()
{
// calculate grid point positions
int gridStep[3];
int pos[3];
idVec3 posFloat;
gridStep[0] = 1;
gridStep[1] = lightGridBounds[0];
gridStep[2] = lightGridBounds[0] * lightGridBounds[1];
int p = 0;
for( int i = 0; i < lightGridBounds[0]; i += 1 )
{
for( int j = 0; j < lightGridBounds[1]; j += 1 )
{
for( int k = 0; k < lightGridBounds[2]; k += 1 )
{
pos[0] = i;
pos[1] = j;
pos[2] = k;
posFloat[0] = i * lightGridSize[0];
posFloat[1] = j * lightGridSize[1];
posFloat[2] = k * lightGridSize[2];
lightGridPoint_t* gridPoint = &lightGridPoints[ pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2] ];
gridPoint->origin = lightGridOrigin + posFloat;
p++;
}
}
}
}
void idRenderWorldLocal::SetupLightGrid()
{
idLib::Printf( "----- SetupLightGrid -----\n" );
idBounds bounds;
bounds.Clear();
for( int i = 0; i < numPortalAreas; i++ )
{
portalArea_t* area = &portalAreas[i];
bounds.AddBounds( area->globalBounds );
}
lightGrid.SetupLightGrid( bounds );
}
static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" };
/// http://www.mpia-hd.mpg.de/~mathar/public/mathar20051002.pdf
/// http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/
static inline float AreaElement( float _x, float _y )
{
return atan2f( _x * _y, sqrtf( _x * _x + _y * _y + 1.0f ) );
}
/// u and v should be center adressing and in [-1.0 + invSize.. 1.0 - invSize] range.
static inline float CubemapTexelSolidAngle( float u, float v, float _invFaceSize )
{
// Specify texel area.
const float x0 = u - _invFaceSize;
const float x1 = u + _invFaceSize;
const float y0 = v - _invFaceSize;
const float y1 = v + _invFaceSize;
// Compute solid angle of texel area.
const float solidAngle = AreaElement( x1, y1 )
- AreaElement( x0, y1 )
- AreaElement( x1, y0 )
+ AreaElement( x0, y0 )
;
return solidAngle;
}
static inline idVec3 MapXYSToDirection( uint64 x, uint64 y, uint64 s, uint64 width, uint64 height )
{
float u = ( ( x + 0.5f ) / float( width ) ) * 2.0f - 1.0f;
float v = ( ( y + 0.5f ) / float( height ) ) * 2.0f - 1.0f;
v *= -1.0f;
idVec3 dir( 0, 0, 0 );
// +x, -x, +y, -y, +z, -z
switch( s )
{
case 0:
dir = idVec3( 1.0f, v, -u );
break;
case 1:
dir = idVec3( -1.0f, v, u );
break;
case 2:
dir = idVec3( u, 1.0f, -v );
break;
case 3:
dir = idVec3( u, -1.0f, v );
break;
case 4:
dir = idVec3( u, v, 1.0f );
break;
case 5:
dir = idVec3( -u, v, -1.0f );
break;
}
dir.Normalize();
return dir;
}
void CalculateLightGridPointJob( calcLightGridPointParms_t* parms )
{
byte* buffers[6];
int start = Sys_Milliseconds();
for( int i = 0; i < 6; i++ )
{
buffers[ i ] = parms->buffers[ i ];
}
const float invDstSize = 1.0f / float( parms->outHeight );
const int numMips = idMath::BitsForInteger( parms->outHeight );
const idVec2i sourceImageSize( parms->outHeight, parms->outHeight );
// build L4 Spherical Harmonics from source image
SphericalHarmonicsT<idVec3, 4> shRadiance;
for( int i = 0; i < shSize( 4 ); i++ )
{
shRadiance[i].Zero();
}
#if 0
// build SH by only iterating over the octahedron
// RB: not used because I don't know the texel area of an octahedron pixel and the cubemap texel area is too small
// however it would be nice to use this because it would be 6 times faster
idVec4 dstRect = R_CalculateMipRect( parms->outHeight, 0 );
for( int x = dstRect.x; x < ( dstRect.x + dstRect.z ); x++ )
{
for( int y = dstRect.y; y < ( dstRect.y + dstRect.w ); y++ )
{
idVec2 octCoord = NormalizedOctCoord( x, y, dstRect.z );
// convert UV coord to 3D direction
idVec3 dir;
dir.FromOctahedral( octCoord );
float u, v;
idVec3 radiance;
R_SampleCubeMapHDR( dir, parms->outHeight, buffers, &radiance[0], u, v );
//radiance = dir * 0.5 + idVec3( 0.5f, 0.5f, 0.5f );
// convert from [0 .. size-1] to [-1.0 + invSize .. 1.0 - invSize]
const float uu = 2.0f * ( u * invDstSize ) - 1.0f;
const float vv = 2.0f * ( v * invDstSize ) - 1.0f;
float texelArea = CubemapTexelSolidAngle( uu, vv, invDstSize );
const SphericalHarmonicsT<float, 4>& sh = shEvaluate<4>( dir );
bool shValid = true;
for( int i = 0; i < 25; i++ )
{
if( IsNAN( sh[i] ) )
{
shValid = false;
break;
}
}
if( shValid )
{
shAddWeighted( shRadiance, sh, radiance * texelArea );
}
}
}
#else
// build SH by iterating over all cubemap pixels
idVec4 dstRect = R_CalculateMipRect( parms->outHeight, 0 );
for( int side = 0; side < 6; side++ )
{
for( int x = 0; x < sourceImageSize.x; x++ )
{
for( int y = 0; y < sourceImageSize.y; y++ )
{
// convert UV coord to 3D direction
idVec3 dir = MapXYSToDirection( x, y, side, sourceImageSize.x, sourceImageSize.y );
float u, v;
idVec3 radiance;
R_SampleCubeMapHDR( dir, parms->outHeight, buffers, &radiance[0], u, v );
//radiance = dir * 0.5 + idVec3( 0.5f, 0.5f, 0.5f );
// convert from [0 .. size-1] to [-1.0 + invSize .. 1.0 - invSize]
const float uu = 2.0f * ( u * invDstSize ) - 1.0f;
const float vv = 2.0f * ( v * invDstSize ) - 1.0f;
float texelArea = CubemapTexelSolidAngle( uu, vv, invDstSize );
const SphericalHarmonicsT<float, 4>& sh = shEvaluate<4>( dir );
bool shValid = true;
for( int i = 0; i < 25; i++ )
{
if( IsNAN( sh[i] ) )
{
shValid = false;
break;
}
}
if( shValid )
{
shAddWeighted( shRadiance, sh, radiance * texelArea );
}
}
}
}
#endif
// reset image to black
for( int x = 0; x < parms->outWidth; x++ )
{
for( int y = 0; y < parms->outHeight; y++ )
{
parms->outBuffer[( y * parms->outWidth + x ) * 3 + 0] = F32toF16( 0 );
parms->outBuffer[( y * parms->outWidth + x ) * 3 + 1] = F32toF16( 0 );
parms->outBuffer[( y * parms->outWidth + x ) * 3 + 2] = F32toF16( 0 );
}
}
for( int mip = 0; mip < numMips; mip++ )
{
float roughness = ( float )mip / ( float )( numMips - 1 );
idVec4 dstRect = R_CalculateMipRect( parms->outHeight, mip );
for( int x = dstRect.x; x < ( dstRect.x + dstRect.z ); x++ )
{
for( int y = dstRect.y; y < ( dstRect.y + dstRect.w ); y++ )
{
idVec2 octCoord;
if( mip > 0 )
{
// move back to [0, 1] coords
octCoord = NormalizedOctCoord( x - dstRect.x, y - dstRect.y, dstRect.z );
}
else
{
octCoord = NormalizedOctCoord( x, y, dstRect.z );
}
// convert UV coord to 3D direction
idVec3 dir;
dir.FromOctahedral( octCoord );
idVec3 outColor( 0, 0, 0 );
#if 1
// generate ambient colors by evaluating the L4 Spherical Harmonics
SphericalHarmonicsT<float, 4> shDirection = shEvaluate<4>( dir );
idVec3 sampleIrradianceSh = shEvaluateDiffuse<idVec3, 4>( shRadiance, dir ) / idMath::PI;
outColor[0] = Max( 0.0f, sampleIrradianceSh.x );
outColor[1] = Max( 0.0f, sampleIrradianceSh.y );
outColor[2] = Max( 0.0f, sampleIrradianceSh.z );
#else
// generate ambient colors using Monte Carlo method
for( int s = 0; s < parms->samples; s++ )
{
idVec2 Xi = Hammersley2D( s, parms->samples );
idVec3 H = ImportanceSampleGGX( Xi, dir, 0.95f );
float u, v;
idVec3 radiance;
R_SampleCubeMapHDR( H, parms->outHeight, buffers, &radiance[0], u, v );
outColor[0] += radiance[0];
outColor[1] += radiance[1];
outColor[2] += radiance[2];
}
outColor[0] /= parms->samples;
outColor[1] /= parms->samples;
outColor[2] /= parms->samples;
#endif
//outColor = dir * 0.5 + idVec3( 0.5f, 0.5f, 0.5f );
parms->outBuffer[( y * parms->outWidth + x ) * 3 + 0] = F32toF16( outColor[0] );
parms->outBuffer[( y * parms->outWidth + x ) * 3 + 1] = F32toF16( outColor[1] );
parms->outBuffer[( y * parms->outWidth + x ) * 3 + 2] = F32toF16( outColor[2] );
}
}
}
int end = Sys_Milliseconds();
parms->time = end - start;
}
REGISTER_PARALLEL_JOB( CalculateLightGridPointJob, "CalculateLightGridPointJob" );
void R_MakeAmbientGridPoint( const char* baseName, const char* suffix, int outSize, bool deleteTempFiles, bool useThreads )
{
idStr fullname;
renderView_t ref;
viewDef_t primary;
byte* buffers[6];
int width = 0, height = 0;
// read all of the images
for( int i = 0 ; i < 6 ; i++ )
{
fullname.Format( "env/%s%s.exr", baseName, envDirection[i] );
const bool captureToImage = false;
common->UpdateScreen( captureToImage );
R_LoadImage( fullname, &buffers[i], &width, &height, NULL, true, NULL );
if( !buffers[i] )
{
common->Printf( "loading %s failed.\n", fullname.c_str() );
for( i-- ; i >= 0 ; i-- )
{
Mem_Free( buffers[i] );
}
return;
}
}
// set up the job
calcLightGridPointParms_t* jobParms = new calcLightGridPointParms_t;
for( int i = 0; i < 6; i++ )
{
jobParms->buffers[ i ] = buffers[ i ];
}
jobParms->samples = 1000;
jobParms->filename.Format( "env/%s%s.exr", baseName, suffix );
jobParms->printProgress = !useThreads;
jobParms->outWidth = int( outSize * 1.5f );
jobParms->outHeight = outSize;
jobParms->outBuffer = ( halfFloat_t* )R_StaticAlloc( idMath::Ceil( outSize * outSize * 3 * sizeof( halfFloat_t ) * 1.5f ), TAG_IMAGE );
tr.lightGridJobs.Append( jobParms );
if( useThreads )
{
tr.envprobeJobList->AddJob( ( jobRun_t )CalculateLightGridPointJob, jobParms );
}
else
{
CalculateLightGridPointJob( jobParms );
}
if( deleteTempFiles )
{
for( int i = 0 ; i < 6 ; i++ )
{
fullname.Format( "env/%s%s.exr", baseName, envDirection[i] );
fileSystem->RemoveFile( fullname );
}
}
}
CONSOLE_COMMAND( generateLightGrid, "Generate light grid data", NULL )
{
idStr fullname;
idStr baseName;
renderView_t ref;
int blends;
const char* extension;
int size;
static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" };
if( !tr.primaryWorld )
{
common->Printf( "No primary world loaded.\n" );
return;
}
bool useThreads = false;
baseName = tr.primaryWorld->mapName;
baseName.StripFileExtension();
size = RADIANCE_CUBEMAP_SIZE;
blends = 1;
if( !tr.primaryView )
{
common->Printf( "No primary view.\n" );
return;
}
const viewDef_t primary = *tr.primaryView;
//--------------------------------------------
// CAPTURE SCENE LIGHTING TO CUBEMAPS
//--------------------------------------------
for( int i = 0; i < tr.primaryWorld->lightGrid.lightGridPoints.Num(); i++ )
{
lightGridPoint_t* gridPoint = &tr.primaryWorld->lightGrid.lightGridPoints[i];
for( int j = 0 ; j < 6 ; j++ )
{
ref = primary.renderView;
ref.rdflags = RDF_NOAMBIENT | RDF_IRRADIANCE;
ref.fov_x = ref.fov_y = 90;
ref.vieworg = gridPoint->origin;
ref.viewaxis = tr.cubeAxis[j];
extension = envDirection[ j ];
fullname.Format( "env/%s/lightgridpoint%i%s", baseName.c_str(), i, extension );
tr.TakeScreenshot( size, size, fullname, blends, &ref, EXR );
//tr.CaptureRenderToFile( fullname, false );
}
}
common->Printf( "Wrote a env set with the name %s\n", baseName.c_str() );
//--------------------------------------------
// GENERATE IRRADIANCE
//--------------------------------------------
CommandlineProgressBar progressBar( tr.primaryWorld->lightGrid.lightGridPoints.Num() );
if( !useThreads )
{
progressBar.Start();
}
int start = Sys_Milliseconds();
for( int i = 0; i < tr.primaryWorld->lightGrid.lightGridPoints.Num(); i++ )
{
lightGridPoint_t* gridPoint = &tr.primaryWorld->lightGrid.lightGridPoints[i];
fullname.Format( "%s/lightgridpoint%i", baseName.c_str(), i );
R_MakeAmbientGridPoint( fullname.c_str(), "_amb", IRRADIANCE_CUBEMAP_SIZE, true, useThreads );
if( !useThreads )
{
progressBar.Increment();
}
}
if( useThreads )
{
//tr.envprobeJobList->Submit();
tr.envprobeJobList->Submit( NULL, JOBLIST_PARALLELISM_MAX_CORES );
tr.envprobeJobList->Wait();
}
for( int j = 0; j < tr.lightGridJobs.Num(); j++ )
{
calcLightGridPointParms_t* job = tr.lightGridJobs[ j ];
R_WriteEXR( job->filename, ( byte* )job->outBuffer, 3, job->outWidth, job->outHeight, "fs_basepath" );
common->Printf( "%s convolved in %5.1f seconds\n\n", job->filename.c_str(), job->time * 0.001f );
for( int i = 0; i < 6; i++ )
{
if( job->buffers[i] )
{
Mem_Free( job->buffers[i] );
}
}
Mem_Free( job->outBuffer );
delete job;
}
tr.lightGridJobs.Clear();
int end = Sys_Milliseconds();
common->Printf( "convolved probes in %5.1f seconds\n\n", ( end - start ) * 0.001f );
}
#if 0
// straight port of Quake 3
void idRenderWorldLocal::SetupEntityGridLighting( idRenderEntityLocal* def )
{
// lighting calculations
#if 0
if( def->lightgridCalculated )
{
return;
}
def->lightgridCalculated = true;
#endif
if( lightGridPoints.Num() > 0 )
{
idVec3 lightOrigin;
int pos[3];
int i, j;
int gridPointIndex;
lightGridPoint_t* gridPoint;
lightGridPoint_t* gridPoint2;
float frac[3];
int gridStep[3];
idVec3 direction;
idVec3 direction2;
float lattitude;
float longitude;
float totalFactor;
#if 0
if( forcedOrigin )
{
VectorCopy( forcedOrigin, lightOrigin );
}
else
{
if( ent->e.renderfx & RF_LIGHTING_ORIGIN )
{
// seperate lightOrigins are needed so an object that is
// sinking into the ground can still be lit, and so
// multi-part models can be lit identically
VectorCopy( ent->e.lightingOrigin, lightOrigin );
}
else
{
VectorCopy( ent->e.origin, lightOrigin );
}
}
#else
// some models, like empty particles have no volume
#if 1
lightOrigin = def->parms.origin;
#else
if( def->referenceBounds.IsCleared() )
{
lightOrigin = def->parms.origin;
}
else
{
lightOrigin = def->volumeMidPoint;
}
#endif
#endif
lightOrigin -= lightGridOrigin;
for( i = 0; i < 3; i++ )
{
float v;
v = lightOrigin[i] * ( 1.0f / lightGridSize[i] );
pos[i] = floor( v );
frac[i] = v - pos[i];
if( pos[i] < 0 )
{
pos[i] = 0;
}
else if( pos[i] >= lightGridBounds[i] - 1 )
{
pos[i] = lightGridBounds[i] - 1;
}
}
def->ambientLight.Zero();
def->directedLight.Zero();
direction.Zero();
// trilerp the light value
gridStep[0] = 1;
gridStep[1] = lightGridBounds[0];
gridStep[2] = lightGridBounds[0] * lightGridBounds[1];
gridPointIndex = pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2];
gridPoint = &lightGridPoints[ gridPointIndex ];
totalFactor = 0;
for( i = 0; i < 8; i++ )
{
float factor;
factor = 1.0;
gridPoint2 = gridPoint;
for( j = 0; j < 3; j++ )
{
if( i & ( 1 << j ) )
{
int gridPointIndex2 = gridPointIndex + gridStep[j];
if( gridPointIndex2 < 0 || gridPointIndex2 >= lightGridPoints.Num() )
{
continue;
}
factor *= frac[j];
gridPoint2 = &lightGridPoints[ gridPointIndex + gridStep[j] ];
}
else
{
factor *= ( 1.0f - frac[j] );
}
}
if( !( gridPoint2->ambient[0] + gridPoint2->ambient[1] + gridPoint2->ambient[2] ) )
{
continue; // ignore samples in walls
}
totalFactor += factor;
def->ambientLight[0] += factor * gridPoint2->ambient[0] * ( 1.0f / 255.0f );
def->ambientLight[1] += factor * gridPoint2->ambient[1] * ( 1.0f / 255.0f );
def->ambientLight[2] += factor * gridPoint2->ambient[2] * ( 1.0f / 255.0f );
def->directedLight[0] += factor * gridPoint2->directed[0] * ( 1.0f / 255.0f );
def->directedLight[1] += factor * gridPoint2->directed[1] * ( 1.0f / 255.0f );
def->directedLight[2] += factor * gridPoint2->directed[2] * ( 1.0f / 255.0f );
lattitude = DEG2RAD( gridPoint2->latLong[1] * ( 360.0f / 255.0f ) );
longitude = DEG2RAD( gridPoint2->latLong[0] * ( 360.0f / 255.0f ) );
direction2[0] = idMath::Cos( lattitude ) * idMath::Sin( longitude );
direction2[1] = idMath::Sin( lattitude ) * idMath::Sin( longitude );
direction2[2] = idMath::Cos( longitude );
direction += ( direction2 * factor );
//direction += ( gridPoint2->dir * factor );
}
#if 1
if( totalFactor > 0 && totalFactor < 0.99 )
{
totalFactor = 1.0f / totalFactor;
def->ambientLight *= totalFactor;
def->directedLight *= totalFactor;
}
#endif
def->ambientLight[0] = idMath::ClampFloat( 0, 1, def->ambientLight[0] );
def->ambientLight[1] = idMath::ClampFloat( 0, 1, def->ambientLight[1] );
def->ambientLight[2] = idMath::ClampFloat( 0, 1, def->ambientLight[2] );
def->directedLight[0] = idMath::ClampFloat( 0, 1, def->directedLight[0] );
def->directedLight[1] = idMath::ClampFloat( 0, 1, def->directedLight[1] );
def->directedLight[2] = idMath::ClampFloat( 0, 1, def->directedLight[2] );
def->lightDir = direction;
def->lightDir.Normalize();
#if 0
if( VectorLength( ent->ambientLight ) < r_forceAmbient->value )
{
ent->ambientLight[0] = r_forceAmbient->value;
ent->ambientLight[1] = r_forceAmbient->value;
ent->ambientLight[2] = r_forceAmbient->value;
}
#endif
}
}
#endif

View file

@ -858,6 +858,7 @@ bool idRenderWorldLocal::InitFromMap( const char* name )
TouchWorldModels();
AddWorldModelEntities();
ClearPortalStates();
SetupLightGrid();
return true;
}
common->Printf( "idRenderWorldLocal::InitFromMap: timestamp has changed, reloading.\n" );
@ -1047,6 +1048,7 @@ bool idRenderWorldLocal::InitFromMap( const char* name )
AddWorldModelEntities();
ClearPortalStates();
SetupLightGrid();
// done!
return true;

View file

@ -3,7 +3,7 @@
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2014-2016 Robert Beckebans
Copyright (C) 2014-2021 Robert Beckebans
Copyright (C) 2014-2016 Kot in Action Creative Artel
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -59,6 +59,42 @@ typedef struct doublePortal_s
struct doublePortal_s* nextFoggedPortal;
} doublePortal_t;
// RB: added Quake 3 style light grid
struct lightGridPoint_t
{
idVec3 origin; // not saved to .proc
byte ambient[3];
byte directed[3];
byte latLong[2];
};
class LightGrid
{
private:
idVec3 lightGridOrigin;
idVec3 lightGridSize;
int lightGridBounds[3];
public:
idList<lightGridPoint_t> lightGridPoints;
//LightGrid();
// setup light grid for given world bounds
void SetupLightGrid( const idBounds& bounds );
void ProbeIndexToGridIndex( const int probeIndex, int gridIndex[3] );
idVec3 GetProbeIndexDebugColor( const int probeIndex );
// fetch grid lighting on a per object basis
void SetupEntityGridLighting( idRenderEntityLocal* def );
private:
void CalculateLightGridPointPositions();
};
// RB end
typedef struct portalArea_s
{
@ -187,6 +223,9 @@ public:
doublePortal_t* doublePortals;
int numInterAreaPortals;
// RB: added Quake 3 style light grid
LightGrid lightGrid;
idList<idRenderModel*, TAG_MODEL> localModels;
idList<idRenderEntityLocal*, TAG_ENTITY> entityDefs;
@ -337,6 +376,15 @@ public:
//-------------------------------
// tr_light.c
void CreateLightDefInteractions( idRenderLightLocal* const ldef, const int renderViewID );
// RB begin
//--------------------------
// RenderWorld_lightgrid.cpp
private:
void SetupLightGrid();
// RB end
};
// if an entity / light combination has been evaluated and found to not genrate any surfaces or shadows,