Post tutorial Report RSS [Source Engine] Implementing Basic SSAO Shader | Part 2

It is the second part of 'Implementing Basic SSAO Shader' tutorial. In this part I will show you how to make the shader work in game.

Posted by on - Intermediate Client Side Coding

Hello everyone! This is the second part of 'Implementing Basic SSAO Shader' tutorial. If you haven't the first part, here it is.

In this part I will show you how to make SSAO work in game and say some words about blur.

SSAO Blur

The one of right ways (that gives better results) to blur SSAO is to use the depth blur. In Crossroads I used simple 3x3 gaussian blur found in the Internet, here is code for it, but you really should look for better shader to blur SSAO.

Pixel Shader

Lets name it 'gauss_blur_3x3.fxc'. Vertex shader code is the same as 'ssao_vs30' or 'ssao_combine_vs30'.

// Original shader by brooknovak
#include "common_ps_fxc.h"

sampler texturesampler			: register( s0 );

float2	g_TexelSize				: register( c0 );

struct PS_INPUT
{
	HALF2 vTexCoord			:	TEXCOORD0;
};

// Blurs using a 3x3 filter kernel
float4 main( const PS_INPUT i ) : COLOR
{
	// TOP ROW
	float4 s11 = tex2D( texturesampler, i.vTexCoord + float2( -g_TexelSize ) );						// LEFT
	float4 s12 = tex2D( texturesampler, i.vTexCoord + float2( 0, -g_TexelSize.y ) );				// MIDDLE
	float4 s13 = tex2D( texturesampler, i.vTexCoord + float2( g_TexelSize.x , -g_TexelSize.y ) );	// RIGHT
 
	// MIDDLE ROW
	float4 s21 = tex2D( texturesampler, i.vTexCoord + float2( -g_TexelSize.x, 0 ) );				// LEFT
	float4 col = tex2D( texturesampler, i.vTexCoord );												// DEAD CENTER
	float4 s23 = tex2D( texturesampler, i.vTexCoord + float2( -g_TexelSize.x, 0 ) );				// RIGHT
 
	// LAST ROW
	float4 s31 = tex2D( texturesampler, i.vTexCoord + float2( -g_TexelSize.x, g_TexelSize.y ) );	// LEFT
	float4 s32 = tex2D( texturesampler, i.vTexCoord + float2( 0, g_TexelSize.y ) );					// MIDDLE
	float4 s33 = tex2D( texturesampler, i.vTexCoord + float2( g_TexelSize ) );						// RIGHT
 
	// Average the color with surrounding samples
	col = ( col + s11 + s12 + s13 + s21 + s23 + s31 + s32 + s33 ) / 9;
	return col;
}

Okay, that's all with blur.

Implementing shaders into 'game_shader_dx9.dll'

If you don't know what is shader dll - read this.

Create three files: 'ssao.cpp', 'ssao_combine.cpp', 'gaussian_blur_3x3.cpp'. A cpp for each shader. Then add them to the project (in MS Visual Studio right click on 'game_shader' project, then click 'Add object', then choose 'Existing object', then choose the files you created).

Then copy the following code in 'ssao.cpp':

//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
//
// Purpose: Crossroads devtest
//
// $NoKeywords: $
//=============================================================================

#include "BaseVSShader.h"
#include "ssao_vs30.inc"
#include "ssao_ps30.inc"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// SHADER PARAMS DEFINED IN SHADER FXC CODE
ConVar cr_ssao_samples( "cr_ssao_samples", "8" );
ConVar cr_ssao_contrast( "cr_ssao_contrast", "2.0" );
ConVar cr_ssao_radius( "cr_ssao_radius", "16" );
ConVar cr_ssao_bias( "cr_ssao_bias", "0.02" );
ConVar cr_ssao_bias_offset( "cr_ssao_bias_offset", "0.05" );
ConVar cr_ssao_illuminfluence( "cr_ssao_illuminfluence", "5.0" );
ConVar cr_ssao_zfar( "cr_ssao_zfar", "8.0" );
ConVar cr_ssao_znear( "cr_ssao_znear", "1.0" );

BEGIN_VS_SHADER_FLAGS( SSAO, "Help for SSAO", SHADER_NOT_EDITABLE )
	BEGIN_SHADER_PARAMS
		SHADER_PARAM( BASETEXTURE, SHADER_PARAM_TYPE_TEXTURE, "_rt_FullFrameFB", "Framebuffer" )
	END_SHADER_PARAMS

	SHADER_INIT_PARAMS()
	{
		SET_FLAGS2( MATERIAL_VAR2_NEEDS_FULL_FRAME_BUFFER_TEXTURE );
	}

	SHADER_FALLBACK
	{
		return 0;
	}

	SHADER_INIT
	{
		if( params[BASETEXTURE]->IsDefined() )
		{
			LoadTexture( BASETEXTURE );
		}
	}

	SHADER_DRAW
	{
		SHADOW_STATE
		{
			pShaderShadow->VertexShaderVertexFormat( VERTEX_POSITION, 1, 0, 0 );

			pShaderShadow->EnableTexture( SHADER_SAMPLER0, true );
	
			// Render targets are pegged as sRGB on POSIX, so just force these reads and writes
			bool bForceSRGBReadAndWrite = IsOSX() && g_pHardwareConfig->CanDoSRGBReadFromRTs();
			pShaderShadow->EnableSRGBRead( SHADER_SAMPLER0, bForceSRGBReadAndWrite );
			pShaderShadow->EnableSRGBWrite( bForceSRGBReadAndWrite );
			
			DECLARE_STATIC_VERTEX_SHADER( ssao_vs30 );
			SET_STATIC_VERTEX_SHADER( ssao_vs30 );

			DECLARE_STATIC_PIXEL_SHADER( ssao_ps30 );
			SET_STATIC_PIXEL_SHADER( ssao_ps30 );
		}

		DYNAMIC_STATE
		{
			BindTexture( SHADER_SAMPLER0, BASETEXTURE, -1 );

			ITexture *src_texture = params[BASETEXTURE]->GetTextureValue();

			int width = src_texture->GetActualWidth();
			int height = src_texture->GetActualHeight();

			float g_TexelSize[2] = { 1.0f / float( width ), 1.0f / float( height ) };

			pShaderAPI->SetPixelShaderConstant( 0, g_TexelSize );
			
			DECLARE_DYNAMIC_VERTEX_SHADER( ssao_vs30 );
			SET_DYNAMIC_VERTEX_SHADER( ssao_vs30 );

			DECLARE_DYNAMIC_PIXEL_SHADER( ssao_ps30 );
			SET_DYNAMIC_PIXEL_SHADER( ssao_ps30 );

			float samples = cr_ssao_samples.GetInt();
			float contrast = cr_ssao_contrast.GetFloat();
			float radius = cr_ssao_radius.GetFloat();
			float bias = cr_ssao_bias.GetFloat();
			float biasoffset = cr_ssao_bias_offset.GetFloat();
			float illuminf = cr_ssao_illuminfluence.GetFloat();
			float zfar = cr_ssao_zfar.GetFloat();
			float znear = cr_ssao_znear.GetFloat();

			pShaderAPI->SetPixelShaderConstant( 1, &samples );
			pShaderAPI->SetPixelShaderConstant( 2, &radius );
			pShaderAPI->SetPixelShaderConstant( 3, &bias );
			pShaderAPI->SetPixelShaderConstant( 4, &illuminf );
			pShaderAPI->SetPixelShaderConstant( 5, &contrast );
			pShaderAPI->SetPixelShaderConstant( 6, &znear );
			pShaderAPI->SetPixelShaderConstant( 7, &zfar );
			pShaderAPI->SetPixelShaderConstant( 8, &biasoffset );
		}
		Draw();
	}
END_SHADER

Then goes 'ssao_combine.cpp':

//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "BaseVSShader.h"
#include "ssao_combine_vs30.inc"
#include "ssao_combine_ps30.inc"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


BEGIN_VS_SHADER_FLAGS( SSAOCOMBINE, "Help for SSAO Combine", SHADER_NOT_EDITABLE )
	BEGIN_SHADER_PARAMS
		SHADER_PARAM( SSAOTEXTURE, SHADER_PARAM_TYPE_TEXTURE, "_rt_SSAO", "SSAO" )
		SHADER_PARAM( BASETEXTURE, SHADER_PARAM_TYPE_TEXTURE, "_rt_FullFrameFB", "Framebuffer" )
	END_SHADER_PARAMS

	SHADER_INIT_PARAMS()
	{
		SET_FLAGS2( MATERIAL_VAR2_NEEDS_FULL_FRAME_BUFFER_TEXTURE );
	}

	SHADER_FALLBACK
	{
		return 0;
	}

	SHADER_INIT
	{
		if( params[SSAOTEXTURE]->IsDefined() )
		{
			LoadTexture( SSAOTEXTURE );
		}

		if( params[BASETEXTURE]->IsDefined() )
		{
			LoadTexture( BASETEXTURE );
		}
	}

	SHADER_DRAW
	{
		SHADOW_STATE
		{
			pShaderShadow->EnableDepthWrites( false );

			pShaderShadow->VertexShaderVertexFormat( VERTEX_POSITION, 1, 0, 0 );
			
			DECLARE_STATIC_VERTEX_SHADER( ssao_combine_vs30 );
			SET_STATIC_VERTEX_SHADER( ssao_combine_vs30 );

			pShaderShadow->EnableTexture( SHADER_SAMPLER0, true );
			pShaderShadow->EnableTexture( SHADER_SAMPLER1, true );

			DECLARE_STATIC_PIXEL_SHADER( ssao_combine_ps30 );
			SET_STATIC_PIXEL_SHADER( ssao_combine_ps30 );
		}

		DYNAMIC_STATE
		{
			BindTexture( SHADER_SAMPLER0, SSAOTEXTURE, -1 );
			BindTexture( SHADER_SAMPLER1, BASETEXTURE, -1 );

			DECLARE_DYNAMIC_VERTEX_SHADER( ssao_combine_vs30 );
			SET_DYNAMIC_VERTEX_SHADER( ssao_combine_vs30 );

			DECLARE_DYNAMIC_PIXEL_SHADER( ssao_combine_ps30 );
			SET_DYNAMIC_PIXEL_SHADER( ssao_combine_ps30 );
		}
		Draw();
	}
END_SHADER

Then goes 'gaussian_blur_3x3.cpp':

//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "BaseVSShader.h"
#include "gauss_blur_3x3_vs30.inc"
#include "gauss_blur_3x3_ps30.inc"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

BEGIN_VS_SHADER_FLAGS( GAUSSBLUR3X3, "Help for GAUSSBLUR3X3", SHADER_NOT_EDITABLE )
	BEGIN_SHADER_PARAMS
		SHADER_PARAM( BASETEXTURE, SHADER_PARAM_TYPE_TEXTURE, "_rt_FullFrameFB", "Render Target" )
	END_SHADER_PARAMS

	SHADER_FALLBACK
	{
		return 0;
	}

	SHADER_INIT
	{
		if( params[BASETEXTURE]->IsDefined() )
		{
			LoadTexture( BASETEXTURE );
		}
	}

	SHADER_DRAW
	{
		SHADOW_STATE
		{
			pShaderShadow->EnableDepthWrites( false );
			pShaderShadow->EnableAlphaWrites( true );

			pShaderShadow->EnableTexture( SHADER_SAMPLER0, true );

			pShaderShadow->VertexShaderVertexFormat( VERTEX_POSITION, 1, 0, 0 );
	
			// Render targets are pegged as sRGB on POSIX, so just force these reads and writes
			bool bForceSRGBReadAndWrite = IsOSX() && g_pHardwareConfig->CanDoSRGBReadFromRTs();
			pShaderShadow->EnableSRGBRead( SHADER_SAMPLER0, bForceSRGBReadAndWrite );
			pShaderShadow->EnableSRGBWrite( bForceSRGBReadAndWrite );
			
			DECLARE_STATIC_VERTEX_SHADER( gauss_blur_3x3_vs30 );
			SET_STATIC_VERTEX_SHADER( gauss_blur_3x3_vs30 );

			DECLARE_STATIC_PIXEL_SHADER( gauss_blur_3x3_ps30 );
			SET_STATIC_PIXEL_SHADER( gauss_blur_3x3_ps30 );
		}

		DYNAMIC_STATE
		{
			BindTexture( SHADER_SAMPLER0, BASETEXTURE, -1 );

			ITexture *src_texture = params[BASETEXTURE]->GetTextureValue();

			int width = src_texture->GetActualWidth();
			int height = src_texture->GetActualHeight();

			float dX = 1.0f / width;
			float dY = 1.0f / height;

			float fTexelSize[2] = { dX, dY };

			pShaderAPI->SetPixelShaderConstant( 0, fTexelSize );

			DECLARE_DYNAMIC_VERTEX_SHADER( gauss_blur_3x3_vs30 );
			SET_DYNAMIC_VERTEX_SHADER( gauss_blur_3x3_vs30 );

			DECLARE_DYNAMIC_PIXEL_SHADER( gauss_blur_3x3_ps30 );
			SET_DYNAMIC_PIXEL_SHADER( gauss_blur_3x3_ps30 );
		}
		Draw();
	}
END_SHADER

Okay, now you can compile the shader dll. If you are lucky it should compile with no errors.

Thats all with shader dll, the only thing left is to create new render target for ssao pass and apply the shader material to screen.

Creating render target for SSAO:

Open your usual mod source code and in 'view.cpp' navigate to:

void CViewRender::Init( void )

and add this code to the end of that function:

		int iW, iH;
		materials->GetBackBufferDimensions( iW, iH );
		materials->BeginRenderTargetAllocation();
		materials->CreateNamedRenderTargetTextureEx( "_rt_SSAO", iW, iH, RT_SIZE_NO_CHANGE, materials->GetBackBufferFormat(), 
													MATERIAL_RT_DEPTH_NONE, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_RENDERTARGET, 0 );
		materials->EndRenderTargetAllocation();

Then go to function:

CViewRender::RenderView

and put the following code right before line 'if ( mat_viewportupscale.GetBool() && mat_viewportscale.GetFloat() < 1.0f )'

	// Crossroads devtest SSAO
	if( cr_ssao_enable.GetBool() )
	{
		DoSSAO( view );
	}

Then navigate to line:

ConVar r_worldlistcache( "r_worldlistcache", "1" );

And right after it add this:

//Crossroads devtest
ConVar cr_ssao_enable( "cr_ssao_enable", "1", FCVAR_ARCHIVE );

You can name it whatever you want.

Then go to 'viewpostprocess.cpp' and add the following code to very end of the file:

// Crossroads devtest
void DoSSAO( const CViewSetup &view )
{
	UpdateScreenEffectTexture();

	CMatRenderContextPtr pRenderContext( materials );

	ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
	int nSrcWidth = pSrc->GetActualWidth();
	int nSrcHeight = pSrc->GetActualHeight();
	
	ITexture *pSSAOTex = materials->FindTexture( "_rt_SSAO", TEXTURE_GROUP_RENDER_TARGET );

	int nViewportWidth = 0;
	int nViewportHeight = 0;
	int nDummy = 0;
	pRenderContext->GetViewport( nDummy, nDummy, nViewportWidth, nViewportHeight );

	Rect_t	DestRect;
	DestRect.x = 0;
	DestRect.y = 0;
	DestRect.width = nSrcWidth;
	DestRect.height = nSrcHeight;

	IMaterial *pSSAOCalcMat = materials->FindMaterial( "dev/ssao", TEXTURE_GROUP_OTHER, true );

	if ( pSSAOCalcMat == NULL )
		return;

	// ssao in Crossroads consist of 3 separate passes:
	// 1. ssao calculation (outputs white texture with black shadows)
	pRenderContext->PushRenderTargetAndViewport( pSSAOTex );
	pRenderContext->DrawScreenSpaceRectangle(
		pSSAOCalcMat,
		0, 0, nViewportWidth, nViewportHeight,
		0, 0, nSrcWidth-1, nSrcHeight-1,
		nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() );
	pRenderContext->PopRenderTargetAndViewport();
	
	// 2. blurring that texture to avoid grain
	if( cr_ssao_blur.GetBool() )
	{
		IMaterial *pSSAOBlurMat = materials->FindMaterial( "dev/ssaoblur", TEXTURE_GROUP_OTHER, true );

		pRenderContext->DrawScreenSpaceRectangle(
			pSSAOBlurMat,
			0, 0, nViewportWidth, nViewportHeight,
			0, 0, nSrcWidth-1, nSrcHeight-1,
			nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() );
		
		pRenderContext->CopyRenderTargetToTextureEx( pSSAOTex, 0, &DestRect, NULL );
	}

	// 3. combine what we got with framebuffer texture
	if( cr_ssao_combine.GetBool() )
	{
		IMaterial *pSSAOCombineMat = materials->FindMaterial( "dev/ssao_combine", TEXTURE_GROUP_OTHER, true );
		
		pRenderContext->DrawScreenSpaceRectangle(
			pSSAOCombineMat,
			0, 0, nViewportWidth, nViewportHeight,
			0, 0, nSrcWidth-1, nSrcHeight-1,
			nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() );
	}
}

Then find the line:

static ConVar mat_postprocess_y( "mat_postprocess_y", "1" );

and right after it add:

//crossroads devtest
ConVar cr_ssao_blur( "cr_ssao_blur", "1" );
ConVar cr_ssao_combine( "cr_ssao_combine", "1" );

Then in 'viewpostprocess.h' after line starting with 'void DumpTGAofRenderTarget( ...' add:

void DoSSAO( const CViewSetup &viewSet ); // crossroads devtest

Also, don't forget to precache our materials (ssao.vmt, ssaoblur.vmt and ssao_combine.vmt). In 'viewrender.cpp' find the line "CLIENTEFFECT_REGISTER_BEGIN( PrecachePostProcessingEffects )" and after it add:

//crossroads devtest
CLIENTEFFECT_MATERIAL( "dev/ssao" )
CLIENTEFFECT_MATERIAL( "dev/ssaoblur" )
CLIENTEFFECT_MATERIAL( "dev/ssao_combine" )

And... I believe that's all. Try to compile the dll, everything should be fine.

Now the last step.

Creating 'ssao', 'ssao_combine' and 'ssaoblur' materials:

Go to your mod folder then materials/dev and create there three files: 'ssao.vmt', 'ssao_combine.vmt' and 'ssaoblur.vmt'.

Open 'ssao.vmt' and paste the following in it:

"ssao"
{
	"$basetexture" "_rt_FullFrameFB"
}

Then paste this in 'ssao_combine.vmt':

"ssaocombine"
{
	"$ssaotexture" "_rt_SSAO"
	"$basetexture" "_rt_FullFrameFB"
}

And finally, 'ssaoblur.vmt':

"GAUSSBLUR3X3"
{
	"$basetexture" "_rt_SSAO"
}

Okay, if I didn't forget anything you will have SSAO in your mod now.

That's all, feel free to ask me questions and suggest fixes for tutorial. Hope you will find it useful!

Post comment Comments
Secret_Coming
Secret_Coming - - 1,057 comments

just wondering, source engine is on c++?

Reply Good karma Bad karma+1 vote
GamerDude27
GamerDude27 - - 946 comments

Pretty much, yeah.

Reply Good karma Bad karma+3 votes
GamerDude27
GamerDude27 - - 946 comments

Would it be possible to make a video tutorial on this?

Reply Good karma Bad karma0 votes
DmitRex Author
DmitRex - - 183 comments

Tell me where you stuck and I will try to help.

Reply Good karma+1 vote
GamerDude27
GamerDude27 - - 946 comments

Could you please help me?

Steamcommunity.com

Reply Good karma Bad karma+1 vote
Eaglesg
Eaglesg - - 212 comments

Great tutorial !
I think that in CViewRender::DrawSSAOPass it is "ITexture *pRenderTarget = materials->FindTexture" and not "ITexture *pRenderTarget = materials->FindMaterial" ;)

Reply Good karma Bad karma+1 vote
DmitRex Author
DmitRex - - 183 comments

Yeah, fixed it. Thanks.

Reply Good karma+2 votes
celisej567
celisej567 - - 39 comments

where did you get CViewRender::DrawSSAOPass

Reply Good karma Bad karma+1 vote
x64L
x64L - - 5 comments

There are lots of errors mate, can you try helping me fixing them?

Reply Good karma Bad karma+1 vote
DmitRex Author
DmitRex - - 183 comments

Thanks for help to Alek from Project 9 dev team who pointed me at some errors. I fixed everything and can say that tutorial is 100% working now.

Reply Good karma+1 vote
Guest
Guest - - 688,627 comments

I get a black screen when i load into a map and if i turn off ssao_combine i get a white screen but i can see some ssao happening.

Reply Good karma Bad karma0 votes
xESWxDEATH
xESWxDEATH - - 1 comments

I do as well

Reply Good karma Bad karma+1 vote
tuxxi
tuxxi - - 1 comments

Hey DmitRex,

Thanks for the tutorial. Unfortunately, I can't quite get this to work in my mod. The SSAO Shader doesn't appear to be doing anything, turning off ssao_combine reveals a completely white texture.

Do you have any idea what I did wrong?
Thanks
tuxxi

Reply Good karma Bad karma+2 votes
3037
3037 - - 487 comments

hello. I dunno if anybody alive here, but. I need help, didnt compile. But i make all right

Reply Good karma Bad karma+1 vote
Strelok_Freeman
Strelok_Freeman - - 66 comments

I do everything twice, i check everything and still get this errors + white diagonal lines on screen. Please, help me!

Error: Material "dev/ssao" uses unknown shader "SSAO"
Error: Material "dev/ssaoblur" uses unknown shader "GAUSSBLUR3X3"
Error: Material "dev/ssao_combine" uses unknown shader "SSAOCOMBINE"

Reply Good karma Bad karma+1 vote
Strelok_Freeman
Strelok_Freeman - - 66 comments

It works but only for sourcemods, vanilla hl2 ignore my custom game_shader_dx9.dll file. Any tips?

Reply Good karma Bad karma+1 vote
SoykaFelish
SoykaFelish - - 4 comments

Hmm.. black screen.

Reply Good karma Bad karma+1 vote
Guest
Guest - - 688,627 comments

This comment is currently awaiting admin approval, join now to view.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: