You should now have a source texture produced by the previous pass described on page 2. You will now need to set a rendertarget of RGBA type again, and set your engine up to render into it. We’ll be supplying the previous pass texture (going to call this the data texture from here on out) into another pair of shaders along with some custom vertex data. What we get out from this will be a greyscale image that you can then use in your lighting pipeline for your ambient occlusion term.
I’m going to skip the algorithm description itself on how we actually use all these values to calculate the ambient occlusion term, but if you want to read more on the subject, the developers of StarCraft2 have produced an excellent paper describing their SSAO method along with other great stuff. My SSAO implementation matches theirs quite closely, albeit with getting my data from a far more streamlined place. I’ve also improved the self-occlusion artefact reduction somewhat.
So, lets get some code together. The first thing we need to do is make a clipspace quad that covers the screen. We’ll be running our SSAO shader over this shortly, but first we need to set it all up. In the vertices for our quad, we need rays to the corners of the viewing frustrum. As these get interpolated in the PS, we’ll have a ray for our current pixel position that points right at it from the camera’s position. Here’s how I set these vertices up. Note that the “Verts” parameter points to a locked dynamic VB in my engine, but in all honesty you can just a bit of system memory for this and draw using the drawprim *_UP methods, there’s only 4 vertices to upload.
// HomoHack is the pixel centre offset. It defaults to 0.5, but can be overridden with 0.35
// when doing point sampling as that works better. For this case we are doing point sampling so pass 0.35
tF32 HomogeniseX (tINT Xc,tF32 HomoHack)
{
// Convert incoming screen x coordinate into -1 to +1 homogeneous space
return ((Xc-HomoHack)*(InvWidthF*2.0f))-1.0f;
}
tF32 HomogeniseY (tINT Yc,tF32 HomoHack)
{
// Convert incoming screen y coordinate into -1 to +1 homogeneous space
return 1.0f-((Yc-HomoHack)*(InvHeightF*2.0f));
}
tVOID BuildFrustrumQuad (tVOID *Verts,RZView* View)
{
// This code gets coordinates in clipspace, accounting for DX9 pixel centre issue etc
// Width and Height are the screen dimensions
tF32 X1=HomogeniseX(0,0.35F);
tF32 X2=HomogeniseX(Width,0.35F);
tF32 Y1=HomogeniseY(0,0.35F);
tF32 Y2=HomogeniseY(Height,0.35F);
// Note that aspect ratio is cunningly being ignored
// Do some math to project the frustrum corners
tF32 FOV=View->GetCamera()->GetFOV();
tF32 Near=View->GetNearClip();
tF32 Far=View->GetFarClip();
tF32 NearH=2*RZMath::Tan(FOV)*Near;
tF32 NearW=NearH;
tF32 FarH=2*RZMath::Tan(FOV)*Far;
tF32 FarW=FarH;
tF32 NearX=NearW*0.5F;
tF32 NearY=NearH*0.5F;
tF32 FarX=FarW*0.5F;
tF32 FarY=FarH*0.5F;
// I'm using a custom vertex format for this that contains
// Position: FLOAT3
// UV: USHORT2
// Ray: FLOAT4
tDATAVERT* Ptr=(tDATAVERT*)Verts;
// TL
Ptr[0].Pos.x=X1;
Ptr[0].Pos.y=Y1;
Ptr[0].Pos.z=0;
// I'm not including code for PackTexCoord. If you don't work
// this way with packed data, just use a float2 instead
Ptr[0].UV=RZCore::Core->VertexMgr->PackTexCoord(0,0);
Ptr[0].Vec.x=-FarX;
Ptr[0].Vec.y=FarY;
Ptr[0].Vec.z=Far;
Ptr[0].Vec.w=0;
// TR
Ptr[1].Pos.x=X2;
Ptr[1].Pos.y=Y1;
Ptr[1].Pos.z=0;
Ptr[1].UV=RZCore::Core->VertexMgr->PackTexCoord(1,0);
Ptr[1].Vec.x=FarX;
Ptr[1].Vec.y=FarY;
Ptr[1].Vec.z=Far;
Ptr[1].Vec.w=0;
// BL
Ptr[2].Pos.x=X1;
Ptr[2].Pos.y=Y2;
Ptr[2].Pos.z=0;
Ptr[2].UV=RZCore::Core->VertexMgr->PackTexCoord(0,1);
Ptr[2].Vec.x=-FarX;
Ptr[2].Vec.y=-FarY;
Ptr[2].Vec.z=Far;
Ptr[2].Vec.w=0;
// BR
Ptr[3].Pos.x=X2;
Ptr[3].Pos.y=Y2;
Ptr[3].Pos.z=0;
Ptr[3].UV=RZCore::Core->VertexMgr->PackTexCoord(1,1);
Ptr[3].Vec.x=FarX;
Ptr[3].Vec.y=-FarY;
Ptr[3].Vec.z=Far;
Ptr[3].Vec.w=0;
}