Paranoia2/cl_dll/render/gl_postprocess.cpp

600 lines
14 KiB
C++

//
// written by BUzer for HL: Paranoia modification
//
// 2006
#include "hud.h"
#include "cl_util.h"
#include "const.h"
#include "gl_local.h"
#include "mathlib.h"
#include "gl_shader.h"
#include "stringlib.h"
#include "gl_world.h"
extern int g_iGunMode;
#define DEAD_GRAYSCALE_TIME 5.0f
#define TARGET_SIZE 256
cvar_t *v_posteffects;
cvar_t *v_grayscale;
cvar_t *v_sunshafts;
// post-process shaders
class CBasePostEffects
{
public:
word blurShader[2]; // e.g. underwater blur
word dofShader; // iron sight with dof
word monoShader; // monochrome effect
word genSunShafts; // sunshafts effect
word drawSunShafts; // sunshafts effect
int target_rgb[2];
float grayScaleFactor;
float blurFactor[2];
bool m_bUseTarget;
// DOF parameters
float m_flCachedDepth;
float m_flLastDepth;
float m_flStartDepth;
float m_flOffsetDepth;
float m_flStartTime;
float m_flDelayTime;
int g_iGunLastMode;
float m_flStartLength;
float m_flOffsetLength;
float m_flLastLength;
float m_flDOFStartTime;
// sunshafts variables
Vector m_vecSunLightColor;
Vector m_vecSunPosition;
void InitScreenColor( void );
void InitScreenDepth( void );
void InitTargetColor( int slot );
void RequestScreenColor( void );
void RequestScreenDepth( void );
void RequestTargetCopy( int slot );
bool ProcessDepthOfField( void );
bool ProcessSunShafts( void );
void InitDepthOfField( void );
void SetNormalViewport( void );
void SetTargetViewport( void );
bool Begin( void );
void End( void );
};
void CBasePostEffects :: InitScreenColor( void )
{
if( tr.screen_color )
{
FREE_TEXTURE( tr.screen_color );
tr.screen_color = 0;
}
tr.screen_color = CREATE_TEXTURE( "*screencolor", glState.width, glState.height, NULL, TF_COLORBUFFER );
}
void CBasePostEffects :: InitScreenDepth( void )
{
if( tr.screen_depth )
{
FREE_TEXTURE( tr.screen_depth );
tr.screen_depth = 0;
}
tr.screen_depth = CREATE_TEXTURE( "*screendepth", glState.width, glState.height, NULL, TF_DEPTHBUFFER );
}
void CBasePostEffects :: InitTargetColor( int slot )
{
if( target_rgb[slot] )
{
FREE_TEXTURE( target_rgb[slot] );
target_rgb[slot] = 0;
}
target_rgb[slot] = CREATE_TEXTURE( va( "*target%i", slot ), TARGET_SIZE, TARGET_SIZE, NULL, TF_IMAGE );
}
void CBasePostEffects :: RequestScreenColor( void )
{
GL_BindTexture( GL_TEXTURE0, tr.screen_color );
pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glState.width, glState.height );
}
void CBasePostEffects :: RequestScreenDepth( void )
{
GL_BindTexture( GL_TEXTURE0, tr.screen_depth );
pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glState.width, glState.height );
}
void CBasePostEffects :: RequestTargetCopy( int slot )
{
GL_BindTexture( GL_TEXTURE0, target_rgb[slot] );
pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, TARGET_SIZE, TARGET_SIZE );
}
void CBasePostEffects :: SetNormalViewport( void )
{
pglViewport( RI->glstate.viewport[0], RI->glstate.viewport[1], RI->glstate.viewport[2], RI->glstate.viewport[3] );
}
void CBasePostEffects :: SetTargetViewport( void )
{
pglViewport( 0, 0, TARGET_SIZE, TARGET_SIZE );
}
void CBasePostEffects :: InitDepthOfField( void )
{
g_iGunLastMode = 1;
}
bool CBasePostEffects :: ProcessDepthOfField( void )
{
if( !CVAR_TO_BOOL( r_dof ) || g_iGunMode == 0 )
return false; // disabled or unitialized
if( g_iGunMode != g_iGunLastMode )
{
if( g_iGunMode == 1 )
{
// disable iron sight
m_flStartLength = m_flLastLength;
m_flOffsetLength = -m_flStartLength;
m_flDOFStartTime = tr.time;
}
else
{
// enable iron sight
m_flStartLength = m_flLastLength;
m_flOffsetLength = r_dof_focal_length->value;
m_flDOFStartTime = tr.time;
}
// ALERT( at_console, "Iron sight changed( %i )\n", g_iGunMode );
g_iGunLastMode = g_iGunMode;
}
if( g_iGunLastMode == 1 && m_flDOFStartTime == 0.0f )
return false; // iron sight disabled
if( !Begin( )) return false;
if( m_flDOFStartTime != 0.0f )
{
float flDegree = (tr.time - m_flDOFStartTime) / 0.3f;
if( flDegree >= 1.0f )
{
// all done. holds the final value
m_flLastLength = m_flStartLength + m_flOffsetLength;
m_flDOFStartTime = 0.0f; // done
}
else
{
// evaluate focal length
m_flLastLength = m_flStartLength + m_flOffsetLength * flDegree;
}
}
float zNear = Z_NEAR; // fixed
float zFar = RI->view.farClip;
float depthValue = 0.0f;
// get current depth value
pglReadPixels( glState.width >> 1, glState.height >> 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depthValue );
depthValue = RemapVal( depthValue, 0.0, 0.8, 0.0, 1.0 );
depthValue = -zFar * zNear / ( depthValue * ( zFar - zNear ) - zFar ); // linearize it
float holdTime = bound( 0.01f, r_dof_hold_time->value, 0.5f );
float changeTime = bound( 0.1f, r_dof_change_time->value, 2.0f );
if( Q_round( m_flCachedDepth, 10 ) != Q_round( depthValue, 10 ))
{
m_flStartTime = 0.0f; // cancelling changes
m_flDelayTime = tr.time; // make sure what focal point is not changed more than 0.5 secs
m_flStartDepth = m_flLastDepth; // get last valid depth
m_flOffsetDepth = depthValue - m_flStartDepth;
m_flCachedDepth = depthValue;
}
if(( tr.time - m_flDelayTime ) > holdTime && m_flStartTime == 0.0f && m_flDelayTime != 0.0f )
{
// begin the change depth
m_flStartTime = tr.time;
}
if( m_flStartTime != 0.0f )
{
float flDegree = (tr.time - m_flStartTime) / changeTime;
if( flDegree >= 1.0f )
{
// all done. holds the final value
m_flLastDepth = m_flStartDepth + m_flOffsetDepth;
m_flStartTime = m_flDelayTime = 0.0f;
}
else
{
// evaluate focal depth
m_flLastDepth = m_flStartDepth + m_flOffsetDepth * flDegree;
}
}
return true;
}
bool CBasePostEffects :: ProcessSunShafts( void )
{
if( !CVAR_TO_BOOL( v_sunshafts ))
return false;
if( !FBitSet( world->features, WORLD_HAS_SKYBOX ))
return false;
if( tr.sky_normal == g_vecZero )
return false;
// update blur params
blurFactor[0] = 0.15f;
blurFactor[1] = 0.15f;
if( tr.sun_light_enabled ) m_vecSunLightColor = tr.sun_diffuse;
else m_vecSunLightColor = tr.sky_ambient * (1.0f/128.0f) * tr.diffuseFactor;
Vector sunPos = tr.cached_vieworigin + tr.sky_normal * 1000.0;
ColorNormalize( m_vecSunLightColor, m_vecSunLightColor );
Vector ndc, view;
// project sunpos to screen
R_TransformWorldToDevice( sunPos, ndc );
R_TransformDeviceToScreen( ndc, m_vecSunPosition );
m_vecSunPosition.z = DotProduct( -tr.sky_normal, GetVForward( ));
if( m_vecSunPosition.z < 0.01f )
return false; // fade out
// convert to screen pixels
m_vecSunPosition.x = m_vecSunPosition.x / glState.width;
m_vecSunPosition.y = m_vecSunPosition.y / glState.height;
return Begin();
}
bool CBasePostEffects :: Begin( void )
{
// we are in cubemap rendering mode
if( !RP_NORMALPASS( ))
return false;
if( !CVAR_TO_BOOL( v_posteffects ))
return false;
GL_Setup2D();
return true;
}
void CBasePostEffects :: End( void )
{
GL_CleanUpTextureUnits( 0 );
GL_BindShader( NULL );
GL_Setup3D();
}
static CBasePostEffects post;
void InitPostEffects( void )
{
char options[MAX_OPTIONS_LENGTH];
v_posteffects = CVAR_REGISTER( "gl_posteffects", "1", FCVAR_ARCHIVE );
v_sunshafts = CVAR_REGISTER( "gl_sunshafts", "1", FCVAR_ARCHIVE );
v_grayscale = CVAR_REGISTER( "gl_grayscale", "0", 0 );
memset( &post, 0, sizeof( post ));
// monochrome effect
post.monoShader = GL_FindShader( "postfx/monochrome", "postfx/generic", "postfx/monochrome" );
// gaussian blur for X
GL_SetShaderDirective( options, "BLUR_X" );
post.blurShader[0] = GL_FindShader( "postfx/gaussblur", "postfx/generic", "postfx/gaussblur", options );
// gaussian blur for Y
GL_SetShaderDirective( options, "BLUR_Y" );
post.blurShader[1] = GL_FindShader( "postfx/gaussblur", "postfx/generic", "postfx/gaussblur", options );
// DOF with bokeh
post.dofShader = GL_FindShader( "postfx/dofbokeh", "postfx/generic", "postfx/dofbokeh" );
// prepare sunshafts
post.genSunShafts = GL_FindShader( "postfx/genshafts", "postfx/generic", "postfx/genshafts" );
// render sunshafts
post.drawSunShafts = GL_FindShader( "postfx/drawshafts", "postfx/generic", "postfx/drawshafts" );
}
void InitPostTextures( void )
{
post.InitScreenColor();
post.InitScreenDepth();
post.InitTargetColor( 0 );
post.InitDepthOfField();
}
static float GetGrayscaleFactor( void )
{
float grayscale = v_grayscale->value;
if( gHUD.m_flDeadTime )
{
float fact = (tr.time - gHUD.m_flDeadTime) / DEAD_GRAYSCALE_TIME;
fact = Q_min( fact, 1.0f );
grayscale = Q_max( fact, grayscale );
}
return grayscale;
}
// rectangle version
void RenderFSQ( void )
{
float screenWidth = (float)glState.width;
float screenHeight = (float)glState.height;
pglBegin( GL_QUADS );
pglTexCoord2f( 0.0f, screenHeight );
pglVertex2f( 0.0f, 0.0f );
pglTexCoord2f( screenWidth, screenHeight );
pglVertex2f( screenWidth, 0.0f );
pglTexCoord2f( screenWidth, 0.0f );
pglVertex2f( screenWidth, screenHeight );
pglTexCoord2f( 0.0f, 0.0f );
pglVertex2f( 0.0f, screenHeight );
pglEnd();
}
void RenderFSQ( int wide, int tall )
{
float screenWidth = (float)wide;
float screenHeight = (float)tall;
pglBegin( GL_QUADS );
pglTexCoord2f( 0.0f, 1.0f );
pglNormal3fv( tr.screen_normals[0] );
pglVertex2f( 0.0f, 0.0f );
pglTexCoord2f( 1.0f, 1.0f );
pglNormal3fv( tr.screen_normals[1] );
pglVertex2f( screenWidth, 0.0f );
pglTexCoord2f( 1.0f, 0.0f );
pglNormal3fv( tr.screen_normals[2] );
pglVertex2f( screenWidth, screenHeight );
pglTexCoord2f( 0.0f, 0.0f );
pglNormal3fv( tr.screen_normals[3] );
pglVertex2f( 0.0f, screenHeight );
pglEnd();
}
void GL_DrawScreenSpaceQuad( void )
{
pglBegin( GL_QUADS );
pglTexCoord2f( 0.0f, 1.0f );
pglNormal3fv( tr.screen_normals[0] );
pglVertex3f( 0.0f, 0.0f, 0.0f );
pglNormal3fv( tr.screen_normals[1] );
pglTexCoord2f( 0.0f, 0.0f );
pglVertex3f( 0.0f, glState.height, 0.0f );
pglNormal3fv( tr.screen_normals[2] );
pglTexCoord2f( 1.0f, 0.0f );
pglVertex3f( glState.width, glState.height, 0.0f );
pglNormal3fv( tr.screen_normals[3] );
pglTexCoord2f( 1.0f, 1.0f );
pglVertex3f( glState.width, 0.0f, 0.0f );
pglEnd();
}
void V_RenderPostEffect( word hProgram )
{
if( hProgram <= 0 )
{
GL_BindShader( NULL );
return; // bad shader?
}
if( RI->currentshader != &glsl_programs[hProgram] )
{
// force to bind new shader
GL_BindShader( &glsl_programs[hProgram] );
}
glsl_program_t *shader = RI->currentshader;
// setup specified uniforms (and texture bindings)
for( int i = 0; i < shader->numUniforms; i++ )
{
uniform_t *u = &shader->uniforms[i];
switch( u->type )
{
case UT_SCREENMAP:
if( post.m_bUseTarget ) // HACKHACK
u->SetValue( post.target_rgb[0] );
else u->SetValue( tr.screen_color );
break;
case UT_DEPTHMAP:
u->SetValue( tr.screen_depth );
break;
case UT_COLORMAP:
u->SetValue( post.target_rgb[0] );
break;
case UT_GRAYSCALE:
u->SetValue( post.grayScaleFactor );
break;
case UT_BLURFACTOR:
u->SetValue( post.blurFactor[0], post.blurFactor[1] );
break;
case UT_SCREENSIZEINV:
u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height );
break;
case UT_SCREENWIDTH:
u->SetValue( (float)glState.width );
break;
case UT_SCREENHEIGHT:
u->SetValue( (float)glState.height );
break;
case UT_FOCALDEPTH:
u->SetValue( post.m_flLastDepth );
break;
case UT_FOCALLENGTH:
u->SetValue( post.m_flLastLength );
break;
case UT_DOFDEBUG:
u->SetValue( CVAR_TO_BOOL( r_dof_debug ));
break;
case UT_FSTOP:
u->SetValue( r_dof_fstop->value );
break;
case UT_ZFAR:
u->SetValue( RI->view.farClip );
break;
case UT_GAMMATABLE:
u->SetValue( &tr.gamma_table[0][0], 64 );
break;
case UT_DIFFUSEFACTOR:
u->SetValue( tr.diffuseFactor );
break;
case UT_AMBIENTFACTOR:
u->SetValue( tr.ambientFactor );
break;
case UT_SUNREFRACT:
u->SetValue( tr.sun_refract );
break;
case UT_REALTIME:
u->SetValue( (float)tr.time );
break;
case UT_LIGHTDIFFUSE:
u->SetValue( post.m_vecSunLightColor.x, post.m_vecSunLightColor.y, post.m_vecSunLightColor.z );
break;
case UT_LIGHTORIGIN:
u->SetValue( post.m_vecSunPosition.x, post.m_vecSunPosition.y, post.m_vecSunPosition.z );
break;
case UT_FOGPARAMS:
u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity );
break;
default:
ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name );
break;
}
}
// render a fullscreen quad
RenderFSQ( glState.width, glState.height );
}
void RenderBlur( float blurX, float blurY )
{
if( !blurX && !blurY )
return;
// update blur params
post.blurFactor[0] = blurX;
post.blurFactor[1] = blurY;
if( !post.Begin( )) return;
// do vertical blur
post.RequestScreenColor();
V_RenderPostEffect( post.blurShader[0] );
// do horizontal blur
post.RequestScreenColor();
V_RenderPostEffect( post.blurShader[1] );
post.End();
}
void RenderMonochrome( void )
{
post.grayScaleFactor = GetGrayscaleFactor();
if( post.grayScaleFactor <= 0.0f ) return;
if( !post.Begin( )) return;
// apply monochromatic
post.RequestScreenColor();
V_RenderPostEffect( post.monoShader );
post.End();
}
void RenderUnderwaterBlur( void )
{
if( !CVAR_TO_BOOL( cv_water ) || tr.waterlevel < 3 )
return;
float factor = sin( tr.time * 0.1f * ( M_PI * 2.7f ));
float blurX = RemapVal( factor, -1.0f, 1.0f, 0.18f, 0.23f );
float blurY = RemapVal( factor, -1.0f, 1.0f, 0.15f, 0.24f );
RenderBlur( blurX, blurY );
}
void RenderNerveGasBlur( void )
{
if( gHUD.m_flBlurAmount <= 0.0f )
return;
float factor = sin( tr.time * 0.4f * ( M_PI * 1.7f ));
float blurX = RemapVal( factor, -1.0f, 1.0f, 0.0f, 0.3f );
float blurY = RemapVal( factor, -1.0f, 1.0f, 0.0f, 0.3f );
blurX = bound( 0.0f, blurX, gHUD.m_flBlurAmount );
blurY = bound( 0.0f, blurY, gHUD.m_flBlurAmount );
RenderBlur( blurX, blurY );
}
void RenderDOF( void )
{
if( !post.ProcessDepthOfField( ))
return;
post.RequestScreenColor();
post.RequestScreenDepth();
V_RenderPostEffect( post.dofShader );
post.End();
}
void RenderSunShafts( void )
{
if( !post.ProcessSunShafts( ))
return;
post.RequestScreenColor();
post.RequestScreenDepth();
// we operate in small window to increase speedup
post.SetTargetViewport();
V_RenderPostEffect( post.genSunShafts );
post.RequestTargetCopy( 0 );
post.m_bUseTarget = true;
V_RenderPostEffect( post.blurShader[0] );
post.RequestTargetCopy( 0 );
V_RenderPostEffect( post.blurShader[1] );
post.RequestTargetCopy( 0 );
post.m_bUseTarget = false;
// back to normal size
post.SetNormalViewport();
V_RenderPostEffect( post.drawSunShafts );
post.End();
}