Whilst there is still the on going problem with the Shadow Renderer, I didn’t want to hold the project up too long so I thought I’d implement Screen Space Ambient Occlusion (SSAO).
The last post had a link to the latest source code, so I would go ahead and download this before continuing, then we are all on the same page.
We’ll start with some new modifications to the “DeferredRenderer” class. Firstly, we need a new Scene RenderTarget and also a RenderTargetBinding array.
RenderTarget2D sceneRT;
RenderTargetBinding[] renderTargets;
Also, we can create our new “SSAORenderer” class object:
SSAORenderer ssaoRenderer;
Instantiate the new Scene RenderTarget, the RenderTargetBinding array and the SSAORenderer in the constructor:
sceneRT = new RenderTarget2D(device, backbufferWidth, backbufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
renderTargets = new RenderTargetBinding[] { colorRT, normalRT, depthRT };
ssaoRenderer = new SSAORenderer(device, content,
device.PresentationParameters.BackBufferWidth,
device.PresentationParameters.BackBufferHeight);
We will need to set this in the “CombineGBuffer” method:
void CombineGBuffer(ref RenderTarget2D shadowOcclusion)
{
device.SetRenderTarget(sceneRT);
finalEffect.Parameters["colorMap"].SetValue(colorRT);
finalEffect.Parameters["lightMap"].SetValue(lightRT);
finalEffect.Parameters["shadowMap"].SetValue(shadowOcclusion);
finalEffect.Parameters["halfPixel"].SetValue(halfPixel);
finalEffect.CurrentTechnique.Passes[0].Apply();
fullscreenQuad.Draw();
}
This will set the Scene RenderTarget so that we can use it in the new “SSAORenderer” class that we’ll create shortly. We can also use this new RenderTarget for Post Processing.
The last change that we need to make to the “DeferredRenderer” class is in the “Draw” method, and that is calling the “SSAORenderer Draw” method after the “CombineGBuffer” method:
public void Draw(GameTime gameTime)
{
SetGBuffer();
ClearGBuffer();
sceneManager.Draw();
ResolveGBuffer();
DrawDepth();
var shadowOcclusion = shadowRenderer.Draw(device, depthRT, sceneManager);
DrawLights();
CombineGBuffer(ref shadowOcclusion);
ssaoRenderer.Draw(device, renderTargets, sceneRT, sceneManager, null);
DrawDebug(ref shadowOcclusion);
}
As you can see we are using our new Scene RenderTarget. This is so we can pass a final deferred rendered scene into the SSAO Renderer ready for the Ambient Occlusion application.
Let’s create our new “SSAORenderer” class. In the “Renderers” folder, create a new class called “SSAORenderer”. Add the usual namespaces:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using ProjectVanquish.Core;
Add the following “Effect” declarations:
Effect ssao, ssaoBlur, composer;
Add the following RenderTargets:
RenderTarget2D ssaoRT, blurRT;
Just a few more declarations which we’ll wrap up in one code block:
float sampleRadius, distanceScale;
Texture2D randomNormals;
QuadRenderer fullscreenQuad;
Now, we can create our constructor:
public SSAORenderer(GraphicsDevice device, ContentManager content, int Width, int Height)
{
// Load SSAO effects
ssao = content.Load<Effect>("Shaders/SSAO/SSAO");
ssaoBlur = content.Load<Effect>("Shaders/SSAO/Blur");
composer = content.Load<Effect>("Shaders/SSAO/Final");
// Create RenderTargets
ssaoRT = new RenderTarget2D(device, Width, Height, false, SurfaceFormat.Color, DepthFormat.None);
blurRT = new RenderTarget2D(device, Width, Height, false, SurfaceFormat.Color, DepthFormat.None);
fullscreenQuad = new QuadRenderer(device);
randomNormals = content.Load<Texture2D>("Textures/RandomNormal");
// Set Sample Radius to Default
sampleRadius = 0;
// Set Distance Scale to Default
distanceScale = 0;
}
You’ll see that we are loading some Effects, but we’ll come back to these once we’ve finished with the class. We’ll need to declare 4 new methods:
void BlurSSAO(GraphicsDevice device)
{
}
void Compose(GraphicsDevice device, RenderTarget2D scene, RenderTarget2D output)
{
}
public void Draw(GraphicsDevice device, RenderTargetBinding[] gBuffer, RenderTarget2D sceneRT, SceneManager scene, RenderTarget2D output)
{
}
void RenderSSAO(GraphicsDevice device, RenderTargetBinding[] gBuffer, SceneManager scene)
{
}
Starting with the “Draw” method, we’ll call these new methods in order so we build our Ambient Occlusion onto the Scene RenderTarget:
public void Draw(GraphicsDevice device, RenderTargetBinding[] gBuffer, RenderTarget2D sceneRT,
SceneManager scene, RenderTarget2D output)
{
device.BlendState = BlendState.Opaque;
device.DepthStencilState = DepthStencilState.Default;
device.RasterizerState = RasterizerState.CullCounterClockwise;
RenderSSAO(device, gBuffer, scene);
BlurSSAO(device);
Compose(device, sceneRT, output);
}
So you can see that we are rendering the SSAO first, then applying a blur and then composing the final scene. Ok, we’ll start by adding the code for the “RenderSSAO” scene:
void RenderSSAO(GraphicsDevice device, RenderTargetBinding[] gBuffer, SceneManager scene)
{
device.SetRenderTarget(ssaoRT);
device.Clear(Color.White);
device.Textures[2] = gBuffer[2].RenderTarget;
device.SamplerStates[2] = SamplerState.PointClamp;
device.Textures[3] = randomNormals;
device.SamplerStates[3] = SamplerState.LinearWrap;
// Calculate Frustum Corner of the Camera
Vector3 cornerFrustum = Vector3.Zero;
cornerFrustum.Y = (float)Math.Tan(Math.PI / 3.0 / 2.0) * scene.Camera.FarClip;
cornerFrustum.X = cornerFrustum.Y * scene.Camera.AspectRatio;
cornerFrustum.Z = scene.Camera.FarClip;
// Set SSAO parameters
ssao.Parameters["Projection"].SetValue(scene.Camera.ProjectionMatrix);
ssao.Parameters["cornerFustrum"].SetValue(cornerFrustum);
ssao.Parameters["sampleRadius"].SetValue(sampleRadius);
ssao.Parameters["distanceScale"].SetValue(distanceScale);
ssao.Parameters["GBufferTextureSize"].SetValue(new Vector2(ssaoRT.Width, ssaoRT.Height));
// Apply Effect
ssao.CurrentTechnique.Passes[0].Apply();
// Draw
fullscreenQuad.Draw();
}
As mentioned before, we are setting a lot of Effect parameters, but we will come back to the Effects later. On to the “BlurSSAO” method:
void BlurSSAO(GraphicsDevice device)
{
device.SetRenderTarget(blurRT);
device.Clear(Color.White);
device.Textures[3] = ssaoRT;
device.SamplerStates[3] = SamplerState.LinearClamp;
// Set Blur parameters
ssaoBlur.Parameters["blurDirection"].SetValue(Vector2.One);
ssaoBlur.Parameters["targetSize"].SetValue(new Vector2(ssaoRT.Width, ssaoRT.Height));
// Apply Effect
ssaoBlur.CurrentTechnique.Passes[0].Apply();
// Draw
fullscreenQuad.Draw();
}
Now we just need to fill out the “Compose” method:
void Compose(GraphicsDevice device, RenderTarget2D sceneRT, RenderTarget2D output)
{
device.SetRenderTarget(output);
device.Clear(Color.White);
device.Textures[0] = sceneRT;
device.SamplerStates[0] = SamplerState.LinearClamp;
device.Textures[1] = blurRT;
device.SamplerStates[1] = SamplerState.LinearClamp;
// Set Composition Parameters
composer.Parameters["halfPixel"].SetValue(new Vector2(1.0f / ssaoRT.Width, 1.0f / ssaoRT.Height));
// Apply Effect
composer.CurrentTechnique.Passes[0].Apply();
// Draw
fullscreenQuad.Draw();
}
That’s the last of the class code. I’ll add a new post to include the Shaders to make the posts more manageable.