Implemented GGX Split Sum approximation using a 2D atlas trick

This commit is contained in:
Robert Beckebans 2021-03-22 20:45:25 +01:00
parent 779534626f
commit 2391ba1b43
14 changed files with 845 additions and 372 deletions

View file

@ -1,11 +1,11 @@
astyle.exe -v --formatted --options=astyle-options.ini --exclude="libs" --exclude="extern" --recursive *.h
astyle.exe -v --formatted --options=astyle-options.ini --exclude="libs" --exclude="extern" --exclude="d3xp/gamesys/SysCvar.cpp" --exclude="d3xp/gamesys/Callbacks.cpp" --exclude="sys/win32/win_cpu.cpp" --exclude="sys/win32/win_main.cpp" --recursive *.cpp
REM astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/imgui/*.h
REM astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/imgui/*.cpp
astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/imgui/*.h
astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/imgui/*.cpp
REM astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/stb/*.h
REM astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/tinyexr/*.h
astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/stb/*.h
astyle.exe -v --formatted --options=astyle-options.ini --recursive libs/tinyexr/*.h
astyle.exe -v -Q --options=astyle-options.ini --recursive ../base/renderprogs/*.hlsl

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").
@ -43,6 +43,8 @@ If you have questions concerning this license or the applicable additional terms
#include "DXT/DXTCodec.h"
#include "Color/ColorSpace.h"
#include "../libs/mesa/format_r11g11b10f.h"
idCVar image_highQualityCompression( "image_highQualityCompression", "0", CVAR_BOOL, "Use high quality (slow) compression" );
idCVar r_useHighQualitySky( "r_useHighQualitySky", "1", CVAR_BOOL | CVAR_ARCHIVE, "Use high quality skyboxes" );
@ -303,6 +305,319 @@ void idBinaryImage::Load2DFromMemory( int width, int height, const byte* pic_con
Mem_Free( pic );
}
/*
========================
RB idBinaryImage::Load2DAtlasMipchainFromMemory
========================
*/
void idBinaryImage::Load2DAtlasMipchainFromMemory( int width, int height, const byte* pic_const, int numLevels, textureFormat_t& textureFormat, textureColor_t& colorFormat )
{
int sourceWidth = width * ( 2.0f / 3.0f ); // RB
fileData.textureType = TT_2D;
fileData.format = textureFormat;
fileData.colorFormat = CFM_DEFAULT;
fileData.width = sourceWidth;
fileData.height = height;
fileData.numLevels = numLevels;
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d)", width, height ) );
byte* sourcePic = ( byte* )Mem_Alloc( width * height * 4, TAG_TEMP );
memcpy( sourcePic, pic_const, width * height * 4 );
if( colorFormat == CFM_YCOCG_DXT5 )
{
// convert the image data to YCoCg and use the YCoCgDXT5 compressor
idColorSpace::ConvertRGBToCoCg_Y( sourcePic, sourcePic, width, height );
}
else if( colorFormat == CFM_NORMAL_DXT5 )
{
// Blah, HQ swizzles automatically, Fast doesn't
if( !image_highQualityCompression.GetBool() )
{
for( int i = 0; i < width * height; i++ )
{
sourcePic[i * 4 + 3] = sourcePic[i * 4 + 0];
sourcePic[i * 4 + 0] = 0;
sourcePic[i * 4 + 2] = 0;
}
}
}
else if( colorFormat == CFM_GREEN_ALPHA )
{
for( int i = 0; i < width * height; i++ )
{
sourcePic[i * 4 + 1] = sourcePic[i * 4 + 3];
sourcePic[i * 4 + 0] = 0;
sourcePic[i * 4 + 2] = 0;
sourcePic[i * 4 + 3] = 0;
}
}
images.SetNum( numLevels );
const int numColors = 5;
static idVec4 colors[numColors] = { colorBlue, colorCyan, colorGreen, colorYellow, colorRed };
for( int level = 0; level < images.Num(); level++ )
{
idBinaryImageData& img = images[ level ];
// RB: create shrunk image which is a copy of the sub image in the atlas
idVec4 rect = R_CalculateMipRect( sourceWidth, level );
int scaledWidth = rect.z;
int scaledHeight = rect.w;
byte* pic = ( byte* )Mem_Alloc( scaledWidth * scaledHeight * 4, TAG_TEMP );
for( int x = rect.x; x < ( rect.x + rect.z ); x++ )
//for( int x = 0; x < rect.z; x++ )
{
for( int y = rect.y; y < ( rect.y + rect.w ); y++ )
//for( int y = 0; y < rect.w; y++ )
{
int sx = x - rect.x;
int sy = y - rect.y;
#if 1
pic[( sy * scaledWidth + sx ) * 4 + 0] = sourcePic[( y * width + x ) * 4 + 0];
pic[( sy * scaledWidth + sx ) * 4 + 1] = sourcePic[( y * width + x ) * 4 + 1];
pic[( sy * scaledWidth + sx ) * 4 + 2] = sourcePic[( y * width + x ) * 4 + 2];
pic[( sy * scaledWidth + sx ) * 4 + 3] = sourcePic[( y * width + x ) * 4 + 3];
#else
int colorIdx = level % numColors;
float color[3];
color[0] = colors[ colorIdx ].x;
color[0] = colors[ colorIdx ].y;
color[0] = colors[ colorIdx ].z;
uint32_t value = float3_to_r11g11b10f( color );
union
{
uint32 i;
byte b[4];
} tmp;
tmp.i = value;
//*( uint32_t* )pic[( sy * scaledWidth + sx ) * 3] = value;
pic[( sy * scaledWidth + sx ) * 4 + 0] = tmp.b[0];
pic[( sy * scaledWidth + sx ) * 4 + 1] = tmp.b[1];
pic[( sy * scaledWidth + sx ) * 4 + 2] = tmp.b[2];
pic[( sy * scaledWidth + sx ) * 4 + 3] = tmp.b[3];
#endif
}
}
// RB end
commonLocal.LoadPacifierBinarizeMiplevel( level + 1, numLevels );
// Images that are going to be DXT compressed and aren't multiples of 4 need to be
// padded out before compressing.
byte* dxtPic = pic;
int dxtWidth = 0;
int dxtHeight = 0;
if( textureFormat == FMT_DXT5 || textureFormat == FMT_DXT1 )
{
if( ( scaledWidth & 3 ) || ( scaledHeight & 3 ) )
{
dxtWidth = ( scaledWidth + 3 ) & ~3;
dxtHeight = ( scaledHeight + 3 ) & ~3;
dxtPic = ( byte* )Mem_ClearedAlloc( dxtWidth * 4 * dxtHeight, TAG_IMAGE );
for( int i = 0; i < scaledHeight; i++ )
{
memcpy( dxtPic + i * dxtWidth * 4, pic + i * scaledWidth * 4, scaledWidth * 4 );
}
}
else
{
dxtPic = pic;
dxtWidth = scaledWidth;
dxtHeight = scaledHeight;
}
}
img.level = level;
img.destZ = 0;
img.width = scaledWidth;
img.height = scaledHeight;
// compress data or convert floats as necessary
if( textureFormat == FMT_DXT1 )
{
idDxtEncoder dxt;
img.Alloc( dxtWidth * dxtHeight / 2 );
if( image_highQualityCompression.GetBool() )
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - DXT1HQ", width, height ) );
dxt.CompressImageDXT1HQ( dxtPic, img.data, dxtWidth, dxtHeight );
}
else
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - DXT1Fast", width, height ) );
dxt.CompressImageDXT1Fast( dxtPic, img.data, dxtWidth, dxtHeight );
}
}
else if( textureFormat == FMT_DXT5 )
{
idDxtEncoder dxt;
img.Alloc( dxtWidth * dxtHeight );
if( colorFormat == CFM_NORMAL_DXT5 )
{
if( image_highQualityCompression.GetBool() )
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - NormalMapDXT5HQ", width, height ) );
dxt.CompressNormalMapDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight );
}
else
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - NormalMapDXT5Fast", width, height ) );
dxt.CompressNormalMapDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight );
}
}
else if( colorFormat == CFM_YCOCG_DXT5 )
{
if( image_highQualityCompression.GetBool() )
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - YCoCgDXT5HQ", width, height ) );
dxt.CompressYCoCgDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight );
}
else
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - YCoCgDXT5Fast", width, height ) );
dxt.CompressYCoCgDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight );
}
}
else
{
fileData.colorFormat = colorFormat = CFM_DEFAULT;
if( image_highQualityCompression.GetBool() )
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - DXT5HQ", width, height ) );
dxt.CompressImageDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight );
}
else
{
commonLocal.LoadPacifierBinarizeInfo( va( "(%d x %d) - DXT5Fast", width, height ) );
dxt.CompressImageDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight );
}
}
}
else if( textureFormat == FMT_LUM8 || textureFormat == FMT_INT8 )
{
// LUM8 and INT8 just read the red channel
img.Alloc( scaledWidth * scaledHeight );
for( int i = 0; i < img.dataSize; i++ )
{
img.data[ i ] = pic[ i * 4 ];
}
}
else if( textureFormat == FMT_ALPHA )
{
// ALPHA reads the alpha channel
img.Alloc( scaledWidth * scaledHeight );
for( int i = 0; i < img.dataSize; i++ )
{
img.data[ i ] = pic[ i * 4 + 3 ];
}
}
else if( textureFormat == FMT_L8A8 )
{
// L8A8 reads the alpha and red channels
img.Alloc( scaledWidth * scaledHeight * 2 );
for( int i = 0; i < img.dataSize / 2; i++ )
{
img.data[ i * 2 + 0 ] = pic[ i * 4 + 0 ];
img.data[ i * 2 + 1 ] = pic[ i * 4 + 3 ];
}
}
else if( textureFormat == FMT_RGB565 )
{
img.Alloc( scaledWidth * scaledHeight * 2 );
for( int i = 0; i < img.dataSize / 2; i++ )
{
unsigned short color = ( ( pic[ i * 4 + 0 ] >> 3 ) << 11 ) | ( ( pic[ i * 4 + 1 ] >> 2 ) << 5 ) | ( pic[ i * 4 + 2 ] >> 3 );
img.data[ i * 2 + 0 ] = ( color >> 8 ) & 0xFF;
img.data[ i * 2 + 1 ] = color & 0xFF;
}
}
else if( textureFormat == FMT_RG16F )
{
// RB: copy it as it was a RGBA8 because of the same size
img.Alloc( scaledWidth * scaledHeight * 4 );
for( int i = 0; i < img.dataSize; i++ )
{
img.data[ i ] = pic[ i ];
}
}
else if( textureFormat == FMT_R11G11B10F )
{
// RB: copy it as it was a RGBA8 because of the same size
img.Alloc( scaledWidth * scaledHeight * 4 );
for( int i = 0; i < img.dataSize; i++ )
{
img.data[ i ] = pic[ i ];
}
}
else if( textureFormat == FMT_RGBA16F )
{
img.Alloc( scaledWidth * scaledHeight * 8 );
for( int i = 0; i < img.dataSize; i++ )
{
img.data[ i ] = pic[ i ];
}
}
else
{
fileData.format = textureFormat = FMT_RGBA8;
img.Alloc( scaledWidth * scaledHeight * 4 );
for( int i = 0; i < img.dataSize; i++ )
{
img.data[ i ] = pic[ i ];
}
}
// if we had to pad to quads, free the padded version
if( pic != dxtPic )
{
Mem_Free( dxtPic );
dxtPic = NULL;
}
// downsample for the next level
/*
byte* shrunk = NULL;
if( gammaMips )
{
shrunk = R_MipMapWithGamma( pic, scaledWidth, scaledHeight );
}
else
{
shrunk = R_MipMap( pic, scaledWidth, scaledHeight );
}
Mem_Free( pic );
pic = shrunk;
*/
Mem_Free( pic );
}
Mem_Free( sourcePic );
}
/*
========================
PadImageTo4x4

View file

@ -53,6 +53,7 @@ public:
}
void Load2DFromMemory( int width, int height, const byte* pic_const, int numLevels, textureFormat_t& textureFormat, textureColor_t& colorFormat, bool gammaMips );
void Load2DAtlasMipchainFromMemory( int width, int height, const byte* pic_const, int numLevels, textureFormat_t& textureFormat, textureColor_t& colorFormat );
void LoadCubeFromMemory( int width, const byte* pics[6], int numLevels, textureFormat_t& textureFormat, bool gammaMips );
ID_TIME_T LoadFromGeneratedFile( ID_TIME_T sourceFileTime );

View file

@ -35,7 +35,7 @@ static const int MAX_SSAO_BUFFERS = 2;
static const int MAX_HIERARCHICAL_ZBUFFERS = 6; // native resolution + 5 MIP LEVELS
static const int RADIANCE_CUBEMAP_SIZE = 256;
static const int IRRADIANCE_CUBEMAP_SIZE = 32;
static const int IRRADIANCE_CUBEMAP_SIZE = 128;
#if 1
static int shadowMapResolutions[MAX_SHADOWMAP_RESOLUTIONS] = { 2048, 1024, 512, 512, 256 };

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) 2013-2017 Robert Beckebans
Copyright (C) 2013-2021 Robert Beckebans
Copyright (C) 2016-2017 Dustin Land
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -236,7 +236,9 @@ typedef enum
CF_2D, // not a cube map
CF_NATIVE, // _px, _nx, _py, etc, directly sent to GL
CF_CAMERA, // _forward, _back, etc, rotated and flipped as needed before sending to GL
CF_2D_ARRAY // not a cube map but not a single 2d texture either
CF_PANORAMA, // TODO latlong encoded HDRI panorama typically used by Substance or Blender
CF_2D_ARRAY, // not a cube map but not a single 2d texture either
CF_2D_PACKED_MIPCHAIN // usually 2d but can be an octahedron, packed mipmaps into single 2d texture atlas and limited to dim^2
} cubeFiles_t;
enum imageFileType_t
@ -643,6 +645,9 @@ void R_VerticalFlip( byte* data, int width, int height );
void R_RotatePic( byte* data, int width );
void R_ApplyCubeMapTransforms( int i, byte* data, int size );
idVec4 R_CalculateMipRect( uint dimensions, uint mip );
int R_CalculateUsedAtlasPixels( int dimensions );
/*
====================================================================

View file

@ -981,6 +981,9 @@ static void LoadEXR( const char* filename, unsigned char** pic, int* width, int*
free( rgba );
}
// RB: EXR needs to be flipped to match the .tga behavior
//R_VerticalFlip( *pic, *width, *height );
Mem_Free( ( void* )fbuffer );
}

View file

@ -1056,8 +1056,8 @@ void idImageManager::CreateIntrinsicImages()
// RB begin
// FIXME change back to TF_DEFAULT
defaultUACIrradianceCube = ImageFromFile( "env/UAC3_amb", TF_NEAREST, TR_CLAMP, TD_R11G11B10F, CF_2D );
defaultUACRadianceCube = ImageFromFile( "env/UAC3_spec", TF_NEAREST, TR_CLAMP, TD_R11G11B10F, CF_2D );
defaultUACIrradianceCube = ImageFromFile( "env/UAC5_amb", TF_DEFAULT, TR_CLAMP, TD_R11G11B10F, CF_2D_PACKED_MIPCHAIN );
defaultUACRadianceCube = ImageFromFile( "env/UAC5_spec", TF_DEFAULT, TR_CLAMP, TD_R11G11B10F, CF_2D_PACKED_MIPCHAIN );
// RB end
release_assert( loadingIconImage->referencedOutsideLevelLoad );

View file

@ -319,7 +319,7 @@ void idImage::ActuallyLoadImage( bool fromBackEnd )
{
opts.textureType = TT_2D_ARRAY;
}
else if( cubeFiles != CF_2D )
else if( cubeFiles == CF_NATIVE || cubeFiles == CF_CAMERA )
{
opts.textureType = TT_CUBIC;
repeat = TR_CLAMP;
@ -439,7 +439,7 @@ void idImage::ActuallyLoadImage( bool fromBackEnd )
//else if( toolUsage )
// binarizeReason = va( "binarize: tool usage '%s'", generatedName.c_str() );
if( cubeFiles != CF_2D )
if( cubeFiles == CF_NATIVE || cubeFiles == CF_CAMERA )
{
int size;
byte* pics[6];
@ -520,6 +520,13 @@ void idImage::ActuallyLoadImage( bool fromBackEnd )
opts.width = width;
opts.height = height;
opts.numLevels = 0;
// RB
if( cubeFiles == CF_2D_PACKED_MIPCHAIN )
{
opts.width = width * ( 2.0f / 3.0f );
}
DeriveOpts();
// foresthale 2014-05-30: give a nice progress display when binarizing
@ -547,7 +554,14 @@ void idImage::ActuallyLoadImage( bool fromBackEnd )
}
// RB: convert to compressed DXT or whatever choosen target format
im.Load2DFromMemory( opts.width, opts.height, pic, opts.numLevels, opts.format, opts.colorFormat, opts.gammaMips );
if( cubeFiles == CF_2D_PACKED_MIPCHAIN )
{
im.Load2DAtlasMipchainFromMemory( width, opts.height, pic, opts.numLevels, opts.format, opts.colorFormat );
}
else
{
im.Load2DFromMemory( opts.width, opts.height, pic, opts.numLevels, opts.format, opts.colorFormat, opts.gammaMips );
}
commonLocal.LoadPacifierBinarizeEnd();
Mem_Free( pic );
@ -650,6 +664,7 @@ void idImage::Print() const
NAME_FORMAT( RGBA16F );
NAME_FORMAT( RGBA32F );
NAME_FORMAT( R32F );
NAME_FORMAT( R11G11B10F );
// RB end
NAME_FORMAT( DEPTH );
NAME_FORMAT( X16 );

View file

@ -3,6 +3,7 @@
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2021 Robert Beckebans
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -570,3 +571,35 @@ void R_ApplyCubeMapTransforms( int iter, byte* data, int size )
}
}
// This is the most efficient way to atlas a mip chain to a 2d texture
// https://twitter.com/SebAaltonen/status/1327188239451611139
idVec4 R_CalculateMipRect( uint dimensions, uint mip )
{
uint pixels_mip = dimensions >> mip;
idVec4 uv_rect = idVec4( 0, 0, pixels_mip, pixels_mip );
if( mip > 0 )
{
uv_rect.x = dimensions;
uv_rect.y = dimensions - pixels_mip * 2;
}
return uv_rect;
}
int R_CalculateUsedAtlasPixels( int dimensions )
{
int numPixels = 0;
const int numMips = idMath::BitsForInteger( dimensions );
for( int mip = 0; mip < numMips; mip++ )
{
idVec4 dstRect = R_CalculateMipRect( dimensions, mip );
numPixels += ( dstRect.z * dstRect.w );
}
return numPixels;
}

View file

@ -2096,6 +2096,11 @@ void idRenderBackend::AmbientPass( const drawSurf_t* const* drawSurfs, int numDr
return;
}
if( viewDef->renderView.rdflags & RDF_NOAMBIENT )
{
return;
}
#if defined( USE_VULKAN )
if( fillGbuffer )
{

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) 2012-2020 Robert Beckebans
Copyright (C) 2012-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").

View file

@ -838,6 +838,24 @@ void R_ReadTiledPixels( int width, int height, byte* buffer, renderView_t* ref =
glPixelStorei( GL_PACK_ROW_LENGTH, RADIANCE_CUBEMAP_SIZE );
glReadPixels( 0, 0, w, h, GL_RGB, GL_HALF_FLOAT, buffer );
#if 0
// TODO vertical flip with half floats
{
int i, j;
uint64 temp;
for( i = 0 ; i < width ; i++ )
{
for( j = 0 ; j < height / 2 ; j++ )
{
temp = *( ( uint64* )buffer + j * width + i );
*( ( uint64* )buffer + j * width + i ) = *( ( uint64* )buffer + ( height - 1 - j ) * width + i );
*( ( uint64* )buffer + ( height - 1 - j ) * width + i ) = temp;
}
}
}
#endif
Framebuffer::Unbind();
}
else

View file

@ -769,10 +769,10 @@ void R_DeriveEnvprobeData( RenderEnvprobeLocal* probe )
// TODO get preconvolved cubemaps
fullname.Format( "env/%s/envprobe%i_amb", basename.c_str(), probeIndex );
probe->irradianceImage = globalImages->ImageFromFile( fullname, TF_NEAREST, TR_CLAMP, TD_R11G11B10F, CF_2D );
probe->irradianceImage = globalImages->ImageFromFile( fullname, TF_DEFAULT, TR_CLAMP, TD_R11G11B10F, CF_2D_PACKED_MIPCHAIN );
fullname.Format( "env/%s/envprobe%i_spec", basename.c_str(), probeIndex );
probe->radianceImage = globalImages->ImageFromFile( fullname, TF_NEAREST, TR_CLAMP, TD_R11G11B10F, CF_2D );
probe->radianceImage = globalImages->ImageFromFile( fullname, TF_DEFAULT, TR_CLAMP, TD_R11G11B10F, CF_2D_PACKED_MIPCHAIN );
// ------------------------------------
// compute the light projection matrix

View file

@ -1,4 +1,4 @@
/*
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
@ -496,6 +496,439 @@ idVec2 IntegrateBRDF( float NdotV, float roughness, int sampleCount )
return idVec2( A, B );
}
// Compute normalized oct coord, mapping top left of top left pixel to (-1,-1)
idVec2 NormalizedOctCoord( int x, int y, const int probeSideLength )
{
const int margin = 0;
int probeWithBorderSide = probeSideLength + margin;
idVec2 octFragCoord = idVec2( ( x - margin ) % probeWithBorderSide, ( y - margin ) % probeWithBorderSide );
// Add back the half pixel to get pixel center normalized coordinates
return ( idVec2( octFragCoord ) + idVec2( 0.5f, 0.5f ) ) * ( 2.0f / float( probeSideLength ) ) - idVec2( 1.0f, 1.0f );
}
/*
==================
R_MakeAmbientMap_f
R_MakeAmbientMap_f <basename> [size]
Saves out env/<basename>_amb_ft.tga, etc
==================
*/
void R_MakeAmbientMap( const char* baseName, const char* suffix, int outSize, bool specular )
{
idStr fullname;
renderView_t ref;
viewDef_t primary;
byte* buffers[6];
int width = 0, height = 0;
memset( &cubeAxis, 0, sizeof( cubeAxis ) );
cubeAxis[0][0][0] = 1;
cubeAxis[0][1][2] = 1;
cubeAxis[0][2][1] = 1;
cubeAxis[1][0][0] = -1;
cubeAxis[1][1][2] = -1;
cubeAxis[1][2][1] = 1;
cubeAxis[2][0][1] = 1;
cubeAxis[2][1][0] = -1;
cubeAxis[2][2][2] = -1;
cubeAxis[3][0][1] = -1;
cubeAxis[3][1][0] = -1;
cubeAxis[3][2][2] = 1;
cubeAxis[4][0][2] = 1;
cubeAxis[4][1][0] = -1;
cubeAxis[4][2][1] = 1;
cubeAxis[5][0][2] = -1;
cubeAxis[5][1][0] = 1;
cubeAxis[5][2][1] = 1;
// 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;
}
}
bool pacifier = true;
// resample with hemispherical blending
int samples = 1000;
int outWidth = int( outSize * 1.5f );
int outHeight = outSize;
//halfFloat_t* outBuffer = ( halfFloat_t* )_alloca( outSize * outSize * 3 * sizeof( halfFloat_t ) );
halfFloat_t* outBuffer = ( halfFloat_t* )R_StaticAlloc( idMath::Ceil( outSize * outSize * 3 * sizeof( halfFloat_t ) * 1.5f ), TAG_IMAGE );
{
// output an octahedron probe
CommandlineProgressBar progressBar( R_CalculateUsedAtlasPixels( outSize ) );
int start = Sys_Milliseconds();
const float invDstSize = 1.0f / float( outSize );
const int numMips = idMath::BitsForInteger( outSize );
// reset image to black
for( int x = 0; x < outWidth; x++ )
{
for( int y = 0; y < outHeight; y++ )
{
outBuffer[( y * outWidth + x ) * 3 + 0] = F32toF16( 0 );
outBuffer[( y * outWidth + x ) * 3 + 1] = F32toF16( 0 );
outBuffer[( y * outWidth + x ) * 3 + 2] = F32toF16( 0 );
}
}
for( int mip = 0; mip < numMips; mip++ )
{
float roughness = ( float )mip / ( float )( numMips - 1 );
idVec4 dstRect = R_CalculateMipRect( outSize, 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 N;
N.FromOctahedral( octCoord );
#if 1
// RB: Split Sum approximation explanation
// Epic Games makes a further approximation by assuming the view direction
// (and thus the specular reflection direction) to be equal to the output sample direction ωo.
// This translates itself to the following code:
const idVec3 R = N;
const idVec3 V = R;
idVec3 prefilteredColor( 0, 0, 0 );
if( specular )
{
float totalWeight = 0.0f;
for( int s = 0; s < samples; s++ )
{
idVec2 Xi = Hammersley2D( s, samples );
idVec3 H = ImportanceSampleGGX( Xi, N, roughness );
idVec3 L = ( 2.0 * ( H * ( V * H ) ) - V );
float NdotL = Max( ( N * L ), 0.0f );
if( NdotL > 0.0 )
{
float sample[3];
R_SampleCubeMapHDR( H, width, buffers, sample );
prefilteredColor[0] += sample[0] * NdotL;
prefilteredColor[1] += sample[1] * NdotL;
prefilteredColor[2] += sample[2] * NdotL;
totalWeight += NdotL;
}
}
prefilteredColor[0] /= totalWeight;
prefilteredColor[1] /= totalWeight;
prefilteredColor[2] /= totalWeight;
}
else
{
for( int s = 0; s < samples; s++ )
{
idVec2 Xi = Hammersley2D( s, samples );
idVec3 H = ImportanceSampleGGX( Xi, N, 0.95f );
float sample[3];
R_SampleCubeMapHDR( H, width, buffers, sample );
prefilteredColor[0] += sample[0];
prefilteredColor[1] += sample[1];
prefilteredColor[2] += sample[2];
}
prefilteredColor[0] /= samples;
prefilteredColor[1] /= samples;
prefilteredColor[2] /= samples;
}
outBuffer[( y * outWidth + x ) * 3 + 0] = F32toF16( prefilteredColor[0] );
outBuffer[( y * outWidth + x ) * 3 + 1] = F32toF16( prefilteredColor[1] );
outBuffer[( y * outWidth + x ) * 3 + 2] = F32toF16( prefilteredColor[2] );
#else
outBuffer[( y * outWidth + x ) * 3 + 0] = F32toF16( ( N.x * 0.5f + 0.5f ) );
outBuffer[( y * outWidth + x ) * 3 + 1] = F32toF16( ( N.y * 0.5f + 0.5f ) );
outBuffer[( y * outWidth + x ) * 3 + 2] = F32toF16( ( N.z * 0.5f + 0.5f ) );
#endif
progressBar.Increment();
}
}
}
fullname.Format( "env/%s%s.exr", baseName, suffix );
//common->Printf( "writing %s\n", fullname.c_str() );
const bool captureToImage = false;
common->UpdateScreen( captureToImage );
//R_WriteTGA( fullname, outBuffer, outSize, outSize, false, "fs_basepath" );
//R_WritePNG( fullname, outBuffer, 4, outSize, outSize, true, "fs_basepath" );
R_WriteEXR( fullname, ( byte* )outBuffer, 3, outWidth, outHeight, "fs_basepath" );
int end = Sys_Milliseconds();
common->Printf( "env/%s convolved in %5.1f seconds\n\n", baseName, ( end - start ) * 0.001f );
}
for( int i = 0 ; i < 6 ; i++ )
{
if( buffers[i] )
{
Mem_Free( buffers[i] );
}
}
Mem_Free( outBuffer );
}
/*
==================
R_MakeAmbientMap_f
R_MakeAmbientMap_f <basename> [size]
Saves out env/<basename>_amb_ft.tga, etc
==================
*/
//void R_MakeAmbientMap_f( const idCmdArgs& args )
CONSOLE_COMMAND( makeAmbientMap, "Saves out env/<basename>_amb_ft.tga, etc", NULL )
{
const char* baseName;
int outSize;
float roughness;
if( args.Argc() != 2 && args.Argc() != 3 && args.Argc() != 4 )
{
common->Printf( "USAGE: makeAmbientMap <basename> [size]\n" );
return;
}
baseName = args.Argv( 1 );
if( args.Argc() >= 3 )
{
outSize = atoi( args.Argv( 2 ) );
}
else
{
outSize = 32;
}
if( args.Argc() == 4 )
{
roughness = atof( args.Argv( 3 ) );
}
else
{
roughness = 0.95f;
}
if( roughness > 0.8f )
{
R_MakeAmbientMap( baseName, "_amb", outSize, false );
}
else
{
R_MakeAmbientMap( baseName, "_spec", outSize, true );
}
}
CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL )
{
idStr fullname;
idStr baseName;
idMat3 axis[6], oldAxis;
idVec3 oldPosition;
renderView_t ref;
int blends;
const char* extension;
int size;
int old_fov_x, old_fov_y;
static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" };
if( !tr.primaryWorld )
{
common->Printf( "No primary world loaded.\n" );
return;
}
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;
memset( &axis, 0, sizeof( axis ) );
// +X
axis[0][0][0] = 1;
axis[0][1][2] = 1;
axis[0][2][1] = 1;
// -X
axis[1][0][0] = -1;
axis[1][1][2] = -1;
axis[1][2][1] = 1;
// +Y
axis[2][0][1] = 1;
axis[2][1][0] = -1;
axis[2][2][2] = -1;
// -Y
axis[3][0][1] = -1;
axis[3][1][0] = -1;
axis[3][2][2] = 1;
// +Z
axis[4][0][2] = 1;
axis[4][1][0] = -1;
axis[4][2][1] = 1;
// -Z
axis[5][0][2] = -1;
axis[5][1][0] = 1;
axis[5][2][1] = 1;
//--------------------------------------------
// CAPTURE SCENE LIGHTING TO CUBEMAPS
//--------------------------------------------
// so we return to that axis and fov after the fact.
oldPosition = primary.renderView.vieworg;
oldAxis = primary.renderView.viewaxis;
old_fov_x = primary.renderView.fov_x;
old_fov_y = primary.renderView.fov_y;
for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ )
{
RenderEnvprobeLocal* def = tr.primaryWorld->envprobeDefs[i];
if( def == NULL )
{
continue;
}
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 = def->parms.origin;
ref.viewaxis = axis[j];
extension = envDirection[ j ];
fullname.Format( "env/%s/envprobe%i%s", baseName.c_str(), i, extension );
tr.TakeScreenshot( size, size, fullname, blends, &ref, EXR );
//tr.CaptureRenderToFile( fullname, false );
}
}
// restore the original axis and fov
/*
ref.vieworg = oldPosition;
ref.viewaxis = oldAxis;
ref.fov_x = old_fov_x;
ref.fov_y = old_fov_y;
cvarSystem->SetCVarInteger( "r_windowWidth", res_w );
cvarSystem->SetCVarInteger( "r_windowHeight", res_h );
R_SetNewMode( false ); // the same as "vid_restart"
*/
common->Printf( "Wrote a env set with the name %s\n", baseName.c_str() );
//--------------------------------------------
// CONVOLVE CUBEMAPS
//--------------------------------------------
int start = Sys_Milliseconds();
for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ )
{
RenderEnvprobeLocal* def = tr.primaryWorld->envprobeDefs[i];
if( def == NULL )
{
continue;
}
fullname.Format( "%s/envprobe%i", baseName.c_str(), i );
R_MakeAmbientMap( fullname.c_str(), "_amb", IRRADIANCE_CUBEMAP_SIZE, false );
R_MakeAmbientMap( fullname.c_str(), "_spec", RADIANCE_CUBEMAP_SIZE, true );
}
int end = Sys_Milliseconds();
common->Printf( "convolved probes in %5.1f seconds\n\n", ( end - start ) * 0.001f );
}
//void R_MakeBrdfLut_f( const idCmdArgs& args )
CONSOLE_COMMAND( makeBrdfLUT, "make a GGX BRDF lookup table", NULL )
{
@ -608,358 +1041,3 @@ static const unsigned char brfLutTexBytes[] =
Mem_Free( ldrBuffer );
Mem_Free( hdrBuffer );
}
// Compute normalized oct coord, mapping top left of top left pixel to (-1,-1)
idVec2 NormalizedOctCoord( int x, int y, const int probeSideLength )
{
const int margin = 0;
int probeWithBorderSide = probeSideLength + margin;
idVec2 octFragCoord = idVec2( ( x - margin ) % probeWithBorderSide, ( y - margin ) % probeWithBorderSide );
// Add back the half pixel to get pixel center normalized coordinates
return ( idVec2( octFragCoord ) + idVec2( 0.5f, 0.5f ) ) * ( 2.0f / float( probeSideLength ) ) - idVec2( 1.0f, 1.0f );
}
/*
==================
R_MakeAmbientMap_f
R_MakeAmbientMap_f <basename> [size]
Saves out env/<basename>_amb_ft.tga, etc
==================
*/
void R_MakeAmbientMap( const char* baseName, const char* suffix, int outSize, float roughness )
{
idStr fullname;
renderView_t ref;
viewDef_t primary;
byte* buffers[6];
int width = 0, height = 0;
memset( &cubeAxis, 0, sizeof( cubeAxis ) );
cubeAxis[0][0][0] = 1;
cubeAxis[0][1][2] = 1;
cubeAxis[0][2][1] = 1;
cubeAxis[1][0][0] = -1;
cubeAxis[1][1][2] = -1;
cubeAxis[1][2][1] = 1;
cubeAxis[2][0][1] = 1;
cubeAxis[2][1][0] = -1;
cubeAxis[2][2][2] = -1;
cubeAxis[3][0][1] = -1;
cubeAxis[3][1][0] = -1;
cubeAxis[3][2][2] = 1;
cubeAxis[4][0][2] = 1;
cubeAxis[4][1][0] = -1;
cubeAxis[4][2][1] = 1;
cubeAxis[5][0][2] = -1;
cubeAxis[5][1][0] = 1;
cubeAxis[5][2][1] = 1;
// 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;
}
}
bool pacifier = true;
// resample with hemispherical blending
int samples = 1000;
//halfFloat_t* outBuffer = ( halfFloat_t* )_alloca( outSize * outSize * 3 * sizeof( halfFloat_t ) );
halfFloat_t* outBuffer = ( halfFloat_t* )R_StaticAlloc( outSize * outSize * 3 * sizeof( halfFloat_t ) , TAG_IMAGE );
{
// output an octahedron probe
CommandlineProgressBar progressBar( outSize * outSize );
int start = Sys_Milliseconds();
const float invDstSize = 1.0f / float( outSize );
for( int x = 0 ; x < outSize ; x++ )
{
for( int y = 0 ; y < outSize ; y++ )
{
idVec3 dir;
float total[3];
// convert UV coord from [0, 1] to [-1, 1] space
const float u = 2.0f * x * invDstSize - 1.0f;
const float v = 2.0f * y * invDstSize - 1.0f;
idVec2 octCoord = NormalizedOctCoord( x, y, outSize );
// convert UV coord to 3D direction
dir.FromOctahedral( octCoord );
#if 1
total[0] = total[1] = total[2] = 0;
//float roughness = map ? 0.1 : 0.95; // small for specular, almost hemisphere for ambient
for( int s = 0 ; s < samples ; s++ )
{
idVec2 Xi = Hammersley2D( s, samples );
idVec3 test = ImportanceSampleGGX( Xi, dir, roughness );
float result[3];
//test = dir;
R_SampleCubeMapHDR( test, width, buffers, result );
total[0] += result[0];
total[1] += result[1];
total[2] += result[2];
}
outBuffer[( y * outSize + x ) * 3 + 0] = F32toF16( total[0] / samples );
outBuffer[( y * outSize + x ) * 3 + 1] = F32toF16( total[1] / samples );
outBuffer[( y * outSize + x ) * 3 + 2] = F32toF16( total[2] / samples );
#else
outBuffer[( y * outSize + x ) * 3 + 0] = F32toF16( ( dir.x * 0.5f + 0.5f ) );
outBuffer[( y * outSize + x ) * 3 + 1] = F32toF16( ( dir.y * 0.5f + 0.5f ) );
outBuffer[( y * outSize + x ) * 3 + 2] = F32toF16( ( dir.z * 0.5f + 0.5f ) );
#endif
progressBar.Increment();
}
}
fullname.Format( "env/%s%s.exr", baseName, suffix );
//common->Printf( "writing %s\n", fullname.c_str() );
const bool captureToImage = false;
common->UpdateScreen( captureToImage );
//R_WriteTGA( fullname, outBuffer, outSize, outSize, false, "fs_basepath" );
//R_WritePNG( fullname, outBuffer, 4, outSize, outSize, true, "fs_basepath" );
R_WriteEXR( fullname, (byte*)outBuffer, 3, outSize, outSize, "fs_basepath" );
int end = Sys_Milliseconds();
common->Printf( "env/%s convolved in %5.1f seconds\n\n", baseName, ( end - start ) * 0.001f );
}
for( int i = 0 ; i < 6 ; i++ )
{
if( buffers[i] )
{
Mem_Free( buffers[i] );
}
}
Mem_Free( outBuffer );
}
/*
==================
R_MakeAmbientMap_f
R_MakeAmbientMap_f <basename> [size]
Saves out env/<basename>_amb_ft.tga, etc
==================
*/
//void R_MakeAmbientMap_f( const idCmdArgs& args )
CONSOLE_COMMAND( makeAmbientMap, "Saves out env/<basename>_amb_ft.tga, etc", NULL )
{
const char* baseName;
int outSize;
float roughness;
if( args.Argc() != 2 && args.Argc() != 3 && args.Argc() != 4 )
{
common->Printf( "USAGE: makeAmbientMap <basename> [size]\n" );
return;
}
baseName = args.Argv( 1 );
if( args.Argc() >= 3 )
{
outSize = atoi( args.Argv( 2 ) );
}
else
{
outSize = 32;
}
if( args.Argc() == 4 )
{
roughness = atof( args.Argv( 3 ) );
}
else
{
roughness = 0.95f;
}
if( roughness > 0.8f )
{
R_MakeAmbientMap( baseName, "_amb", outSize, roughness );
}
else
{
R_MakeAmbientMap( baseName, "_spec", outSize, roughness );
}
}
CONSOLE_COMMAND( generateEnvironmentProbes, "Generate environment probes", NULL )
{
idStr fullname;
idStr baseName;
idMat3 axis[6], oldAxis;
idVec3 oldPosition;
renderView_t ref;
int blends;
const char* extension;
int size;
int old_fov_x, old_fov_y;
static const char* envDirection[6] = { "_px", "_nx", "_py", "_ny", "_pz", "_nz" };
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;
memset( &axis, 0, sizeof( axis ) );
// +X
axis[0][0][0] = 1;
axis[0][1][2] = 1;
axis[0][2][1] = 1;
// -X
axis[1][0][0] = -1;
axis[1][1][2] = -1;
axis[1][2][1] = 1;
// +Y
axis[2][0][1] = 1;
axis[2][1][0] = -1;
axis[2][2][2] = -1;
// -Y
axis[3][0][1] = -1;
axis[3][1][0] = -1;
axis[3][2][2] = 1;
// +Z
axis[4][0][2] = 1;
axis[4][1][0] = -1;
axis[4][2][1] = 1;
// -Z
axis[5][0][2] = -1;
axis[5][1][0] = 1;
axis[5][2][1] = 1;
//--------------------------------------------
// CAPTURE SCENE LIGHTING TO CUBEMAPS
//--------------------------------------------
// so we return to that axis and fov after the fact.
oldPosition = primary.renderView.vieworg;
oldAxis = primary.renderView.viewaxis;
old_fov_x = primary.renderView.fov_x;
old_fov_y = primary.renderView.fov_y;
for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ )
{
RenderEnvprobeLocal* def = tr.primaryWorld->envprobeDefs[i];
if( def == NULL )
{
continue;
}
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 = def->parms.origin;
ref.viewaxis = axis[j];
extension = envDirection[ j ];
fullname.Format( "env/%s/envprobe%i%s", baseName.c_str(), i, extension );
tr.TakeScreenshot( size, size, fullname, blends, &ref, EXR );
//tr.CaptureRenderToFile( fullname, false );
}
}
// restore the original axis and fov
/*
ref.vieworg = oldPosition;
ref.viewaxis = oldAxis;
ref.fov_x = old_fov_x;
ref.fov_y = old_fov_y;
cvarSystem->SetCVarInteger( "r_windowWidth", res_w );
cvarSystem->SetCVarInteger( "r_windowHeight", res_h );
R_SetNewMode( false ); // the same as "vid_restart"
*/
common->Printf( "Wrote a env set with the name %s\n", baseName.c_str() );
//--------------------------------------------
// CONVOLVE CUBEMAPS
//--------------------------------------------
int start = Sys_Milliseconds();
for( int i = 0; i < tr.primaryWorld->envprobeDefs.Num(); i++ )
{
RenderEnvprobeLocal* def = tr.primaryWorld->envprobeDefs[i];
if( def == NULL )
{
continue;
}
fullname.Format( "%s/envprobe%i", baseName.c_str(), i );
R_MakeAmbientMap( fullname.c_str(), "_amb", IRRADIANCE_CUBEMAP_SIZE, 0.95f );
R_MakeAmbientMap( fullname.c_str(), "_spec", RADIANCE_CUBEMAP_SIZE, 0.1f );
}
int end = Sys_Milliseconds();
common->Printf( "convolved probes in %5.1f seconds\n\n", ( end - start ) * 0.001f );
}