• View media
  • View media
  • View media
  • View media
  • View media
  • View media
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:

	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

[Source Engine] Implementing Basic SSAO Shader | Part 1

[Source Engine] Implementing Basic SSAO Shader | Part 1

Client Side Coding Tutorial 18 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

Comments  (0 - 10 of 91)
JuleRose
JuleRose

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
TωısтeđHeαdαcħe
TωısтeđHeαdαcħe

Perhaps rather than releasing pictures, you should get your header image working.

Reply Good karma Bad karma-1 votes
JuleRose
JuleRose

Who cares? Honestly...

Reply Good karma Bad karma+2 votes
RobosergTV
RobosergTV

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

Reply Good karma Bad karma+2 votes
SPY-maps
SPY-maps

I am also VERY happy to see that this one is still alive!
Although as a mapper/modder myself i do understand that it takes quit a lot of time and effort to make a new update here. So sometimes we forget a bit to make a new one.

That said, i watched the 2 new movies above and i LOVE them. The first one is really amazing!! I first thought that it was a animated texture, that wall texture with that water fall. But when i looked carefully i noticed that the leaves and trees also move, so it seems that a movie is projected to the wall (?). Whatever, it looks amazing!

And i said it before in other comments, but i keep saying it, i love the overall Borderlands look of this game. So, glad to have seen this new update and please keep developing, i keep tracking!
And much succes with your Greenlight action and everything that can come out of that.

a fellow modder,
Leon

Reply Good karma Bad karma+6 votes
Ferapod Creator
Ferapod

thank you for your kind words.
1 - the big screen with a waterfall and leaves it - the animation texture. absolutely true.
I'm glad you follow my work.
soon will show new updates, where my friend programmer will introduce many new things for gameplay.
blogged about the project to a friend.
Good luck and success

Reply Good karma+4 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
Share
Style
Theme
Sci-Fi
Players
Single Player
Project
Indie
Embed Buttons
Link to Crossroads Episode I by selecting a button and using the embed code provided more...
Crossroads Episode I
Statistics
Last Update
Watchers
548 members
Articles
3
You may also like
Star Wars: Republic Commando
Star Wars: Republic Commando First Person Shooter
Star Wars Battlefront II
Star Wars Battlefront II First Person Shooter
Half-Life: Opposing Force
Half-Life: Opposing Force First Person Shooter
Unreal Tournament
Unreal Tournament First Person Shooter
Crysis 2
Crysis 2 First Person Shooter
Aliens: Colonial Marines
Aliens: Colonial Marines First Person Shooter