/* =========================================================================== 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 . 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 "sys/platform.h" #include "sys/win32/win_local.h" #include "renderer/tr_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(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(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; }