Game Development Community

Screen Space Ambient Occlusion

by Lorne McIntosh · in Torque Game Engine Advanced · 09/09/2008 (7:08 pm) · 114 replies

Hey everyone,

I've developed a Screen Space Ambient Occlusion shader pack for TGEA. It will apply real time ambient occlusion onto any dif, dts, or animating dts in your scene. Check out the video on my website. I'm selling the shader pack through my site to anyone who wants to have great looking ambient occlusion in their levels! Enjoy.

www.ubiqvisuals.com/index.php?option=com_content&view=article&id=46%C2%A0

www.ubiqvisuals.com/images/ssao_sample.jpg

About the author

Ubiq Visuals is a software and creative content developer for the entertainment industry. Our vision is to provide inspiration and the tools for soon-to-be game designers and creative minds of all ages.

#21
09/14/2008 (6:12 pm)
Nice!
#22
09/14/2008 (6:59 pm)
I'll add to what Pat said that integration with this pack worked easily. My engine was already heavily modified including AFX and the integration worked great. $30 well spent.
#23
09/14/2008 (7:18 pm)
I have to agree with the above, great customer service too! :)
#24
09/14/2008 (11:45 pm)
I purchase this software a couple days ago, but i think this software is worth the $30, i will be posting some of my game screen shots soon.
#25
09/15/2008 (8:25 am)
I don't really need what SSAO offers, but I'm very interested in how to properly gather a depth map for adding effects to my waterblocks. Would this be a good example to look at for learning how to do this? $30 would be a steal compared to the time it would take to learn this myself.
#26
09/15/2008 (8:31 am)
Stefan,
I actually implemented two methods for depth gathering this year, and it is well worth the $30. I would have to bribe someone else with at least $30 worth of beer to implement this for me, so that's how I measure it ;)
#27
09/15/2008 (10:14 am)
Hehe, good description. Just one final question, how come lower-poly geometry gets accented when you're using a pixel shader? I thought that was the whole purpose of using a pixel shader. (Edit:) Ie, to avoid artifacts because of only having vertex information.

Thanks guys.
#28
09/15/2008 (1:34 pm)
Hey Lorne, that is really cool! Great work!
#29
09/15/2008 (2:00 pm)
Any chance that this could work on a PS 2.0 card?
#30
09/15/2008 (3:58 pm)
@Stefan
Like Pat said, the SSAO Kit is a good demonstration of gathering a depth map (for Interiors, DTSs etc). It doesn't collect depth information from Terrain blocks though (which you'll probably want for your waterblock effects), so you'll need to add that part yourself.

It's not really that lower poly geometry is accented when using a pixel shader. It's just that it's accented when using *this* pixel shader. In the case of a low-poly cylinder (8 faces, say) SSAO will "highlight" each of the 8 edges because they stick out, and thus collect more light. It's technically correct, but can ruin the effect that it's a perfectly round cylinder if that's what you wanted. You can minimize this effect by increasing the radius parameter, but of course it's a trade-off.

@Mark
Unless you're a shader optimization expert, I'd say there's very little chance of SSAO ever running under PS 2.0 with acceptable results. I tried for a couple of days, (and almost met the 64 instruction limit), but there's also texture-sampling limits... and worse, some sort of "chaining" limit on them. I couldn't figure out how to "un-chain" my texture sampling. It seems like a very complex topic.

@Everyone
Thanks for the great feedback! It brightens my day :)
#31
09/16/2008 (2:52 am)
I'd say the screenshot from Pat Wilson should sell a few copies. Great demonstration shot.
#32
09/16/2008 (3:47 am)
It sure sold one to me.

This is awesome! The code's clean, well commented, paragraphed, easy to read and learn from. I got this primarily for the depth map, but the way SSAO is done is a really good example on how to create other effects that are based on gathering a depth map - like depth of field or velocity based motion blur. Thank you. Well worth the price.
#33
09/16/2008 (8:38 am)
Thanks again. I grabbed the pack.
#34
09/16/2008 (1:17 pm)
I brought the kit but I'm not a very good programmer. The instruction are easy to follow but I get three error and I just can't figure them out, if someone could let me know what I'm doing wrong it would be a great help to me!

thanks in advanced

renderInstMgr.cpp
..\..\..\..\..\engine\source\renderInstance\renderInstMgr.cpp(142) : error C2059: syntax error : '}'
..\..\..\..\..\engine\source\renderInstance\renderInstMgr.cpp(142) : error C2143: syntax error : missing ';' before '}'
..\..\..\..\..\engine\source\renderInstance\renderInstMgr.cpp(142) : error C2059: syntax error : '}'
..\..\..\..\..\engine\source\renderInstance\renderInstMgr.cpp(147) : error C2143: syntax error : missing ';' before '{'
..\..\..\..\..\engine\source\renderInstance\renderInstMgr.cpp(147) : error C2447: '{' : missing function header (old-style formal list?)
Creating browse information file...
Microsoft Browse Information Maintenance Utility Version 8.00.50727
Copyright (C) Microsoft Corporation. All rights reserved.





[
#35
09/16/2008 (1:20 pm)
Code]//-----------------------------------------------------------------------------
#include "renderInstMgr.h"
#include "materials/sceneData.h"
#include "materials/matInstance.h"
#include SHADER_CONSTANT_INCLUDE_FILE
#include "materials/customMaterial.h"
#include "sceneGraph/sceneGraph.h"
#include "gfx/primbuilder.h"
#include "platform/profiler.h"
#include "terrain/sky.h"
#include "renderElemMgr.h"
#include "renderObjectMgr.h"
#include "renderInteriorMgr.h"
#include "renderMeshMgr.h"
#include "renderRefractMgr.h"
#include "renderTranslucentMgr.h"
#include "renderGlowMgr.h"
#include "util/safeDelete.h"
#include "renderDepthMgr.h" //Ubiq SSAO
#include "renderSSAOMgr.h" //Ubiq SSAO

#define POOL_GROW_SIZE 2048
#define HIGH_NUM ((U32(-1)/2) - 1)

RenderInstManager gRenderInstManager;

//*****************************************************************************
// RenderInstance
//*****************************************************************************

//-----------------------------------------------------------------------------
// calcSortPoint
//-----------------------------------------------------------------------------
void RenderInst::calcSortPoint( SceneObject *obj, const Point3F &camPosition )
{
if( !obj ) return;

// This this sort point calculation changes much, visit TSMesh::render, as it
// calculates a sort point per mesh instead of using the object
const Box3F& rBox = obj->getObjBox();
Point3F objSpaceCamPosition = camPosition;
obj->getRenderWorldTransform().mulP( objSpaceCamPosition );
objSpaceCamPosition.convolveInverse( obj->getScale() );
sortPoint = rBox.getClosestPoint( objSpaceCamPosition );
sortPoint.convolve( obj->getScale() );
obj->getRenderTransform().mulP( sortPoint );
}

// methods
void RenderInst::clear()
{
dMemset( this, 0, sizeof(RenderInst) );
light = gClientSceneGraph->getLightManager()->getDefaultLight();
lightSecondary = light;
visibility = 1.0f;
}


//*****************************************************************************
// Render Instance Manager
//*****************************************************************************
RenderInstManager::RenderInstManager()
{
mWarningMat = NULL;
mInitialized = false;
GFXDevice::getDeviceEventSignal().notify(this, &RenderInstManager::handleGFXEvent);
}

//-----------------------------------------------------------------------------
// destructor
//-----------------------------------------------------------------------------
RenderInstManager::~RenderInstManager()
{
uninit();
}

//-----------------------------------------------------------------------------
// init
//-----------------------------------------------------------------------------
void RenderInstManager::handleGFXEvent(GFXDevice::GFXDeviceEventType event)
{
switch (event)
{
case GFXDevice::deInit :
init();
break;
case GFXDevice::deDestroy :
uninit();
break;
}
}

void RenderInstManager::init()
{
if (!mInitialized)
{
initBins();
mInitialized = true;
}
}

void RenderInstManager::uninit()
{
if (mInitialized)
{
uninitBins();
if (mWarningMat)
{
SAFE_DELETE(mWarningMat);
}
mInitialized = false;
}
}

//-----------------------------------------------------------------------------
// initBins
//-----------------------------------------------------------------------------
void RenderInstManager::initBins()
{
mRenderBins.setSize( GFXBin_NumRenderBins );
dMemset( mRenderBins.address(), 0, mRenderBins.size() * sizeof(RenderElemMgr*) );

mRenderBins[GFXBin_Sky] = new RenderObjectMgr;
mRenderBins[GFXBin_Begin] = new RenderObjectMgr;
mRenderBins[GFXBin_Interior] = new RenderInteriorMgr;
mRenderBins[GFXBin_InteriorDynamicLighting] = new RenderInteriorMgr;
mRenderBins[GFXBin_Mesh] = new RenderMeshMgr;
mRenderBins[GFXBin_Shadow] = new RenderObjectMgr;
mRenderBins[GFXBin_MiscObject] = new RenderObjectMgr;
mRenderBins[GFXBin_Decal] = new RenderObjectMgr;
mRenderBins[GFXBin_Refraction] = new RenderRefractMgr;
mRenderBins[GFXBin_Water] = new RenderObjectMgr;
mRenderBins[GFXBin_Foliage] = new RenderObjectMgr;
mRenderBins[GFXBin_Translucent] = new RenderTranslucentMgr;
mRenderBins[GFXBin_Glow] = new RenderGlowMgr;
mRenderBins[GFXBin_Depth] = new RenderDepthMgr; //Ubiq SSAO
mRenderBins[GFXBin_SSAO] = new RenderSSAOMgr; //Ubiq SSAO
}
#36
09/16/2008 (1:20 pm)
}
//-----------------------------------------------------------------------------
// uninitBins
//-----------------------------------------------------------------------------
void RenderInstManager::uninitBins()
{
for( U32 i=0; i {
if( mRenderBins[i] )
{
delete mRenderBins[i];
mRenderBins[i] = NULL;
}
}
}

//-----------------------------------------------------------------------------
// add instance
//-----------------------------------------------------------------------------
void RenderInstManager::addInst( RenderInst *inst )
{
AssertISV( mInitialized, "RenderInstManager not initialized - call console function 'initRenderInstManager()'" );
AssertFatal(inst != NULL, "doh, null instance");

PROFILE_START(RenderInstManager_addInst);

PROFILE_START(RenderInstManager_selectBin0);

Material* instMat = NULL;
if (inst->matInst != NULL)
instMat = inst->matInst->getMaterial();

/* if (instMat && instMat->renderBin != GFXBin_NumRenderBins)
{
PROFILE_END();
AssertFatal(instMat->renderBin >= 0 && instMat->renderBin < GFXBin_NumRenderBins, "doh, invalid bin, check the renderBin property for this material");
mRenderBins[instMat->renderBin]->addElement(inst);
}
// If it's translucent, stick it in the translucent bin no matter what.
else */ if( inst->translucent || (instMat && instMat->isTranslucent() ) || (inst->visibility < 1.0f) )
{
PROFILE_END();
mRenderBins[GFXBin_Translucent]->addElement( inst );
}
else
{
PROFILE_END();

PROFILE_START(RenderInstManager_selectBin1);
// Deal with all the non-translucent cases.
switch( inst->type )
{
case RIT_Interior:
mRenderBins[GFXBin_Interior]->addElement( inst );
break;

case RIT_InteriorDynamicLighting:
mRenderBins[GFXBin_InteriorDynamicLighting]->addElement(inst);
break;

case RIT_Shadow:
mRenderBins[GFXBin_Shadow]->addElement(inst);
break;

case RIT_Decal:
mRenderBins[GFXBin_Decal]->addElement(inst);
break;

case RIT_Sky:
mRenderBins[GFXBin_Sky]->addElement( inst );
break;

case RIT_Water:
mRenderBins[GFXBin_Water]->addElement( inst );
break;

case RIT_Mesh:
mRenderBins[GFXBin_Mesh]->addElement( inst );
break;

case RIT_Foliage:
mRenderBins[GFXBin_Foliage]->addElement( inst );
break;

case RIT_Begin:
mRenderBins[GFXBin_Begin]->addElement( inst );
break;

default:
mRenderBins[GFXBin_MiscObject]->addElement( inst );
break;
}
PROFILE_END();
}
//Ubiq SSAO: non-translucent instances of RIT_Mesh and RIT_Interior go in the depth bin
if(inst->matInst && (inst->type == RIT_Mesh || inst->type == RIT_Interior))
{
mRenderBins[GFXBin_Depth]->addElement( inst );
}

PROFILE_END();

PROFILE_START(RenderInstMgr_specialBin);

// handle extra insertions
if( inst->matInst )
{
PROFILE_START(RenderInstMgr_custDynCast); // wonder if this will actually be non zero on the profiler...
CustomMaterial* custMat = dynamic_cast( inst->matInst->getMaterial() );
PROFILE_END();
if( custMat && custMat->refract )
{
mRenderBins[GFXBin_Refraction]->addElement( inst );
}

if( inst->matInst->hasGlow() &&
!gClientSceneGraph->isReflectPass() &&
!inst->obj )
{
mRenderBins[GFXBin_Glow]->addElement( inst );
}
}
PROFILE_END();

PROFILE_END();
}//-----------------------------------------------------------------------------
// QSort callback function
//-----------------------------------------------------------------------------
S32 FN_CDECL cmpKeyFunc(const void* p1, const void* p2)
{
const RenderElemMgr::MainSortElem* mse1 = (const RenderElemMgr::MainSortElem*) p1;
const RenderElemMgr::MainSortElem* mse2 = (const RenderElemMgr::MainSortElem*) p2;

S32 test1 = S32(mse1->key) - S32(mse2->key);

return ( test1 == 0 ) ? S32(mse1->key2) - S32(mse2->key2) : test1;
}

//-----------------------------------------------------------------------------
// sort
//-----------------------------------------------------------------------------
void RenderInstManager::sort()
{
PROFILE_START(RIM_sort);

if( mRenderBins.size() )
{
for( U32 i=0; i {
if( mRenderBins[i] )
{
mRenderBins[i]->sort();
}
}
}

PROFILE_END();
}

//-----------------------------------------------------------------------------
// clear
//-----------------------------------------------------------------------------
void RenderInstManager::clear()
{
mRIAllocator.clear();
mXformAllocator.clear();
mPrimitiveFirstPassAllocator.clear();

if( mRenderBins.size() )
{
for( U32 i=0; i {
if( mRenderBins[i] )
{
mRenderBins[i]->clear();
}
}
}
}

//-----------------------------------------------------------------------------
// initialize warning material instance
//-----------------------------------------------------------------------------
void RenderInstManager::initWarnMat()
{
if (mWarningMat)
return;

Material *warnMat = static_cast(Sim::findObject( "WarningMaterial" ) );
if( !warnMat )
{
Con::errorf( "Can't find WarningMaterial" );
}
else
{
SceneGraphData sgData;
GFXVertexPNTTB *vertDef = NULL; // the variable itself is the parameter to the template function
mWarningMat = new MatInstance( *warnMat );
mWarningMat->init( sgData, (GFXVertexFlags)getGFXVertFlags( vertDef ) );
}
}

//-----------------------------------------------------------------------------
// render
//-----------------------------------------------------------------------------
void RenderInstManager::render()
{
GFX->pushWorldMatrix();
MatrixF proj = GFX->getProjectionMatrix();

if( mRenderBins.size() )
{
for( U32 i=0; i {
if( mRenderBins[i] )
{
GFX->beginBinNotify( (GFXRenderBinTypes)i );
mRenderBins[i]->render();
GFX->endBinNotify();
}
}
}

GFX->popWorldMatrix();
GFX->setProjectionMatrix( proj );
}
[/code]
#37
09/16/2008 (3:04 pm)
Hi Kory...

Just one small mistake I think... It should be:

case RIT_Begin:
		  mRenderBins[GFXBin_Begin]->addElement( inst );
		  break;

	  default:
		  mRenderBins[GFXBin_MiscObject]->addElement( inst );
		  break;
	  }

	  //Ubiq: all non-translucent instances go in the depth bin
	  if(inst->matInst && (inst->type == RIT_Mesh || inst->type == RIT_Interior))
	  {
		  mRenderBins[GFXBin_Depth]->addElement( inst );
	  }

	  PROFILE_END();
   }

   PROFILE_START(RenderInstMgr_specialBin);

   // handle extra insertions
   if( inst->matInst )
   {
#38
09/16/2008 (3:44 pm)
You might have noticed the slight graininess of Pat's SSAO screenshot. Nobody's asked about it, but I thought I'd explain anyway :)

It's actually on purpose. The final filtering happens for "free" (no GPU cycles) when DirectX samples the SSAO texture into the final render. By sampling half-way between pixels in the texture, you can get it to blend the pixels automatically. In this case it's just used as a cheat to squeeze out some more performance.

This article explains some of the theory:
http://msdn.microsoft.com/en-us/library/bb219690(VS.85).aspx
#39
09/16/2008 (6:15 pm)
Lorne, there's a method of doing Gausian filtering that uses the same trick. You can do it with 3 samples and 2 passes. It's a very cool trick.
#40
09/22/2008 (7:08 pm)
I'm using a scene with a single dts shape in it (the polysoup test mission that comes with afx for 1.7.1. My FPS drop was from 90 to 45 with SSAO on. Although the effect is *very* nice, it does cause some noticable sluggishness when moving around with the mouse, etc. I've tried all the scenes that come with AFX for 1.7.1 and I am getting the same drop of 50% with SSAO on.

Thoughts?