Adding Motion Blur
Introduction
Here's something that is in Garry's Mod: Motion Blur. This method doesn't use any shaders and will therefore work on most graphic cards.
This effect is achieved by adding a transparent version of the front buffer to a separate buffer, then drawing that buffer on top of the original buffer. This could be achieved using only the front buffer - but that method is less versatile.
Creating a new Render Target
Open the file src/cl_dll/rendertexture.cpp, at the top add
#include "materialsystem/materialsystemutil.h"
and near the bottom add
static CTextureReference s_pMotionBlurTex0; ITexture *GetMotionBlurTex0( void ) { if( !s_pMotionBlurTex0 ) { s_pMotionBlurTex0.InitRenderTarget( 256, 256, RT_SIZE_FULL_FRAME_BUFFER, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false ); Assert( !IsErrorTexture( s_pMotionBlurTex0 ) ); } return s_pMotionBlurTex0; }
This is the function that will be used to get a pointer to the render target. The '256' numbers are meaningless, they get overridden by RT_SIZE_FULL_FRAME_BUFFER which forces the texture to be the size of the frame buffer.
So now in the file src/cl_dll/rendertexture.h add:
ITexture *GetMotionBlurTex0( void );
This will allow you to use this function.
Rendering the Motion Blur
First of all, console commands need to be added so they can be used. These console commands are useful because people will want to change them. This code can go pretty much anywhere that it can be seen from void CViewRender::RenderViewEx( const CViewSetup &view, int nClearFlags, int whatToDraw ) in view_scene.cpp.
// To toggle the blur on and off ConVar pp_motionblur("pp_motionblur", "1", 0, "Motion Blur"); // The amount of alpha to use when adding the FB to our custom buffer ConVar pp_motionblur_addalpha("pp_motionblur_addalpha", "0.1", 0, "Motion Blur Alpha"); // The amount of alpha to use when adding our custom buffer to the FB ConVar pp_motionblur_drawalpha("pp_motionblur_drawalpha", "1", 0, "Motion Blur Draw Alpha"); // Delay to add between capturing the FB ConVar pp_motionblur_time("pp_motionblur_time", "0.05", 0, "The amount of time to wait until updating the FB");
This code get stuck on the end of the RenderViewEx section, and should all be pretty self explanatory.
void CViewRender::DoMotionBlur( void ) { if ( pp_motionblur.GetInt() == 0 ) return; static float fNextDrawTime = 0.0f; bool found; IMaterialVar* mv = NULL; IMaterial *pMatScreen = NULL; ITexture *pMotionBlur = NULL; ITexture *pOriginalTexture = NULL; // Get the front buffer material pMatScreen = materials->FindMaterial( "frontbuffer", TEXTURE_GROUP_OTHER, true ); // Get our custom render target pMotionBlur = GetMotionBlurTex0(); // Store the current render target ITexture *pOriginalRenderTarget = materials->GetRenderTarget(); // Set the camera up so we can draw the overlay int oldX, oldY, oldW, oldH; materials->GetViewport( oldX, oldY, oldW, oldH ); materials->MatrixMode( MATERIAL_PROJECTION ); materials->PushMatrix(); materials->LoadIdentity(); materials->MatrixMode( MATERIAL_VIEW ); materials->PushMatrix(); materials->LoadIdentity(); if( gpGlobals->curtime >= fNextDrawTime ) { UpdateScreenEffectTexture( 0 ); // Set the alpha to whatever our console variable is mv = pMatScreen->FindVar( "$alpha", &found, false ); if (found) { if ( fNextDrawTime == 0 ) { mv->SetFloatValue( 1.0f ); } else { mv->SetFloatValue( pp_motionblur_addalpha.GetFloat() ); } } materials->SetRenderTarget( pMotionBlur ); materials->DrawScreenSpaceQuad( pMatScreen ); // Set the next draw time according to the convar fNextDrawTime = gpGlobals->curtime + pp_motionblur_time.GetFloat(); } // Set the alpha mv = pMatScreen->FindVar( "$alpha", &found, false ); if (found) { mv->SetFloatValue( pp_motionblur_drawalpha.GetFloat() ); } // Set the texture to our buffer mv = pMatScreen->FindVar( "$basetexture", &found, false ); if (found) { pOriginalTexture = mv->GetTextureValue(); mv->SetTextureValue( pMotionBlur ); } // Pretend we were never here, set everything back materials->SetRenderTarget( pOriginalRenderTarget ); materials->DrawScreenSpaceQuad( pMatScreen ); // Set our texture back to _rt_FullFrameFB if (found) { mv->SetTextureValue( pOriginalTexture ); } materials->DepthRange( 0.0f, 1.0f ); materials->MatrixMode( MATERIAL_PROJECTION ); materials->PopMatrix(); materials->MatrixMode( MATERIAL_VIEW ); materials->PopMatrix(); }
This should all make sense.
Now in the void CViewRender::RenderViewEx( const CViewSetup &view, int nClearFlags, int whatToDraw ) function which is located in view_scene.cpp, add this:
DoMotionBlur();
Just after the xbox #ifndef, and before
// Draw the 2D graphics
Lastly Add the line after the RenderViewEx virtual void:
void CViewRender::DoMotionBlur( void );
Before the end in the viewrender.h file.
Single Player
If you're working on a Single Player Mod, you may have found that the front buffer wont update on a map change, or player death, this is because when you change the map, the gpGlobals->curtime variable resets to 0.0 seconds, but your fNextDrawTime retains its value.
So when the blur performs the check:
if ( fNextDrawTime < gpGlobals->curtime )
It will not return true until gpGlobals->curtime catches up - so if you played for 30 mins on the last map, the blur will start working 30 minutes into the new map. To do this you need to reset your fNextDrawTime on a map change by using the hack below, or don't use this check at all.
if ( fNextDrawTime - gpGlobals->curtime > 1.0f) { fNextDrawTime = 0.0f; }
This code sits above this line
if( gpGlobals->curtime >= fNextDrawTime )
Material Files
That should do it. You need to add a material file called frontbuffer.vmt in your materials folder - although you could probably use an existing one. Here's an example:
"UnlitGeneric" { "$basetexture" "_rt_FullFrameFB" "$ignorez" 1 }