Adding Motion Blur: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
m (clean up, added orphan, underlinked tags)
 
(66 intermediate revisions by 27 users not shown)
Line 1: Line 1:
[[Category:Programming]]
{{Multiple issues|
[[Category:Tutorials]]
{{Underlinked|date=January 2024}}
[[Image:garry_motionblur.jpg|thumb]]
{{Orphan|date=January 2024}}
== Motion Blur ==
}}


[[File:garry_motionblur.jpg|thumb]]


Here's something that will be in an upcoming version of Garry's Mod. Motion Blur. This method doesn't use any shaders and will work on most graphics cards.
==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.


== Summary ==
{{note|Any Source mods based off of the Orange Box engine version will use Valve's implementation of motion blur.}}
 
This effect is achieved by adding a transparent version of the front buffer to a seperate 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 (which is one thing that the Garry's Mod fans love).


== Creating a new Render Target ==
== Creating a new Render Target ==
Open the file src/cl_dll/rendertexture.cpp, and above the Power of Two Frame Buffer Texture add:


Open the file '''src/cl_dll/rendertexture.cpp''' and near the bottom add
<pre>
 
   static CTextureReference s_pMotionBlurTex0;
   static CTextureReference s_pMoBlurTex0;
    
    
   ITexture *GetMoBlurTex0( void )
   ITexture *GetMotionBlurTex0( void )
   {
   {
   if( !s_pMoBlurTex0 )
   if( !s_pMotionBlurTex0 )
   {
   {
   s_pMoBlurTex0.InitRenderTarget( 256, 256, [[RT_SIZE_FULL_FRAME_BUFFER]], [[IMAGE_FORMAT_ARGB8888]], [[MATERIAL_RT_DEPTH_NONE]] );
   s_pMotionBlurTex0.InitRenderTarget( 256, 256, RT_SIZE_FULL_FRAME_BUFFER,
   Assert( !IsErrorTexture( s_pMoBlurTex0 ) );
IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false );
   Assert( !IsErrorTexture( s_pMotionBlurTex0 ) );
   }
   }
    
    
   return s_pMoBlurTex0;
   return s_pMotionBlurTex0;
   }
   }
</pre>


This is the function we will use 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.
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 *GetMoBlurTex0( void );
So now in the file '''src/cl_dll/rendertexture.h''' directly under #define RENDERTARGETS_H add:


This will allow us to use this function.
<pre>
  ITexture *GetMotionBlurTex0( void );
</pre>


This will allow you to use this function.


== Rendering the Motion Blur ==
== 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 needs to go directly above '''void CViewRender::Render2DEffectsPreHUD( const CViewSetup &view )''' in '''view_scene.cpp'''.


First of all lets add some console commands that we're going to use. I make this console commands because people will want to change them in Garry's Mod. This code can go pretty much anywhere that it can be seen from '''CViewRender::RenderView( const CViewSetup &view, bool drawViewModel )''' in '''view_scene.cpp'''.
<pre>
 
   // To toggle the blur on and off
   // To toggle the blur on and off
   ConVar pp_motionblur("pp_motionblur", "1", 0, "Motion Blur");  
   ConVar pp_motionblur("pp_motionblur", "1", 0, "Motion Blur");  
Line 50: Line 54:
   // Delay to add between capturing the FB
   // 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");
   ConVar pp_motionblur_time("pp_motionblur_time", "0.05", 0, "The amount of time to wait until updating the FB");
</pre>


All pretty self explanatory.
Directly under the console commands add this code.


  void CViewRender::DoMotionBlur( void )
<pre>
  {
void CViewRender::DoMotionBlur( void )
  if ( pp_motionblur.GetInt() == 0 ) return;
{
 
if ( pp_motionblur.GetInt() == 0 ) return;
  static float fNextDrawTime = 0.0f;
 
 
static float fNextDrawTime = 0.0f;
  bool found;
 
  IMaterialVar* mv = NULL;
bool found;
  IMaterial *pMatScreen = NULL;
IMaterialVar* mv = NULL;
  ITexture *pMoBlur = NULL;
IMaterial *pMatScreen = NULL;
 
ITexture *pMotionBlur = NULL;
 
ITexture *pOriginalTexture = NULL;
  // Get the front buffer material
 
  pMatScreen = materials->[[FindMaterial]]( "frontbuffer", [[TEXTURE_GROUP_OTHER]], true );
// Get the front buffer material
  // Get our custom render target
pMatScreen = materials->FindMaterial( "frontbuffer", TEXTURE_GROUP_OTHER, true );
  pMoBlur = GetMoBlurTex0();
// Get our custom render target
  // Store the current render target
pMotionBlur = GetMotionBlurTex0();
  ITexture *pOriginalRenderTarget = materials->[[GetRenderTarget]]();
// Store the current render target
 
ITexture *pOriginalRenderTarget = materials->GetRenderTarget();
  // Set the camera up so we can draw the overlay
 
  int oldX, oldY, oldW, oldH;
// Set the camera up so we can draw the overlay
  materials->GetViewport( oldX, oldY, oldW, oldH );
int oldX, oldY, oldW, oldH;
 
materials->GetViewport( oldX, oldY, oldW, oldH );
  materials->MatrixMode( MATERIAL_PROJECTION );
  materials->PushMatrix();
materials->MatrixMode( MATERIAL_PROJECTION );
  materials->LoadIdentity();
materials->PushMatrix();
 
materials->LoadIdentity();
  materials->MatrixMode( MATERIAL_VIEW );
 
  materials->PushMatrix();
materials->MatrixMode( MATERIAL_VIEW );
  materials->LoadIdentity();
materials->PushMatrix();
 
materials->LoadIdentity();
 
 
  if ( fNextDrawTime < gpGlobals->curtime )
if( gpGlobals->curtime >= fNextDrawTime )  
  {
{
  UpdateScreenEffectTexture( 0 );
UpdateScreenEffectTexture( 0 );
 
 
  // Set the alpha to whatever our console variable is
// Set the alpha to whatever our console variable is
  mv = pMatScreen->FindVar( "$alpha", &found, false );
mv = pMatScreen->FindVar( "$alpha", &found, false );
  if (found)
if (found)
  {
{
  if ( fNextDrawTime == 0 )
if ( fNextDrawTime == 0 )
  {
{
  mv->SetFloatValue( 1.0f );
mv->SetFloatValue( 1.0f );
  }
}
  else
else
  {
{
  mv->SetFloatValue( pp_motionblur_addalpha.GetFloat() );
mv->SetFloatValue( pp_motionblur_addalpha.GetFloat() );
  }
}
  }
}
 
 
  materials->SetRenderTarget( pMoBlur );
materials->SetRenderTarget( pMotionBlur );
  materials->DrawScreenSpaceQuad( pMatScreen );
materials->DrawScreenSpaceQuad( pMatScreen );
 
 
  // Set the next draw time according to the convar
// Set the next draw time according to the convar
  fNextDrawTime = gpGlobals->curtime + pp_motionblur_time.GetFloat();
fNextDrawTime = gpGlobals->curtime + pp_motionblur_time.GetFloat();
  }
}
 
  // Set the alpha
// Set the alpha
  mv = pMatScreen->FindVar( "$alpha", &found, false );
mv = pMatScreen->FindVar( "$alpha", &found, false );
  if (found)
if (found)
  {
{
  mv->[[SetFloatValue]]( pp_motionblur_drawalpha.GetFloat() );
mv->SetFloatValue( pp_motionblur_drawalpha.GetFloat() );
  }
}
 
 
  // Set the texture to our buffer
// Set the texture to our buffer
  mv = pMatScreen->FindVar( "$basetexture", &found, false );
mv = pMatScreen->FindVar( "$basetexture", &found, false );
  if (found)
if (found)
  {
{
  mv->[[SetTextureValue]]( pMatScreen);
pOriginalTexture = mv->GetTextureValue();
  }
mv->SetTextureValue( pMotionBlur );
 
}
  // Pretend we were never here, set everything back
 
  materials->[[SetRenderTarget]]( pOriginalRenderTarget );
// Pretend we were never here, set everything back
  materials->DrawScreenSpaceQuad( pMatMBlur );
materials->SetRenderTarget( pOriginalRenderTarget );
 
materials->DrawScreenSpaceQuad( pMatScreen );
  materials->DepthRange( 0.0f, 1.0f );
  materials->MatrixMode( [[MATERIAL_PROJECTION]] );
      // Set our texture back to _rt_FullFrameFB
  materials->PopMatrix();
if (found)
  materials->MatrixMode( [[MATERIAL_VIEW]] );
{
  materials->PopMatrix();
mv->SetTextureValue( pOriginalTexture );
  }
}
 
materials->DepthRange( 0.0f, 1.0f );
materials->MatrixMode( MATERIAL_PROJECTION );
materials->PopMatrix();
materials->MatrixMode( MATERIAL_VIEW );
materials->PopMatrix();
}
</pre>
 
This should all make sense.
 
Now add this to just before '''// Draw the 2D graphics''' at around line 3540 in '''view_scene.cpp'''
 
<pre>
DoMotionBlur();
</pre>
 
Open up '''view_scene.h''' and put this at the end of file before '''#endif // VIEW_SCENE_H'''
 
<pre>
inline void UpdateScreenEffectTexture( int textureIndex )
{
ITexture *pTexture = GetFullFrameFrameBufferTexture( textureIndex );
materials->CopyRenderTargetToTexture( pTexture );
materials->SetFrameBufferCopyTexture( pTexture, textureIndex );
}
</pre>
 
Lastly open up '''viewrender.h''' and add this line after RenderViewEx at around line 170
 
<pre>
void CViewRender::DoMotionBlur( void );
</pre>
 
== 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.


I hope that all makes sense.
So when the blur performs the check:


Now in the '''CViewRender::RenderView( const CViewSetup &view, bool drawViewModel )''' function which is located in '''view_scene.cpp''', add this:
if ( fNextDrawTime < gpGlobals->curtime )


  DoMotionBlur();
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.


Just before
<pre>
if ( fNextDrawTime - gpGlobals->curtime > 1.0f)
{
  fNextDrawTime = 0.0f;
}
</pre>


  // Draw the 2D graphics
This code sits above this line


== Material Files ==
<pre>if( gpGlobals->curtime >= fNextDrawTime ) </pre>


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 what mine looks like:
== 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]]"
   "UnlitGeneric"
   {
   {
   "$basetexture" "[[_rt_FullFrameFB]]"
   "$basetexture" "_rt_FullFrameFB"
   "$ignorez" 1
   "$ignorez" 1
   }
   }


== Finished ==
[[Category:Outdated programming tutorials]]
 
I chose this tutorial to do because it covered a few things that people continually ask me about. I hope it helps someone.
 
Garry Newman

Latest revision as of 22:36, 21 January 2024

Wikipedia - Letter.png
This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these template messages)
Underlinked - Logo.png
This article needs more Wikipedia icon links to other articles to help Wikipedia icon integrate it into the encyclopedia. Please help improve this article by adding links Wikipedia icon that are relevant to the context within the existing text.
January 2024
Garry motionblur.jpg

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.

Note.pngNote:Any Source mods based off of the Orange Box engine version will use Valve's implementation of motion blur.

Creating a new Render Target

Open the file src/cl_dll/rendertexture.cpp, and above the Power of Two Frame Buffer Texture 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 directly under #define RENDERTARGETS_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 needs to go directly above void CViewRender::Render2DEffectsPreHUD( const CViewSetup &view ) 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");

Directly under the console commands add this code.

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 add this to just before // Draw the 2D graphics at around line 3540 in view_scene.cpp

DoMotionBlur();

Open up view_scene.h and put this at the end of file before #endif // VIEW_SCENE_H

inline void UpdateScreenEffectTexture( int textureIndex )
{
	ITexture *pTexture = GetFullFrameFrameBufferTexture( textureIndex );
	materials->CopyRenderTargetToTexture( pTexture );
	materials->SetFrameBufferCopyTexture( pTexture, textureIndex );
}

Lastly open up viewrender.h and add this line after RenderViewEx at around line 170

void CViewRender::DoMotionBlur( void );

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
 }