The project is created on the engine, Source, immediately tell you that it has no connection with the world of coca Half-life, but it was a project inspired by Half life.

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:

	ITexture *depthOld = materials->FindTexture( "_rt_FullFrameDepth", TEXTURE_GROUP_RENDER_TARGET );
		static int flags = TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_RENDERTARGET;
		if ( depthOld )
			flags = depthOld->GetFlags();
 
		int iW, iH;
		materials->GetBackBufferDimensions( iW, iH );
		materials->BeginRenderTargetAllocation();
		materials->CreateNamedRenderTargetTextureEx( "_rt_SSAO", iW, iH, RT_SIZE_NO_CHANGE, materials->GetBackBufferFormat(), 
													MATERIAL_RT_DEPTH_SHARED, flags, 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 )
{
	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;

	pRenderContext->CopyRenderTargetToTextureEx( pSSAOTex, 0, &DestRect, NULL );

	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->DrawScreenSpaceRectangle(
		pSSAOCalcMat,
		0, 0, nViewportWidth, nViewportHeight,
		0, 0, nSrcWidth-1, nSrcHeight-1,
		nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() );
	
	//save this pass so we can apply additional post process effects to current ones
	pRenderContext->CopyRenderTargetToTextureEx( pSSAOTex, 0, &DestRect, NULL );
	
	// 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_SSAO"
}

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!

DmitRex,

Oak Gear programmer

Comments
Secret_Coming
Secret_Coming

just wondering, source engine is on c++?

Reply Good karma Bad karma+1 vote
GamerDude27
GamerDude27

Pretty much, yeah.

Reply Good karma Bad karma+3 votes
GamerDude27
GamerDude27

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

Reply Good karma Bad karma0 votes
DmitRex Author
DmitRex

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

Reply Good karma+1 vote
GamerDude27
GamerDude27

Could you please help me?

Steamcommunity.com

Reply Good karma Bad karma+1 vote
Eaglesg
Eaglesg

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

Yeah, fixed it. Thanks.

Reply Good karma+2 votes
x64L
x64L

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

Reply Good karma Bad karma+1 vote
DmitRex Author
DmitRex

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

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

I do as well

Reply Good karma Bad karma+1 vote
tuxxi
tuxxi

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
Post a comment
Sign in or join with:

Only registered members can share their thoughts. So come on! Join the community today (totally free - or sign in with your social account on the right) and join in the conversation.

Follow Report Profile
Icon
Crossroads Episode I
Platforms
Windows, Mac, Linux, VR
Developer & Publisher
Oak Gear
Engine
Source
Contact
Send Message
Homepage
Oakgear.net
Release date
Game watch
Follow
Tutorial
Browse
Tutorials
Report
Report
Share
Related Games
Crossroads Episode I
Crossroads Episode I First Person Shooter
Lost Squad
Lost Squad First Person Shooter
Related Engines
Source
Source Commercial
Related Groups
Oak Gear
Oak Gear Developer & Publisher