quadrilateralcowboy/renderer/draw_exp.cpp

2619 lines
80 KiB
C++
Raw Permalink Normal View History

2020-06-12 21:06:25 +00:00
/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
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 "../idlib/precompiled.h"
#pragma hdrstop
#include "tr_local.h"
#include "../sys/win32/win_local.h"
/*
strictly experimental / research codepaths
!!!if we use front facing occluders, we can portal flow from light centers
try depth_component_16 rendering
do we care about portals from light perspective? back / front face issues.
how do we do weapon depth hacks with shadow buffers?
distort their world space vertexes instead of offsetting their depth?
jittering off the side of a projection will give wrong shadows
really huge lights, like sunlight, are going to be problematic with fixed projections
we could tile the projections and let the auto-resize cut them down as necessary
It sucks that depth buffers are non-linear, because the bias and compares change with distance
polygon offset factor causes occasional texture holes from highly angled textures
*/
static bool initialized;
static int lightBufferSize = 1024;
static int maxLightBufferSize = 1024;
static float lightBufferSizeFraction = 0.5;
static int viewBufferSize = 1024;
static int viewBufferHeight = 768;
static int maxViewBufferSize = 1024;
static float viewBufferSizeFraction = 0.5;
static float viewBufferHeightFraction = 0.5;
static bool nativeViewBuffer = false; // true if viewBufferSize is the viewport width
static HPBUFFERARB floatPbuffer;
static HDC floatPbufferDC;
static idImage *floatPbufferImage;
static HPBUFFERARB floatPbuffer2;
static HDC floatPbuffer2DC;
static idImage *floatPbuffer2Image;
static HPBUFFERARB floatPbufferQuarter;
static HDC floatPbufferQuarterDC;
static idImage *floatPbufferQuarterImage;
static HGLRC floatContext;
static HPBUFFERARB shadowPbuffer;
static HDC shadowPbufferDC;
static HPBUFFERARB viewPbuffer;
static HDC viewPbufferDC;
static idImage *shadowImage[3];
static idImage *viewDepthImage;
static idImage *viewAlphaImage;
static idImage *viewShadowImage;
static idImage *jitterImage16;
static idImage *jitterImage4;
static idImage *jitterImage1;
static idImage *random256Image;
static int shadowVertexProgram;
static int shadowFragmentProgram16;
static int shadowFragmentProgram4;
static int shadowFragmentProgram1;
static int shadowFragmentProgram0;
static int screenSpaceShadowVertexProgram;
static int screenSpaceShadowFragmentProgram16;
static int screenSpaceShadowFragmentProgram4;
static int screenSpaceShadowFragmentProgram1;
static int screenSpaceShadowFragmentProgram0;
static int depthMidpointVertexProgram;
static int depthMidpointFragmentProgram;
static int shadowResampleVertexProgram;
static int shadowResampleFragmentProgram;
static int gammaDitherVertexProgram;
static int gammaDitherFragmentProgram;
static int downSampleVertexProgram;
static int downSampleFragmentProgram;
static int bloomVertexProgram;
static int bloomFragmentProgram;
static float viewLightAxialSize;
idCVar r_sb_lightResolution( "r_sb_lightResolution", "1024", CVAR_RENDERER | CVAR_INTEGER, "Pixel dimensions for each shadow buffer, 64 - 2048" );
idCVar r_sb_viewResolution( "r_sb_viewResolution", "1024", CVAR_RENDERER | CVAR_INTEGER, "Width of screen space shadow sampling" );
idCVar r_sb_noShadows( "r_sb_noShadows", "0", CVAR_RENDERER | CVAR_BOOL, "don't draw any occluders" );
idCVar r_sb_usePbuffer( "r_sb_usePbuffer", "1", CVAR_RENDERER | CVAR_BOOL, "draw offscreen" );
idCVar r_sb_jitterScale( "r_sb_jitterScale", "0.006", CVAR_RENDERER | CVAR_FLOAT, "scale factor for jitter offset" );
idCVar r_sb_biasScale( "r_sb_biasScale", "0.0001", CVAR_RENDERER | CVAR_FLOAT, "scale factor for jitter bias" );
idCVar r_sb_samples( "r_sb_samples", "4", CVAR_RENDERER | CVAR_INTEGER, "0, 1, 4, or 16" );
idCVar r_sb_randomize( "r_sb_randomize", "1", CVAR_RENDERER | CVAR_BOOL, "randomly offset jitter texture each draw" );
// polyOfsFactor causes holes in low res images
idCVar r_sb_polyOfsFactor( "r_sb_polyOfsFactor", "2", CVAR_RENDERER | CVAR_FLOAT, "polygonOffset factor for drawing shadow buffer" );
idCVar r_sb_polyOfsUnits( "r_sb_polyOfsUnits", "3000", CVAR_RENDERER | CVAR_FLOAT, "polygonOffset units for drawing shadow buffer" );
idCVar r_sb_occluderFacing( "r_sb_occluderFacing", "0", CVAR_RENDERER | CVAR_INTEGER, "0 = front faces, 1 = back faces, 2 = midway between" );
// r_sb_randomizeBufferOrientation?
idCVar r_sb_frustomFOV( "r_sb_frustomFOV", "92", CVAR_RENDERER | CVAR_FLOAT, "oversize FOV for point light side matching" );
idCVar r_sb_showFrustumPixels( "r_sb_showFrustumPixels", "0", CVAR_RENDERER | CVAR_BOOL, "color the pixels contained in the frustum" );
idCVar r_sb_singleSide( "r_sb_singleSide", "-1", CVAR_RENDERER | CVAR_INTEGER, "only draw a single side (0-5) of point lights" );
idCVar r_sb_useCulling( "r_sb_useCulling", "1", CVAR_RENDERER | CVAR_BOOL, "cull geometry to individual side frustums" );
idCVar r_sb_linearFilter( "r_sb_linearFilter", "1", CVAR_RENDERER | CVAR_BOOL, "use GL_LINEAR instead of GL_NEAREST on shadow maps" );
idCVar r_sb_screenSpaceShadow( "r_sb_screenSpaceShadow", "1", CVAR_RENDERER | CVAR_BOOL, "build shadows in screen space instead of on surfaces" );
idCVar r_hdr_useFloats( "r_hdr_useFloats", "0", CVAR_RENDERER | CVAR_BOOL, "use a floating point rendering buffer" );
idCVar r_hdr_exposure( "r_hdr_exposure", "1.0", CVAR_RENDERER | CVAR_FLOAT, "maximum light scale" );
idCVar r_hdr_bloomFraction( "r_hdr_bloomFraction", "0.1", CVAR_RENDERER | CVAR_FLOAT, "fraction to smear across neighbors" );
idCVar r_hdr_gamma( "r_hdr_gamma", "1", CVAR_RENDERER | CVAR_FLOAT, "monitor gamma power" );
idCVar r_hdr_monitorDither( "r_hdr_monitorDither", "0.01", CVAR_RENDERER | CVAR_FLOAT, "random dither in monitor space" );
// from world space to light origin, looking down the X axis
static float unflippedLightMatrix[16];
// from world space to OpenGL view space, looking down the negative Z axis
static float lightMatrix[16];
// from OpenGL view space to OpenGL NDC ( -1 : 1 in XYZ )
static float lightProjectionMatrix[16];
void RB_ARB2_DrawInteraction( const drawInteraction_t *din );
typedef struct {
const char *name;
int num;
} wglString_t;
wglString_t wglString[] = {
{ "WGL_NUMBER_PIXEL_FORMATS_ARB", 0x2000 },
{ "WGL_DRAW_TO_WINDOW_ARB", 0x2001 },
{ "WGL_DRAW_TO_BITMAP_ARB", 0x2002 },
{ "WGL_ACCELERATION_ARB", 0x2003 },
{ "WGL_NEED_PALETTE_ARB", 0x2004 },
{ "WGL_NEED_SYSTEM_PALETTE_ARB", 0x2005 },
{ "WGL_SWAP_LAYER_BUFFERS_ARB", 0x2006 },
{ "WGL_SWAP_METHOD_ARB", 0x2007 },
{ "WGL_NUMBER_OVERLAYS_ARB", 0x2008 },
{ "WGL_NUMBER_UNDERLAYS_ARB", 0x2009 },
{ "WGL_TRANSPARENT_ARB", 0x200A },
{ "WGL_TRANSPARENT_RED_VALUE_ARB", 0x2037 },
{ "WGL_TRANSPARENT_GREEN_VALUE_ARB", 0x2038 },
{ "WGL_TRANSPARENT_BLUE_VALUE_ARB", 0x2039 },
{ "WGL_TRANSPARENT_ALPHA_VALUE_ARB", 0x203A },
{ "WGL_TRANSPARENT_INDEX_VALUE_ARB", 0x203B },
{ "WGL_SHARE_DEPTH_ARB", 0x200C },
{ "WGL_SHARE_STENCIL_ARB", 0x200D },
{ "WGL_SHARE_ACCUM_ARB", 0x200E },
{ "WGL_SUPPORT_GDI_ARB", 0x200F },
{ "WGL_SUPPORT_OPENGL_ARB", 0x2010 },
{ "WGL_DOUBLE_BUFFER_ARB", 0x2011 },
{ "WGL_STEREO_ARB", 0x2012 },
{ "WGL_PIXEL_TYPE_ARB", 0x2013 },
{ "WGL_COLOR_BITS_ARB", 0x2014 },
{ "WGL_RED_BITS_ARB", 0x2015 },
{ "WGL_RED_SHIFT_ARB", 0x2016 },
{ "WGL_GREEN_BITS_ARB", 0x2017 },
{ "WGL_GREEN_SHIFT_ARB", 0x2018 },
{ "WGL_BLUE_BITS_ARB", 0x2019 },
{ "WGL_BLUE_SHIFT_ARB", 0x201A },
{ "WGL_ALPHA_BITS_ARB", 0x201B },
{ "WGL_ALPHA_SHIFT_ARB", 0x201C },
{ "WGL_ACCUM_BITS_ARB", 0x201D },
{ "WGL_ACCUM_RED_BITS_ARB", 0x201E },
{ "WGL_ACCUM_GREEN_BITS_ARB", 0x201F },
{ "WGL_ACCUM_BLUE_BITS_ARB", 0x2020 },
{ "WGL_ACCUM_ALPHA_BITS_ARB", 0x2021 },
{ "WGL_DEPTH_BITS_ARB", 0x2022 },
{ "WGL_STENCIL_BITS_ARB", 0x2023 },
{ "WGL_AUX_BUFFERS_ARB", 0x2024 },
{ "WGL_NO_ACCELERATION_ARB", 0x2025 },
{ "WGL_GENERIC_ACCELERATION_ARB", 0x2026 },
{ "WGL_FULL_ACCELERATION_ARB", 0x2027 },
{ "WGL_SWAP_EXCHANGE_ARB", 0x2028 },
{ "WGL_SWAP_COPY_ARB", 0x2029 },
{ "WGL_SWAP_UNDEFINED_ARB", 0x202A },
{ "WGL_TYPE_RGBA_ARB", 0x202B },
{ "WGL_TYPE_COLORINDEX_ARB", 0x202C },
};
static const int NUM_WGL_STRINGS = sizeof( wglString ) / sizeof( wglString[0] );
static void R_CheckWglErrors( void ) {
int err = GetLastError();
char *name;
#if 0
LPVOID lpMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
#endif
err &= 0xffff;
switch ( err ) {
case 13: name = "ERROR_INVALID_DATA"; break;
case 6: name = "ERROR_INVALID_HANDLE"; break;
case 4317: name = "ERROR_INVALID_OPERATION"; break;
default: name = va( "code %i", err ); break;
}
common->Printf( "GetLastError: %s\n", name );
}
static void R_MakeCurrent( HDC dc, HGLRC context, HPBUFFERARB pbuffer ) {
if ( pbuffer ) {
if ( !wglReleaseTexImageARB( pbuffer, WGL_FRONT_LEFT_ARB ) ) {
R_CheckWglErrors();
common->Error( "wglReleaseTexImageARB failed" );
}
}
if ( !qwglMakeCurrent( dc, context ) ) {
R_CheckWglErrors();
common->FatalError( "qwglMakeCurrent failed" );
}
}
static void R_BindTexImage( HPBUFFERARB pbuffer ) {
if ( !wglReleaseTexImageARB( pbuffer, WGL_FRONT_LEFT_ARB ) ) {
R_CheckWglErrors();
common->Error( "wglReleaseTexImageARB failed" );
}
if ( !wglBindTexImageARB( pbuffer, WGL_FRONT_LEFT_ARB ) ) {
R_CheckWglErrors();
common->Error( "failed wglBindTexImageARB" );
}
}
static void R_ReportTextureParms( void ) {
int parms[8];
// q glGetTexParameteriv( GL_TEXTURE_RECTANGLE_NV,
qglGetIntegerv( GL_TEXTURE_BINDING_RECTANGLE_NV, parms );
}
/*
====================
RB_CreateBloomTable
====================
*/
static const int BLOOM_RADIUS = 8;
static void RB_CreateBloomTable( void ) {
float bloom[BLOOM_RADIUS];
float total = 0;
// gaussian
float stdDev = 2.0;
for ( int i = 0 ; i < BLOOM_RADIUS ; i++ ) {
float f = (float)i / stdDev;
bloom[i] = exp( -0.5 * f * f );
total += bloom[i];
}
total = ( total - bloom[0] ) * 2 + bloom[0];
// normalize to 1.0 contribution, so a full row or column will equal 1.0
for ( int i = 0 ; i < BLOOM_RADIUS ; i++ ) {
bloom[i] *= 1.0 / total;
common->Printf( "PARAM bloom%i = { %f };\n", i, bloom[i] );
}
}
/*
====================
GL_SelectTextureNoClient
====================
*/
static void GL_SelectTextureNoClient( int unit ) {
backEnd.glState.currenttmu = unit;
qglActiveTextureARB( GL_TEXTURE0_ARB + unit );
RB_LogComment( "glActiveTextureARB( %i )\n", unit );
}
/*
================
R_CreateShadowBufferImage
================
*/
static void R_CreateShadowBufferImage( idImage *image ) {
byte *data = (byte *)Mem_Alloc( lightBufferSize*lightBufferSize );
memset( data, 0, lightBufferSize*lightBufferSize );
image->GenerateImage( (byte *)data, 4, 4,
TF_LINEAR, false, TR_CLAMP_TO_BORDER, TD_HIGH_QUALITY );
// now reset it to a shadow depth image
GL_CheckErrors();
image->uploadWidth = image->uploadHeight = lightBufferSize;
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24_ARB, lightBufferSize, lightBufferSize,
0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, data );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE );
// qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL );
// explicit zero depth border
float color[4];
color[0] = color[1] = color[2] = color[3] = 0;
qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color );
GL_CheckErrors();
Mem_Free( data );
}
static void R_CreateViewAlphaImage( idImage *image ) {
int c = viewBufferSize*viewBufferSize*4;
byte *data = (byte *)Mem_Alloc( c );
// don't let it pick an intensity format
for ( int i = 0 ; i < c ; i++ ) {
data[i] = i;
}
memset( data, 0, viewBufferSize*viewBufferSize );
image->GenerateImage( (byte *)data, viewBufferSize, viewBufferSize,
TF_LINEAR, false, TR_CLAMP, TD_HIGH_QUALITY );
}
static void R_CreateStubImage( idImage *image ) {
float data[3][4][4];
// generate the texture number
qglGenTextures( 1, &image->texnum );
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, image->texnum );
memset( data, 0, sizeof( data ) );
glTexImage2D( GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_RGBA16_NV, 4, 3, 0, GL_RGBA, GL_FLOAT, &data );
}
/*
================
R_CreateJitterImage
================
*/
const static int JITTER_SIZE = 128;
static void R_CreateJitterImage16( idImage *image ) {
byte data[JITTER_SIZE][JITTER_SIZE*16][4];
for ( int i = 0 ; i < JITTER_SIZE ; i++ ) {
for ( int s = 0 ; s < 16 ; s++ ) {
int sOfs = 64 * ( s & 3 );
int tOfs = 64 * ( ( s >> 2 ) & 3 );
for ( int j = 0 ; j < JITTER_SIZE ; j++ ) {
data[i][s*JITTER_SIZE+j][0] = (rand() & 63 ) | sOfs;
data[i][s*JITTER_SIZE+j][1] = (rand() & 63 ) | tOfs;
data[i][s*JITTER_SIZE+j][2] = rand();
data[i][s*JITTER_SIZE+j][3] = 0;
}
}
}
image->GenerateImage( (byte *)data, JITTER_SIZE*16, JITTER_SIZE,
TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}
static void R_CreateJitterImage4( idImage *image ) {
byte data[JITTER_SIZE][JITTER_SIZE*4][4];
for ( int i = 0 ; i < JITTER_SIZE ; i++ ) {
for ( int s = 0 ; s < 4 ; s++ ) {
int sOfs = 128 * ( s & 1 );
int tOfs = 128 * ( ( s >> 1 ) & 1 );
for ( int j = 0 ; j < JITTER_SIZE ; j++ ) {
data[i][s*JITTER_SIZE+j][0] = (rand() & 127 ) | sOfs;
data[i][s*JITTER_SIZE+j][1] = (rand() & 127 ) | tOfs;
data[i][s*JITTER_SIZE+j][2] = rand();
data[i][s*JITTER_SIZE+j][3] = 0;
}
}
}
image->GenerateImage( (byte *)data, JITTER_SIZE*4, JITTER_SIZE,
TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}
static void R_CreateJitterImage1( idImage *image ) {
byte data[JITTER_SIZE][JITTER_SIZE][4];
for ( int i = 0 ; i < JITTER_SIZE ; i++ ) {
for ( int j = 0 ; j < JITTER_SIZE ; j++ ) {
data[i][j][0] = rand();
data[i][j][1] = rand();
data[i][j][2] = rand();
data[i][j][3] = 0;
}
}
image->GenerateImage( (byte *)data, JITTER_SIZE, JITTER_SIZE,
TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}
static void R_CreateRandom256Image( idImage *image ) {
byte data[256][256][4];
for ( int i = 0 ; i < 256 ; i++ ) {
for ( int j = 0 ; j < 256 ; j++ ) {
data[i][j][0] = rand();
data[i][j][1] = rand();
data[i][j][2] = rand();
data[i][j][3] = rand();
}
}
image->GenerateImage( (byte *)data, 256, 256,
TF_NEAREST, false, TR_REPEAT, TD_HIGH_QUALITY );
}
/*
==================
R_PrintPixelFormat
==================
*/
void R_PrintPixelFormat( int pixelFormat ) {
int res;
int iAttribute;
int iValue;
common->Printf( "----- pixelFormat %i -----\n", pixelFormat );
for ( int i = 1 ; i < NUM_WGL_STRINGS ; i++ ) {
iAttribute = wglString[i].num;
res = wglGetPixelFormatAttribivARB( win32.hDC, pixelFormat, 0, 1, &iAttribute, &iValue );
if ( res && iValue ) {
common->Printf( "%s : %i\n", wglString[i].name, iValue );
}
}
}
/*
==================
R_Exp_Allocate
==================
*/
void R_Exp_Allocate( void ) {
// find a pixel format for our floating point pbuffer
int iAttributes[NUM_WGL_STRINGS*2], *atr_p;
FLOAT fAttributes[] = {0, 0};
UINT numFormats;
int pixelformats[1024];
int ret;
int pbiAttributes[] = {0, 0};
initialized = true;
#if 1
//
// allocate the floating point rendering buffer
//
atr_p = iAttributes;
*atr_p++ = WGL_DRAW_TO_PBUFFER_ARB;
*atr_p++ = TRUE;
*atr_p++ = WGL_FLOAT_COMPONENTS_NV;
*atr_p++ = TRUE;
*atr_p++ = WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV;
*atr_p++ = TRUE;
// *atr_p++ = WGL_BIND_TO_TEXTURE_RGBA_ARB;
// *atr_p++ = TRUE;
*atr_p++ = WGL_DEPTH_BITS_ARB;
*atr_p++ = 24;
*atr_p++ = WGL_STENCIL_BITS_ARB;
*atr_p++ = 8;
*atr_p++ = 0;
*atr_p++ = 0;
ret = wglChoosePixelFormatARB( win32.hDC, iAttributes, fAttributes,
sizeof( pixelformats ) / sizeof( pixelformats[0] ), pixelformats, &numFormats );
#if 0
for ( int i = 0 ; i < (int)numFormats ; i++ ) {
R_PrintPixelFormat( pixelformats[i] );
}
#endif
common->Printf( "\nfloatPbuffer:\n" );
R_PrintPixelFormat( pixelformats[0] );
// allocate a pbuffer with this pixel format
int pbiAttributesTexture[] = {
WGL_TEXTURE_FORMAT_ARB, WGL_TEXTURE_FLOAT_RGBA_NV,
WGL_TEXTURE_TARGET_ARB, WGL_TEXTURE_RECTANGLE_NV, // WGL_TEXTURE_2D_ARB,
0, 0};
floatPbuffer = wglCreatePbufferARB( win32.hDC, pixelformats[0], glConfig.vidWidth,
glConfig.vidHeight, pbiAttributesTexture );
if ( !floatPbuffer ) {
common->Printf( "failed to create floatPbuffer.\n" );
GL_CheckErrors();
}
floatPbufferDC = wglGetPbufferDCARB( floatPbuffer );
floatPbufferImage = globalImages->ImageFromFunction( "_floatPbuffer", R_CreateStubImage );
// create a second buffer for ping-pong operations
floatPbuffer2 = wglCreatePbufferARB( win32.hDC, pixelformats[0], glConfig.vidWidth,
glConfig.vidHeight, pbiAttributesTexture );
if ( !floatPbuffer2 ) {
common->Printf( "failed to create floatPbuffer.\n" );
GL_CheckErrors();
}
floatPbuffer2DC = wglGetPbufferDCARB( floatPbuffer2 );
floatPbuffer2Image = globalImages->ImageFromFunction( "_floatPbuffer2", R_CreateStubImage );
// create a third buffer for down sampling operations
floatPbufferQuarter = wglCreatePbufferARB( win32.hDC, pixelformats[0], glConfig.vidWidth / 4,
glConfig.vidHeight / 4, pbiAttributesTexture );
if ( !floatPbufferQuarter ) {
common->Printf( "failed to create floatPbuffer.\n" );
GL_CheckErrors();
}
floatPbufferQuarterDC = wglGetPbufferDCARB( floatPbufferQuarter );
floatPbufferQuarterImage = globalImages->ImageFromFunction( "floatPbufferQuarter", R_CreateStubImage );
// create a new GL context for this pixel format and share textures
floatContext = wglCreateContext( floatPbufferDC );
if ( !floatContext ) {
common->Printf( "failed to create context for floatPbufferDC.\n" );
GL_CheckErrors();
}
if ( !wglShareLists( floatContext, win32.hGLRC ) ) {
common->Printf( "failed to share lists.\n" );
}
// create a rendering context for this pixel format and share textures
// allocate a texture for the rendering
#endif
//=================================================================================
//
// allocate the shadow pbuffer
//
atr_p = iAttributes;
*atr_p++ = WGL_DRAW_TO_PBUFFER_ARB;
*atr_p++ = TRUE;
*atr_p++ = WGL_RED_BITS_ARB;
*atr_p++ = 8;
*atr_p++ = WGL_GREEN_BITS_ARB;
*atr_p++ = 8;
*atr_p++ = WGL_BLUE_BITS_ARB;
*atr_p++ = 8;
*atr_p++ = WGL_ALPHA_BITS_ARB;
*atr_p++ = 8;
*atr_p++ = WGL_DEPTH_BITS_ARB;
*atr_p++ = 24;
*atr_p++ = WGL_STENCIL_BITS_ARB;
*atr_p++ = 8;
*atr_p++ = 0;
*atr_p++ = 0;
ret = wglChoosePixelFormatARB( win32.hDC, iAttributes, fAttributes,
sizeof( pixelformats ) / sizeof( pixelformats[0] ), pixelformats, &numFormats );
#if 0
for ( int i = 0 ; i < (int)numFormats ; i++ ) {
R_PrintPixelFormat( pixelformats[i] );
}
#endif
common->Printf( "\nshadowPbuffer:\n" );
R_PrintPixelFormat( pixelformats[0] );
pixelformats[0] = win32.pixelformat; // forced to do this by wgl...
//-----------------------------------
lightBufferSize = maxLightBufferSize;
// allocate a pbuffer with this pixel format
shadowPbuffer = wglCreatePbufferARB( win32.hDC, pixelformats[0], lightBufferSize,
lightBufferSize, pbiAttributes );
// allocate a rendering context for the pbuffer
shadowPbufferDC = wglGetPbufferDCARB( shadowPbuffer );
// generate the texture number
shadowImage[0] = globalImages->ImageFromFunction( va("_shadowBuffer%i_0",lightBufferSize), R_CreateShadowBufferImage );
shadowImage[1] = globalImages->ImageFromFunction( va("_shadowBuffer%i_1",lightBufferSize), R_CreateShadowBufferImage );
shadowImage[2] = globalImages->ImageFromFunction( va("_shadowBuffer%i_2",lightBufferSize), R_CreateShadowBufferImage );
//-----------------------------------
lightBufferSize = maxViewBufferSize;
// allocate a pbuffer with this pixel format
viewPbuffer = wglCreatePbufferARB( win32.hDC, pixelformats[0], maxViewBufferSize,
maxViewBufferSize, pbiAttributes );
// allocate a rendering context for the pbuffer
viewPbufferDC = wglGetPbufferDCARB( viewPbuffer );
// create the image space depth buffer for image-space shadow trnasforms
viewDepthImage = globalImages->ImageFromFunction("_viewDepth", R_CreateShadowBufferImage );
// create the image space shadow alpha buffer for subsampling the shadow calculation
viewAlphaImage = globalImages->ImageFromFunction("_viewAlpha", R_CreateViewAlphaImage );
//-----------------------------------
// generate the jitter image
jitterImage16 = globalImages->ImageFromFunction( "_jitter16", R_CreateJitterImage16 );
jitterImage4 = globalImages->ImageFromFunction( "_jitter4", R_CreateJitterImage4 );
jitterImage1 = globalImages->ImageFromFunction( "_jitter1", R_CreateJitterImage1 );
depthMidpointVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "depthMidpoint.vfp" );
depthMidpointFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "depthMidpoint.vfp" );
shadowResampleVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "shadowResample.vfp" );
shadowResampleFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowResample.vfp" );
screenSpaceShadowVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "screenSpaceShadow1.vfp" );
screenSpaceShadowFragmentProgram0 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow0.vfp" );
screenSpaceShadowFragmentProgram1 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow1.vfp" );
screenSpaceShadowFragmentProgram4 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow4.vfp" );
screenSpaceShadowFragmentProgram16 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "screenSpaceShadow16.vfp" );
shadowVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "shadowBufferInteraction1.vfp" );
shadowFragmentProgram0 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction0.vfp" );
shadowFragmentProgram1 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction1.vfp" );
shadowFragmentProgram4 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction4.vfp" );
shadowFragmentProgram16 = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "shadowBufferInteraction16.vfp" );
gammaDitherVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "gammaDither.vfp" );
gammaDitherFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "gammaDither.vfp" );
downSampleVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "downSample.vfp" );
downSampleFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "downSample.vfp" );
bloomVertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "bloom.vfp" );
bloomFragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "bloom.vfp" );
random256Image = globalImages->ImageFromFunction( "_random256", R_CreateRandom256Image );
}
//===========================================================================================
static const int CULL_RECEIVER = 1; // still draw occluder, but it is out of the view
static const int CULL_OCCLUDER_AND_RECEIVER = 2; // the surface doesn't effect the view at all
/*
==================
RB_EXP_CullInteractions
Sets surfaceInteraction_t->cullBits
==================
*/
void RB_EXP_CullInteractions( viewLight_t *vLight, idPlane frustumPlanes[6] ) {
for ( idInteraction *inter = vLight->lightDef->firstInteraction ; inter ; inter = inter->lightNext ) {
const idRenderEntityLocal *entityDef = inter->entityDef;
if ( !entityDef ) {
continue;
}
if ( inter->numSurfaces < 1 ) {
continue;
}
int culled = 0;
if ( r_sb_useCulling.GetBool() ) {
// transform light frustum into object space, positive side points outside the light
idPlane localPlanes[6];
int plane;
for ( plane = 0 ; plane < 6 ; plane++ ) {
R_GlobalPlaneToLocal( entityDef->modelMatrix, frustumPlanes[plane], localPlanes[plane] );
}
// cull the entire entity bounding box
// has referenceBounds been tightened to the actual model bounds?
idVec3 corners[8];
for ( int i = 0 ; i < 8 ; i++ ) {
corners[i][0] = entityDef->referenceBounds[i&1][0];
corners[i][1] = entityDef->referenceBounds[(i>>1)&1][1];
corners[i][2] = entityDef->referenceBounds[(i>>2)&1][2];
}
for ( plane = 0 ; plane < 6 ; plane++ ) {
int j;
for ( j = 0 ; j < 8 ; j++ ) {
// if a corner is on the negative side (inside) of the frustum, the surface is not culled
// by this plane
if ( corners[j] * localPlanes[plane].ToVec4().ToVec3() + localPlanes[plane][3] < 0 ) {
break;
}
}
if ( j == 8 ) {
break; // all points outside the light
}
}
if ( plane < 6 ) {
culled = CULL_OCCLUDER_AND_RECEIVER;
}
}
for ( int i = 0 ; i < inter->numSurfaces ; i++ ) {
surfaceInteraction_t *surfInt = &inter->surfaces[i];
if ( !surfInt->ambientTris ) {
continue;
}
surfInt->expCulled = culled;
}
}
}
/*
==================
RB_EXP_RenderOccluders
==================
*/
void RB_EXP_RenderOccluders( viewLight_t *vLight ) {
for ( idInteraction *inter = vLight->lightDef->firstInteraction ; inter ; inter = inter->lightNext ) {
const idRenderEntityLocal *entityDef = inter->entityDef;
if ( !entityDef ) {
continue;
}
if ( inter->numSurfaces < 1 ) {
continue;
}
// no need to check for current on this, because each interaction is always
// a different space
float matrix[16];
myGlMultMatrix( inter->entityDef->modelMatrix, lightMatrix, matrix );
qglLoadMatrixf( matrix );
// draw each surface
for ( int i = 0 ; i < inter->numSurfaces ; i++ ) {
surfaceInteraction_t *surfInt = &inter->surfaces[i];
if ( !surfInt->ambientTris ) {
continue;
}
if ( surfInt->shader && !surfInt->shader->SurfaceCastsShadow() ) {
continue;
}
// cull it
if ( surfInt->expCulled == CULL_OCCLUDER_AND_RECEIVER ) {
continue;
}
// render it
const srfTriangles_t *tri = surfInt->ambientTris;
if ( !tri->ambientCache ) {
R_CreateAmbientCache( const_cast<srfTriangles_t *>(tri), false );
}
idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache );
qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );
qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), ac->st.ToFloatPtr() );
if ( surfInt->shader ) {
surfInt->shader->GetEditorImage()->Bind();
}
RB_DrawElementsWithCounters( tri );
}
}
}
/*
==================
RB_RenderShadowBuffer
==================
*/
void RB_RenderShadowBuffer( viewLight_t *vLight, int side ) {
float xmin, xmax, ymin, ymax;
float width, height;
float zNear;
float fov = r_sb_frustomFOV.GetFloat();
//
// set up 90 degree projection matrix
//
zNear = 4;
ymax = zNear * tan( fov * idMath::PI / 360.0f );
ymin = -ymax;
xmax = zNear * tan( fov * idMath::PI / 360.0f );
xmin = -xmax;
width = xmax - xmin;
height = ymax - ymin;
lightProjectionMatrix[0] = 2 * zNear / width;
lightProjectionMatrix[4] = 0;
lightProjectionMatrix[8] = 0;
lightProjectionMatrix[12] = 0;
lightProjectionMatrix[1] = 0;
lightProjectionMatrix[5] = 2 * zNear / height;
lightProjectionMatrix[9] = 0;
lightProjectionMatrix[13] = 0;
// this is the far-plane-at-infinity formulation, and
// crunches the Z range slightly so w=0 vertexes do not
// rasterize right at the wraparound point
lightProjectionMatrix[2] = 0;
lightProjectionMatrix[6] = 0;
lightProjectionMatrix[10] = -0.999f;
lightProjectionMatrix[14] = -2.0f * zNear;
lightProjectionMatrix[3] = 0;
lightProjectionMatrix[7] = 0;
lightProjectionMatrix[11] = -1;
lightProjectionMatrix[15] = 0;
if ( r_sb_usePbuffer.GetBool() ) {
// set the current openGL drawable to the shadow buffer
R_MakeCurrent( shadowPbufferDC, win32.hGLRC, NULL /* !@# shadowPbuffer */ );
}
qglMatrixMode( GL_PROJECTION );
qglLoadMatrixf( lightProjectionMatrix );
qglMatrixMode( GL_MODELVIEW );
qglViewport( 0, 0, lightBufferSize, lightBufferSize );
qglScissor( 0, 0, lightBufferSize, lightBufferSize );
qglStencilFunc( GL_ALWAYS, 0, 255 );
qglClearColor( 0, 1, 0, 0 );
GL_State( GLS_DEPTHFUNC_LESS | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); // make sure depth mask is off before clear
qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// draw all the occluders
qglColor3f( 1, 1, 1 );
GL_SelectTexture( 0 );
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
backEnd.currentSpace = NULL;
static float s_flipMatrix[16] = {
// convert from our coordinate system (looking down X)
// to OpenGL's coordinate system (looking down -Z)
0, 0, -1, 0,
-1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0, 1
};
float viewMatrix[16];
idVec3 vec;
idVec3 origin = vLight->lightDef->globalLightOrigin;
if ( side == -1 ) {
// projected light
vec = vLight->lightDef->parms.target;
vec.Normalize();
viewMatrix[0] = vec[0];
viewMatrix[4] = vec[1];
viewMatrix[8] = vec[2];
vec = vLight->lightDef->parms.right;
vec.Normalize();
viewMatrix[1] = -vec[0];
viewMatrix[5] = -vec[1];
viewMatrix[9] = -vec[2];
vec = vLight->lightDef->parms.up;
vec.Normalize();
viewMatrix[2] = vec[0];
viewMatrix[6] = vec[1];
viewMatrix[10] = vec[2];
} else {
// side of a point light
memset( viewMatrix, 0, sizeof( viewMatrix ) );
switch ( side ) {
case 0:
viewMatrix[0] = 1;
viewMatrix[9] = 1;
viewMatrix[6] = -1;
break;
case 1:
viewMatrix[0] = -1;
viewMatrix[9] = -1;
viewMatrix[6] = -1;
break;
case 2:
viewMatrix[4] = 1;
viewMatrix[1] = -1;
viewMatrix[10] = 1;
break;
case 3:
viewMatrix[4] = -1;
viewMatrix[1] = -1;
viewMatrix[10] = -1;
break;
case 4:
viewMatrix[8] = 1;
viewMatrix[1] = -1;
viewMatrix[6] = -1;
break;
case 5:
viewMatrix[8] = -1;
viewMatrix[1] = 1;
viewMatrix[6] = -1;
break;
}
}
viewMatrix[12] = -origin[0] * viewMatrix[0] + -origin[1] * viewMatrix[4] + -origin[2] * viewMatrix[8];
viewMatrix[13] = -origin[0] * viewMatrix[1] + -origin[1] * viewMatrix[5] + -origin[2] * viewMatrix[9];
viewMatrix[14] = -origin[0] * viewMatrix[2] + -origin[1] * viewMatrix[6] + -origin[2] * viewMatrix[10];
viewMatrix[3] = 0;
viewMatrix[7] = 0;
viewMatrix[11] = 0;
viewMatrix[15] = 1;
memcpy( unflippedLightMatrix, viewMatrix, sizeof( unflippedLightMatrix ) );
myGlMultMatrix( viewMatrix, s_flipMatrix,lightMatrix);
// create frustum planes
idPlane globalFrustum[6];
// near clip
globalFrustum[0][0] = -viewMatrix[0];
globalFrustum[0][1] = -viewMatrix[4];
globalFrustum[0][2] = -viewMatrix[8];
globalFrustum[0][3] = -(origin[0] * globalFrustum[0][0] + origin[1] * globalFrustum[0][1] + origin[2] * globalFrustum[0][2]);
// far clip
globalFrustum[1][0] = viewMatrix[0];
globalFrustum[1][1] = viewMatrix[4];
globalFrustum[1][2] = viewMatrix[8];
globalFrustum[1][3] = -globalFrustum[0][3] - viewLightAxialSize;
// side clips
globalFrustum[2][0] = -viewMatrix[0] + viewMatrix[1];
globalFrustum[2][1] = -viewMatrix[4] + viewMatrix[5];
globalFrustum[2][2] = -viewMatrix[8] + viewMatrix[9];
globalFrustum[3][0] = -viewMatrix[0] - viewMatrix[1];
globalFrustum[3][1] = -viewMatrix[4] - viewMatrix[5];
globalFrustum[3][2] = -viewMatrix[8] - viewMatrix[9];
globalFrustum[4][0] = -viewMatrix[0] + viewMatrix[2];
globalFrustum[4][1] = -viewMatrix[4] + viewMatrix[6];
globalFrustum[4][2] = -viewMatrix[8] + viewMatrix[10];
globalFrustum[5][0] = -viewMatrix[0] - viewMatrix[2];
globalFrustum[5][1] = -viewMatrix[4] - viewMatrix[6];
globalFrustum[5][2] = -viewMatrix[8] - viewMatrix[10];
// is this nromalization necessary?
for ( int i = 0 ; i < 6 ; i++ ) {
globalFrustum[i].ToVec4().ToVec3().Normalize();
}
for ( int i = 2 ; i < 6 ; i++ ) {
globalFrustum[i][3] = - (origin * globalFrustum[i].ToVec4().ToVec3() );
}
RB_EXP_CullInteractions( vLight, globalFrustum );
// FIXME: we want to skip the sampling as well as the generation when not casting shadows
if ( !r_sb_noShadows.GetBool() && vLight->lightShader->LightCastsShadows() ) {
//
// set polygon offset for the rendering
//
switch ( r_sb_occluderFacing.GetInteger() ) {
case 0: // front sides
qglPolygonOffset( r_sb_polyOfsFactor.GetFloat(), r_sb_polyOfsUnits.GetFloat() );
qglEnable( GL_POLYGON_OFFSET_FILL );
RB_EXP_RenderOccluders( vLight );
qglDisable( GL_POLYGON_OFFSET_FILL );
break;
case 1: // back sides
qglPolygonOffset( -r_sb_polyOfsFactor.GetFloat(), -r_sb_polyOfsUnits.GetFloat() );
qglEnable( GL_POLYGON_OFFSET_FILL );
GL_Cull( CT_BACK_SIDED );
RB_EXP_RenderOccluders( vLight );
GL_Cull( CT_FRONT_SIDED );
qglDisable( GL_POLYGON_OFFSET_FILL );
break;
case 2: // both sides
GL_Cull( CT_BACK_SIDED );
RB_EXP_RenderOccluders( vLight );
GL_Cull( CT_FRONT_SIDED );
shadowImage[2]->Bind();
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, lightBufferSize, lightBufferSize );
RB_EXP_RenderOccluders( vLight );
shadowImage[1]->Bind();
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, lightBufferSize, lightBufferSize );
// fragment program to combine the two depth images
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, depthMidpointVertexProgram );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, depthMidpointFragmentProgram );
qglEnable(GL_VERTEX_PROGRAM_ARB);
qglEnable(GL_FRAGMENT_PROGRAM_ARB);
GL_SelectTextureNoClient( 1 );
shadowImage[1]->Bind();
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
GL_SelectTextureNoClient( 0 );
shadowImage[2]->Bind();
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
// draw a full screen quad
qglMatrixMode( GL_PROJECTION );
qglLoadIdentity();
qglOrtho( 0, 1, 0, 1, -1, 1 );
qglMatrixMode( GL_MODELVIEW );
qglLoadIdentity();
GL_State( GLS_DEPTHFUNC_ALWAYS );
qglBegin( GL_TRIANGLE_FAN );
qglTexCoord2f( 0, 0 );
qglVertex2f( 0, 0 );
qglTexCoord2f( 0, lightBufferSizeFraction );
qglVertex2f( 0, 1 );
qglTexCoord2f( lightBufferSizeFraction, lightBufferSizeFraction );
qglVertex2f( 1, 1 );
qglTexCoord2f( lightBufferSizeFraction, 0 );
qglVertex2f( 1, 0 );
qglEnd();
qglDisable( GL_VERTEX_PROGRAM_ARB );
qglDisable( GL_FRAGMENT_PROGRAM_ARB );
break;
}
}
// copy to the texture
shadowImage[0]->Bind();
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, lightBufferSize, lightBufferSize );
qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
// reset the normal view matrix
qglMatrixMode( GL_PROJECTION );
qglLoadMatrixf( backEnd.viewDef->projectionMatrix );
qglMatrixMode( GL_MODELVIEW );
// the current modelView matrix is not valid
backEnd.currentSpace = NULL;
}
/*
==================
RB_EXP_DrawInteraction
==================
*/
void RB_EXP_DrawInteraction( const drawInteraction_t *din ) {
// load all the vertex program parameters
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr() );
// calculate depth projection for shadow buffer
float sRow[4];
float tRow[4];
float rRow[4];
float qRow[4];
float matrix[16];
float matrix2[16];
myGlMultMatrix( din->surf->space->modelMatrix, lightMatrix, matrix );
myGlMultMatrix( matrix, lightProjectionMatrix, matrix2 );
// the final values need to be in 0.0 : 1.0 range instead of -1 : 1
sRow[0] = 0.5 * lightBufferSizeFraction * ( matrix2[0] + matrix2[3] );
sRow[1] = 0.5 * lightBufferSizeFraction * ( matrix2[4] + matrix2[7] );
sRow[2] = 0.5 * lightBufferSizeFraction * ( matrix2[8] + matrix2[11] );
sRow[3] = 0.5 * lightBufferSizeFraction * ( matrix2[12] + matrix2[15] );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 18, sRow );
tRow[0] = 0.5 * lightBufferSizeFraction * ( matrix2[1] + matrix2[3] );
tRow[1] = 0.5 * lightBufferSizeFraction * ( matrix2[5] + matrix2[7] );
tRow[2] = 0.5 * lightBufferSizeFraction * ( matrix2[9] + matrix2[11] );
tRow[3] = 0.5 * lightBufferSizeFraction * ( matrix2[13] + matrix2[15] );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 19, tRow );
rRow[0] = 0.5 * ( matrix2[2] + matrix2[3] );
rRow[1] = 0.5 * ( matrix2[6] + matrix2[7] );
rRow[2] = 0.5 * ( matrix2[10] + matrix2[11] );
rRow[3] = 0.5 * ( matrix2[14] + matrix2[15] );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 20, rRow );
qRow[0] = matrix2[3];
qRow[1] = matrix2[7];
qRow[2] = matrix2[11];
qRow[3] = matrix2[15];
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 21, qRow );
// testing fragment based normal mapping
if ( r_testARBProgram.GetBool() ) {
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, din->localLightOrigin.ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, din->localViewOrigin.ToFloatPtr() );
}
static const float zero[4] = { 0, 0, 0, 0 };
static const float one[4] = { 1, 1, 1, 1 };
static const float negOne[4] = { -1, -1, -1, -1 };
switch ( din->vertexColor ) {
case SVC_IGNORE:
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, zero );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one );
break;
case SVC_MODULATE:
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, one );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, zero );
break;
case SVC_INVERSE_MODULATE:
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, negOne );
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one );
break;
}
// set the constant colors
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, din->diffuseColor.ToFloatPtr() );
qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, din->specularColor.ToFloatPtr() );
//-----------------------------------------------------
// screen power of two correction factor
float parm[4];
parm[0] = 1.0 / ( JITTER_SIZE * r_sb_samples.GetInteger() ) ;
parm[1] = 1.0 / JITTER_SIZE;
parm[2] = 0;
parm[3] = 1;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, parm );
// jitter tex scale
parm[0] =
parm[1] = r_sb_jitterScale.GetFloat() * lightBufferSizeFraction;
parm[2] = -r_sb_biasScale.GetFloat();
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, parm );
// jitter tex offset
if ( r_sb_randomize.GetBool() ) {
parm[0] = (rand()&255) / 255.0;
parm[1] = (rand()&255) / 255.0;
} else {
parm[0] = parm[1] = 0;
}
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm );
//-----------------------------------------------------
// set the textures
// texture 1 will be the per-surface bump map
GL_SelectTextureNoClient( 1 );
din->bumpImage->Bind();
// texture 2 will be the light falloff texture
GL_SelectTextureNoClient( 2 );
din->lightFalloffImage->Bind();
// texture 3 will be the light projection texture
GL_SelectTextureNoClient( 3 );
din->lightImage->Bind();
// texture 4 is the per-surface diffuse map
GL_SelectTextureNoClient( 4 );
din->diffuseImage->Bind();
// texture 5 is the per-surface specular map
GL_SelectTextureNoClient( 5 );
din->specularImage->Bind();
// draw it
RB_DrawElementsWithCounters( din->surf->geo );
}
/*
=============
RB_EXP_CreateDrawInteractions
=============
*/
void RB_EXP_CreateDrawInteractions( const drawSurf_t *surf ) {
if ( !surf ) {
return;
}
if ( r_sb_screenSpaceShadow.GetBool() ) {
// perform setup here that will be constant for all interactions
GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | backEnd.depthFunc );
if ( r_testARBProgram.GetBool() ) {
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_TEST );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_TEST );
} else {
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_INTERACTION );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_INTERACTION );
}
} else {
// perform setup here that will be constant for all interactions
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | backEnd.depthFunc );
GL_State( GLS_DEPTHMASK | GLS_DEPTHFUNC_ALWAYS );//!@#
// bind the vertex program
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, shadowVertexProgram );
if ( r_sb_samples.GetInteger() == 16 ) {
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram16 );
} else if ( r_sb_samples.GetInteger() == 4 ) {
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram4 );
} else if ( r_sb_samples.GetInteger() == 1 ) {
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram1 );
} else {
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowFragmentProgram0 );
}
}
qglEnable(GL_VERTEX_PROGRAM_ARB);
qglEnable(GL_FRAGMENT_PROGRAM_ARB);
// enable the vertex arrays
qglEnableVertexAttribArrayARB( 8 );
qglEnableVertexAttribArrayARB( 9 );
qglEnableVertexAttribArrayARB( 10 );
qglEnableVertexAttribArrayARB( 11 );
qglEnableClientState( GL_COLOR_ARRAY );
// texture 0 is the normalization cube map for the vector towards the light
GL_SelectTextureNoClient( 0 );
if ( backEnd.vLight->lightShader->IsAmbientLight() ) {
globalImages->normalCubeMapImage->Bind();
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_AMBIENT);
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_AMBIENT);
} else {
globalImages->normalCubeMapImage->Bind();
}
// texture 6 is the specular lookup table
GL_SelectTextureNoClient( 6 );
if ( r_testARBProgram.GetBool() ) {
globalImages->specular2DTableImage->Bind(); // variable specularity in alpha channel
} else {
globalImages->specularTableImage->Bind();
}
for ( ; surf ; surf=surf->nextOnLight ) {
// perform setup here that will not change over multiple interaction passes
if ( backEnd.vLight->lightShader->IsAmbientLight() ) {
float parm[4];
parm[0] = surf->space->modelMatrix[0];
parm[1] = surf->space->modelMatrix[4];
parm[2] = surf->space->modelMatrix[8];
parm[3] = 0;
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 20, parm );
parm[0] = surf->space->modelMatrix[1];
parm[1] = surf->space->modelMatrix[5];
parm[2] = surf->space->modelMatrix[9];
parm[3] = 0;
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 21, parm );
parm[0] = surf->space->modelMatrix[2];
parm[1] = surf->space->modelMatrix[6];
parm[2] = surf->space->modelMatrix[10];
parm[3] = 0;
qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 22, parm );
GL_SelectTextureNoClient( 0 );
const shaderStage_t *stage = backEnd.vLight->lightShader->GetStage( 0 );
if ( stage->newStage ) {
stage->newStage->fragmentProgramImages[7]->BindFragment();
}
}
// set the vertex pointers
idDrawVert *ac = (idDrawVert *)vertexCache.Position( surf->geo->ambientCache );
qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), ac->color );
qglVertexAttribPointerARB( 11, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->normal.ToFloatPtr() );
qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() );
qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() );
qglVertexAttribPointerARB( 8, 2, GL_FLOAT, false, sizeof( idDrawVert ), ac->st.ToFloatPtr() );
qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );
// this may cause RB_ARB2_DrawInteraction to be exacuted multiple
// times with different colors and images if the surface or light have multiple layers
if ( r_sb_screenSpaceShadow.GetBool() ) {
RB_CreateSingleDrawInteractions( surf, RB_ARB2_DrawInteraction );
} else {
RB_CreateSingleDrawInteractions( surf, RB_EXP_DrawInteraction );
}
}
qglDisableVertexAttribArrayARB( 8 );
qglDisableVertexAttribArrayARB( 9 );
qglDisableVertexAttribArrayARB( 10 );
qglDisableVertexAttribArrayARB( 11 );
qglDisableClientState( GL_COLOR_ARRAY );
// disable features
GL_SelectTextureNoClient( 6 );
globalImages->BindNull();
GL_SelectTextureNoClient( 5 );
globalImages->BindNull();
GL_SelectTextureNoClient( 4 );
globalImages->BindNull();
GL_SelectTextureNoClient( 3 );
globalImages->BindNull();
GL_SelectTextureNoClient( 2 );
globalImages->BindNull();
GL_SelectTextureNoClient( 1 );
globalImages->BindNull();
backEnd.glState.currenttmu = -1;
GL_SelectTexture( 0 );
qglDisable(GL_VERTEX_PROGRAM_ARB);
qglDisable(GL_FRAGMENT_PROGRAM_ARB);
}
void InvertByTranspose( const float a[16], float r[16] ) {
r[ 0] = a[ 0];
r[ 1] = a[ 4];
r[ 2] = a[ 8];
r[ 3] = 0;
r[ 4] = a[ 1];
r[ 5] = a[ 5];
r[ 6] = a[ 9];
r[ 7] = 0;
r[ 8] = a[ 2];
r[ 9] = a[ 6];
r[10] = a[10];
r[11] = 0;
r[12] = -(r[ 0]*a[12] + r[ 4]*a[13] + r[ 8]*a[14]);
r[13] = -(r[ 1]*a[12] + r[ 5]*a[13] + r[ 9]*a[14]);
r[14] = -(r[ 2]*a[12] + r[ 6]*a[13] + r[10]*a[14]);
r[15] = 1;
}
void FullInvert( const float a[16], float r[16] ) {
idMat4 am;
for ( int i = 0 ; i < 4 ; i++ ) {
for ( int j = 0 ; j < 4 ; j++ ) {
am[i][j] = a[j*4+i];
}
}
// idVec4 test( 100, 100, 100, 1 );
// idVec4 transformed, inverted;
// transformed = test * am;
if ( !am.InverseSelf() ) {
common->Error( "Invert failed" );
}
// inverted = transformed * am;
for ( int i = 0 ; i < 4 ; i++ ) {
for ( int j = 0 ; j < 4 ; j++ ) {
r[j*4+i] = am[i][j];
}
}
}
/*
==================
RB_Exp_TrianglesForFrustum
==================
*/
const srfTriangles_t *RB_Exp_TrianglesForFrustum( viewLight_t *vLight, int side ) {
const srfTriangles_t *tri;
static srfTriangles_t frustumTri;
static idDrawVert verts[5];
static glIndex_t indexes[18] = { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 1, 2, 1, 4, 2, 4, 3 };
if ( side == -1 ) {
tri = vLight->frustumTris;
} else {
memset( verts, 0, sizeof( verts ) );
for ( int i = 0 ; i < 5 ; i++ ) {
verts[i].xyz = vLight->globalLightOrigin;
}
memset( &frustumTri, 0, sizeof( frustumTri ) );
frustumTri.indexes = indexes;
frustumTri.verts = verts;
frustumTri.numIndexes = 18;
frustumTri.numVerts = 5;
tri = &frustumTri;
float size = viewLightAxialSize;
switch ( side ) {
case 0:
verts[1].xyz[0] += size;
verts[2].xyz[0] += size;
verts[3].xyz[0] += size;
verts[4].xyz[0] += size;
verts[1].xyz[1] += size;
verts[1].xyz[2] += size;
verts[2].xyz[1] -= size;
verts[2].xyz[2] += size;
verts[3].xyz[1] -= size;
verts[3].xyz[2] -= size;
verts[4].xyz[1] += size;
verts[4].xyz[2] -= size;
break;
case 1:
verts[1].xyz[0] -= size;
verts[2].xyz[0] -= size;
verts[3].xyz[0] -= size;
verts[4].xyz[0] -= size;
verts[1].xyz[1] -= size;
verts[1].xyz[2] += size;
verts[2].xyz[1] += size;
verts[2].xyz[2] += size;
verts[3].xyz[1] += size;
verts[3].xyz[2] -= size;
verts[4].xyz[1] -= size;
verts[4].xyz[2] -= size;
break;
case 2:
verts[1].xyz[1] += size;
verts[2].xyz[1] += size;
verts[3].xyz[1] += size;
verts[4].xyz[1] += size;
verts[1].xyz[0] -= size;
verts[1].xyz[2] += size;
verts[2].xyz[0] += size;
verts[2].xyz[2] += size;
verts[3].xyz[0] += size;
verts[3].xyz[2] -= size;
verts[4].xyz[0] -= size;
verts[4].xyz[2] -= size;
break;
case 3:
verts[1].xyz[1] -= size;
verts[2].xyz[1] -= size;
verts[3].xyz[1] -= size;
verts[4].xyz[1] -= size;
verts[1].xyz[0] += size;
verts[1].xyz[2] += size;
verts[2].xyz[0] -= size;
verts[2].xyz[2] += size;
verts[3].xyz[0] -= size;
verts[3].xyz[2] -= size;
verts[4].xyz[0] += size;
verts[4].xyz[2] -= size;
break;
case 4:
verts[1].xyz[2] += size;
verts[2].xyz[2] += size;
verts[3].xyz[2] += size;
verts[4].xyz[2] += size;
verts[1].xyz[0] += size;
verts[1].xyz[1] += size;
verts[2].xyz[0] -= size;
verts[2].xyz[1] += size;
verts[3].xyz[0] -= size;
verts[3].xyz[1] -= size;
verts[4].xyz[0] += size;
verts[4].xyz[1] -= size;
break;
case 5:
verts[1].xyz[2] -= size;
verts[2].xyz[2] -= size;
verts[3].xyz[2] -= size;
verts[4].xyz[2] -= size;
verts[1].xyz[0] -= size;
verts[1].xyz[1] += size;
verts[2].xyz[0] += size;
verts[2].xyz[1] += size;
verts[3].xyz[0] += size;
verts[3].xyz[1] -= size;
verts[4].xyz[0] -= size;
verts[4].xyz[1] -= size;
break;
}
frustumTri.ambientCache = vertexCache.AllocFrameTemp( verts, sizeof( verts ) );
}
return tri;
}
/*
==================
RB_Exp_SelectFrustum
==================
*/
void RB_Exp_SelectFrustum( viewLight_t *vLight, int side ) {
qglLoadMatrixf( backEnd.viewDef->worldSpace.modelViewMatrix );
const srfTriangles_t *tri = RB_Exp_TrianglesForFrustum( vLight, side );
idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache );
qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );
qglDisable( GL_TEXTURE_2D );
qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
// clear stencil buffer
qglEnable( GL_SCISSOR_TEST );
qglEnable( GL_STENCIL_TEST );
qglClearStencil( 1 );
qglClear( GL_STENCIL_BUFFER_BIT );
// draw front faces of the light frustum, incrementing the stencil buffer on depth fail
// so we can't draw on those pixels
GL_State( GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
qglStencilFunc( GL_ALWAYS, 0, 255 );
qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP );
GL_Cull( CT_FRONT_SIDED );
RB_DrawElementsWithCounters( tri );
// draw back faces of the light frustum with
// depth test greater
// stencil test of equal 1
// zero stencil stencil when depth test passes, so subsequent surface drawing
// can occur on those pixels
// this pass does all the shadow filtering
qglStencilFunc( GL_EQUAL, 1, 255 );
qglStencilOp( GL_KEEP, GL_KEEP, GL_ZERO );
GL_Cull( CT_BACK_SIDED );
qglDepthFunc( GL_GREATER );
// write to destination alpha
if ( r_sb_showFrustumPixels.GetBool() ) {
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
qglDisable( GL_TEXTURE_2D );
qglColor4f( 0, 0.25, 0, 1 );
} else {
GL_State( GLS_COLORMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
qglEnable(GL_VERTEX_PROGRAM_ARB);
qglEnable(GL_FRAGMENT_PROGRAM_ARB);
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, screenSpaceShadowVertexProgram );
switch ( r_sb_samples.GetInteger() ) {
case 0:
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram0 );
break;
case 1:
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram1 );
break;
case 4:
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram4 );
break;
case 16:
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, screenSpaceShadowFragmentProgram16 );
break;
}
}
/*
texture[0] = view depth texture
texture[1] = jitter texture
texture[2] = light depth texture
*/
GL_SelectTextureNoClient( 2 );
shadowImage[0]->Bind();
GL_SelectTextureNoClient( 1 );
if ( r_sb_samples.GetInteger() == 16 ) {
jitterImage16->Bind();
} else if ( r_sb_samples.GetInteger() == 4 ) {
jitterImage4->Bind();
} else {
jitterImage1->Bind();
}
GL_SelectTextureNoClient( 0 );
viewDepthImage->Bind();
/*
PARAM positionToDepthTexScale = program.local[0]; # fragment.position to screen depth texture transformation
PARAM zProject = program.local[1]; # projection[10], projection[14], 0, 0
PARAM positionToViewSpace = program.local[2]; # X add, Y add, X mul, Y mul
PARAM viewToLightS = program.local[3];
PARAM viewToLightT = program.local[4];
PARAM viewToLightR = program.local[5];
PARAM viewToLightQ = program.local[6];
PARAM positionToJitterTexScale = program.local[7]; # fragment.position to jitter texture
PARAM jitterTexScale = program.local[8];
PARAM jitterTexOffset = program.local[9];
*/
float parm[4];
int pot;
// calculate depth projection for shadow buffer
float sRow[4];
float tRow[4];
float rRow[4];
float qRow[4];
float invertedView[16];
float invertedProjection[16];
float matrix[16];
float matrix2[16];
// we need the inverse of the projection matrix to go from NDC to view
FullInvert( backEnd.viewDef->projectionMatrix, invertedProjection );
/*
from window to NDC:
( x - xMid ) * 1.0 / xMid
( y - yMid ) * 1.0 / yMid
( z - 0.5 ) * 2
from NDC to clip coordinates:
rcp(1/w)
*/
// we need the inverse of the viewMatrix to go from view (looking down negative Z) to world
InvertByTranspose( backEnd.viewDef->worldSpace.modelViewMatrix, invertedView );
// then we go from world to light view space (looking down negative Z)
myGlMultMatrix( invertedView, lightMatrix, matrix );
// then to light projection, giving X/w, Y/w, Z/w in the -1 : 1 range
myGlMultMatrix( matrix, lightProjectionMatrix, matrix2 );
// the final values need to be in 0.0 : 1.0 range instead of -1 : 1
sRow[0] = 0.5 * ( matrix2[0] + matrix2[3] ) * lightBufferSizeFraction;
sRow[1] = 0.5 * ( matrix2[4] + matrix2[7] ) * lightBufferSizeFraction;
sRow[2] = 0.5 * ( matrix2[8] + matrix2[11] ) * lightBufferSizeFraction;
sRow[3] = 0.5 * ( matrix2[12] + matrix2[15] ) * lightBufferSizeFraction;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, sRow );
tRow[0] = 0.5 * ( matrix2[1] + matrix2[3] ) * lightBufferSizeFraction;
tRow[1] = 0.5 * ( matrix2[5] + matrix2[7] ) * lightBufferSizeFraction;
tRow[2] = 0.5 * ( matrix2[9] + matrix2[11] ) * lightBufferSizeFraction;
tRow[3] = 0.5 * ( matrix2[13] + matrix2[15] ) * lightBufferSizeFraction;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, tRow );
rRow[0] = 0.5 * ( matrix2[2] + matrix2[3] );
rRow[1] = 0.5 * ( matrix2[6] + matrix2[7] );
rRow[2] = 0.5 * ( matrix2[10] + matrix2[11] );
rRow[3] = 0.5 * ( matrix2[14] + matrix2[15] );
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 5, rRow );
qRow[0] = matrix2[3];
qRow[1] = matrix2[7];
qRow[2] = matrix2[11];
qRow[3] = matrix2[15];
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 6, qRow );
//-----------------------------------------------------
// these should be constant for the entire frame
// convert 0..viewport-1 sizes to fractions inside the POT screen depth texture
int w = viewBufferSize;
pot = MakePowerOfTwo( w );
parm[0] = 1.0 / maxViewBufferSize; // * ( (float)viewBufferSize / w );
int h = viewBufferHeight;
pot = MakePowerOfTwo( h );
parm[1] = parm[0]; // 1.0 / pot;
parm[2] = 0;
parm[3] = 1;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );
// zProject values
parm[0] = backEnd.viewDef->projectionMatrix[10];
parm[1] = backEnd.viewDef->projectionMatrix[14];
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm );
// positionToViewSpace
parm[0] = -1.0 / backEnd.viewDef->projectionMatrix[0];
parm[1] = -1.0 / backEnd.viewDef->projectionMatrix[5];
parm[2] = 2.0/viewBufferSize;
parm[3] = 2.0/viewBufferSize;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, parm );
// positionToJitterTexScale
parm[0] = 1.0 / ( JITTER_SIZE * r_sb_samples.GetInteger() ) ;
parm[1] = 1.0 / JITTER_SIZE;
parm[2] = 0;
parm[3] = 1;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 7, parm );
// jitter tex scale
parm[0] =
parm[1] = r_sb_jitterScale.GetFloat() * lightBufferSizeFraction;
parm[2] = -r_sb_biasScale.GetFloat();
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 8, parm );
// jitter tex offset
if ( r_sb_randomize.GetBool() ) {
parm[0] = (rand()&255) / 255.0;
parm[1] = (rand()&255) / 255.0;
} else {
parm[0] = parm[1] = 0;
}
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 9, parm );
//-----------------------------------------------------
RB_DrawElementsWithCounters( tri );
qglDisable(GL_VERTEX_PROGRAM_ARB);
qglDisable(GL_FRAGMENT_PROGRAM_ARB);
GL_Cull( CT_FRONT_SIDED );
// qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
qglDepthFunc( GL_LEQUAL );
if ( r_sb_showFrustumPixels.GetBool() ) {
qglEnable( GL_TEXTURE_2D );
qglColor3f( 1, 1, 1 );
}
// after all the frustums have been drawn, the surfaces that have been drawn on will get interactions
// scissor may still be a win even with the stencil test for very fast rejects
qglStencilFunc( GL_EQUAL, 0, 255 );
qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
// we can avoid clearing the stencil buffer by changing the hasLight value for each light
}
/*
==================
R_EXP_CalcLightAxialSize
all light side projections must currently match, so non-centered
and non-cubic lights must take the largest length
==================
*/
float R_EXP_CalcLightAxialSize( viewLight_t *vLight ) {
float max = 0;
if ( !vLight->lightDef->parms.pointLight ) {
idVec3 dir = vLight->lightDef->parms.target - vLight->lightDef->parms.origin;
max = dir.Length();
return max;
}
for ( int i = 0 ; i < 3 ; i++ ) {
float dist = fabs(vLight->lightDef->parms.lightCenter[i] );
dist += vLight->lightDef->parms.lightRadius[i];
if ( dist > max ) {
max = dist;
}
}
return max;
}
/*
==================
R_EXP_RenderViewDepthImage
This could be avoided by drop sampling the native view depth buffer with render to texture
Bilerp might even be aprorpiate, although it would cause issues at edges
==================
*/
void RB_T_FillDepthBuffer( const drawSurf_t *surf );
void R_EXP_RenderViewDepthImage( void ) {
if ( !r_sb_screenSpaceShadow.GetBool() ) {
return;
}
// if the screen resolution is exactly the window width, we can
// use the depth buffer we already have
if ( 0 ) { // nativeViewBuffer ) {
viewDepthImage->CopyDepthbuffer( backEnd.viewDef->viewport.x1,
backEnd.viewDef->viewport.y1, backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1,
backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1 );
} else {
RB_LogComment( "---------- R_EXP_RenderViewDepthImage ----------\n" );
if ( r_sb_usePbuffer.GetBool() ) {
GL_CheckErrors();
// set the current openGL drawable to the shadow buffer
R_MakeCurrent( viewPbufferDC, win32.hGLRC, NULL /* !@# viewPbuffer */ );
}
// render the depth to the new size
qglViewport( 0, 0, viewBufferSize, viewBufferHeight );
qglScissor( 0, 0, viewBufferSize, viewBufferHeight );
qglClear( GL_DEPTH_BUFFER_BIT );
qglStencilFunc( GL_ALWAYS, 0, 255 );
// the first texture will be used for alpha tested surfaces
GL_SelectTexture( 0 );
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
GL_State( GLS_DEPTHFUNC_LESS );
RB_RenderDrawSurfListWithFunction( backEnd.viewDef->drawSurfs, backEnd.viewDef->numDrawSurfs, RB_T_FillDepthBuffer );
//
// copy it to a texture
//
viewDepthImage->Bind();
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, viewBufferSize, viewBufferHeight );
if ( r_sb_usePbuffer.GetBool() ) {
// set the normal screen drawable current
R_MakeCurrent( win32.hDC, win32.hGLRC, NULL );
}
// reset the window clipping
qglMatrixMode( GL_PROJECTION );
qglLoadMatrixf( backEnd.viewDef->projectionMatrix );
qglMatrixMode( GL_MODELVIEW );
qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );
qglScissor( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );
// the current modelView matrix is not valid
backEnd.currentSpace = NULL;
}
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
}
/*
==================
RB_EXP_SetNativeBuffer
This is always the back buffer, and scissor is set full screen
==================
*/
void RB_EXP_SetNativeBuffer( void ) {
// set the normal screen drawable current
R_MakeCurrent( win32.hDC, win32.hGLRC, NULL );
qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );
backEnd.currentScissor = backEnd.viewDef->viewport;
if ( r_useScissor.GetBool() ) {
qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 );
}
}
/*
==================
RB_EXP_SetRenderBuffer
This may be to a float pBuffer, and scissor is set to cover only the light
==================
*/
void RB_EXP_SetRenderBuffer( viewLight_t *vLight ) {
if ( r_hdr_useFloats.GetBool() ) {
R_MakeCurrent( floatPbufferDC, floatContext, floatPbuffer );
} else {
if ( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) {
GL_CheckErrors();
common->FatalError( "Couldn't return to normal drawing context" );
}
}
qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1,
tr.viewportOffset[1] + backEnd.viewDef->viewport.y1,
backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1,
backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 );
if ( !vLight ) {
backEnd.currentScissor = backEnd.viewDef->viewport;
} else {
backEnd.currentScissor = vLight->scissorRect;
}
if ( r_useScissor.GetBool() ) {
qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1,
backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1,
backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1,
backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 );
}
}
/*
==================
RB_shadowResampleAlpha
==================
*/
void RB_shadowResampleAlpha( void ) {
viewAlphaImage->Bind();
// we could make this a subimage, but it isn't relevent once we have render-to-texture
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, viewBufferSize, viewBufferHeight );
RB_EXP_SetRenderBuffer( backEnd.vLight );
//=====================
qglLoadMatrixf( backEnd.viewDef->worldSpace.modelViewMatrix );
// this uses the full light, not side frustums
const srfTriangles_t *tri = backEnd.vLight->frustumTris;
idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache );
qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() );
// clear stencil buffer
qglEnable( GL_SCISSOR_TEST );
qglEnable( GL_STENCIL_TEST );
qglClearStencil( 1 );
qglClear( GL_STENCIL_BUFFER_BIT );
// draw front faces of the light frustum, incrementing the stencil buffer on depth fail
// so we can't draw on those pixels
GL_State( GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
qglStencilFunc( GL_ALWAYS, 0, 255 );
qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP );
GL_Cull( CT_FRONT_SIDED );
// set fragment / vertex program?
RB_DrawElementsWithCounters( tri );
// draw back faces of the light frustum with
// depth test greater
// stencil test of equal 1
// zero stencil stencil when depth test passes, so subsequent interaction drawing
// can occur on those pixels
// this pass does all the shadow filtering
qglStencilFunc( GL_EQUAL, 1, 255 );
qglStencilOp( GL_KEEP, GL_KEEP, GL_ZERO );
// write to destination alpha
if ( r_sb_showFrustumPixels.GetBool() ) {
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
qglDisable( GL_TEXTURE_2D );
qglColor4f( 0, 0.25, 0, 1 );
} else {
GL_State( GLS_COLORMASK | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS );
qglEnable(GL_VERTEX_PROGRAM_ARB);
qglEnable(GL_FRAGMENT_PROGRAM_ARB);
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, shadowResampleVertexProgram );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, shadowResampleFragmentProgram );
// convert 0..viewport-1 sizes to fractions inside the POT screen depth texture
// shrink by one unit for bilerp
float parm[4];
parm[0] = 1.0 / (maxViewBufferSize+1) * viewBufferSize / maxViewBufferSize;
parm[1] = parm[0];
parm[2] = 0;
parm[3] = 1;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );
}
GL_Cull( CT_BACK_SIDED );
qglDepthFunc( GL_GREATER );
RB_DrawElementsWithCounters( tri );
qglDisable(GL_VERTEX_PROGRAM_ARB);
qglDisable(GL_FRAGMENT_PROGRAM_ARB);
GL_Cull( CT_FRONT_SIDED );
qglDepthFunc( GL_LEQUAL );
if ( r_sb_showFrustumPixels.GetBool() ) {
qglEnable( GL_TEXTURE_2D );
qglColor3f( 1, 1, 1 );
}
// after all the frustums have been drawn, the surfaces that have been drawn on will get interactions
// scissor may still be a win even with the stencil test for very fast rejects
qglStencilFunc( GL_EQUAL, 0, 255 );
qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
}
/*
==================
RB_EXP_CoverScreen
==================
*/
void RB_EXP_CoverScreen( void ) {
// draw a full screen quad
qglMatrixMode( GL_PROJECTION );
qglLoadIdentity();
qglOrtho( 0, 1, 0, 1, -1, 1 );
qglMatrixMode( GL_MODELVIEW );
qglLoadIdentity();
qglBegin( GL_TRIANGLE_FAN );
qglVertex2f( 0, 0 );
qglVertex2f( 0, 1 );
qglVertex2f( 1, 1 );
qglVertex2f( 1, 0 );
qglEnd();
}
/*
==================
RB_EXP_ReadFloatBuffer
==================
*/
void RB_EXP_ReadFloatBuffer( void ) {
int pixels = glConfig.vidWidth * glConfig.vidHeight;
float *buf = (float *)R_StaticAlloc( pixels * 4 * sizeof( float ) );
qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_FLOAT, buf );
float mins[4] = { 9999, 9999, 9999, 9999 };
float maxs[4] = { -9999, -9999, -9999, -9999 };
for ( int i = 0 ; i < pixels ; i++ ) {
for ( int j = 0 ; j < 4 ; j++ ) {
float v = buf[ i*4 + j ];
if ( v < mins[j] ) {
mins[j] = v;
}
if ( v > maxs[j] ) {
maxs[j] = v;
}
}
}
RB_EXP_SetNativeBuffer();
qglLoadIdentity();
qglMatrixMode( GL_PROJECTION );
GL_State( GLS_DEPTHFUNC_ALWAYS );
qglColor3f( 1, 1, 1 );
qglPushMatrix();
qglLoadIdentity();
qglDisable( GL_TEXTURE_2D );
qglOrtho( 0, 1, 0, 1, -1, 1 );
qglRasterPos2f( 0.01f, 0.01f );
qglDrawPixels( glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_FLOAT, buf );
qglPopMatrix();
qglEnable( GL_TEXTURE_2D );
qglMatrixMode( GL_MODELVIEW );
R_StaticFree( buf );
}
void RB_TestGamma( void );
/*
==================
RB_EXP_GammaDither
==================
*/
void RB_EXP_GammaDither( void ) {
if ( !r_hdr_useFloats.GetBool() ) {
return;
}
#if 0
r_testGamma.SetBool( true );
RB_TestGamma();
r_testGamma.SetBool( false );
#endif
RB_EXP_SetNativeBuffer();
/*
# texture 0 is the high dynamic range buffer
# texture 1 is the random dither texture
# texture 2 is the light bloom texture
# writes result.color as the 32 bit dithered and gamma corrected values
PARAM exposure = program.local[0]; # multiply HDR value by this to get screen pixels
PARAM gammaPower = program.local[1];
PARAM monitorDither = program.local[2];
PARAM positionToDitherScale = program.local[3];
PARAM bloomFraction = program.local[4];
PARAM positionToBloomScale = program.local[5];
*/
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB,gammaDitherVertexProgram );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, gammaDitherFragmentProgram );
qglEnable(GL_VERTEX_PROGRAM_ARB);
qglEnable(GL_FRAGMENT_PROGRAM_ARB);
qglActiveTextureARB( GL_TEXTURE2_ARB );
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferQuarterImage->texnum );
R_BindTexImage( floatPbufferQuarter );
qglActiveTextureARB( GL_TEXTURE1_ARB );
random256Image->BindFragment();
qglActiveTextureARB( GL_TEXTURE0_ARB );
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferImage->texnum );
R_BindTexImage( floatPbuffer );
float parm[4];
parm[0] = r_hdr_exposure.GetFloat();
parm[1] = 0;
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );
parm[0] = r_hdr_gamma.GetFloat();
parm[1] = 0;
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm );
parm[0] = r_hdr_monitorDither.GetFloat();
parm[1] = 0;
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, parm );
parm[0] = 1.0 / 256;
parm[1] = parm[0];
parm[2] = rand()/65535.0;
parm[3] = rand()/65535.0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, parm );
parm[0] = 1.0 - r_hdr_bloomFraction.GetFloat();
parm[1] = r_hdr_bloomFraction.GetFloat();
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm );
parm[0] = 0.25;
parm[1] = 0.25;
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 5, parm );
qglDisable( GL_STENCIL_TEST );
qglDisable( GL_SCISSOR_TEST );
qglDisable( GL_DEPTH_TEST );
RB_EXP_CoverScreen();
qglEnable( GL_DEPTH_TEST );
qglDisable(GL_VERTEX_PROGRAM_ARB);
qglDisable(GL_FRAGMENT_PROGRAM_ARB);
}
/*
==================
RB_EXP_Bloom
==================
*/
void RB_EXP_Bloom( void ) {
if ( !r_hdr_useFloats.GetBool() ) {
return;
}
if ( r_hdr_bloomFraction.GetFloat() == 0 ) {
return;
}
GL_CheckErrors();
//
// mip map
//
// draw to the second floatPbuffer
R_MakeCurrent( floatPbuffer2DC, floatContext, floatPbuffer2 );
GL_State( 0 );
qglDisable( GL_DEPTH_TEST );
qglDisable( GL_SCISSOR_TEST );
qglEnable(GL_VERTEX_PROGRAM_ARB);
qglEnable(GL_FRAGMENT_PROGRAM_ARB);
qglClearColor( 1.0, 0.5, 0, 0 );
qglClear( GL_COLOR_BUFFER_BIT );
qglViewport( 0, 0, glConfig.vidWidth>>1, glConfig.vidHeight>>1 );
// read from the original floatPbuffer
qglActiveTextureARB( GL_TEXTURE0_ARB );
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferImage->texnum );
R_BindTexImage( floatPbuffer );
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, downSampleVertexProgram );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, downSampleFragmentProgram );
RB_EXP_CoverScreen();
//
// mip map again
//
qglViewport( 0, 0, glConfig.vidWidth>>2, glConfig.vidHeight>>2 );
// draw to the second floatPbuffer
R_MakeCurrent( floatPbufferQuarterDC, floatContext, floatPbufferQuarter );
// read from the original floatPbuffer
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbuffer2Image->texnum );
R_BindTexImage( floatPbuffer2 );
RB_EXP_CoverScreen();
//
// blur horizontally
//
/*
# texture 0 is the high dynamic range buffer
# writes result.color as the fp16 result of a smeared bloom
PARAM step = program.local[0]; # { 1, 0 } or { 0, 1 } for horizontal / vertical separation
*/
// draw to the second floatPbuffer
R_MakeCurrent( floatPbuffer2DC, floatContext, floatPbuffer2 );
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, bloomVertexProgram );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, bloomFragmentProgram );
qglEnable(GL_VERTEX_PROGRAM_ARB);
qglEnable(GL_FRAGMENT_PROGRAM_ARB);
GL_SelectTextureNoClient( 0 );
// blur horizontally first to the second floatPbuffer
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbufferQuarterImage->texnum );
R_BindTexImage( floatPbufferQuarter );
float parm[4];
parm[0] = 1;
parm[1] = 0;
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );
RB_EXP_CoverScreen();
//
// now blur vertically back to the quarter pbuffer
//
R_MakeCurrent( floatPbufferQuarterDC, floatContext, floatPbufferQuarter );
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, floatPbuffer2Image->texnum );
R_BindTexImage( floatPbuffer2 );
parm[0] = 0;
parm[1] = 1;
parm[2] = 0;
parm[3] = 0;
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm );
RB_EXP_CoverScreen();
//========================
qglEnable( GL_DEPTH_TEST );
qglEnable( GL_SCISSOR_TEST );
qglDisable(GL_VERTEX_PROGRAM_ARB);
qglDisable(GL_FRAGMENT_PROGRAM_ARB);
GL_CheckErrors();
}
/*
==================
RB_Exp_DrawInteractions
==================
*/
void RB_Exp_DrawInteractions( void ) {
if ( !initialized ) {
R_Exp_Allocate();
}
if ( !backEnd.viewDef->viewLights ) {
return;
}
// validate the samples
if ( r_sb_samples.GetInteger() != 16 && r_sb_samples.GetInteger() != 4 && r_sb_samples.GetInteger() != 1 ) {
r_sb_samples.SetInteger( 0 );
}
// validate the light resolution
if ( r_sb_lightResolution.GetInteger() < 64 ) {
r_sb_lightResolution.SetInteger( 64 );
} else if ( r_sb_lightResolution.GetInteger() > maxLightBufferSize ) {
r_sb_lightResolution.SetInteger( maxLightBufferSize );
}
lightBufferSize = r_sb_lightResolution.GetInteger();
lightBufferSizeFraction = (float)lightBufferSize / maxLightBufferSize;
// validate the view resolution
if ( r_sb_viewResolution.GetInteger() < 64 ) {
r_sb_viewResolution.SetInteger( 64 );
} else if ( r_sb_viewResolution.GetInteger() > maxViewBufferSize ) {
r_sb_viewResolution.SetInteger( maxViewBufferSize );
}
viewBufferSize = r_sb_viewResolution.GetInteger();
viewBufferHeight = viewBufferSize * 3 / 4;
viewBufferSizeFraction = (float)viewBufferSize / maxViewBufferSize;
viewBufferHeightFraction = (float)viewBufferHeight / maxViewBufferSize;
if ( viewBufferSize == backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1 ) {
nativeViewBuffer = true;
} else {
nativeViewBuffer = false;
}
// set up for either point sampled or percentage-closer filtering for the shadow sampling
shadowImage[0]->BindFragment();
if ( r_sb_linearFilter.GetBool() ) {
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
} else {
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
}
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE );
globalImages->BindNull();
// copy the current depth buffer to a texture for image-space shadowing,
// or re-render at a lower resolution
R_EXP_RenderViewDepthImage();
GL_SelectTexture( 0 );
qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
// disable stencil shadow test
qglStencilFunc( GL_ALWAYS, 128, 255 );
// the jitter image will be used to offset sample centers
GL_SelectTextureNoClient( 8 );
if ( r_sb_samples.GetInteger() == 16 ) {
jitterImage16->BindFragment();
} else if ( r_sb_samples.GetInteger() == 4 ) {
jitterImage4->BindFragment();
} else {
jitterImage1->BindFragment();
}
// if we are using a float buffer, clear it now
if ( r_hdr_useFloats.GetBool() ) {
RB_EXP_SetRenderBuffer( NULL );
// we need to set a lot of things, because this is a completely different context
RB_SetDefaultGLState();
qglClearColor( 0.001f, 1.0f, 0.01f, 0.1f );
qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// clear the z buffer, set the projection matrix, etc
RB_BeginDrawingView();
RB_STD_FillDepthBuffer( (drawSurf_t **)&backEnd.viewDef->drawSurfs[0], backEnd.viewDef->numDrawSurfs );
}
//
// for each light, perform adding and shadowing
//
for ( viewLight_t *vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) {
backEnd.vLight = vLight;
const idMaterial *lightShader = vLight->lightShader;
// do fogging later
if ( lightShader->IsFogLight() ) {
continue;
}
if ( lightShader->IsBlendLight() ) {
continue;
}
if ( !vLight->localInteractions && !vLight->globalInteractions
&& !vLight->translucentInteractions ) {
continue;
}
if ( !vLight->frustumTris->ambientCache ) {
R_CreateAmbientCache( const_cast<srfTriangles_t *>(vLight->frustumTris), false );
}
// all light side projections must currently match, so non-centered
// and non-cubic lights must take the largest length
viewLightAxialSize = R_EXP_CalcLightAxialSize( vLight );
int side, sideStop;
if ( vLight->lightDef->parms.pointLight ) {
if ( r_sb_singleSide.GetInteger() != -1 ) {
side = r_sb_singleSide.GetInteger();
sideStop = side+1;
} else {
side = 0;
sideStop = 6;
}
} else {
side = -1;
sideStop = 0;
}
for ( ; side < sideStop ; side++ ) {
// FIXME: check for frustums completely off the screen
// render a shadow buffer
RB_RenderShadowBuffer( vLight, side );
// back to view rendering, possibly in the off-screen buffer
if ( nativeViewBuffer || !r_sb_screenSpaceShadow.GetBool() ) {
// directly to screen
RB_EXP_SetRenderBuffer( vLight );
} else {
// to off screen buffer
if ( r_sb_usePbuffer.GetBool() ) {
GL_CheckErrors();
// set the current openGL drawable to the shadow buffer
R_MakeCurrent( viewPbufferDC, win32.hGLRC, viewPbuffer );
}
qglViewport( 0, 0, viewBufferSize, viewBufferHeight );
qglScissor( 0, 0, viewBufferSize, viewBufferHeight ); // !@# FIXME: scale light scissor
}
// render the shadows into destination alpha on the included pixels
RB_Exp_SelectFrustum( vLight, side );
if ( !r_sb_screenSpaceShadow.GetBool() ) {
// bind shadow buffer to texture
GL_SelectTextureNoClient( 7 );
shadowImage[0]->BindFragment();
RB_EXP_CreateDrawInteractions( vLight->localInteractions );
RB_EXP_CreateDrawInteractions( vLight->globalInteractions );
backEnd.depthFunc = GLS_DEPTHFUNC_LESS;
RB_EXP_CreateDrawInteractions( vLight->translucentInteractions );
backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL;
}
}
// render the native window coordinates interactions
if ( r_sb_screenSpaceShadow.GetBool() ) {
if ( !nativeViewBuffer ) {
RB_shadowResampleAlpha();
qglEnable( GL_STENCIL_TEST );
} else {
RB_EXP_SetRenderBuffer( vLight );
if ( r_ignore.GetBool() ) {
qglEnable( GL_STENCIL_TEST ); //!@#
} else {
qglDisable( GL_STENCIL_TEST ); //!@#
}
}
RB_EXP_CreateDrawInteractions( vLight->localInteractions );
RB_EXP_CreateDrawInteractions( vLight->globalInteractions );
backEnd.depthFunc = GLS_DEPTHFUNC_LESS;
RB_EXP_CreateDrawInteractions( vLight->translucentInteractions );
backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL;
}
}
GL_SelectTexture( 0 );
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
// experimental transfer function
for ( int i = 7 ; i >= 0 ; i-- ) {
GL_SelectTextureNoClient( i );
globalImages->BindNull();
}
GL_State( 0 );
RB_EXP_Bloom();
RB_EXP_GammaDither();
// these haven't been state saved
for ( int i = 0 ; i < 8 ; i++ ) {
backEnd.glState.tmu[i].current2DMap = -1;
}
// take it out of texture compare mode so I can testImage it for debugging
shadowImage[0]->BindFragment();
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
}
/*
==================
R_Exp_Init
==================
*/
void R_Exp_Init( void ) {
glConfig.allowExpPath = false;
common->Printf( "---------- R_Exp_Init ----------\n" );
if ( !glConfig.ARBVertexProgramAvailable || !glConfig.ARBFragmentProgramAvailable ) {
common->Printf( "Not available.\n" );
return;
}
RB_CreateBloomTable();
#if 0
if ( !R_CheckExtension( "GL_NV_float_buffer" ) ) {
common->Printf( "Not available.\n" );
return;
}
if ( !R_CheckExtension( "GL_NV_texture_rectangle" ) ) {
common->Printf( "Not available.\n" );
return;
}
#endif
#if 0
qglCombinerParameterfvNV = (void (APIENTRY *)( GLenum pname, const GLfloat *params ))
GLimp_ExtensionPointer( "glCombinerParameterfvNV" );
#endif
common->Printf( "Available.\n" );
if ( !idStr::Icmp( r_renderer.GetString(), "exp" ) ) {
R_Exp_Allocate();
}
common->Printf( "--------------------------------------------\n" );
glConfig.allowExpPath = true;
}