• Register
Post article RSS Articles

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!

[Source Engine] Implementing Basic SSAO Shader | Part 1

[Source Engine] Implementing Basic SSAO Shader | Part 1

Client Side Coding Tutorial 19 comments

In this tutorial I will show you (not teach) how to implement basic SSAO shader into Source Engine.

Test tutorial Hammer "Propper"

Test tutorial Hammer "Propper"

Props Modelling Tutorial

Test tutorial Hammer "Propper" Static and Dunamic model

Post comment Comments  (0 - 10 of 92)
todeblyad
todeblyad

great, another dead mod

Reply Good karma Bad karma+1 vote
Guest
Guest

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

SweetRamona
SweetRamona

Is this Mod still being worked on?

Reply Good karma Bad karma+3 votes
RocketSurgery
RocketSurgery

is it still coming along?

Reply Good karma Bad karma+1 vote
SweetRamona
SweetRamona

Question: Will this possibly be released this year or next?

Reply Good karma Bad karma+6 votes
Ferapod Creator
Ferapod

Multiplayer will be coming out this year. will be an update on the wishes of the players

Reply Good karma+4 votes
Guest
Guest

The models and textures were very good. I didn't understand what was happening because I don't speak Russian. and there was a vent (2nd door on the right, 3rd door on the left) that was too low to enter and there were no other entrances into the room.

Reply Good karma Bad karma+1 vote
Ferapod Creator
Ferapod

do not worry, the demo version of the style shows, that's all

Reply Good karma+3 votes
AlekseyNeoJapan
AlekseyNeoJapan

This is still being worked on!! yessss

Reply Good karma Bad karma+2 votes
RobosergTV
RobosergTV

Побыстрее бы )

Reply Good karma Bad karma+2 votes
Post a comment

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

X