Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2950 lines
113 KiB

//========== Copyright © 2008, Valve Corporation, All rights reserved. ==========
//
// Purpose:
//
//==============================================================================
#include "cbase.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/itexture.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "materialsystem/materialsystem_config.h"
#include "tier1/callqueue.h"
#include "colorcorrectionmgr.h"
#include "view_scene.h"
#include "c_world.h"
#include "renderparm.h"
#include "shaderapi/ishaderapi.h"
#include "ProxyEntity.h"
#include "imaterialproxydict.h"
#include "model_types.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
// mapmaker controlled autoexposure
bool g_bUseCustomAutoExposureMin = false;
bool g_bUseCustomAutoExposureMax = false;
bool g_bUseCustomBloomScale = false;
float g_flCustomAutoExposureMin = 0;
float g_flCustomAutoExposureMax = 0;
float g_flCustomBloomScale = 0.0f;
float g_flCustomBloomScaleMinimum = 0.0f;
extern void GetTonemapSettingsFromEnvTonemapController( void );
// mapmaker controlled depth of field
bool g_bDOFEnabled = false;
float g_flDOFNearBlurDepth = 50.0f;
float g_flDOFNearFocusDepth = 200.0f;
float g_flDOFFarFocusDepth = 250.0f;
float g_flDOFFarBlurDepth = 1000.0f;
float g_flDOFNearBlurRadius = 0.0f;
float g_flDOFFarBlurRadius = 5.0f;
bool g_bFlashlightIsOn = false;
// hdr parameters
ConVar mat_bloomscale( "mat_bloomscale", "1" );
ConVar mat_hdr_level( "mat_hdr_level", "2" );
ConVar mat_bloomamount_rate( "mat_bloomamount_rate", "0.05f", FCVAR_CHEAT );
static ConVar debug_postproc( "mat_debug_postprocessing_effects", "0", FCVAR_NONE, "0 = off, 1 = show post-processing passes in quadrants of the screen, 2 = only apply post-processing to the centre of the screen" );
static ConVar mat_dynamic_tonemapping( "mat_dynamic_tonemapping", "1", FCVAR_CHEAT );
static ConVar mat_tonemapping_occlusion_use_stencil( "mat_tonemapping_occlusion_use_stencil", "0" );
static ConVar mat_autoexposure_max( "mat_autoexposure_max", "2" );
static ConVar mat_autoexposure_min( "mat_autoexposure_min", "0.5" );
static ConVar mat_show_histogram( "mat_show_histogram", "0" );
ConVar mat_hdr_uncapexposure( "mat_hdr_uncapexposure", "0", FCVAR_CHEAT );
ConVar mat_force_bloom("mat_force_bloom","0", FCVAR_CHEAT);
ConVar mat_disable_bloom("mat_disable_bloom","0");
ConVar mat_debug_bloom("mat_debug_bloom","0", FCVAR_CHEAT);
ConVar mat_colorcorrection( "mat_colorcorrection", "1", FCVAR_DEVELOPMENTONLY );
ConVar mat_accelerate_adjust_exposure_down( "mat_accelerate_adjust_exposure_down", "3.0", FCVAR_CHEAT );
ConVar mat_hdr_manual_tonemap_rate( "mat_hdr_manual_tonemap_rate", "1.0" );
// fudge factor to make non-hdr bloom more closely match hdr bloom. Because of auto-exposure, high
// bloomscales don't blow out as much in hdr. this factor was derived by comparing images in a
// reference scene.
ConVar mat_non_hdr_bloom_scalefactor("mat_non_hdr_bloom_scalefactor",".3");
// Apply addition scale to the final bloom scale
static ConVar mat_bloom_scalefactor_scalar( "mat_bloom_scalefactor_scalar", "1.0", FCVAR_RELEASE );
//ConVar mat_exposure_center_region_x( "mat_exposure_center_region_x","0.75", FCVAR_CHEAT );
//ConVar mat_exposure_center_region_y( "mat_exposure_center_region_y","0.80", FCVAR_CHEAT );
ConVar mat_exposure_center_region_x( "mat_exposure_center_region_x","0.9", FCVAR_CHEAT );
ConVar mat_exposure_center_region_y( "mat_exposure_center_region_y","0.85", FCVAR_CHEAT );
ConVar mat_tonemap_algorithm( "mat_tonemap_algorithm", "1", FCVAR_CHEAT, "0 = Original Algorithm 1 = New Algorithm" );
ConVar mat_tonemap_percent_target( "mat_tonemap_percent_target", "60.0", FCVAR_CHEAT );
ConVar mat_tonemap_percent_bright_pixels( "mat_tonemap_percent_bright_pixels", "2.0", FCVAR_CHEAT );
ConVar mat_tonemap_min_avglum( "mat_tonemap_min_avglum", "3.0", FCVAR_CHEAT );
ConVar mat_force_tonemap_scale( "mat_force_tonemap_scale", "0.0", FCVAR_CHEAT );
ConVar mat_fullbright( "mat_fullbright", "0", FCVAR_CHEAT );
ConVar mat_grain_enable( "mat_grain_enable", "0" );
ConVar mat_vignette_enable( "mat_vignette_enable", "0" );
ConVar mat_local_contrast_enable( "mat_local_contrast_enable", "0" );
static void SetRenderTargetAndViewPort(ITexture *rt)
{
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetRenderTarget(rt);
if ( rt )
{
pRenderContext->Viewport(0,0,rt->GetActualWidth(),rt->GetActualHeight());
}
}
enum HistogramEntryState_t
{
HESTATE_INITIAL = 0,
HESTATE_FIRST_QUERY_IN_FLIGHT,
HESTATE_QUERY_IN_FLIGHT,
HESTATE_QUERY_DONE,
};
#define NUM_HISTOGRAM_BUCKETS 31
#define NUM_HISTOGRAM_BUCKETS_NEW 17
#define MAX_QUERIES_PER_FRAME 1
class CHistogramBucket
{
public:
HistogramEntryState_t m_state;
OcclusionQueryObjectHandle_t m_hOcclusionQueryHandle;
int m_nFrameQueued; // when this query was last queued
int m_nPixels; // # of pixels this histogram represents
int m_nPixelsInRange;
float m_flMinLuminance, m_flMaxLuminance; // the luminance range this entry was queried with
float m_flScreenMinX, m_flScreenMinY, m_flScreenMaxX, m_flScreenMaxY; // range is 0..1 in fractions of the screen
bool ContainsValidData( void )
{
return ( m_state == HESTATE_QUERY_DONE ) || ( m_state == HESTATE_QUERY_IN_FLIGHT );
}
void IssueQuery( int nFrameNum );
};
void CHistogramBucket::IssueQuery( int nFrameNum )
{
CMatRenderContextPtr pRenderContext( materials );
if ( !m_hOcclusionQueryHandle )
{
m_hOcclusionQueryHandle = pRenderContext->CreateOcclusionQueryObject();
}
int nViewportX, nViewportY, nViewportWidth, nViewportHeight;
pRenderContext->GetViewport( nViewportX, nViewportY, nViewportWidth, nViewportHeight );
// Find min and max gamma-space text range
float flTestRangeMin = ( m_flMinLuminance == 0.0f ) ? -1e20f : m_flMinLuminance; // Count all pixels < 0.0 as 0.0 (for float HDR buffers)
float flTestRangeMax = ( m_flMaxLuminance == 1.0f ) ? 1e20f : m_flMaxLuminance; // Count all pixels >1.0 as 1.0
// Set stencil bits where the colors match
IMaterial *pLumCompareMaterial = materials->FindMaterial( "dev/lumcompare", TEXTURE_GROUP_OTHER, true );
IMaterialVar *pMinVar = pLumCompareMaterial->FindVar( "$C0_X", NULL );
pMinVar->SetFloatValue( flTestRangeMin );
IMaterialVar *pMaxVar = pLumCompareMaterial->FindVar( "$C0_Y", NULL );
pMaxVar->SetFloatValue( flTestRangeMax );
int nScreenMinX = FLerp( nViewportX, ( nViewportX + nViewportWidth - 1 ), 0, 1, m_flScreenMinX );
int nScreenMaxX = FLerp( nViewportX, ( nViewportX + nViewportWidth - 1 ), 0, 1, m_flScreenMaxX );
int nScreenMinY = FLerp( nViewportY, ( nViewportY + nViewportHeight - 1 ), 0, 1, m_flScreenMinY );
int nScreenMaxY = FLerp( nViewportY, ( nViewportY + nViewportHeight - 1 ), 0, 1, m_flScreenMaxY );
float flExposureWidthScale, flExposureHeightScale;
// Shrink region of interest if the flashlight is on
flExposureWidthScale = ( 0.5f * ( 1.0f - mat_exposure_center_region_x.GetFloat() ) );
flExposureHeightScale = ( 0.5f * ( 1.0f - mat_exposure_center_region_y.GetFloat() ) );
int nBorderWidth = ( nScreenMaxX - nScreenMinX + 1 ) * flExposureWidthScale;
int nBorderHeight = ( nScreenMaxY - nScreenMinY + 1 ) * flExposureHeightScale;
// Do luminance compare
m_nPixels = ( 1 + nScreenMaxX - nScreenMinX ) * ( 1 + nScreenMaxY - nScreenMinY );
ShaderStencilState_t state;
if ( mat_tonemapping_occlusion_use_stencil.GetInt() )
{
state.m_nWriteMask = 1;
state.m_bEnable = true;
state.m_PassOp = SHADER_STENCILOP_SET_TO_REFERENCE;
state.m_CompareFunc = SHADER_STENCILFUNC_ALWAYS;
state.m_FailOp = SHADER_STENCILOP_KEEP;
state.m_ZFailOp = SHADER_STENCILOP_KEEP;
state.m_nReferenceValue = 1;
pRenderContext->SetStencilState( state );
}
else
{
pRenderContext->BeginOcclusionQueryDrawing( m_hOcclusionQueryHandle );
}
int nWindowWidth = 0;
int nWindowHeight = 0;
pRenderContext->GetWindowSize( nWindowWidth, nWindowHeight );
nScreenMinX += nBorderWidth;
nScreenMinY += nBorderHeight;
nScreenMaxX -= nBorderWidth;
nScreenMaxY -= nBorderHeight;
pRenderContext->DrawScreenSpaceRectangle( pLumCompareMaterial,
nScreenMinX - nViewportX, nScreenMinY - nViewportY,
1 + nScreenMaxX - nScreenMinX,
1 + nScreenMaxY - nScreenMinY,
nScreenMinX, nScreenMinY,
nScreenMaxX, nScreenMaxY,
nWindowWidth, nWindowHeight );
if ( mat_tonemapping_occlusion_use_stencil.GetInt() )
{
// Start counting how many pixels had their stencil bit set via an occlusion query
pRenderContext->BeginOcclusionQueryDrawing( m_hOcclusionQueryHandle );
// Issue an occlusion query using stencil as the mask
state.m_bEnable = true;
state.m_nTestMask = 1;
state.m_PassOp = SHADER_STENCILOP_KEEP;
state.m_CompareFunc = SHADER_STENCILFUNC_EQUAL;
state.m_FailOp = SHADER_STENCILOP_KEEP;
state.m_ZFailOp = SHADER_STENCILOP_KEEP;
state.m_nReferenceValue = 1;
pRenderContext->SetStencilState( state );
IMaterial *pLumCompareStencilMaterial = materials->FindMaterial( "dev/no_pixel_write", TEXTURE_GROUP_OTHER, true);
pRenderContext->DrawScreenSpaceRectangle( pLumCompareStencilMaterial,
nScreenMinX, nScreenMinY,
1 + nScreenMaxX - nScreenMinX,
1 + nScreenMaxY - nScreenMinY,
nScreenMinX, nScreenMinY,
nScreenMaxX, nScreenMaxY,
nWindowWidth, nWindowHeight );
ShaderStencilState_t stateDisable;
stateDisable.m_bEnable = false;
pRenderContext->SetStencilState( stateDisable );
}
pRenderContext->EndOcclusionQueryDrawing( m_hOcclusionQueryHandle );
if ( m_state == HESTATE_INITIAL )
m_state = HESTATE_FIRST_QUERY_IN_FLIGHT;
else
m_state = HESTATE_QUERY_IN_FLIGHT;
m_nFrameQueued = nFrameNum;
}
#define HISTOGRAM_BAR_SIZE 200
class CTonemapSystem
{
CHistogramBucket m_histogramBucketArray[NUM_HISTOGRAM_BUCKETS];
int m_nCurrentQueryFrame;
int m_nCurrentAlgorithm;
float m_flTargetTonemapScale;
float m_flCurrentTonemapScale;
int m_nNumMovingAverageValid;
float m_movingAverageTonemapScale[10];
bool m_bOverrideTonemapScaleEnabled;
float m_flOverrideTonemapScale;
public:
void IssueAndReceiveBucketQueries();
void UpdateBucketRanges();
float FindLocationOfPercentBrightPixels( float flPercentBrightPixels, float flPercentTarget );
float ComputeTargetTonemapScalar( bool bGetIdealTargetForDebugMode );
void UpdateMaterialSystemTonemapScalar();
void SetTargetTonemappingScale( float flTonemapScale );
void ResetTonemappingScale( float flTonemapScale );
void SetTonemapScale( IMatRenderContext *pRenderContext, float newvalue, float minvalue, float maxvalue );
float GetTargetTonemappingScale() { return m_flTargetTonemapScale; }
float GetCurrentTonemappingScale() { return m_flCurrentTonemapScale; }
void SetOverrideTonemapScale( bool bEnableOverride, float flTonemapScale );
// Dev functions
void DisplayHistogram();
// Constructor
CTonemapSystem()
{
m_nCurrentQueryFrame = 0;
m_nCurrentAlgorithm = -1;
m_flTargetTonemapScale = 1.0f;
m_flCurrentTonemapScale = 1.0f;
m_nNumMovingAverageValid = 0;
for ( int i = 0; i < ARRAYSIZE( m_movingAverageTonemapScale ) - 1; i++ )
{
m_movingAverageTonemapScale[i] = 1.0f;
}
m_bOverrideTonemapScaleEnabled = false;
m_flOverrideTonemapScale = 1.0f;
UpdateBucketRanges();
}
};
CTonemapSystem * GetCurrentTonemappingSystem()
{
static CTonemapSystem s_HDR_HistogramSystem[ MAX_SPLITSCREEN_PLAYERS ];
int slot = GET_ACTIVE_SPLITSCREEN_SLOT();
return &( s_HDR_HistogramSystem[ slot ] );
}
void CTonemapSystem::IssueAndReceiveBucketQueries()
{
UpdateBucketRanges();
// Find which histogram entries should have something done this frame
int nQueriesIssuedThisFrame = 0;
m_nCurrentQueryFrame++;
int nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS_NEW;
for ( int i = 0; i < nNumHistogramBuckets; i++ )
{
switch ( m_histogramBucketArray[i].m_state )
{
case HESTATE_INITIAL:
if ( nQueriesIssuedThisFrame<MAX_QUERIES_PER_FRAME )
{
m_histogramBucketArray[i].IssueQuery(m_nCurrentQueryFrame);
nQueriesIssuedThisFrame++;
}
break;
case HESTATE_FIRST_QUERY_IN_FLIGHT:
case HESTATE_QUERY_IN_FLIGHT:
if ( m_nCurrentQueryFrame > m_histogramBucketArray[i].m_nFrameQueued + 2 )
{
CMatRenderContextPtr pRenderContext( materials );
int np = pRenderContext->OcclusionQuery_GetNumPixelsRendered(
m_histogramBucketArray[i].m_hOcclusionQueryHandle );
if ( np != -1 ) // -1 = Query not finished...wait until next time
{
m_histogramBucketArray[i].m_nPixelsInRange = np;
m_histogramBucketArray[i].m_state = HESTATE_QUERY_DONE;
}
}
break;
}
}
// Now, issue queries for the oldest finished queries we have
while ( nQueriesIssuedThisFrame < MAX_QUERIES_PER_FRAME )
{
int nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS_NEW;
int nOldestSoFar = -1;
for ( int i = 0; i < nNumHistogramBuckets; i++ )
{
if ( ( m_histogramBucketArray[i].m_state == HESTATE_QUERY_DONE ) &&
( ( nOldestSoFar == -1 ) || ( m_histogramBucketArray[i].m_nFrameQueued < m_histogramBucketArray[nOldestSoFar].m_nFrameQueued ) ) )
{
nOldestSoFar = i;
}
}
if ( nOldestSoFar == -1 ) // Nothing to do
break;
m_histogramBucketArray[nOldestSoFar].IssueQuery( m_nCurrentQueryFrame );
nQueriesIssuedThisFrame++;
}
}
float CTonemapSystem::FindLocationOfPercentBrightPixels( float flPercentBrightPixels, float flPercentTargetToSnapToIfInSameBin = -1.0f )
{
if ( mat_tonemap_algorithm.GetInt() == 1 ) // New algorithm
{
int nTotalValidPixels = 0;
for ( int i = 0; i < ( NUM_HISTOGRAM_BUCKETS_NEW - 1 ); i++ )
{
if ( m_histogramBucketArray[i].ContainsValidData() )
{
nTotalValidPixels += m_histogramBucketArray[i].m_nPixelsInRange;
}
}
if ( nTotalValidPixels == 0 )
{
return -1.0f;
}
// Find where percent range border is
float flTotalPercentRangeTested = 0.0f;
float flTotalPercentPixelsTested = 0.0f;
for ( int i = ( NUM_HISTOGRAM_BUCKETS_NEW - 2 ); i >= 0; i-- ) // Start at the bright end
{
if ( !m_histogramBucketArray[i].ContainsValidData() )
return -1.0f;
float flPixelPercentNeeded = ( flPercentBrightPixels / 100.0f ) - flTotalPercentPixelsTested;
float flThisBinPercentOfTotalPixels = float( m_histogramBucketArray[i].m_nPixelsInRange ) / float( nTotalValidPixels );
float flThisBinLuminanceRange = m_histogramBucketArray[i].m_flMaxLuminance - m_histogramBucketArray[i].m_flMinLuminance;
if ( flThisBinPercentOfTotalPixels >= flPixelPercentNeeded ) // We found the bin needed
{
if ( flPercentTargetToSnapToIfInSameBin >= 0.0f )
{
if ( ( m_histogramBucketArray[i].m_flMinLuminance <= ( flPercentTargetToSnapToIfInSameBin / 100.0f ) ) && ( m_histogramBucketArray[i].m_flMaxLuminance >= ( flPercentTargetToSnapToIfInSameBin / 100.0f ) ) )
{
// Sticky bin...We're in the same bin as the target so keep the tonemap scale where it is
return ( flPercentTargetToSnapToIfInSameBin / 100.0f );
}
}
float flPercentOfThesePixelsNeeded = flPixelPercentNeeded / flThisBinPercentOfTotalPixels;
float flPercentLocationOfBorder = 1.0f - ( flTotalPercentRangeTested + ( flThisBinLuminanceRange * flPercentOfThesePixelsNeeded ) );
flPercentLocationOfBorder = MAX( m_histogramBucketArray[i].m_flMinLuminance, MIN( m_histogramBucketArray[i].m_flMaxLuminance, flPercentLocationOfBorder ) ); // Clamp to this bin just in case
return flPercentLocationOfBorder;
}
flTotalPercentPixelsTested += flThisBinPercentOfTotalPixels;
flTotalPercentRangeTested += flThisBinLuminanceRange;
}
return -1.0f;
}
else
{
// Don't know what to do for other algorithms yet
return -1.0f;
}
}
float CTonemapSystem::ComputeTargetTonemapScalar( bool bGetIdealTargetForDebugMode = false )
{
if ( mat_tonemap_algorithm.GetInt() == 1 ) // New algorithm
{
float flPercentLocationOfTarget;
if ( bGetIdealTargetForDebugMode == true)
flPercentLocationOfTarget = FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat() ); // Don't pass in the second arg so the scalar doesn't snap to a bin
else
flPercentLocationOfTarget = FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetFloat() );
if ( flPercentLocationOfTarget < 0.0f ) // This is the return error code
{
flPercentLocationOfTarget = mat_tonemap_percent_target.GetFloat() / 100.0f; // Pretend we're at the target
}
// Make sure this is > 0.0f
flPercentLocationOfTarget = MAX( 0.0001f, flPercentLocationOfTarget );
// Compute target scalar
float flTargetScalar = ( mat_tonemap_percent_target.GetFloat() / 100.0f ) / flPercentLocationOfTarget;
// Compute secondary target scalar
float flAverageLuminanceLocation = FindLocationOfPercentBrightPixels( 50.0f );
if ( flAverageLuminanceLocation > 0.0f )
{
float flTargetScalar2 = ( mat_tonemap_min_avglum.GetFloat() / 100.0f ) / flAverageLuminanceLocation;
// Only override it if it's trying to brighten the image more than the primary algorithm
if ( flTargetScalar2 > flTargetScalar )
{
flTargetScalar = flTargetScalar2;
}
}
// Apply this against last frames scalar
CMatRenderContextPtr pRenderContext( materials );
float flLastScale = m_flCurrentTonemapScale;
flTargetScalar *= flLastScale;
flTargetScalar = MAX( 0.001f, flTargetScalar );
return flTargetScalar;
}
else // Original tonemapping algorithm
{
float flScaleValue = 1.0f;
if ( m_histogramBucketArray[NUM_HISTOGRAM_BUCKETS-1].ContainsValidData() )
{
flScaleValue = m_histogramBucketArray[NUM_HISTOGRAM_BUCKETS-1].m_nPixels * ( 1.0f / m_histogramBucketArray[NUM_HISTOGRAM_BUCKETS-1].m_nPixelsInRange );
}
if ( !IsFinite( flScaleValue ) )
{
flScaleValue = 1.0f;
}
float flTotal = 0.0f;
int nTotalPixels = 0;
for ( int i=0; i<NUM_HISTOGRAM_BUCKETS-1; i++ )
{
if ( m_histogramBucketArray[i].ContainsValidData() )
{
flTotal += flScaleValue * m_histogramBucketArray[i].m_nPixelsInRange * AVG( m_histogramBucketArray[i].m_flMinLuminance, m_histogramBucketArray[i].m_flMaxLuminance );
nTotalPixels += m_histogramBucketArray[i].m_nPixels;
}
}
float flAverageLuminance = 0.5f;
if ( nTotalPixels > 0 )
flAverageLuminance = flTotal * ( 1.0f / nTotalPixels );
else
flAverageLuminance = 0.5f;
// Make sure this is > 0.0f
flAverageLuminance = MAX( 0.0001f, flAverageLuminance );
// Compute target scalar
float flTargetScalar = 0.005f / flAverageLuminance;
return flTargetScalar;
}
}
static float GetCurrentBloomScale( void )
{
// Use the appropriate bloom scale settings. Mapmakers's overrides the convar settings.
float flCurrentBloomScale = 1.0f;
if ( g_bUseCustomBloomScale )
{
flCurrentBloomScale = g_flCustomBloomScale;
}
else
{
flCurrentBloomScale = mat_bloomscale.GetFloat();
}
return flCurrentBloomScale;
}
static void GetExposureRange( float *pflAutoExposureMin, float *pflAutoExposureMax )
{
// Get min
if ( ( g_bUseCustomAutoExposureMin ) && ( g_flCustomAutoExposureMin > 0.0f ) )
{
*pflAutoExposureMin = g_flCustomAutoExposureMin;
}
else
{
*pflAutoExposureMin = mat_autoexposure_min.GetFloat();
}
// Get max
if ( ( g_bUseCustomAutoExposureMax ) && ( g_flCustomAutoExposureMax > 0.0f ) )
{
*pflAutoExposureMax = g_flCustomAutoExposureMax;
}
else
{
*pflAutoExposureMax = mat_autoexposure_max.GetFloat();
}
// Override
if ( mat_hdr_uncapexposure.GetInt() )
{
*pflAutoExposureMax = 100.0f;
*pflAutoExposureMin = 0.0f;
}
// Make sure min <= max
if ( *pflAutoExposureMin > *pflAutoExposureMax )
{
*pflAutoExposureMax = *pflAutoExposureMin;
}
}
void CTonemapSystem::UpdateBucketRanges()
{
// Only update if our mode changed
if ( m_nCurrentAlgorithm == mat_tonemap_algorithm.GetInt() )
return;
m_nCurrentAlgorithm = mat_tonemap_algorithm.GetInt();
//==================================================================//
// Force fallback to original tone mapping algorithm for these mods //
//==================================================================//
static bool s_bFirstTime = true;
if ( engine == NULL )
{
// Force this code to get hit again so we can change algorithm based on the client
m_nCurrentAlgorithm = -1;
}
else if ( s_bFirstTime == true )
{
s_bFirstTime = false;
// This seems like a bad idea but it's fine for now
const char *sModsForOriginalAlgorithm[] = { "dod", "cstrike", "lostcoast" };
for ( int i=0; i<3; i++ )
{
if ( strlen( engine->GetGameDirectory() ) >= strlen( sModsForOriginalAlgorithm[i] ) )
{
if ( stricmp( &( engine->GetGameDirectory()[strlen( engine->GetGameDirectory() ) - strlen( sModsForOriginalAlgorithm[i] )] ), sModsForOriginalAlgorithm[i] ) == 0 )
{
mat_tonemap_algorithm.SetValue( 0 ); // Original algorithm
m_nCurrentAlgorithm = mat_tonemap_algorithm.GetInt();
break;
}
}
}
}
// Get num buckets
int nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS_NEW;
m_nCurrentQueryFrame = 0;
for ( int nBucket = 0; nBucket < nNumHistogramBuckets; nBucket++ )
{
CHistogramBucket *pBucket = &( m_histogramBucketArray[ nBucket ] );
pBucket->m_state = HESTATE_INITIAL;
pBucket->m_flScreenMinX = 0.0f;
pBucket->m_flScreenMaxX = 1.0f;
pBucket->m_flScreenMinY = 0.0f;
pBucket->m_flScreenMaxY = 1.0f;
if ( nBucket != ( nNumHistogramBuckets - 1 ) ) // Last bucket is special
{
if ( mat_tonemap_algorithm.GetInt() == 0 ) // Original algorithm
{
// Use a logarithmic ramp for high range in the low range
pBucket->m_flMinLuminance = -0.01f + exp( FLerp( log( 0.01f ), log( 0.01f + 1.0f ), 0.0f, nNumHistogramBuckets - 1.0f, nBucket ) );
pBucket->m_flMaxLuminance = -0.01f + exp( FLerp( log( 0.01f ), log( 0.01f + 1.0f ), 0.0f, nNumHistogramBuckets - 1.0f, nBucket + 1.0f ) );
}
else
{
// Use even distribution
pBucket->m_flMinLuminance = float( nBucket ) / float( nNumHistogramBuckets - 1 );
pBucket->m_flMaxLuminance = float( nBucket + 1 ) / float( nNumHistogramBuckets - 1 );
// Use a distribution with slightly more bins in the low range
pBucket->m_flMinLuminance = pBucket->m_flMinLuminance > 0.0f ? powf( pBucket->m_flMinLuminance, 2.5f ) : pBucket->m_flMinLuminance;
pBucket->m_flMaxLuminance = pBucket->m_flMaxLuminance > 0.0f ? powf( pBucket->m_flMaxLuminance, 2.5f ) : pBucket->m_flMaxLuminance;
}
}
else
{
// The last bucket is used as a test to determine the return range for occlusion
// queries to use as a scale factor. some boards (nvidia) have their occlusion
// query return values larger when using AA.
pBucket->m_flMinLuminance = 0.0f;
pBucket->m_flMaxLuminance = 100000.0f;
}
//Warning( "Bucket %d: min/max %f / %f ", nBucket, pBucket->m_flMinLuminance, pBucket->m_flMaxLuminance );
}
}
void CTonemapSystem::SetOverrideTonemapScale( bool bEnableOverride, float flTonemapScale )
{
m_bOverrideTonemapScaleEnabled = bEnableOverride;
m_flOverrideTonemapScale = flTonemapScale;
}
void CTonemapSystem::DisplayHistogram()
{
if ( !mat_show_histogram.GetInt() || !mat_dynamic_tonemapping.GetInt() || ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE ) )
return;
// Get render context
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->PushRenderTargetAndViewport();
// Prep variables for drawing histogram
int nViewportX, nViewportY, nViewportWidth, nViewportHeight;
pRenderContext->GetViewport( nViewportX, nViewportY, nViewportWidth, nViewportHeight );
// Get num bins
int nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS-1;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumHistogramBuckets = NUM_HISTOGRAM_BUCKETS_NEW-1;
// Count total pixels in current bins
int nMaxValidPixels = 0;
int nTotalValidPixels = 0;
int nTotalGraphPixelsWide = 0;
for ( int nBucket = 0; nBucket < nNumHistogramBuckets; nBucket++ )
{
CHistogramBucket *pBucket = &( m_histogramBucketArray[ nBucket ] );
if ( pBucket->ContainsValidData() )
{
nTotalValidPixels += pBucket->m_nPixelsInRange;
if ( pBucket->m_nPixelsInRange > nMaxValidPixels )
{
nMaxValidPixels = pBucket->m_nPixelsInRange;
}
}
int nWidth = MAX( 1, 500 * ( pBucket->m_flMaxLuminance - pBucket->m_flMinLuminance ) );
nTotalGraphPixelsWide += nWidth + 2;
}
// Clear background to gray for screenshots
//int nBoxWidth = ( nTotalGraphPixelsWide + 20 );
//pRenderContext->ClearColor3ub( 150, 150, 150 );
//pRenderContext->Viewport( nViewportWidth - nBoxWidth, 0, nBoxWidth, 245 );
//pRenderContext->ClearBuffers( true, true );
// Output some text data
if ( !IsX360() )
{
engine->Con_NPrintf( 23 + ( nViewportY / 10 ), "(Histogram luminance is in linear space)" );
engine->Con_NPrintf( 27 + ( nViewportY / 10 ), "AvgLum @ %4.2f%% mat_tonemap_min_avglum = %4.2f%% Using %d pixels Override(%s): %4.2f",
MAX( 0.0f, FindLocationOfPercentBrightPixels( 50.0f ) ) * 100.0f, mat_tonemap_min_avglum.GetFloat(), nTotalValidPixels, m_bOverrideTonemapScaleEnabled ? "On" : "Off", m_flOverrideTonemapScale );
engine->Con_NPrintf( 29 + ( nViewportY / 10 ), "BloomScale = %4.2f mat_hdr_manual_tonemap_rate = %4.2f mat_accelerate_adjust_exposure_down = %4.2f",
GetCurrentBloomScale(), mat_hdr_manual_tonemap_rate.GetFloat(), mat_accelerate_adjust_exposure_down.GetFloat() );
}
int xpStart = nViewportX + nViewportWidth - nTotalGraphPixelsWide - 10;
if ( IsX360() )
{
xpStart -= 50;
}
int yOffset = 4 + nViewportY;
int xp = xpStart;
for ( int nBucket = 0; nBucket < nNumHistogramBuckets; nBucket++ )
{
int np = 0;
CHistogramBucket &e = m_histogramBucketArray[ nBucket ];
if ( e.ContainsValidData() )
np += e.m_nPixelsInRange;
int width = MAX( 1, 500 * ( e.m_flMaxLuminance - e.m_flMinLuminance ) );
//Warning( "Bucket %d: min/max %f / %f. m_nPixelsInRange=%d m_nPixels=%d\n", nBucket, e.m_flMinLuminance, e.m_flMaxLuminance, e.m_nPixelsInRange, e.m_nPixels );
if ( np )
{
int height = MAX( 1, MIN( HISTOGRAM_BAR_SIZE, ( (float)np / (float)nMaxValidPixels ) * HISTOGRAM_BAR_SIZE ) );
pRenderContext->ClearColor3ub( 255, 0, 0 );
pRenderContext->Viewport( xp, yOffset + HISTOGRAM_BAR_SIZE - height, width, height );
pRenderContext->ClearBuffers( true, true );
}
else
{
int height = 1;
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->Viewport( xp, yOffset + HISTOGRAM_BAR_SIZE - height, width, height );
pRenderContext->ClearBuffers( true, true );
}
xp += width + 2;
}
if ( mat_tonemap_algorithm.GetInt() == 1 ) // New algorithm only
{
float flYellowTargetPixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * mat_tonemap_percent_target.GetFloat() / 100.0f ) );
float flYellowAveragePixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * mat_tonemap_min_avglum.GetFloat() / 100.0f ) );
float flTargetPixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetFloat() ) ) );
float flAveragePixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * FindLocationOfPercentBrightPixels( 50.0f ) ) );
// Draw target yellow border bar
int nHeight = HISTOGRAM_BAR_SIZE * 3 / 4;
int nHeightOffset = -( HISTOGRAM_BAR_SIZE - nHeight ) / 2;
// Green is current percent target location
pRenderContext->Viewport( flYellowTargetPixelStart-1, yOffset + nHeightOffset + HISTOGRAM_BAR_SIZE - nHeight - 2, 8, nHeight + 4 );
pRenderContext->ClearColor3ub( 0, 127, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flYellowTargetPixelStart+1, yOffset + nHeightOffset + HISTOGRAM_BAR_SIZE - nHeight, 4, nHeight );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flTargetPixelStart+1, yOffset + nHeightOffset + HISTOGRAM_BAR_SIZE - nHeight, 4, nHeight );
pRenderContext->ClearColor3ub( 0, 255, 0 );
pRenderContext->ClearBuffers( true, true );
// Blue is average luminance location
pRenderContext->Viewport( flYellowAveragePixelStart-1, yOffset + nHeightOffset + HISTOGRAM_BAR_SIZE - nHeight - 2, 8, nHeight + 4 );
pRenderContext->ClearColor3ub( 0, 114, 188 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flYellowAveragePixelStart+1, yOffset + nHeightOffset + HISTOGRAM_BAR_SIZE - nHeight, 4, nHeight );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flAveragePixelStart+1, yOffset + nHeightOffset + HISTOGRAM_BAR_SIZE - nHeight, 4, nHeight );
pRenderContext->ClearColor3ub( 0, 191, 243 );
pRenderContext->ClearBuffers( true, true );
}
// Show actual tonemap value
if ( 1 )
{
float flAutoExposureMin;
float flAutoExposureMax;
GetExposureRange( &flAutoExposureMin, &flAutoExposureMax );
float flBarWidth = nTotalGraphPixelsWide;
float flBarStart = xpStart;
pRenderContext->Viewport( flBarStart, yOffset + HISTOGRAM_BAR_SIZE - 4 + 20, flBarWidth, 4 );
pRenderContext->ClearColor3ub( 200, 200, 200 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flBarStart, yOffset + HISTOGRAM_BAR_SIZE - 4 + 20 + 1, flBarWidth, 2 );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flBarStart + ( flBarWidth * ( ( m_flCurrentTonemapScale - flAutoExposureMin ) / ( flAutoExposureMax - flAutoExposureMin ) ) ) - 1,
yOffset + HISTOGRAM_BAR_SIZE - 4 + 20 - 6 - 1, 4 + 2, 16 + 2 );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flBarStart + ( flBarWidth * ( ( m_flCurrentTonemapScale - flAutoExposureMin ) / ( flAutoExposureMax - flAutoExposureMin ) ) ),
yOffset + HISTOGRAM_BAR_SIZE - 4 + 20 - 6, 4, 16 );
pRenderContext->ClearColor3ub( 255, 255, 0 );
pRenderContext->ClearBuffers( true, true );
if ( !IsX360() )
engine->Con_NPrintf( 21 + ( nViewportY / 10 ), "%.2f %.2f %.2f", flAutoExposureMin, ( flAutoExposureMax + flAutoExposureMin ) / 2.0f, flAutoExposureMax );
}
// Last bar doesn't clear properly so draw an extra pixel
pRenderContext->Viewport( 0, 0, 1, 1 );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->PopRenderTargetAndViewport();
}
// Global postprocessing disable switch
static bool s_bOverridePostProcessingDisable = false;
void UpdateMaterialSystemTonemapScalar()
{
GetCurrentTonemappingSystem()->UpdateMaterialSystemTonemapScalar();
}
void CTonemapSystem::UpdateMaterialSystemTonemapScalar()
{
if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE )
{
// Deal with forced tone map scalar
float flForcedTonemapScale = mat_force_tonemap_scale.GetFloat();
if ( mat_fullbright.GetInt() == 1 )
{
flForcedTonemapScale = 1.0f;
}
if ( flForcedTonemapScale > 0.0f )
{
ResetTonemappingScale( flForcedTonemapScale );
// Send this value to the material system
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetToneMappingScaleLinear( Vector( m_flCurrentTonemapScale, m_flCurrentTonemapScale, m_flCurrentTonemapScale ) );
return;
}
// Override tone map scalar
if ( m_bOverrideTonemapScaleEnabled )
{
float flAutoExposureMin;
float flAutoExposureMax;
GetExposureRange( &flAutoExposureMin, &flAutoExposureMax );
float fScale = clamp( m_flOverrideTonemapScale, flAutoExposureMin, flAutoExposureMax );
ResetTonemappingScale( fScale );
// Send this value to the material system
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetToneMappingScaleLinear( Vector( m_flCurrentTonemapScale, m_flCurrentTonemapScale, m_flCurrentTonemapScale ) );
return;
}
// Send this value to the material system
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetToneMappingScaleLinear( Vector( m_flCurrentTonemapScale, m_flCurrentTonemapScale, m_flCurrentTonemapScale ) );
}
else
{
// Send 1.0 to the material system since HDR is disabled
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetToneMappingScaleLinear( Vector( 1.0f, 1.0f, 1.0f ) );
}
}
void CTonemapSystem::ResetTonemappingScale( float flTonemapScale )
{
if ( flTonemapScale <= 0.0f )
{
// L4D Hack to reset the tonemapping scale to the average of min and max since we have such dark lighting
// compared to our other games. 1.0 is no longer a good value when changing spectator targets.
float flAutoExposureMin = 0.0f;
float flAutoExposureMax = 0.0f;
GetExposureRange( &flAutoExposureMin, &flAutoExposureMax );
flTonemapScale = ( flAutoExposureMin + flAutoExposureMax ) * 0.5f;
flTonemapScale = clamp( flTonemapScale, 1.0f, 10.0f ); // Restrict this to the 1-10 range
}
// Force current and target tonemap scalar
m_flCurrentTonemapScale = flTonemapScale;
m_flTargetTonemapScale = flTonemapScale;
// Clear averaging history
m_nNumMovingAverageValid = 0;
}
void CTonemapSystem::SetTargetTonemappingScale( float flTonemapScale )
{
Assert( IsFinite( flTonemapScale ) );
if ( IsFinite( flTonemapScale ) )
{
m_flTargetTonemapScale = flTonemapScale;
}
}
// Local contrast setting
PostProcessParameters_t s_LocalPostProcessParameters[ MAX_SPLITSCREEN_PLAYERS ];
// view fade param settings
static Vector4D s_viewFadeColor[ MAX_SPLITSCREEN_PLAYERS ];
static bool s_bViewFadeModulate[ MAX_SPLITSCREEN_PLAYERS ];
class PPInit
{
public:
PPInit()
{
for ( int i = 0; i < MAX_SPLITSCREEN_PLAYERS; ++i )
{
s_viewFadeColor[ i ].Init( 0.0f, 0.0f, 0.0f, 0.0f );
s_bViewFadeModulate[ i ] = false;
}
}
};
static PPInit g_PPInit;
void ResetToneMapping( float flTonemappingScale )
{
GetCurrentTonemappingSystem()->ResetTonemappingScale( flTonemappingScale );
// Send this value to the material system
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetToneMappingScaleLinear( Vector( flTonemappingScale, flTonemappingScale, flTonemappingScale ) );
}
void CTonemapSystem::SetTonemapScale( IMatRenderContext *pRenderContext, float flTargetTonemapScalar, float flMinValue, float flMaxValue )
{
Assert( IsFinite( flTargetTonemapScalar ) );
if ( !IsFinite( flTargetTonemapScalar ) )
return;
//=========================================================================//
// Save off new target tonemap scalar so we can compute a weighted average //
//=========================================================================//
if ( m_nNumMovingAverageValid < ARRAYSIZE( m_movingAverageTonemapScale ))
{
m_movingAverageTonemapScale[ m_nNumMovingAverageValid++ ] = flTargetTonemapScalar;
}
else
{
// Scroll, losing oldest
for ( int i = 0; i < ARRAYSIZE( m_movingAverageTonemapScale ) - 1; i++ )
m_movingAverageTonemapScale[ i ] = m_movingAverageTonemapScale[ i + 1 ];
m_movingAverageTonemapScale[ ARRAYSIZE( m_movingAverageTonemapScale ) - 1 ] = flTargetTonemapScalar;
}
//==================================================================//
// Compute a weighted average of the last 10 target tonemap scalars //
//==================================================================//
if ( m_nNumMovingAverageValid == ARRAYSIZE( m_movingAverageTonemapScale ) ) // If we have a full buffer
{
float flWeightedAverage = 0.0f;
float flSumWeights = 0.0f;
int iMidPoint = ARRAYSIZE( m_movingAverageTonemapScale ) / 2;
for ( int i = 0; i < ARRAYSIZE( m_movingAverageTonemapScale ); i++ )
{
float flWeight = abs( i - iMidPoint ) * ( 1.0f / ( ARRAYSIZE( m_movingAverageTonemapScale ) / 2 ) );
flSumWeights += flWeight;
flWeightedAverage += flWeight * m_movingAverageTonemapScale[i];
}
flWeightedAverage *= ( 1.0f / flSumWeights );
flWeightedAverage = clamp( flWeightedAverage, flMinValue, flMaxValue );
SetTargetTonemappingScale( flWeightedAverage );
}
else
{
SetTargetTonemappingScale( flTargetTonemapScalar );
}
//=======================================//
// Smoothly lerp to the target over time //
//=======================================//
float flElapsedTime = MAX( gpGlobals->frametime, 0.0f ); // Clamp to positive
float flRate = mat_hdr_manual_tonemap_rate.GetFloat();
if ( mat_tonemap_algorithm.GetInt() == 1 )
{
flRate *= 2.0f; // Default 2x for the new tone mapping algorithm so it feels the same as the original
}
if ( flRate == 0.0f ) // Zero indicates instantaneous tonemap scaling
{
m_flCurrentTonemapScale = m_flTargetTonemapScale;
}
else
{
if ( m_flTargetTonemapScale < m_flCurrentTonemapScale )
{
float acc_exposure_adjust = mat_accelerate_adjust_exposure_down.GetFloat();
// Adjust at up to 4x rate when over-exposed.
flRate = MIN( ( acc_exposure_adjust * flRate ), FLerp( flRate, ( acc_exposure_adjust * flRate ), 0.0f, 1.5f, ( m_flCurrentTonemapScale - m_flTargetTonemapScale ) ) );
}
float flRateTimesTime = flRate * flElapsedTime;
if ( mat_tonemap_algorithm.GetInt() == 1 )
{
// For the new tone mapping algorithm, limit the rate based on the number of bins to
// help reduce the tone map scalar "riding the wave" of the histogram re-building
//Warning( "flRateTimesTime = %.4f", flRateTimesTime );
flRateTimesTime = MIN( flRateTimesTime, ( 1.0f / ( float )( NUM_HISTOGRAM_BUCKETS_NEW - 1 ) ) * 0.25f );
//Warning( " --> %.4f\n", flRateTimesTime );
}
float flAlpha = clamp( flRateTimesTime, 0.0f, 1.0f );
m_flCurrentTonemapScale = ( m_flTargetTonemapScale * flAlpha ) + ( m_flCurrentTonemapScale * ( 1.0f - flAlpha ) );
//m_flCurrentTonemapScale = FLerp( m_flCurrentTonemapScale, m_flTargetTonemapScale, flAlpha );
if ( !IsFinite( m_flCurrentTonemapScale ) )
{
Assert( 0 );
m_flCurrentTonemapScale = m_flTargetTonemapScale;
}
}
//==========================================//
// Step on values if we're forcing a scalar //
//==========================================//
float flForcedTonemapScale = mat_force_tonemap_scale.GetFloat();
if ( flForcedTonemapScale > 0.0f )
{
ResetTonemappingScale( flForcedTonemapScale );
}
}
//=====================================================================================================================
// Public functions for messing with tone mapping
//=====================================================================================================================
float GetCurrentTonemapScale()
{
return GetCurrentTonemappingSystem()->GetCurrentTonemappingScale();
}
void SetOverrideTonemapScale( bool bEnableOverride, float flTonemapScale )
{
GetCurrentTonemappingSystem()->SetOverrideTonemapScale( bEnableOverride, flTonemapScale );
}
void SetOverridePostProcessingDisable( bool bForceOff )
{
s_bOverridePostProcessingDisable = bForceOff;
}
void SetPostProcessParams( const PostProcessParameters_t *pPostProcessParameters )
{
int nSplitScreenSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
s_LocalPostProcessParameters[ nSplitScreenSlot ] = *pPostProcessParameters;
}
void SetViewFadeParams( byte r, byte g, byte b, byte a, bool bModulate )
{
int nSplitScreenSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
s_viewFadeColor[ nSplitScreenSlot ].Init( float(r)/255.0f, float(g)/255.0f, float(b)/255.0f, float(a)/255.0f );
s_bViewFadeModulate[ nSplitScreenSlot ] = bModulate;
}
//=====================================================================================================================
// BloomAdd material proxy ============================================================================================
//=====================================================================================================================
class CBloomAddMaterialProxy : public CEntityMaterialProxy
{
public:
CBloomAddMaterialProxy();
virtual ~CBloomAddMaterialProxy() {}
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( C_BaseEntity *pEntity );
virtual IMaterial *GetMaterial();
private:
IMaterialVar *m_pMaterialParam_BloomAmount;
public:
static void SetBloomAmount( float flBloomAmount ) { s_flBloomAmount = flBloomAmount; }
private:
static float s_flBloomAmount;
};
float CBloomAddMaterialProxy::s_flBloomAmount = 1.0f;
CBloomAddMaterialProxy::CBloomAddMaterialProxy()
: m_pMaterialParam_BloomAmount( NULL )
{
}
bool CBloomAddMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool bFoundVar = false;
m_pMaterialParam_BloomAmount = pMaterial->FindVar( "$c0_x", &bFoundVar, false );
return true;
}
void CBloomAddMaterialProxy::OnBind( C_BaseEntity *pEnt )
{
if ( m_pMaterialParam_BloomAmount )
m_pMaterialParam_BloomAmount->SetFloatValue( s_flBloomAmount );
}
IMaterial *CBloomAddMaterialProxy::GetMaterial()
{
if ( m_pMaterialParam_BloomAmount == NULL)
return NULL;
return m_pMaterialParam_BloomAmount->GetOwningMaterial();
}
EXPOSE_MATERIAL_PROXY( CBloomAddMaterialProxy, BloomAdd );
//=====================================================================================================================
// Engine_Post material proxy ============================================================================================
//=====================================================================================================================
static ConVar mat_software_aa_strength( "mat_software_aa_strength", "-1.0", 0, "Software AA - perform a software anti-aliasing post-process (an alternative/supplement to MSAA). This value sets the strength of the effect: (0.0 - off), (1.0 - full)" );
static ConVar mat_software_aa_quality( "mat_software_aa_quality", "0", 0, "Software AA quality mode: (0 - 5-tap filter), (1 - 9-tap filter)" );
static ConVar mat_software_aa_edge_threshold( "mat_software_aa_edge_threshold", "1.0", 0, "Software AA - adjusts the sensitivity of the software AA shader's edge detection (default 1.0 - a lower value will soften more edges, a higher value will soften fewer)" );
static ConVar mat_software_aa_blur_one_pixel_lines( "mat_software_aa_blur_one_pixel_lines", "0.5", 0, "How much software AA should blur one-pixel thick lines: (0.0 - none), (1.0 - lots)" );
static ConVar mat_software_aa_tap_offset( "mat_software_aa_tap_offset", "1.0", 0, "Software AA - adjusts the displacement of the taps used by the software AA shader (default 1.0 - a lower value will make the image sharper, higher will make it blurrier)" );
static ConVar mat_software_aa_debug( "mat_software_aa_debug", "0", 0, "Software AA debug mode: (0 - off), (1 - show number of 'unlike' samples: 0->black, 1->red, 2->green, 3->blue), (2 - show anti-alias blend strength), (3 - show averaged 'unlike' colour)" );
static ConVar mat_software_aa_strength_vgui( "mat_software_aa_strength_vgui", "-1.0", 0, "Same as mat_software_aa_strength, but forced to this value when called by the post vgui AA pass." );
class CEnginePostMaterialProxy : public CEntityMaterialProxy
{
public:
CEnginePostMaterialProxy();
virtual ~CEnginePostMaterialProxy();
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( C_BaseEntity *pEntity );
virtual IMaterial *GetMaterial();
private:
IMaterialVar *m_pMaterialParam_AAValues;
IMaterialVar *m_pMaterialParam_AAValues2;
IMaterialVar *m_pMaterialParam_BloomEnable;
IMaterialVar *m_pMaterialParam_BloomAmount;
IMaterialVar *m_pMaterialParam_BloomUVTransform;
IMaterialVar *m_pMaterialParam_ColCorrectEnable;
IMaterialVar *m_pMaterialParam_ColCorrectNumLookups;
IMaterialVar *m_pMaterialParam_ColCorrectDefaultWeight;
IMaterialVar *m_pMaterialParam_ColCorrectLookupWeights;
IMaterialVar *m_pMaterialParam_LocalContrastStrength;
IMaterialVar *m_pMaterialParam_LocalContrastEdgeStrength;
IMaterialVar *m_pMaterialParam_VignetteStart;
IMaterialVar *m_pMaterialParam_VignetteEnd;
IMaterialVar *m_pMaterialParam_VignetteBlurEnable;
IMaterialVar *m_pMaterialParam_VignetteBlurStrength;
IMaterialVar *m_pMaterialParam_FadeToBlackStrength;
IMaterialVar *m_pMaterialParam_DepthBlurFocalDistance;
IMaterialVar *m_pMaterialParam_DepthBlurStrength;
IMaterialVar *m_pMaterialParam_ScreenBlurStrength;
IMaterialVar *m_pMaterialParam_FilmGrainStrength;
IMaterialVar *m_pMaterialParam_VomitEnable;
IMaterialVar *m_pMaterialParam_VomitColor1;
IMaterialVar *m_pMaterialParam_VomitColor2;
IMaterialVar *m_pMaterialParam_FadeColor;
IMaterialVar *m_pMaterialParam_FadeType;
public:
static void SetupEnginePostMaterial( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, const Vector2D & destTexSize,
bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength, float flBloomAmount );
static void SetupEnginePostMaterialAA( bool bPerformSoftwareAA, float flAAStrength );
static void SetupEnginePostMaterialTextureTransform( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, Vector2D destTexSize );
private:
static float s_vBloomAAValues[4];
static float s_vBloomAAValues2[4];
static float s_vBloomUVTransform[4];
static int s_PostBloomEnable;
static float s_PostBloomAmount;
};
float CEnginePostMaterialProxy::s_vBloomAAValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
float CEnginePostMaterialProxy::s_vBloomAAValues2[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
float CEnginePostMaterialProxy::s_vBloomUVTransform[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
int CEnginePostMaterialProxy::s_PostBloomEnable = 1;
float CEnginePostMaterialProxy::s_PostBloomAmount = 1.0f;
CEnginePostMaterialProxy::CEnginePostMaterialProxy()
{
m_pMaterialParam_AAValues = NULL;
m_pMaterialParam_AAValues2 = NULL;
m_pMaterialParam_BloomUVTransform = NULL;
m_pMaterialParam_BloomEnable = NULL;
m_pMaterialParam_BloomAmount = NULL;
m_pMaterialParam_ColCorrectEnable = NULL;
m_pMaterialParam_ColCorrectNumLookups = NULL;
m_pMaterialParam_ColCorrectDefaultWeight = NULL;
m_pMaterialParam_ColCorrectLookupWeights = NULL;
m_pMaterialParam_LocalContrastStrength = NULL;
m_pMaterialParam_LocalContrastEdgeStrength = NULL;
m_pMaterialParam_VignetteStart = NULL;
m_pMaterialParam_VignetteEnd = NULL;
m_pMaterialParam_VignetteBlurEnable = NULL;
m_pMaterialParam_VignetteBlurStrength = NULL;
m_pMaterialParam_FadeToBlackStrength = NULL;
m_pMaterialParam_DepthBlurFocalDistance = NULL;
m_pMaterialParam_DepthBlurStrength = NULL;
m_pMaterialParam_ScreenBlurStrength = NULL;
m_pMaterialParam_FilmGrainStrength = NULL;
}
CEnginePostMaterialProxy::~CEnginePostMaterialProxy()
{
// Do nothing
}
bool CEnginePostMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool bFoundVar = false;
m_pMaterialParam_AAValues = pMaterial->FindVar( "$AAInternal1", &bFoundVar, false );
m_pMaterialParam_AAValues2 = pMaterial->FindVar( "$AAInternal3", &bFoundVar, false );
m_pMaterialParam_BloomUVTransform = pMaterial->FindVar( "$AAInternal2", &bFoundVar, false );
m_pMaterialParam_BloomEnable = pMaterial->FindVar( "$bloomEnable", &bFoundVar, false );
m_pMaterialParam_BloomAmount = pMaterial->FindVar( "$bloomAmount", &bFoundVar, false );
m_pMaterialParam_ColCorrectEnable = pMaterial->FindVar( "$colCorrectEnable", &bFoundVar, false );
m_pMaterialParam_ColCorrectNumLookups = pMaterial->FindVar( "$colCorrect_NumLookups", &bFoundVar, false );
m_pMaterialParam_ColCorrectDefaultWeight = pMaterial->FindVar( "$colCorrect_DefaultWeight", &bFoundVar, false );
m_pMaterialParam_ColCorrectLookupWeights = pMaterial->FindVar( "$colCorrect_LookupWeights", &bFoundVar, false );
m_pMaterialParam_LocalContrastStrength = pMaterial->FindVar( "$localContrastScale", &bFoundVar, false );
m_pMaterialParam_LocalContrastEdgeStrength = pMaterial->FindVar( "$localContrastEdgeScale", &bFoundVar, false );
m_pMaterialParam_VignetteStart = pMaterial->FindVar( "$localContrastVignetteStart", &bFoundVar, false );
m_pMaterialParam_VignetteEnd = pMaterial->FindVar( "$localContrastVignetteEnd", &bFoundVar, false );
m_pMaterialParam_VignetteBlurEnable = pMaterial->FindVar( "$blurredVignetteEnable", &bFoundVar, false );
m_pMaterialParam_VignetteBlurStrength = pMaterial->FindVar( "$blurredVignetteScale", &bFoundVar, false );
m_pMaterialParam_FadeToBlackStrength = pMaterial->FindVar( "$fadeToBlackScale", &bFoundVar, false );
m_pMaterialParam_DepthBlurFocalDistance = pMaterial->FindVar( "$depthBlurFocalDistance", &bFoundVar, false );
m_pMaterialParam_DepthBlurStrength = pMaterial->FindVar( "$depthBlurStrength", &bFoundVar, false );
m_pMaterialParam_ScreenBlurStrength = pMaterial->FindVar( "$screenBlurStrength", &bFoundVar, false );
m_pMaterialParam_FilmGrainStrength = pMaterial->FindVar( "$noiseScale", &bFoundVar, false );
m_pMaterialParam_VomitEnable = pMaterial->FindVar( "$vomitEnable", &bFoundVar, false );
m_pMaterialParam_VomitColor1 = pMaterial->FindVar( "$vomitColor1", &bFoundVar, false );
m_pMaterialParam_VomitColor2 = pMaterial->FindVar( "$vomitColor2", &bFoundVar, false );
m_pMaterialParam_FadeColor = pMaterial->FindVar( "$fadeColor", &bFoundVar, false );
m_pMaterialParam_FadeType = pMaterial->FindVar( "$fade", &bFoundVar, false );
return true;
}
void CEnginePostMaterialProxy::OnBind( C_BaseEntity *pEnt )
{
int nSplitScreenSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
if ( m_pMaterialParam_AAValues )
m_pMaterialParam_AAValues->SetVecValue( s_vBloomAAValues, 4 );
if ( m_pMaterialParam_AAValues2 )
m_pMaterialParam_AAValues2->SetVecValue( s_vBloomAAValues2, 4 );
if ( m_pMaterialParam_BloomUVTransform )
m_pMaterialParam_BloomUVTransform->SetVecValue( s_vBloomUVTransform, 4 );
if ( m_pMaterialParam_BloomEnable )
m_pMaterialParam_BloomEnable->SetIntValue( s_PostBloomEnable );
if ( m_pMaterialParam_BloomAmount )
m_pMaterialParam_BloomAmount->SetFloatValue( s_PostBloomAmount );
if ( m_pMaterialParam_LocalContrastStrength )
m_pMaterialParam_LocalContrastStrength->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_LOCAL_CONTRAST_STRENGTH ] );
if ( m_pMaterialParam_LocalContrastEdgeStrength )
m_pMaterialParam_LocalContrastEdgeStrength->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_LOCAL_CONTRAST_EDGE_STRENGTH ] );
if ( m_pMaterialParam_VignetteStart )
m_pMaterialParam_VignetteStart->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_VIGNETTE_START ] );
if ( m_pMaterialParam_VignetteEnd )
m_pMaterialParam_VignetteEnd->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_VIGNETTE_END ] );
if ( m_pMaterialParam_VignetteBlurEnable )
m_pMaterialParam_VignetteBlurEnable->SetIntValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_VIGNETTE_BLUR_STRENGTH ] > 0.0f ? 1 : 0 );
if ( m_pMaterialParam_VignetteBlurStrength )
m_pMaterialParam_VignetteBlurStrength->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_VIGNETTE_BLUR_STRENGTH ] );
if ( m_pMaterialParam_FadeToBlackStrength )
m_pMaterialParam_FadeToBlackStrength->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_FADE_TO_BLACK_STRENGTH ] );
if ( m_pMaterialParam_DepthBlurFocalDistance )
m_pMaterialParam_DepthBlurFocalDistance->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_DEPTH_BLUR_FOCAL_DISTANCE ] );
if ( m_pMaterialParam_DepthBlurStrength )
m_pMaterialParam_DepthBlurStrength->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_DEPTH_BLUR_STRENGTH ] );
if ( m_pMaterialParam_ScreenBlurStrength )
m_pMaterialParam_ScreenBlurStrength->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_SCREEN_BLUR_STRENGTH ] );
if ( m_pMaterialParam_FilmGrainStrength )
m_pMaterialParam_FilmGrainStrength->SetFloatValue( s_LocalPostProcessParameters[ nSplitScreenSlot ].m_flParameters[ PPPN_FILM_GRAIN_STRENGTH ] );
if ( m_pMaterialParam_FadeType )
{
int nFadeType = ( s_bViewFadeModulate[nSplitScreenSlot] ) ? 2 : 1;
nFadeType = ( s_viewFadeColor[nSplitScreenSlot][3] > 0.0f ) ? nFadeType : 0;
m_pMaterialParam_FadeType->SetIntValue( nFadeType );
}
if ( m_pMaterialParam_FadeColor )
{
m_pMaterialParam_FadeColor->SetVecValue( s_viewFadeColor[nSplitScreenSlot].Base(), 4 );
}
}
IMaterial *CEnginePostMaterialProxy::GetMaterial()
{
if ( m_pMaterialParam_AAValues == NULL)
return NULL;
return m_pMaterialParam_AAValues->GetOwningMaterial();
}
void CEnginePostMaterialProxy::SetupEnginePostMaterialAA( bool bPerformSoftwareAA, float flAAStrength )
{
if ( bPerformSoftwareAA )
{
// Pass ConVars to the material by proxy
// - the strength of the AA effect (from 0 to 1)
// - how much to allow 1-pixel lines to be blurred (from 0 to 1)
// - pick one of the two quality modes (5-tap or 9-tap filter)
// - optionally enable one of several debug modes (via dynamic combos)
// NOTE: this order matches pixel shader constants in Engine_Post_ps2x.fxc
s_vBloomAAValues[0] = flAAStrength;
s_vBloomAAValues[1] = 1.0f - mat_software_aa_blur_one_pixel_lines.GetFloat();
s_vBloomAAValues[2] = mat_software_aa_quality.GetInt();
s_vBloomAAValues[3] = mat_software_aa_debug.GetInt();
s_vBloomAAValues2[0] = mat_software_aa_edge_threshold.GetFloat();
s_vBloomAAValues2[1] = mat_software_aa_tap_offset.GetFloat();
//s_vBloomAAValues2[2] = unused;
//s_vBloomAAValues2[3] = unused;
}
else
{
// Zero-strength AA is interpreted as "AA disabled"
s_vBloomAAValues[0] = 0.0f;
}
}
void CEnginePostMaterialProxy::SetupEnginePostMaterialTextureTransform( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, Vector2D fbSize )
{
// Engine_Post uses a UV transform (from (quarter-res) bloom texture coords ('1')
// to (full-res) framebuffer texture coords ('2')).
//
// We compute the UV transform as an offset and a scale, using the texture coordinates
// of the top-left corner of the screen to compute the offset and the coordinate
// change from the top-left to the bottom-right of the quad to compute the scale.
// Take texel coordinates (start = top-left, end = bottom-right):
Vector2D texelStart1 = Vector2D( fullViewportBloomUVs.x, fullViewportBloomUVs.y );
Vector2D texelStart2 = Vector2D( fullViewportFBUVs.x, fullViewportFBUVs.y );
Vector2D texelEnd1 = Vector2D( fullViewportBloomUVs.z, fullViewportBloomUVs.w );
Vector2D texelEnd2 = Vector2D( fullViewportFBUVs.z, fullViewportFBUVs.w );
// ...and transform to UV coordinates:
Vector2D texRes1 = fbSize / 4;
Vector2D texRes2 = fbSize;
Vector2D uvStart1 = ( texelStart1 + Vector2D(0.5,0.5) ) / texRes1;
Vector2D uvStart2 = ( texelStart2 + Vector2D(0.5,0.5) ) / texRes2;
Vector2D dUV1 = ( texelEnd1 - texelStart1 ) / texRes1;
Vector2D dUV2 = ( texelEnd2 - texelStart2 ) / texRes2;
// We scale about the rect's top-left pixel centre (not the origin) in UV-space:
// uv' = ((uv - uvStart1)*uvScale + uvStart1) + uvOffset
// = uvScale*uv + uvOffset + uvStart1*(1 - uvScale)
Vector2D uvOffset = uvStart2 - uvStart1;
Vector2D uvScale = dUV2 / dUV1;
uvOffset = uvOffset + uvStart1*(Vector2D(1,1) - uvScale);
s_vBloomUVTransform[0] = uvOffset.x;
s_vBloomUVTransform[1] = uvOffset.y;
s_vBloomUVTransform[2] = uvScale.x;
s_vBloomUVTransform[3] = uvScale.y;
}
void CEnginePostMaterialProxy::SetupEnginePostMaterial( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, const Vector2D & destTexSize,
bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength, float flBloomAmount )
{
s_PostBloomEnable = bPerformBloom ? 1 : 0;
s_PostBloomAmount = flBloomAmount;
SetupEnginePostMaterialAA( bPerformSoftwareAA, flAAStrength );
SetupEnginePostMaterialTextureTransform( fullViewportBloomUVs, fullViewportFBUVs, destTexSize );
}
EXPOSE_MATERIAL_PROXY( CEnginePostMaterialProxy, engine_post );
static void DrawBloomDebugBoxes( IMatRenderContext *pRenderContext, int nX, int nY, int nWidth, int nHeight )
{
// draw inset rects which should have a centered bloom
pRenderContext->PushRenderTargetAndViewport();
pRenderContext->SetRenderTarget(NULL);
// full screen clear
pRenderContext->Viewport( nX, nY, nWidth, nHeight );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
// inset for screensafe
int inset = 64;
int size = 32;
// centerish, translating
static int wx = 0;
wx = ( wx + 1 ) & 63;
pRenderContext->Viewport( nWidth / 2 + nX + wx, nY + nHeight / 2, size, size );
pRenderContext->ClearColor3ub( 255, 255, 255 );
pRenderContext->ClearBuffers( true, true );
// upper left
pRenderContext->Viewport( nX + inset, nY + inset, size, size );
pRenderContext->ClearBuffers( true, true );
// upper right
pRenderContext->Viewport( nX + nWidth - inset - size, nY + inset, size, size );
pRenderContext->ClearBuffers( true, true );
// lower right
pRenderContext->Viewport( nX + nWidth - inset - size, nY + nHeight - inset - size, size, size );
pRenderContext->ClearBuffers( true, true );
// lower left
pRenderContext->Viewport( nX + inset, nX + nHeight - inset - size, size, size );
pRenderContext->ClearBuffers( true, true );
// restore
pRenderContext->PopRenderTargetAndViewport();
}
static float GetBloomAmount( void )
{
HDRType_t hdrType = g_pMaterialSystemHardwareConfig->GetHDRType();
bool bBloomEnabled = (mat_hdr_level.GetInt() >= 1);
if ( !engine->MapHasHDRLighting() )
bBloomEnabled = false;
if ( mat_force_bloom.GetInt() )
bBloomEnabled = true;
if ( mat_disable_bloom.GetInt() )
bBloomEnabled = false;
if ( building_cubemaps.GetBool() )
bBloomEnabled = false;
if ( mat_fullbright.GetInt() == 1 )
{
bBloomEnabled = false;
}
float flBloomAmount=0.0;
if (bBloomEnabled)
{
static float currentBloomAmount = 1.0f;
float rate = mat_bloomamount_rate.GetFloat();
// Use the appropriate bloom scale settings. Mapmakers's overrides the convar settings.
currentBloomAmount = GetCurrentBloomScale() * rate + ( 1.0f - rate ) * currentBloomAmount;
flBloomAmount = currentBloomAmount;
if (IsX360())
{
//we want to scale the bloom effect down because the effect textures are lower reolution on the 360.
//target match 1280x1024
if ( (g_pMaterialSystem->GetCurrentConfigForVideoCard().m_VideoMode.m_Height == 720) )
{
flBloomAmount *= (720.0f/1024.0f);
}
else //640x480
{
flBloomAmount *= (480.0f/1024.0f);
}
}
}
if ( hdrType == HDR_TYPE_NONE )
{
flBloomAmount *= mat_non_hdr_bloom_scalefactor.GetFloat();
}
flBloomAmount *= mat_bloom_scalefactor_scalar.GetFloat();
return flBloomAmount;
}
static bool s_bScreenEffectTextureIsUpdated = false;
// WARNING: This function sets rendertarget and viewport. Save and restore is left to the caller.
static void DownsampleFBQuarterSize( IMatRenderContext *pRenderContext, int nSrcWidth, int nSrcHeight, ITexture* pDest,
bool bFloatHDR = false )
{
Assert( pRenderContext );
Assert( pDest );
IMaterial *downsample_mat = materials->FindMaterial( bFloatHDR ? "dev/downsample" : "dev/downsample_non_hdr", TEXTURE_GROUP_OTHER, true );
// *Everything* in here relies on the small RTs being exactly 1/4 the full FB res
Assert( pDest->GetActualWidth() == nSrcWidth / 4 );
Assert( pDest->GetActualHeight() == nSrcHeight / 4 );
/*
bool bFound;
IMaterialVar *pbloomexpvar = downsample_mat->FindVar( "$bloomexp", &bFound, false );
if ( bFound )
{
pbloomexpvar->SetFloatValue( g_flBloomExponent );
}
IMaterialVar *pbloomsaturationvar = downsample_mat->FindVar( "$bloomsaturation", &bFound, false );
if ( bFound )
{
pbloomsaturationvar->SetFloatValue( g_flBloomSaturation );
}
*/
// downsample fb to rt0
SetRenderTargetAndViewPort( pDest );
// note the -2's below. Thats because we are downsampling on each axis and the shader
// accesses pixels on both sides of the source coord
pRenderContext->DrawScreenSpaceRectangle( downsample_mat, 0, 0, nSrcWidth/4, nSrcHeight/4,
0, 0, nSrcWidth-2, nSrcHeight-2,
nSrcWidth, nSrcHeight );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( pDest, 0, NULL, NULL );
}
}
static void Generate8BitBloomTexture( IMatRenderContext *pRenderContext,
int x, int y, int w, int h, bool bExtractBloomRange, bool bClearRGB = true )
{
pRenderContext->PushRenderTargetAndViewport();
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
int nSrcWidth = pSrc->GetActualWidth();
int nSrcHeight = pSrc->GetActualHeight(); //,nViewportHeight;
IMaterial *xblur_mat = materials->FindMaterial( "dev/blurfilterx_nohdr", TEXTURE_GROUP_OTHER, true );
IMaterial *yblur_mat = NULL;
if ( bClearRGB )
{
yblur_mat = materials->FindMaterial( "dev/blurfiltery_nohdr_clear", TEXTURE_GROUP_OTHER, true );
}
else
{
yblur_mat = materials->FindMaterial( "dev/blurfiltery_nohdr", TEXTURE_GROUP_OTHER, true );
}
ITexture *dest_rt0 = materials->FindTexture( "_rt_SmallFB0", TEXTURE_GROUP_RENDER_TARGET );
ITexture *dest_rt1 = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET );
// *Everything* in here relies on the small RTs being exactly 1/4 the full FB res
Assert( dest_rt0->GetActualWidth() == pSrc->GetActualWidth() / 4 );
Assert( dest_rt0->GetActualHeight() == pSrc->GetActualHeight() / 4 );
Assert( dest_rt1->GetActualWidth() == pSrc->GetActualWidth() / 4 );
Assert( dest_rt1->GetActualHeight() == pSrc->GetActualHeight() / 4 );
// downsample fb to rt0
if ( bExtractBloomRange )
{
DownsampleFBQuarterSize( pRenderContext, nSrcWidth, nSrcHeight, dest_rt0 );
}
else
{
// just downsample, don't apply bloom extraction math
DownsampleFBQuarterSize( pRenderContext, nSrcWidth, nSrcHeight, dest_rt0, true );
}
// guassian blur x rt0 to rt1
SetRenderTargetAndViewPort( dest_rt1 );
pRenderContext->DrawScreenSpaceRectangle( xblur_mat, 0, 0, nSrcWidth/4, nSrcHeight/4,
0, 0, nSrcWidth/4-1, nSrcHeight/4-1,
nSrcWidth/4, nSrcHeight/4 );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( dest_rt1, 0, NULL, NULL );
}
// GR - gaussian blur y rt1 to rt0
SetRenderTargetAndViewPort( dest_rt0 );
IMaterialVar *pBloomAmountVar = yblur_mat->FindVar( "$bloomamount", NULL );
pBloomAmountVar->SetFloatValue( 1.0f ); // the bloom amount is now applied in engine_post or bloomadd materials
pRenderContext->DrawScreenSpaceRectangle( yblur_mat, 0, 0, nSrcWidth / 4, nSrcHeight / 4,
0, 0, nSrcWidth / 4 - 1, nSrcHeight / 4 - 1,
nSrcWidth / 4, nSrcHeight / 4 );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( dest_rt0, 0, NULL, NULL );
}
pRenderContext->PopRenderTargetAndViewport();
}
static void DoTonemapping( IMatRenderContext *pRenderContext, int nX, int nY, int nWidth, int nHeight, float flAutoExposureMin, float flAutoExposureMax )
{
// Skip if HDR disabled
if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE )
return;
// Update HDR histogram
if ( mat_dynamic_tonemapping.GetInt() )
{
if ( s_bScreenEffectTextureIsUpdated == false )
{
// FIXME: nX/nY/nWidth/nHeight are used here, but the equivalent parameters are ignored in Generate8BitBloomTexture
UpdateScreenEffectTexture( 0, nX, nY, nWidth, nHeight, false );
s_bScreenEffectTextureIsUpdated = true;
}
GetCurrentTonemappingSystem()->IssueAndReceiveBucketQueries();
float flTargetScalar = GetCurrentTonemappingSystem()->ComputeTargetTonemapScalar();
float flTargetScalarClamped = MAX( flAutoExposureMin, MIN( flAutoExposureMax, flTargetScalar ) );
flTargetScalarClamped = MAX( 0.001f, flTargetScalarClamped ); // Don't let this go to 0!
GetCurrentTonemappingSystem()->SetTonemapScale( pRenderContext, flTargetScalarClamped, flAutoExposureMin, flAutoExposureMax );
if ( mat_show_histogram.GetInt() )
{
bool bDrawTextThisFrame = true;
if ( IsX360() )
{
static float s_flLastTimeUpdate = 0.0f;
if ( int( gpGlobals->curtime ) - int( s_flLastTimeUpdate ) >= 2 )
{
s_flLastTimeUpdate = gpGlobals->curtime;
bDrawTextThisFrame = true;
}
else
{
bDrawTextThisFrame = false;
}
}
if ( bDrawTextThisFrame == true )
{
if ( mat_tonemap_algorithm.GetInt() == 0 )
{
engine->Con_NPrintf( 25 + ( nY / 10 ), "(Original algorithm) Target Scalar = %4.2f Min/Max( %4.2f, %4.2f ) Current Scalar: %4.2f",
flTargetScalar, flAutoExposureMin, flAutoExposureMax, GetCurrentTonemappingSystem()->GetCurrentTonemappingScale() );
}
else
{
if ( IsX360() )
{
engine->Con_NPrintf( 25 + ( nY / 10 ), "[mat_show_histogram] Target Scalar = %4.2f Min/Max( %4.2f, %4.2f ) Final Scalar: %4.2f\n",
GetCurrentTonemappingSystem()->ComputeTargetTonemapScalar( true ), flAutoExposureMin, flAutoExposureMax, GetCurrentTonemappingSystem()->GetCurrentTonemappingScale() );
}
else
{
engine->Con_NPrintf( 25 + ( nY / 10 ), "%.2f%% of pixels above %d%% target @ %4.2f%% Target Scalar = %4.2f Min/Max( %4.2f, %4.2f ) Final Scalar: %4.2f",
mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetInt(),
( GetCurrentTonemappingSystem()->FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetFloat() ) * 100.0f ),
GetCurrentTonemappingSystem()->ComputeTargetTonemapScalar( true ), flAutoExposureMin, flAutoExposureMax, GetCurrentTonemappingSystem()->GetCurrentTonemappingScale() );
}
}
}
}
}
}
static void CenterScaleQuadUVs( Vector4D & quadUVs, const Vector2D & uvScale )
{
Vector2D uvMid = 0.5f*Vector2D( ( quadUVs.z + quadUVs.x ), ( quadUVs.w + quadUVs.y ) );
Vector2D uvRange= 0.5f*Vector2D( ( quadUVs.z - quadUVs.x ), ( quadUVs.w - quadUVs.y ) );
quadUVs.x = uvMid.x - uvScale.x*uvRange.x;
quadUVs.y = uvMid.y - uvScale.y*uvRange.y;
quadUVs.z = uvMid.x + uvScale.x*uvRange.x;
quadUVs.w = uvMid.y + uvScale.y*uvRange.y;
}
static ConVar r_queued_post_processing( "r_queued_post_processing", "0" );
// How much to dice up the screen during post-processing on 360
// This has really marginal effects, but 4x1 does seem vaguely better for post-processing
static ConVar mat_postprocess_x( "mat_postprocess_x", "4" );
static ConVar mat_postprocess_y( "mat_postprocess_y", "1" );
static ConVar mat_postprocess_enable( "mat_postprocess_enable", "1", FCVAR_CHEAT );
void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, bool bPostVGui )
{
// don't do this if disabled or in alt-tab
if ( s_bOverridePostProcessingDisable || w <=0 || h <= 0 )
{
return;
}
CMatRenderContextPtr pRenderContext( materials );
if ( r_queued_post_processing.GetInt() )
{
ICallQueue *pCallQueue = pRenderContext->GetCallQueue();
if ( pCallQueue )
{
pCallQueue->QueueCall( DoEnginePostProcessing, x, y, w, h, bFlashlightIsOn, bPostVGui );
return;
}
}
#if defined( _X360 )
pRenderContext->PushVertexShaderGPRAllocation( 16 ); //max out pixel shader threads
#endif
GetTonemapSettingsFromEnvTonemapController();
g_bFlashlightIsOn = bFlashlightIsOn;
// Use the appropriate autoexposure min / max settings.
// Mapmaker's overrides the convar settings.
float flAutoExposureMin;
float flAutoExposureMax;
GetExposureRange( &flAutoExposureMin, &flAutoExposureMax );
if ( mat_debug_bloom.GetInt() == 1 )
{
DrawBloomDebugBoxes( pRenderContext, x, y, w, h );
}
s_bScreenEffectTextureIsUpdated = false; // Force an update in tone mapping code
DoTonemapping( pRenderContext, x, y, w, h, flAutoExposureMin, flAutoExposureMax );
if ( mat_postprocess_enable.GetInt() == 0 )
{
GetCurrentTonemappingSystem()->DisplayHistogram();
#if defined( _X360 )
pRenderContext->PopVertexShaderGPRAllocation();
#endif
return;
}
// Set software-AA on by default for 360
if ( mat_software_aa_strength.GetFloat() == -1.0f )
{
if ( IsX360() )
{
mat_software_aa_strength.SetValue( 1.0f );
if ( g_pMaterialSystem->GetCurrentConfigForVideoCard().m_VideoMode.m_Height > 480 )
{
mat_software_aa_quality.SetValue( 0 );
}
else
{
// For standard-def, we have fewer pixels so we can afford 'high quality' mode (5->9 taps/pixel)
mat_software_aa_quality.SetValue( 1 );
// Disable in 480p for now
mat_software_aa_strength.SetValue( 0.0f );
}
}
else
{
mat_software_aa_strength.SetValue( 0.0f );
}
}
// Same trick for setting up the vgui aa strength
if ( mat_software_aa_strength_vgui.GetFloat() == -1.0f )
{
if ( IsX360() && (g_pMaterialSystem->GetCurrentConfigForVideoCard().m_VideoMode.m_Height == 720) )
{
mat_software_aa_strength_vgui.SetValue( 2.0f );
}
else
{
mat_software_aa_strength_vgui.SetValue( 1.0f );
}
}
float flAAStrength;
// We do a second AA blur pass over the TF intro menus. use mat_software_aa_strength_vgui there instead
if ( IsX360() && bPostVGui )
{
flAAStrength = mat_software_aa_strength_vgui.GetFloat();
}
else
{
flAAStrength = mat_software_aa_strength.GetFloat();
}
// Bloom, software-AA and color-correction (applied in 1 pass, after generation of the bloom texture)
float flBloomScale = GetBloomAmount();
bool bPerformSoftwareAA = ( flAAStrength != 0.0f );
bool bPerformBloom = !bPostVGui && ( flBloomScale > 0.0f );
bool bPerformColCorrect = !bPostVGui &&
g_pColorCorrectionMgr->HasNonZeroColorCorrectionWeights() &&
mat_colorcorrection.GetInt();
pRenderContext->EnableColorCorrection( bPerformColCorrect );
bool bPerformLocalContrastEnhancement = false;
IMaterial* pPostMat;
if ( engine->IsSplitScreenActive() )
pPostMat = materials->FindMaterial( "dev/engine_post_splitscreen", TEXTURE_GROUP_OTHER, true );
else
pPostMat = materials->FindMaterial( "dev/engine_post", TEXTURE_GROUP_OTHER, true );
if ( pPostMat )
{
IMaterialVar* pMatVar = pPostMat->FindVar( "$localcontrastenable", NULL, false );
if ( pMatVar )
{
bPerformLocalContrastEnhancement = pMatVar->GetIntValue() && mat_local_contrast_enable.GetBool();
}
}
if ( true )
{
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
int nSrcWidth = pSrc->GetActualWidth();
int nSrcHeight = pSrc->GetActualHeight();
ITexture *dest_rt1 = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET );
if ( !s_bScreenEffectTextureIsUpdated )
{
UpdateScreenEffectTexture( 0, x, y, w, h, false );
s_bScreenEffectTextureIsUpdated = true;
}
if ( bPerformBloom || bPerformLocalContrastEnhancement )
{
Generate8BitBloomTexture( pRenderContext, x, y, w, h, true, false );
}
// Now add bloom (dest_rt0) to the framebuffer and perform software anti-aliasing and
// colour correction, all in one pass (improves performance, reduces quantization errors)
//
// First, set up texel coords (in the bloom and fb textures) at the centres of the outer pixel of the viewport:
float flFbWidth = ( float )pSrc->GetActualWidth();
float flFbHeight = ( float )pSrc->GetActualHeight();
Vector4D fullViewportPostSrcCorners( 0.0f, -0.5f, nSrcWidth/4-1, nSrcHeight/4-1 );
Vector4D fullViewportPostSrcRect( nSrcWidth * ( ( x + 0 ) / flFbWidth ) / 4.0f + 0.0f, nSrcHeight * ( ( y + 0 ) / flFbHeight ) / 4.0f - 0.5f,
nSrcWidth * ( ( x + w ) / flFbWidth ) / 4.0f - 1.0f, nSrcHeight * ( ( y + h ) / flFbHeight ) / 4.0f - 1.0f );
Vector4D fullViewportPostDestCorners( 0.0f, 0.0f, nSrcWidth - 1, nSrcHeight - 1 );
Rect_t fullViewportPostDestRect = { x, y, w, h };
Vector2D destTexSize( nSrcWidth, nSrcHeight );
// When the viewport is not fullscreen, the UV-space size of a pixel changes
// (due to a stretchrect blit being used in UpdateScreenEffectTexture()), so
// we need to adjust the corner-pixel UVs sent to our drawrect call:
Vector2D uvScale( ( nSrcWidth - ( nSrcWidth / (float)w ) ) / ( nSrcWidth - 1 ),
( nSrcHeight - ( nSrcHeight / (float)h ) ) / ( nSrcHeight - 1 ) );
CenterScaleQuadUVs( fullViewportPostSrcCorners, uvScale );
CenterScaleQuadUVs( fullViewportPostDestCorners, uvScale );
Rect_t partialViewportPostDestRect = fullViewportPostDestRect;
Vector4D partialViewportPostSrcCorners = fullViewportPostSrcCorners;
if ( debug_postproc.GetInt() == 2 )
{
// Restrict the post effects to the centre quarter of the screen
// (we only use a portion of the bloom texture, so this *does* affect bloom texture UVs)
partialViewportPostDestRect.x += 0.25f*fullViewportPostDestRect.width;
partialViewportPostDestRect.y += 0.25f*fullViewportPostDestRect.height;
partialViewportPostDestRect.width -= 0.50f*fullViewportPostDestRect.width;
partialViewportPostDestRect.height -= 0.50f*fullViewportPostDestRect.height;
// This math interprets texel coords as being at corner pixel centers (*not* at corner vertices):
Vector2D uvScale( 1.0f - ( (w / 2) / (float)(w - 1) ),
1.0f - ( (h / 2) / (float)(h - 1) ) );
CenterScaleQuadUVs( partialViewportPostSrcCorners, uvScale );
}
// Temporary hack... Color correction was crashing on the first frame
// when run outside the debugger for some mods (DoD). This forces it to skip
// a frame, ensuring we don't get the weird texture crash we otherwise would.
// FIXME: This will be removed when the true cause is found [added: Main CL 144694]
static bool bFirstFrame = !IsX360();
if ( !bFirstFrame || !bPerformColCorrect )
{
HDRType_t hdrType = g_pMaterialSystemHardwareConfig->GetHDRType();
if ( hdrType == HDR_TYPE_FLOAT )
{
// reset to render the final combine passes to the "real" display backbuffer
pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_BACK_BUFFER_INDEX, BACK_BUFFER_INDEX_DEFAULT );
pRenderContext->SetRenderTarget( NULL );
}
Vector4D v4dFullViewportPostDestRect( fullViewportPostDestRect.x, fullViewportPostDestRect.y,
fullViewportPostDestRect.x + fullViewportPostDestRect.width - 1,
fullViewportPostDestRect.y + fullViewportPostDestRect.height - 1 );
CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcRect, v4dFullViewportPostDestRect, destTexSize, bPerformSoftwareAA, bPerformBloom, bPerformColCorrect, flAAStrength, flBloomScale );
pRenderContext->DrawScreenSpaceRectangle( pPostMat,
0, 0,
partialViewportPostDestRect.width, partialViewportPostDestRect.height,
fullViewportPostSrcRect.x, fullViewportPostSrcRect.y,
fullViewportPostSrcRect.z, fullViewportPostSrcRect.w,
dest_rt1->GetActualWidth(), dest_rt1->GetActualHeight(),
GetClientWorldEntity()->GetClientRenderable(),
mat_postprocess_x.GetInt(), mat_postprocess_y.GetInt() );
}
bFirstFrame = false;
}
GetCurrentTonemappingSystem()->DisplayHistogram();
#if defined( _X360 )
pRenderContext->PopVertexShaderGPRAllocation();
#endif
}
void DoBlurFade( float flStrength, float flDesaturate, int x, int y, int w, int h )
{
if ( flStrength < 0.0001f )
{
return;
}
UpdateScreenEffectTexture();
CMatRenderContextPtr pRenderContext( materials );
Generate8BitBloomTexture( pRenderContext, x, y, w, h, false, false );
int nViewportX, nViewportY, nViewportWidth, nViewportHeight;
pRenderContext->GetViewport( nViewportX, nViewportY, nViewportWidth, nViewportHeight );
int nRtWidth, nRtHeight;
pRenderContext->GetRenderTargetDimensions( nRtWidth, nRtHeight );
IMaterial* pMat = materials->FindMaterial( "dev/fade_blur", TEXTURE_GROUP_OTHER, true );
bool bFound = false;
IMaterialVar* pVar = pMat->FindVar( "$c0_x", &bFound );
if ( pVar )
{
pVar->SetFloatValue( flStrength );
}
// Desaturate strength
pVar = pMat->FindVar( "$c1_x", &bFound );
if ( pVar )
{
pVar->SetFloatValue( flDesaturate );
}
pRenderContext->DrawScreenSpaceRectangle( pMat, 0, 0, nViewportWidth, nViewportHeight,
nViewportX, nViewportY,
nViewportX + nViewportWidth - 1, nViewportY + nViewportHeight - 1,
nRtWidth, nRtHeight );
}
// Motion Blur Material Proxy =========================================================================================
static float g_vMotionBlurValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
static float g_vMotionBlurViewportValues[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
class CMotionBlurMaterialProxy : public CEntityMaterialProxy
{
public:
CMotionBlurMaterialProxy();
virtual ~CMotionBlurMaterialProxy();
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( C_BaseEntity *pEntity );
virtual IMaterial *GetMaterial();
private:
IMaterialVar *m_pMaterialParam;
IMaterialVar *m_pMaterialParamViewport;
};
CMotionBlurMaterialProxy::CMotionBlurMaterialProxy()
{
m_pMaterialParam = NULL;
}
CMotionBlurMaterialProxy::~CMotionBlurMaterialProxy()
{
// Do nothing
}
bool CMotionBlurMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool bFoundVar = false;
m_pMaterialParam = pMaterial->FindVar( "$MotionBlurInternal", &bFoundVar, false );
if ( bFoundVar == false)
return false;
m_pMaterialParamViewport = pMaterial->FindVar( "$MotionBlurViewportInternal", &bFoundVar, false );
if ( bFoundVar == false)
return false;
return true;
}
void CMotionBlurMaterialProxy::OnBind( C_BaseEntity *pEnt )
{
if ( m_pMaterialParam != NULL )
{
m_pMaterialParam->SetVecValue( g_vMotionBlurValues, 4 );
}
if ( m_pMaterialParamViewport != NULL )
{
m_pMaterialParamViewport->SetVecValue( g_vMotionBlurViewportValues, 4 );
}
}
IMaterial *CMotionBlurMaterialProxy::GetMaterial()
{
if ( m_pMaterialParam == NULL)
return NULL;
return m_pMaterialParam->GetOwningMaterial();
}
EXPOSE_MATERIAL_PROXY( CMotionBlurMaterialProxy, MotionBlur );
//=====================================================================================================================
// Image-space Motion Blur ============================================================================================
//=====================================================================================================================
ConVar mat_motion_blur_enabled( "mat_motion_blur_enabled", "1" );
ConVar mat_motion_blur_forward_enabled( "mat_motion_blur_forward_enabled", "0" );
ConVar mat_motion_blur_falling_min( "mat_motion_blur_falling_min", "10.0" );
ConVar mat_motion_blur_falling_max( "mat_motion_blur_falling_max", "20.0" );
ConVar mat_motion_blur_falling_intensity( "mat_motion_blur_falling_intensity", "1.0" );
//ConVar mat_motion_blur_roll_intensity( "mat_motion_blur_roll_intensity", "1.0" );
ConVar mat_motion_blur_rotation_intensity( "mat_motion_blur_rotation_intensity", "1.0" );
ConVar mat_motion_blur_strength( "mat_motion_blur_strength", "1.0" );
struct MotionBlurHistory_t
{
MotionBlurHistory_t()
{
m_flLastTimeUpdate = 0.0f;
m_flPreviousPitch = 0.0f;
m_flPreviousYaw = 0.0f;
m_vPreviousPositon.Init( 0.0f, 0.0f, 0.0f );
m_mPreviousFrameBasisVectors;
m_flNoRotationalMotionBlurUntil = 0.0f;
SetIdentityMatrix( m_mPreviousFrameBasisVectors );
}
float m_flLastTimeUpdate;
float m_flPreviousPitch;
float m_flPreviousYaw;
Vector m_vPreviousPositon;
matrix3x4_t m_mPreviousFrameBasisVectors;
float m_flNoRotationalMotionBlurUntil;
};
void DoImageSpaceMotionBlur( const CViewSetup &view )
{
if ( ( !mat_motion_blur_enabled.GetInt() ) || ( view.m_nMotionBlurMode == MOTION_BLUR_DISABLE ) )
{
return;
}
int x = view.x;
int y = view.y;
int w = view.width;
int h = view.height;
bool bSFMBlur = ( view.m_nMotionBlurMode == MOTION_BLUR_SFM );
//======================================================================================================//
// Get these convars here to make it easier to remove them later and to default each client differently //
//======================================================================================================//
float flMotionBlurRotationIntensity = mat_motion_blur_rotation_intensity.GetFloat() * 0.15f; // The default is to not blur past 15% of the range
float flMotionBlurRollIntensity = 0.3f; // * mat_motion_blur_roll_intensity.GetFloat(); // The default is to not blur past 30% of the range
float flMotionBlurFallingIntensity = mat_motion_blur_falling_intensity.GetFloat();
float flMotionBlurFallingMin = mat_motion_blur_falling_min.GetFloat();
float flMotionBlurFallingMax = mat_motion_blur_falling_max.GetFloat();
float flMotionBlurGlobalStrength = mat_motion_blur_strength.GetFloat();
//===============================================================================//
// Set global g_vMotionBlurValues[4] values so material proxy can get the values //
//===============================================================================//
if ( true )
{
//=====================//
// Previous frame data //
//=====================//
static MotionBlurHistory_t s_History[ MAX_SPLITSCREEN_PLAYERS ];
ASSERT_LOCAL_PLAYER_RESOLVABLE();
MotionBlurHistory_t &history = s_History[ GET_ACTIVE_SPLITSCREEN_SLOT() ];
//float vPreviousSideVec[3] = { s_mPreviousFrameBasisVectors[0][1], s_mPreviousFrameBasisVectors[1][1], s_mPreviousFrameBasisVectors[2][1] };
//float vPreviousForwardVec[3] = { s_mPreviousFrameBasisVectors[0][0], s_mPreviousFrameBasisVectors[1][0], s_mPreviousFrameBasisVectors[2][0] };
//float vPreviousUpVec[3] = { s_mPreviousFrameBasisVectors[0][2], s_mPreviousFrameBasisVectors[1][2], s_mPreviousFrameBasisVectors[2][2] };
float flTimeElapsed;
// Motion blur driven by CViewSetup, not engine time (currently only driven by SFM)
if ( bSFMBlur )
{
history.m_flLastTimeUpdate = 0.0f; // Don't care about these, but zero them out
history.m_flNoRotationalMotionBlurUntil = 0.0f; //
flTimeElapsed = view.m_flShutterTime;
history.m_vPreviousPositon[0] = view.m_vShutterOpenPosition.x; //
history.m_vPreviousPositon[1] = view.m_vShutterOpenPosition.y; // Slam "previous" values to shutter open values
history.m_vPreviousPositon[2] = view.m_vShutterOpenPosition.z; //
AngleMatrix( view.m_shutterOpenAngles, history.m_mPreviousFrameBasisVectors );//
history.m_flPreviousPitch = view.m_shutterOpenAngles[PITCH]; // Get "previous" pitch & wrap to +-180
while ( history.m_flPreviousPitch > 180.0f )
history.m_flPreviousPitch -= 360.0f;
while ( history.m_flPreviousPitch < -180.0f )
history.m_flPreviousPitch += 360.0f;
history.m_flPreviousYaw = view.m_shutterOpenAngles[YAW]; // Get "previous" yaw & wrap to +-180
while ( history.m_flPreviousYaw > 180.0f )
history.m_flPreviousYaw -= 360.0f;
while ( history.m_flPreviousYaw < -180.0f )
history.m_flPreviousYaw += 360.0f;
}
else // view.m_nDoMotionBlurMode == MOTION_BLUR_GAME
{
flTimeElapsed = gpGlobals->realtime - history.m_flLastTimeUpdate;
}
//===================================//
// Get current pitch & wrap to +-180 //
//===================================//
float flCurrentPitch = view.angles[PITCH];
if ( bSFMBlur )
flCurrentPitch = view.m_shutterCloseAngles[PITCH];
while ( flCurrentPitch > 180.0f )
flCurrentPitch -= 360.0f;
while ( flCurrentPitch < -180.0f )
flCurrentPitch += 360.0f;
//=================================//
// Get current yaw & wrap to +-180 //
//=================================//
float flCurrentYaw = view.angles[YAW];
if ( bSFMBlur )
flCurrentYaw = view.m_shutterCloseAngles[YAW];
while ( flCurrentYaw > 180.0f )
flCurrentYaw -= 360.0f;
while ( flCurrentYaw < -180.0f )
flCurrentYaw += 360.0f;
/*engine->Con_NPrintf( 0, "Blur Pitch: %6.2f Yaw: %6.2f", flCurrentPitch, flCurrentYaw );
engine->Con_NPrintf( 1, "Blur FOV: %6.2f Aspect: %6.2f Ortho: %s", view.fov, view.m_flAspectRatio, view.m_bOrtho ? "Yes" : "No" );
engine->Con_NPrintf( 2, "View Angles: %6.2f %6.2f %6.2f", XYZ(view.angles) );*/
//===========================//
// Get current basis vectors //
//===========================//
matrix3x4_t mCurrentBasisVectors;
if ( bSFMBlur )
{
AngleMatrix( view.m_shutterCloseAngles, mCurrentBasisVectors );
}
else
{
AngleMatrix( view.angles, mCurrentBasisVectors );
}
Vector vCurrentSideVec( mCurrentBasisVectors[0][1], mCurrentBasisVectors[1][1], mCurrentBasisVectors[2][1] );
Vector vCurrentForwardVec( mCurrentBasisVectors[0][0], mCurrentBasisVectors[1][0], mCurrentBasisVectors[2][0] );
//Vector vCurrentUpVec( mCurrentBasisVectors[0][2], mCurrentBasisVectors[1][2], mCurrentBasisVectors[2][2] );
//===========================================================================//
// Get current position (shutter close time when SFM is driving motion blur) //
//===========================================================================//
Vector vCurrentPosition = view.origin;
if ( bSFMBlur )
{
vCurrentPosition[0] = view.m_vShutterClosePosition.x;
vCurrentPosition[1] = view.m_vShutterClosePosition.y;
vCurrentPosition[2] = view.m_vShutterClosePosition.z;
}
//===============================================================//
// Evaluate change in position to determine if we need to update //
//===============================================================//
Vector vPositionChange( 0.0f, 0.0f, 0.0f );
VectorSubtract( history.m_vPreviousPositon, vCurrentPosition, vPositionChange );
if ( ( VectorLength( vPositionChange ) > 30.0f ) && ( flTimeElapsed >= 0.5f ) && !bSFMBlur )
{
//=======================================================//
// If we moved a far distance in one frame and more than //
// half a second elapsed, disable motion blur this frame //
//=======================================================//
//engine->Con_NPrintf( 8, " Pos change && time > 0.5 seconds %f ", gpGlobals->realtime );
g_vMotionBlurValues[0] = 0.0f;
g_vMotionBlurValues[1] = 0.0f;
g_vMotionBlurValues[2] = 0.0f;
g_vMotionBlurValues[3] = 0.0f;
}
else if ( ( flTimeElapsed > ( 1.0f / 15.0f ) ) && !bSFMBlur )
{
//==========================================//
// If slower than 15 fps, don't motion blur //
//==========================================//
g_vMotionBlurValues[0] = 0.0f;
g_vMotionBlurValues[1] = 0.0f;
g_vMotionBlurValues[2] = 0.0f;
g_vMotionBlurValues[3] = 0.0f;
}
else if ( ( VectorLength( vPositionChange ) > 50.0f ) && !bSFMBlur )
{
//================================================================================//
// We moved a far distance in a frame, use the same motion blur as last frame //
// because I think we just went through a portal (should we ifdef this behavior?) //
//================================================================================//
//engine->Con_NPrintf( 8, " Position changed %f units @ %.2f time ", VectorLength( vPositionChange ), gpGlobals->realtime );
history.m_flNoRotationalMotionBlurUntil = gpGlobals->realtime + 1.0f; // Wait a second until the portal craziness calms down
}
else
{
//====================//
// Normal update path //
//====================//
// Compute horizontal and vertical fov
float flHorizontalFov = view.fov;
float flVerticalFov = ( view.m_flAspectRatio <= 0.0f ) ? ( view.fov ) : ( view.fov / view.m_flAspectRatio );
//engine->Con_NPrintf( 2, "Horizontal Fov: %6.2f Vertical Fov: %6.2f", flHorizontalFov, flVerticalFov );
//=====================//
// Forward motion blur //
//=====================//
float flViewDotMotion = DotProduct( vCurrentForwardVec, vPositionChange );
if ( mat_motion_blur_forward_enabled.GetBool() ) // Want forward and falling
g_vMotionBlurValues[2] = flViewDotMotion;
else // Falling only
g_vMotionBlurValues[2] = flViewDotMotion * fabs( vCurrentForwardVec[2] ); // Only want this if we're looking up or down;
//====================================//
// Yaw (Compensate for circle strafe) //
//====================================//
float flSideDotMotion = DotProduct( vCurrentSideVec, vPositionChange );
float flYawDiffOriginal = history.m_flPreviousYaw - flCurrentYaw;
if ( ( ( history.m_flPreviousYaw - flCurrentYaw > 180.0f ) || ( history.m_flPreviousYaw - flCurrentYaw < -180.0f ) ) &&
( ( history.m_flPreviousYaw + flCurrentYaw > -180.0f ) && ( history.m_flPreviousYaw + flCurrentYaw < 180.0f ) ) )
flYawDiffOriginal = history.m_flPreviousYaw + flCurrentYaw;
float flYawDiffAdjusted = flYawDiffOriginal + ( flSideDotMotion / 3.0f ); // Yes, 3.0 is a magic number, sue me
// Make sure the adjustment only lessens the effect, not magnify it or reverse it
if ( flYawDiffOriginal < 0.0f )
flYawDiffAdjusted = clamp ( flYawDiffAdjusted, flYawDiffOriginal, 0.0f );
else
flYawDiffAdjusted = clamp ( flYawDiffAdjusted, 0.0f, flYawDiffOriginal );
// Use pitch to dampen yaw
float flUndampenedYaw = flYawDiffAdjusted / flHorizontalFov;
g_vMotionBlurValues[0] = flUndampenedYaw * ( 1.0f - ( fabs( flCurrentPitch ) / 90.0f ) ); // Dampen horizontal yaw blur based on pitch
//engine->Con_NPrintf( 4, "flSideDotMotion: %6.2f yaw diff: %6.2f ( %6.2f, %6.2f )", flSideDotMotion, ( s_flPreviousYaw - flCurrentYaw ), flYawDiffOriginal, flYawDiffAdjusted );
//=======================================//
// Pitch (Compensate for forward motion) //
//=======================================//
float flPitchCompensateMask = 1.0f - ( ( 1.0f - fabs( vCurrentForwardVec[2] ) ) * ( 1.0f - fabs( vCurrentForwardVec[2] ) ) );
float flPitchDiffOriginal = history.m_flPreviousPitch - flCurrentPitch;
float flPitchDiffAdjusted = flPitchDiffOriginal;
if ( flCurrentPitch > 0.0f )
flPitchDiffAdjusted = flPitchDiffOriginal - ( ( flViewDotMotion / 2.0f ) * flPitchCompensateMask ); // Yes, 2.0 is a magic number, sue me
else
flPitchDiffAdjusted = flPitchDiffOriginal + ( ( flViewDotMotion / 2.0f ) * flPitchCompensateMask ); // Yes, 2.0 is a magic number, sue me
// Make sure the adjustment only lessens the effect, not magnify it or reverse it
if ( flPitchDiffOriginal < 0.0f )
flPitchDiffAdjusted = clamp ( flPitchDiffAdjusted, flPitchDiffOriginal, 0.0f );
else
flPitchDiffAdjusted = clamp ( flPitchDiffAdjusted, 0.0f, flPitchDiffOriginal );
g_vMotionBlurValues[1] = flPitchDiffAdjusted / flVerticalFov;
//engine->Con_NPrintf( 5, "flViewDotMotion %6.2f, flPitchCompensateMask %6.2f, flPitchDiffOriginal %6.2f, flPitchDiffAdjusted %6.2f, g_vMotionBlurValues[1] %6.2f", flViewDotMotion, flPitchCompensateMask, flPitchDiffOriginal, flPitchDiffAdjusted, g_vMotionBlurValues[1]);
//========================================================//
// Roll (Enabled when we're looking down and yaw changes) //
//========================================================//
g_vMotionBlurValues[3] = flUndampenedYaw; // Roll starts out as undampened yaw intensity and is then scaled by pitch
g_vMotionBlurValues[3] *= ( fabs( flCurrentPitch ) / 90.0f ) * ( fabs( flCurrentPitch ) / 90.0f ) * ( fabs( flCurrentPitch ) / 90.0f ); // Dampen roll based on pitch^3
//engine->Con_NPrintf( 4, "[2] before scale and bias: %6.2f", g_vMotionBlurValues[2] );
//engine->Con_NPrintf( 5, "[3] before scale and bias: %6.2f", g_vMotionBlurValues[3] );
//==============================================================//
// Time-adjust falling effect until we can do something smarter //
//==============================================================//
if ( flTimeElapsed > 0.0f )
g_vMotionBlurValues[2] /= flTimeElapsed * 30.0f; // 1/30th of a second?
else
g_vMotionBlurValues[2] = 0.0f;
// Scale and bias values after time adjustment
g_vMotionBlurValues[2] = clamp( ( fabs( g_vMotionBlurValues[2] ) - flMotionBlurFallingMin ) / ( flMotionBlurFallingMax - flMotionBlurFallingMin ), 0.0f, 1.0f ) * ( g_vMotionBlurValues[2] >= 0.0f ? 1.0f : -1.0f );
g_vMotionBlurValues[2] /= 30.0f; // To counter-adjust for time adjustment above
//=================//
// Apply intensity //
//=================//
g_vMotionBlurValues[0] *= flMotionBlurRotationIntensity * flMotionBlurGlobalStrength;
g_vMotionBlurValues[1] *= flMotionBlurRotationIntensity * flMotionBlurGlobalStrength;
g_vMotionBlurValues[2] *= flMotionBlurFallingIntensity * flMotionBlurGlobalStrength;
g_vMotionBlurValues[3] *= flMotionBlurRollIntensity * flMotionBlurGlobalStrength;
//===============================================================//
// Dampen motion blur from 100%-0% as fps drops from 50fps-30fps //
//===============================================================//
if ( !IsX360() && !bSFMBlur ) // I'm not doing this on the 360 yet since I can't test it. SFM doesn't need it either
{
float flSlowFps = 30.0f;
float flFastFps = 50.0f;
float flCurrentFps = ( flTimeElapsed > 0.0f ) ? ( 1.0f / flTimeElapsed ) : 0.0f;
float flDampenFactor = clamp( ( ( flCurrentFps - flSlowFps ) / ( flFastFps - flSlowFps ) ), 0.0f, 1.0f );
//engine->Con_NPrintf( 4, "gpGlobals->realtime %.2f gpGlobals->curtime %.2f", gpGlobals->realtime, gpGlobals->curtime );
//engine->Con_NPrintf( 5, "flCurrentFps %.2f", flCurrentFps );
//engine->Con_NPrintf( 7, "flTimeElapsed %.2f", flTimeElapsed );
g_vMotionBlurValues[0] *= flDampenFactor;
g_vMotionBlurValues[1] *= flDampenFactor;
g_vMotionBlurValues[2] *= flDampenFactor;
g_vMotionBlurValues[3] *= flDampenFactor;
//engine->Con_NPrintf( 6, "Dampen: %.2f", flDampenFactor );
}
//engine->Con_NPrintf( 6, "Final values: { %6.2f%%, %6.2f%%, %6.2f%%, %6.2f%% }", g_vMotionBlurValues[0]*100.0f, g_vMotionBlurValues[1]*100.0f, g_vMotionBlurValues[2]*100.0f, g_vMotionBlurValues[3]*100.0f );
}
//============================================//
// Zero out blur if still in that time window //
//============================================//
if ( !bSFMBlur && ( gpGlobals->realtime < history.m_flNoRotationalMotionBlurUntil ) )
{
//engine->Con_NPrintf( 9, " No Rotation @ %f ", gpGlobals->realtime );
// Zero out rotational blur but leave forward/falling blur alone
g_vMotionBlurValues[0] = 0.0f; // X
g_vMotionBlurValues[1] = 0.0f; // Y
g_vMotionBlurValues[3] = 0.0f; // Roll
}
else
{
history.m_flNoRotationalMotionBlurUntil = 0.0f;
}
//================================================================================//
// Disable roll and forward blur if in split screen and reduce the blur intensity //
//================================================================================//
if ( engine->IsSplitScreenActive() )
{
g_vMotionBlurValues[0] *= 0.25f; // X
g_vMotionBlurValues[1] *= 0.25f; // Y
g_vMotionBlurValues[2] = 0.0f;
g_vMotionBlurValues[3] = 0.0f;
}
//====================================//
// Store current frame for next frame //
//====================================//
VectorCopy( vCurrentPosition, history.m_vPreviousPositon );
history.m_mPreviousFrameBasisVectors = mCurrentBasisVectors;
history.m_flPreviousPitch = flCurrentPitch;
history.m_flPreviousYaw = flCurrentYaw;
history.m_flLastTimeUpdate = gpGlobals->realtime;
}
//engine->Con_NPrintf( 6, "Final values: { %6.2f%%, %6.2f%%, %6.2f%%, %6.2f%% }", g_vMotionBlurValues[0]*100.0f, g_vMotionBlurValues[1]*100.0f, g_vMotionBlurValues[2]*100.0f, g_vMotionBlurValues[3]*100.0f );
//==========================================//
// Set global g_vMotionBlurViewportValues[] //
//==========================================//
if ( true )
{
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
float flSrcWidth = ( float )pSrc->GetActualWidth();
float flSrcHeight = ( float )pSrc->GetActualHeight();
// NOTE #1: float4 stored as ( minx, miny, maxy, maxx )...z&w have been swapped to save pixel shader instructions
// NOTE #2: This code should definitely work for 2 players (horizontal or vertical), or 4 players (4 corners), but
// it might have to be modified if we ever want to support other split screen configurations
int nOffset; // Offset by one pixel to land in the correct half
// Left
nOffset = ( x > 0 ) ? 1 : 0;
g_vMotionBlurViewportValues[0] = ( float )( x + nOffset ) / ( flSrcWidth - 1 );
// Right
nOffset = ( x < ( flSrcWidth - 1 ) ) ? -1 : 0;
g_vMotionBlurViewportValues[3] = ( float )( x + w + nOffset ) / ( flSrcWidth - 1 );
// Top
nOffset = ( y > 0 ) ? 1 : 0; // Offset by one pixel to land in the correct half
g_vMotionBlurViewportValues[1] = ( float )( y + nOffset ) / ( flSrcHeight - 1 );
// Bottom
nOffset = ( y < ( flSrcHeight - 1 ) ) ? -1 : 0;
g_vMotionBlurViewportValues[2] = ( float )( y + h + nOffset ) / ( flSrcHeight - 1 );
// Only allow clamping to happen in the middle of the screen, so nudge the clamp values out if they're on the border of the screen
for ( int i = 0; i < 4; i++ )
{
if ( g_vMotionBlurViewportValues[i] <= 0.0f )
g_vMotionBlurViewportValues[i] = -1.0f;
else if ( g_vMotionBlurViewportValues[i] >= 1.0f )
g_vMotionBlurViewportValues[i] = 2.0f;
}
}
//=============================================================================================//
// Render quad and let material proxy pick up the g_vMotionBlurValues[4] values just set above //
//=============================================================================================//
if ( true )
{
CMatRenderContextPtr pRenderContext( materials );
//pRenderContext->PushRenderTargetAndViewport();
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
int nSrcWidth = pSrc->GetActualWidth();
int nSrcHeight = pSrc->GetActualHeight();
int nViewportWidth, nViewportHeight, nDummy;
pRenderContext->GetViewport( nDummy, nDummy, nViewportWidth, nViewportHeight );
UpdateScreenEffectTexture( 0, x, y, w, h, false );
// Get material pointer
IMaterial *pMatMotionBlur = materials->FindMaterial( "dev/motion_blur", TEXTURE_GROUP_OTHER, true );
//SetRenderTargetAndViewPort( dest_rt0 );
//pRenderContext->PopRenderTargetAndViewport();
if ( pMatMotionBlur != NULL )
{
pRenderContext->DrawScreenSpaceRectangle(
pMatMotionBlur,
0, 0, nViewportWidth, nViewportHeight,
x, y, x + w-1, y + h-1,
nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() );
}
}
}
//=====================================================================================================================
// Depth of field =====================================================================================================
//=====================================================================================================================
ConVar mat_dof_enabled( "mat_dof_enabled", "1" );
ConVar mat_dof_override( "mat_dof_override", "0" );
ConVar mat_dof_near_blur_depth( "mat_dof_near_blur_depth", "20.0" );
ConVar mat_dof_near_focus_depth( "mat_dof_near_focus_depth", "100.0" );
ConVar mat_dof_far_focus_depth( "mat_dof_far_focus_depth", "250.0" );
ConVar mat_dof_far_blur_depth( "mat_dof_far_blur_depth", "1000.0" );
ConVar mat_dof_near_blur_radius( "mat_dof_near_blur_radius", "10.0" );
ConVar mat_dof_far_blur_radius( "mat_dof_far_blur_radius", "5.0" );
ConVar mat_dof_quality( "mat_dof_quality", "0" );
static float GetNearBlurDepth()
{
return mat_dof_override.GetBool() ? mat_dof_near_blur_depth.GetFloat() : g_flDOFNearBlurDepth;
}
static float GetNearFocusDepth()
{
return mat_dof_override.GetBool() ? mat_dof_near_focus_depth.GetFloat() : g_flDOFNearFocusDepth;
}
static float GetFarFocusDepth()
{
return mat_dof_override.GetBool() ? mat_dof_far_focus_depth.GetFloat() : g_flDOFFarFocusDepth;
}
static float GetFarBlurDepth()
{
return mat_dof_override.GetBool() ? mat_dof_far_blur_depth.GetFloat() : g_flDOFFarBlurDepth;
}
static float GetNearBlurRadius()
{
return mat_dof_override.GetBool() ? mat_dof_near_blur_radius.GetFloat() : g_flDOFNearBlurRadius;
}
static float GetFarBlurRadius()
{
return mat_dof_override.GetBool() ? mat_dof_far_blur_radius.GetFloat() : g_flDOFFarBlurRadius;
}
bool IsDepthOfFieldEnabled()
{
const CViewSetup *pViewSetup = view->GetViewSetup();
if ( !pViewSetup )
return false;
// We need high-precision depth, which we currently only get in float HDR mode
if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_FLOAT )
return false;
if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 92 )
return false;
// Only SFM sets this at the moment...it supersedes mat_dof_ convars if true
if ( pViewSetup->m_bDoDepthOfField )
return true;
if ( !mat_dof_enabled.GetBool() )
return false;
if ( mat_dof_override.GetBool() == true )
{
return mat_dof_enabled.GetBool();
}
else
{
return g_bDOFEnabled;
}
}
static inline bool SetMaterialVarFloat( IMaterial* pMat, const char* pVarName, float flValue )
{
Assert( pMat != NULL );
Assert( pVarName != NULL );
if ( pMat == NULL || pVarName == NULL )
{
return false;
}
bool bFound = false;
IMaterialVar* pVar = pMat->FindVar( pVarName, &bFound );
if ( bFound )
{
pVar->SetFloatValue( flValue );
}
return bFound;
}
static inline bool SetMaterialVarInt( IMaterial* pMat, const char* pVarName, int nValue )
{
Assert( pMat != NULL );
Assert( pVarName != NULL );
if ( pMat == NULL || pVarName == NULL )
{
return false;
}
bool bFound = false;
IMaterialVar* pVar = pMat->FindVar( pVarName, &bFound );
if ( bFound )
{
pVar->SetIntValue( nValue );
}
return bFound;
}
void DoDepthOfField( const CViewSetup &view )
{
if ( !IsDepthOfFieldEnabled() )
{
return;
}
// Copy from backbuffer to _rt_FullFrameFB
UpdateScreenEffectTexture( 0, view.x, view.y, view.width, view.height, false ); // Do we need to check if we already did this?
CMatRenderContextPtr pRenderContext( materials );
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
int nSrcWidth = pSrc->GetActualWidth();
int nSrcHeight = pSrc->GetActualHeight();
if ( mat_dof_quality.GetInt() < 2 )
{
/////////////////////////////////////
// Downsample backbuffer to 1/4 size
/////////////////////////////////////
// Update downsampled framebuffer. TODO: Don't do this again for the bloom if we already did it here...
pRenderContext->PushRenderTargetAndViewport();
ITexture *dest_rt0 = materials->FindTexture( "_rt_SmallFB0", TEXTURE_GROUP_RENDER_TARGET );
// *Everything* in here relies on the small RTs being exactly 1/4 the full FB res
Assert( dest_rt0->GetActualWidth() == pSrc->GetActualWidth() / 4 );
Assert( dest_rt0->GetActualHeight() == pSrc->GetActualHeight() / 4 );
// Downsample fb to rt0
DownsampleFBQuarterSize( pRenderContext, nSrcWidth, nSrcHeight, dest_rt0, true );
//////////////////////////////////////
// Additional blur using 3x3 gaussian
//////////////////////////////////////
IMaterial *pMat = materials->FindMaterial( "dev/blurgaussian_3x3", TEXTURE_GROUP_OTHER, true );
if ( pMat == NULL )
return;
SetMaterialVarFloat( pMat, "$c0_x", 0.5f / (float)dest_rt0->GetActualWidth() );
SetMaterialVarFloat( pMat, "$c0_y", 0.5f / (float)dest_rt0->GetActualHeight() );
SetMaterialVarFloat( pMat, "$c1_x", -0.5f / (float)dest_rt0->GetActualWidth() );
SetMaterialVarFloat( pMat, "$c1_y", 0.5f / (float)dest_rt0->GetActualHeight() );
ITexture *dest_rt1 = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET );
SetRenderTargetAndViewPort( dest_rt1 );
pRenderContext->DrawScreenSpaceRectangle(
pMat, 0, 0, nSrcWidth/4, nSrcHeight/4,
0, 0, dest_rt0->GetActualWidth()-1, dest_rt0->GetActualHeight()-1,
dest_rt0->GetActualWidth(), dest_rt0->GetActualHeight() );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( dest_rt1, 0, NULL, NULL );
}
pRenderContext->PopRenderTargetAndViewport();
}
// Render depth-of-field quad
int nViewportWidth = 0;
int nViewportHeight = 0;
int nDummy = 0;
pRenderContext->GetViewport( nDummy, nDummy, nViewportWidth, nViewportHeight );
IMaterial *pMatDOF = materials->FindMaterial( "dev/depth_of_field", TEXTURE_GROUP_OTHER, true );
if ( pMatDOF == NULL )
return;
SetMaterialVarFloat( pMatDOF, "$nearPlane", view.zNear );
SetMaterialVarFloat( pMatDOF, "$farPlane", view.zFar );
// Only SFM drives this bool at the moment...
if ( view.m_bDoDepthOfField )
{
SetMaterialVarFloat( pMatDOF, "$nearBlurDepth", view.m_flNearBlurDepth );
SetMaterialVarFloat( pMatDOF, "$nearFocusDepth", view.m_flNearFocusDepth );
SetMaterialVarFloat( pMatDOF, "$farFocusDepth", view.m_flFarFocusDepth );
SetMaterialVarFloat( pMatDOF, "$farBlurDepth", view.m_flFarBlurDepth );
SetMaterialVarFloat( pMatDOF, "$nearBlurRadius", view.m_flNearBlurRadius );
SetMaterialVarFloat( pMatDOF, "$farBlurRadius", view.m_flFarBlurRadius );
SetMaterialVarInt( pMatDOF, "$quality", view.m_nDoFQuality );
}
else // pull from convars/globals
{
SetMaterialVarFloat( pMatDOF, "$nearBlurDepth", GetNearBlurDepth() );
SetMaterialVarFloat( pMatDOF, "$nearFocusDepth", GetNearFocusDepth() );
SetMaterialVarFloat( pMatDOF, "$farFocusDepth", GetFarFocusDepth() );
SetMaterialVarFloat( pMatDOF, "$farBlurDepth", GetFarBlurDepth() );
SetMaterialVarFloat( pMatDOF, "$nearBlurRadius", GetNearBlurRadius() );
SetMaterialVarFloat( pMatDOF, "$farBlurRadius", GetFarBlurRadius() );
SetMaterialVarInt( pMatDOF, "$quality", mat_dof_quality.GetInt() );
}
pRenderContext->DrawScreenSpaceRectangle(
pMatDOF,
0, 0, nViewportWidth, nViewportHeight,
0, 0, nSrcWidth-1, nSrcHeight-1,
nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() );
}
void DrawModulationQuad( IMaterial *pMaterial, IMatRenderContext *pRenderContext, uint8 r, uint8 g, uint8 b, uint8 a, float fDepth )
{
pRenderContext->EnableClipping( false );
pRenderContext->Bind( pMaterial );
IMesh* pMesh = pRenderContext->GetDynamicMesh( true );
pRenderContext->MatrixMode( MATERIAL_MODEL );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
int w, h;
pRenderContext->GetRenderTargetDimensions( w, h );
if ( ( w == 0 ) || ( h == 0 ) )
return;
// This is the size of the back-buffer we're reading from.
int bw, bh;
bw = w; bh = h;
float s0, t0;
float s1, t1;
float flOffsetS = (bw != 0.0f) ? 1.0f / bw : 0.0f;
float flOffsetT = (bh != 0.0f) ? 1.0f / bh : 0.0f;
s0 = 0.5f * flOffsetS;
t0 = 0.5f * flOffsetT;
s1 = (w-0.5f) * flOffsetS;
t1 = (h-0.5f) * flOffsetT;
CMeshBuilder meshBuilder;
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
meshBuilder.Position3f( -1.0f, -1.0f, fDepth );
//meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f );
//meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f );
//meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f );
meshBuilder.TexCoord2f( 0, s0, t1 );
meshBuilder.Color4ub( r, g, b, a );
meshBuilder.AdvanceVertex();
meshBuilder.Position3f( -1.0f, 1, fDepth );
//meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f );
//meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f );
//meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f );
meshBuilder.TexCoord2f( 0, s0, t0 );
meshBuilder.Color4ub( r, g, b, a );
meshBuilder.AdvanceVertex();
meshBuilder.Position3f( 1, 1, fDepth );
//meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f );
//meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f );
//meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f );
meshBuilder.TexCoord2f( 0, s1, t0 );
meshBuilder.Color4ub( r, g, b, a );
meshBuilder.AdvanceVertex();
meshBuilder.Position3f( 1, -1.0f, fDepth );
//meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f );
//meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f );
//meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f );
meshBuilder.TexCoord2f( 0, s1, t1 );
meshBuilder.Color4ub( r, g, b, a );
meshBuilder.AdvanceVertex();
meshBuilder.End();
pMesh->Draw();
pRenderContext->MatrixMode( MATERIAL_MODEL );
pRenderContext->PopMatrix();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PopMatrix();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PopMatrix();
pRenderContext->EnableClipping( true );
}
ConVar cl_blurClearAlpha( "cl_blurClearAlpha", "0", 0, "0-255, but 0 has errors at the moment" );
ConVar cl_blurDebug( "cl_blurDebug", "0" );
ConVar cl_blurTapSize( "cl_blurTapSize", "0.5" );
ConVar cl_blurPasses( "cl_blurPasses", "1" );
void BlurEntity( IClientRenderable *pRenderable, bool bPreDraw, int drawFlags, const RenderableInstance_t &instance, const CViewSetup &view, int x, int y, int w, int h )
{
ITexture *pFullFrameFB = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
ITexture *dest_rt[2];
dest_rt[0] = materials->FindTexture( "_rt_SmallFB0", TEXTURE_GROUP_RENDER_TARGET );
dest_rt[1] = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET );
IMaterial *pBlurPass[2];
pBlurPass[0] = materials->FindMaterial( "dev/blurentity_blurpass0", TEXTURE_GROUP_OTHER );
pBlurPass[1] = materials->FindMaterial( "dev/blurentity_blurpass1", TEXTURE_GROUP_OTHER );
IMaterial *pEntBlurCopyBack[2];
pEntBlurCopyBack[0] = materials->FindMaterial( "dev/blurentity_copyback0", TEXTURE_GROUP_OTHER );
pEntBlurCopyBack[1] = materials->FindMaterial( "dev/blurentity_copyback1", TEXTURE_GROUP_OTHER );
IMaterial *pEntBlurAlphaSilhoutte = materials->FindMaterial( "dev/blurentity_alphasilhoutte", TEXTURE_GROUP_OTHER );
if( !pFullFrameFB ||
!dest_rt[0] || !dest_rt[1] ||
!pBlurPass[0] || !pBlurPass[1] ||
!pEntBlurCopyBack[0] || !pEntBlurCopyBack[1] ||
!pEntBlurAlphaSilhoutte )
{
return; //missing a vital texture/material
}
// Copy from backbuffer to _rt_FullFrameFB
UpdateScreenEffectTexture( 0, x, y, w, h, true ); // Do we need to check if we already did this?
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->PushRenderTargetAndViewport();
int nSrcWidth = pFullFrameFB->GetActualWidth();
int nSrcHeight = pFullFrameFB->GetActualHeight();
pRenderContext->OverrideAlphaWriteEnable( true, true ); //ensure we're always copying alpha values in every shader since we're using alpha as a mask when drawing to the back buffer
//replace the alpha channel with a silhoutte of the desired entity. We'll use the blurred alpha when rendering back to the back buffer
{
SetRenderTargetAndViewPort( pFullFrameFB );
pRenderContext->ClearColor4ub( 255, 255, 255, cl_blurClearAlpha.GetInt() );
pRenderContext->ClearBuffersObeyStencilEx( cl_blurDebug.GetBool(), true, true ); //clear out the existing alpha and depth
if( bPreDraw ) //in pre-draw mode, this renderable hasn't drawn it's colors anywhere yet, add them to _rt_FullFrameFB
pRenderable->DrawModel( drawFlags, instance );
//just write 1.0 to alpha, don't alter color information
if( !cl_blurDebug.GetBool() )
pRenderContext->OverrideColorWriteEnable( true, false );
modelrender->ForcedMaterialOverride( pEntBlurAlphaSilhoutte );
pRenderable->DrawModel( drawFlags, instance );
modelrender->ForcedMaterialOverride( NULL );
if( !cl_blurDebug.GetBool() )
pRenderContext->OverrideColorWriteEnable( false, false );
}
IMaterial *pEntBlurCopyBackFinal = NULL; //the material to use when copying the blur back to the backbuffer
//generate blur texture
{
/////////////////////////////////////
// Downsample backbuffer to 1/4 size
/////////////////////////////////////
// *Everything* in here relies on the small RTs being exactly 1/4 the full FB res
Assert( dest_rt[0]->GetActualWidth() == pFullFrameFB->GetActualWidth() / 4 );
Assert( dest_rt[0]->GetActualHeight() == pFullFrameFB->GetActualHeight() / 4 );
// Downsample fb to rt0
DownsampleFBQuarterSize( pRenderContext, nSrcWidth, nSrcHeight, dest_rt[0], true );
//////////////////////////////////////
// Additional blur
//////////////////////////////////////
float flBlurTapSize = cl_blurTapSize.GetFloat();
for( int i = 0; i != 2; ++i )
{
SetMaterialVarFloat( pBlurPass[i], "$c0_x", flBlurTapSize / (float)dest_rt[i]->GetActualWidth() );
SetMaterialVarFloat( pBlurPass[i], "$c0_y", flBlurTapSize / (float)dest_rt[i]->GetActualHeight() );
}
int iBlurPasses = cl_blurPasses.GetInt();
for( int i = 0; i < iBlurPasses; ++i )
{
int iSrc = i & 1;
int iDest = 1 - iSrc;
SetRenderTargetAndViewPort( dest_rt[iDest] );
pRenderContext->DrawScreenSpaceRectangle(
pBlurPass[iSrc], 0, 0, nSrcWidth/4, nSrcHeight/4,
0, 0, dest_rt[iSrc]->GetActualWidth()-1, dest_rt[iSrc]->GetActualHeight()-1,
dest_rt[iSrc]->GetActualWidth(), dest_rt[iSrc]->GetActualHeight() );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( dest_rt[iDest], 0, NULL, NULL );
}
}
pEntBlurCopyBackFinal = pEntBlurCopyBack[iBlurPasses & 1];
}
pRenderContext->OverrideAlphaWriteEnable( false, true );
pRenderContext->PopRenderTargetAndViewport();
//render back to the screen. We use the depth of the closest bbox point as our quad depth
{
const Vector &vRenderOrigin = pRenderable->GetRenderOrigin();
const QAngle &qRenderAngles = pRenderable->GetRenderAngles();
Vector vMins, vMaxs;
pRenderable->GetRenderBounds( vMins, vMaxs );
VMatrix matWorld, matView, matProj, matWorldView, matWorldViewProj;
//since the model matrix isn't necessarily set for this renderable, construct it manually
matWorld.SetupMatrixOrgAngles( vRenderOrigin, qRenderAngles );
pRenderContext->GetMatrix( MATERIAL_VIEW, &matView );
pRenderContext->GetMatrix( MATERIAL_PROJECTION, &matProj );
MatrixMultiply( matView, matWorld, matWorldView );
MatrixMultiply( matProj, matWorldView, matWorldViewProj );
float fClosestBBoxDepth = 1.0f;
Vector4D vTest;
vTest.w = 1.0f;
for( int i = 0; i != 8; ++i )
{
vTest.x = (i & (1 << 0)) ? vMaxs.x : vMins.x;
vTest.y = (i & (1 << 1)) ? vMaxs.y : vMins.y;
vTest.z = (i & (1 << 2)) ? vMaxs.z : vMins.z;
Vector4D vOut;
matWorldViewProj.V4Mul( vTest, vOut );
float fDepth = vOut.z/vOut.w;
if( fDepth < fClosestBBoxDepth )
fClosestBBoxDepth = fDepth;
}
if( fClosestBBoxDepth < 0.0f )
fClosestBBoxDepth = 0.0f;
DrawModulationQuad( pEntBlurCopyBackFinal, pRenderContext, 255, 255, 255, 255, fClosestBBoxDepth );
}
if( bPreDraw && ( instance.m_nAlpha == 255 ) && ( ( drawFlags & STUDIO_TRANSPARENCY ) == 0 ) ) //write depth out to the depth buffer
{
modelrender->ForcedMaterialOverride( NULL, OVERRIDE_DEPTH_WRITE );
pRenderable->DrawModel( drawFlags, instance );
modelrender->ForcedMaterialOverride( NULL );
}
}