Difference between revisions of "L4D Glow Effect"

From Valve Developer Community
Jump to: navigation, search
(Created page with '{{stub}} == Introduction == This article will show you how to implement the Left For Dead (L4D) entity glow effect that is used for players, zombies, and items you can grab. Thi…')
 
Line 32: Line 32:
  
 
== The Code ==
 
== The Code ==
 +
I am going to present the code in chunks to discuss what each does. You can download the code in it's entirety here:
  
 +
[[L4D_Glow_Effect_CPP|ge_screeneffects.cpp]]<br>
 +
[[L4D_Glow_Effect_H|ge_screeneffects.h]]
  
 +
=== Header ===
 +
<source lang=cpp>
 +
class CEntGlowEffect : public IScreenSpaceEffect
 +
{
 +
public:
 +
CEntGlowEffect( void ) { };
 +
 +
virtual void Init( void );
 +
virtual void Shutdown( void );
 +
virtual void SetParameters( KeyValues *params ) {};
 +
virtual void Enable( bool bEnable ) {};
 +
virtual bool IsEnabled( void ) { return true; }
 +
 +
virtual void RegisterEnt( EHANDLE hEnt, Color glowColor = Color(255,255,255,64), float fGlowScale = 1.0f );
 +
virtual void DeregisterEnt( EHANDLE hEnt );
 +
 +
virtual void SetEntColor( EHANDLE hEnt, Color glowColor );
 +
virtual void SetEntGlowScale( EHANDLE hEnt, float fGlowScale );
 +
 +
virtual void Render( int x, int y, int w, int h );
 +
 +
protected:
 +
int FindGlowEnt( EHANDLE hEnt );
 +
void RenderToStencil( int idx, IMatRenderContext *pRenderContext );
 +
void RenderToGlowTexture( int idx, IMatRenderContext *pRenderContext );
 +
 +
private:
 +
struct sGlowEnt
 +
{
 +
EHANDLE m_hEnt;
 +
float m_fColor[4];
 +
float m_fGlowScale;
 +
};
 +
 +
CUtlVector<sGlowEnt*> m_vGlowEnts;
 +
 +
CTextureReference m_GlowBuff1;
 +
CTextureReference m_GlowBuff2;
 +
 +
CMaterialReference m_WhiteMaterial;
 +
CMaterialReference m_EffectMaterial;
 +
 +
CMaterialReference m_BlurX;
 +
CMaterialReference m_BlurY;
 +
};
 +
</source>
  
 +
The glow effect inherits from IScreenSpaceEffect which is part of the screen space effect manager that processes effects during the render sequence. The effects are processed after the Viewmodel is rendered, color correction is applied, and HDR is applied, but before the HUD is drawn. This means the glow effect will be unaffected by HDR and color correction (GOOD!) but will show through the viewmodel (Maybe BAD?). Unfortunately, there is no real good way to solve this problem without adversely affecting the rendering of the effect or the viewmodel.
 +
 +
Anyway, all the functions are implemented from the abstract class, but I added a couple (ie. RegisterEnt and DeregisterEnt) which allow us to easily add and remove entities to apply the glow effect on.
 +
 +
=== CEntGlowEffect::Init() ===
 +
<source lang=cpp>
 +
ADD_SCREENSPACE_EFFECT( CEntGlowEffect, ge_entglow );
 +
 +
void CEntGlowEffect::Init( void )
 +
{
 +
// Initialize the white overlay material to render our model with
 +
KeyValues *pVMTKeyValues = new KeyValues( "VertexLitGeneric" );
 +
pVMTKeyValues->SetString( "$basetexture", "vgui/white" );
 +
pVMTKeyValues->SetInt( "$vertexcolor", 1 );
 +
pVMTKeyValues->SetInt( "$vertexalpha", 1 );
 +
pVMTKeyValues->SetInt( "$model", 1 );
 +
m_WhiteMaterial.Init( "__geglowwhite", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues );
 +
 +
// Initialize the Effect material that will be blitted to the Frame Buffer
 +
pVMTKeyValues->Clear();
 +
pVMTKeyValues->SetName( "UnlitGeneric" );
 +
pVMTKeyValues->SetString( "$basetexture", "_rt_FullFrameFB" );
 +
pVMTKeyValues->SetInt( "$additive", 1 );
 +
m_EffectMaterial.Init( "__geglowcomposite", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues );
 +
 +
// Initialize render targets for our blurring
 +
m_GlowBuff1.InitRenderTarget( ScreenWidth()/2, ScreenHeight()/2, RT_SIZE_DEFAULT, IMAGE_FORMAT_RGBA8888, MATERIAL_RT_DEPTH_SEPARATE, false, "_rt_geglowbuff1" );
 +
m_GlowBuff2.InitRenderTarget( ScreenWidth()/2, ScreenHeight()/2, RT_SIZE_DEFAULT, IMAGE_FORMAT_RGBA8888, MATERIAL_RT_DEPTH_SEPARATE, false, "_rt_geglowbuff2" );
 +
 +
// Load the blur textures
 +
m_BlurX.Init( materials->FindMaterial("pp/ge_blurx", TEXTURE_GROUP_OTHER, true) );
 +
m_BlurY.Init( materials->FindMaterial("pp/ge_blury", TEXTURE_GROUP_OTHER, true) );
 +
}
 +
</source>
 +
 +
The first bit tells the effect manager about us and provides an interface to create the effect. More about that later in the Implementation stage.
 +
 +
The init function is called when the effect is created. This sets up all our materials and textures we will be using in order to render the effect. '''m_WhiteMaterial''' will be used to draw the model onto the stencil and the blur buffer in a constant shade of color. '''m_EffectMaterial''' is what the final result is going to be rendered from. '''m_GlowBuff1''' and '''m_GlowBuff2''' are our Render Targets for the glow effect and blurring. '''m_BlurX''' and '''m_BlurY''' are the blur shaders that will actually perform the blur.
 
[[Category:Programming]]
 
[[Category:Programming]]

Revision as of 19:49, 27 November 2009

Introduction

This article will show you how to implement the Left For Dead (L4D) entity glow effect that is used for players, zombies, and items you can grab. This effect is not a shader and does not require you to edit any rendering code. I have included a couple of tweaking options and possibility for future enhancement.

Requirements

  • Strong C++ background
  • Knowledge of general rendering process

The Breakdown

Rendering the glow effect requires three distinct steps. The first step is to draw the entity onto the engine's Stencil Buffer. The second step is to draw the entity with the color of the glow you want onto a render buffer. The third step is to blur the render buffer and draw it onto the screen while respecting the Stencil Buffer.

Buffers

Stencil Buffer: A grayscale buffer implemented by DirectX that can be written to and read from much like a mask in Photoshop. The Stencil Buffer is used in the Scratch Source SDK to read the ambient occlusion depth map and apply appropriate shading in areas. We will be using the Stencil Buffer to "cutout" the entities we want to glow so only the blurred image that extends beyond the entity will show giving the effect of a halo.

Render Target (Buffer): A Render Target is a special texture that is registered with the engine and can be written to by the renderer. We will declare two render targets (buffers). One will hold the cumulative images from the glowing entities and the other will be used to blur with.

Material vs. Texture

When dealing with the rendering engine it is important to understand the distinction between a material and a texture.

Material: A material encapsulates a particular shader and it's associated parameters. A material is what defines how the renderer should render.

Texture: A texture is pure image information. To set the texture of a material you typically use $basetexture and whenever the material is rendered that texture is used in the shader

Different Rendering Components

There are three distinct rendering components in the source engine.

  • IMatRenderContext (pRenderContext)
  • IVRenderView (render)
  • IVModelRender (modelrender)

We will be using all three of these in order to achieve the desired effect. Each one does its part to draw our world, however the details are beyond the scope of this tutorial.

The Code

I am going to present the code in chunks to discuss what each does. You can download the code in it's entirety here:

ge_screeneffects.cpp
ge_screeneffects.h

Header

class CEntGlowEffect : public IScreenSpaceEffect
{
public:
	CEntGlowEffect( void ) { };
 
	virtual void Init( void );
	virtual void Shutdown( void );
	virtual void SetParameters( KeyValues *params ) {};
	virtual void Enable( bool bEnable ) {};
	virtual bool IsEnabled( void ) { return true; }
 
	virtual void RegisterEnt( EHANDLE hEnt, Color glowColor = Color(255,255,255,64), float fGlowScale = 1.0f );
	virtual void DeregisterEnt( EHANDLE hEnt );
 
	virtual void SetEntColor( EHANDLE hEnt, Color glowColor );
	virtual void SetEntGlowScale( EHANDLE hEnt, float fGlowScale );
 
	virtual void Render( int x, int y, int w, int h );
 
protected:
	int FindGlowEnt( EHANDLE hEnt );
	void RenderToStencil( int idx, IMatRenderContext *pRenderContext );
	void RenderToGlowTexture( int idx, IMatRenderContext *pRenderContext );
 
private:
	struct sGlowEnt
	{
		EHANDLE	m_hEnt;
		float	m_fColor[4];
		float	m_fGlowScale;
	};
 
	CUtlVector<sGlowEnt*>	m_vGlowEnts;
 
	CTextureReference	m_GlowBuff1;
	CTextureReference	m_GlowBuff2;
 
	CMaterialReference	m_WhiteMaterial;
	CMaterialReference	m_EffectMaterial;
 
	CMaterialReference	m_BlurX;
	CMaterialReference	m_BlurY;
};

The glow effect inherits from IScreenSpaceEffect which is part of the screen space effect manager that processes effects during the render sequence. The effects are processed after the Viewmodel is rendered, color correction is applied, and HDR is applied, but before the HUD is drawn. This means the glow effect will be unaffected by HDR and color correction (GOOD!) but will show through the viewmodel (Maybe BAD?). Unfortunately, there is no real good way to solve this problem without adversely affecting the rendering of the effect or the viewmodel.

Anyway, all the functions are implemented from the abstract class, but I added a couple (ie. RegisterEnt and DeregisterEnt) which allow us to easily add and remove entities to apply the glow effect on.

CEntGlowEffect::Init()

ADD_SCREENSPACE_EFFECT( CEntGlowEffect, ge_entglow );

void CEntGlowEffect::Init( void ) 
{
	// Initialize the white overlay material to render our model with
	KeyValues *pVMTKeyValues = new KeyValues( "VertexLitGeneric" );
	pVMTKeyValues->SetString( "$basetexture", "vgui/white" );
	pVMTKeyValues->SetInt( "$vertexcolor", 1 );
	pVMTKeyValues->SetInt( "$vertexalpha", 1 );
	pVMTKeyValues->SetInt( "$model", 1 );
	m_WhiteMaterial.Init( "__geglowwhite", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues );
 
	// Initialize the Effect material that will be blitted to the Frame Buffer
	pVMTKeyValues->Clear();
	pVMTKeyValues->SetName( "UnlitGeneric" );
	pVMTKeyValues->SetString( "$basetexture", "_rt_FullFrameFB" );
	pVMTKeyValues->SetInt( "$additive", 1 );
	m_EffectMaterial.Init( "__geglowcomposite", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues );
 
	// Initialize render targets for our blurring
	m_GlowBuff1.InitRenderTarget( ScreenWidth()/2, ScreenHeight()/2, RT_SIZE_DEFAULT, IMAGE_FORMAT_RGBA8888, MATERIAL_RT_DEPTH_SEPARATE, false, "_rt_geglowbuff1" );
	m_GlowBuff2.InitRenderTarget( ScreenWidth()/2, ScreenHeight()/2, RT_SIZE_DEFAULT, IMAGE_FORMAT_RGBA8888, MATERIAL_RT_DEPTH_SEPARATE, false, "_rt_geglowbuff2" );
 
	// Load the blur textures
	m_BlurX.Init( materials->FindMaterial("pp/ge_blurx", TEXTURE_GROUP_OTHER, true) );
	m_BlurY.Init( materials->FindMaterial("pp/ge_blury", TEXTURE_GROUP_OTHER, true) );
}

The first bit tells the effect manager about us and provides an interface to create the effect. More about that later in the Implementation stage.

The init function is called when the effect is created. This sets up all our materials and textures we will be using in order to render the effect. m_WhiteMaterial will be used to draw the model onto the stencil and the blur buffer in a constant shade of color. m_EffectMaterial is what the final result is going to be rendered from. m_GlowBuff1 and m_GlowBuff2 are our Render Targets for the glow effect and blurring. m_BlurX and m_BlurY are the blur shaders that will actually perform the blur.