Deconstructing Crysis' SSAO Shader

Started by
5 comments, last by jstroh 15 years, 10 months ago
pixout_cl Deferred_SSAO_Pass_PS(vert2fragSSAO IN)
{
  pixout_cl OUT;
  
	// define kernel
	const half step = 1.f - 1.f/8.f;
	half n = 0;
	const half fScale = 0.025f; 
	const half3 arrKernel[8] =
	{
		normalize(half3( 1, 1, 1))*fScale*(n+=step),
		normalize(half3(-1,-1,-1))*fScale*(n+=step),
		normalize(half3(-1,-1, 1))*fScale*(n+=step),
		normalize(half3(-1, 1,-1))*fScale*(n+=step),
		normalize(half3(-1, 1 ,1))*fScale*(n+=step),
		normalize(half3( 1,-1,-1))*fScale*(n+=step),
		normalize(half3( 1,-1, 1))*fScale*(n+=step),
		normalize(half3( 1, 1,-1))*fScale*(n+=step),
	};

	// create random rot matrix
	half3 rotSample = tex2D(sRotSampler4x4_16, IN.ScreenTC.zw).rgb;
	rotSample = (2.0 * rotSample - 1.0);

  half fSceneDepth = tex2D( sceneDepthSampler, IN.ScreenTC.xy ).r;  	  

	// range conversions
  half fSceneDepthM = fSceneDepth * PS_NearFarClipDist.y;  

	half3 vSampleScale = SSAO_params.zzw
		* saturate(fSceneDepthM / 5.3f) // make area smaller if distance less than 5 meters
    * (1.f + fSceneDepthM / 8.f ); // make area bigger if distance more than 32 meters

  float fDepthRangeScale = PS_NearFarClipDist.y / vSampleScale.z * 0.85f;
	
	// convert from meters into SS units
	vSampleScale.xy *= 1.0f / fSceneDepthM;
	vSampleScale.z  *= 2.0f / PS_NearFarClipDist.y;

  float fDepthTestSoftness = 64.f/vSampleScale.z;

	// sample
  half4 vSkyAccess = 0.f;
  half4 arrSceneDepth2[2];      
  half3 vIrrSample;
  half4 vDistance;
  float4 fRangeIsInvalid;

  const half bHQ = (GetShaderQuality()==QUALITY_HIGH);

  float fHQScale = 0.5f;

  for(int i=0; i<2; i++)
  {    
    vIrrSample = mirror(arrKernel[i*4+0], rotSample) * vSampleScale;		
    arrSceneDepth2[0].x = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    if (bHQ)
    {
      vIrrSample.xyz *= fHQScale;
      arrSceneDepth2[1].x = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    }

    vIrrSample = mirror(arrKernel[i*4+1], rotSample) * vSampleScale;		
    arrSceneDepth2[0].y = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    if (bHQ)
    {
      vIrrSample.xyz *= fHQScale;
      arrSceneDepth2[1].y = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    }

    vIrrSample = mirror(arrKernel[i*4+2], rotSample) * vSampleScale;		
    arrSceneDepth2[0].z = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    if (bHQ)
    {
      vIrrSample.xyz *= fHQScale;
      arrSceneDepth2[1].z = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    }

    vIrrSample = mirror(arrKernel[i*4+3], rotSample) * vSampleScale;		
    arrSceneDepth2[0].w = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    if (bHQ)
    {
      vIrrSample.xyz *= fHQScale;
      arrSceneDepth2[1].w = tex2D( sceneDepthSamplerAO, IN.ScreenTC.xy + vIrrSample.xy ).r + vIrrSample.z;  
    }

    float fDefVal = 0.55f;

    for(int s=0; s<(bHQ ? 2 : 1); s++)
    {
      vDistance = fSceneDepth - arrSceneDepth2; 
      float4 vDistanceScaled = vDistance * fDepthRangeScale;
      fRangeIsInvalid = (saturate( abs(vDistanceScaled) ) + saturate( vDistanceScaled ))/<span class="cpp-number">2</span>;  
      vSkyAccess += lerp(saturate((-vDistance)*fDepthTestSoftness), fDefVal, fRangeIsInvalid);
    }
  }

  OUT.Color = dot( vSkyAccess, (bHQ ? <span class="cpp-number">1</span>/<span class="cpp-number">16</span>.0f : <span class="cpp-number">1</span>/<span class="cpp-number">8</span>.0f)*<span class="cpp-number">2</span>.<span class="cpp-number">0</span> ) - SSAO_params.y; <span class="cpp-comment">// 0.075f</span>
  OUT.Color = saturate(lerp( <span class="cpp-number">0</span>.9f, OUT.Color, SSAO_params.x ));
  
	<span class="cpp-keyword">return</span> OUT;
}





</pre></div><!–ENDSCRIPT–>

Yes I know about http://www.gamedev.net/community/forums/topic.asp?topic_id=474166&PageSize=25&WhichPage=1.

I'm having a bit of trouble understanding the whole shader so I thought I'd post it here and see if anyone would mind explaining it line by line.

EDIT: er way arrKErnel is a circle isn't it… when the "box corners" get reflected about a random normal.

I see that arrKernel is a box centered at the origin that gets shrunk a couple times and normalized and then a random normal is read in for reflection. Then the depthbuffer output is read in and taken out of [0-1].

Past that I'm kind of lost. Do I have it right so far?

<!–EDIT–><span class=editedby><!–/EDIT–>[Edited by - jstroh on May 29, 2008 1:11:47 PM]<!–EDIT–></span><!–/EDIT–>
Advertisement
Once you've sampled the depth buffer, you reconstruct a view-space or world-space position from that depth value. Starting from this position, you then use your random vectors (what you have stored in arrKernel) reflected off the random normal to "visit" a few locations in relatively close proximity to the original position. For each of these new locations, you figure out the screen-space position and use that to once again sample the depth buffer. With this depth value the *actual* position at this pixel is determined and compared to the original sample position.

In basic terms what you're doing is for each pixel you're reaching our blindly and saying "is there anything nearby?". If many samples are close by you darken that pixel, since it's more likely to be occluded. If many samples far away, you don't darken the pixel.
Quote:Original post by MJP
Once you've sampled the depth buffer, you reconstruct a view-space or world-space position from that depth value. Starting from this position, you then use your random vectors (what you have stored in arrKernel) reflected off the random normal to "visit" a few locations in relatively close proximity to the original position. For each of these new locations, you figure out the screen-space position and use that to once again sample the depth buffer. With this depth value the *actual* position at this pixel is determined and compared to the original sample position.

In basic terms what you're doing is for each pixel you're reaching our blindly and saying "is there anything nearby?". If many samples are close by you darken that pixel, since it's more likely to be occluded. If many samples far away, you don't darken the pixel.


That's the clearest explanation I've heard so far, thankyou ;o

Ok now for some specifics of this shader:

(do you know why they do this?)

rotSample = (2.0 * rotSample - 1.0); 

(what about here, this makes little sense to me :P)

	half3 vSampleScale = SSAO_params.zzw		* saturate(fSceneDepthM / 5.3f) // make area smaller if distance less than 5 meters    * (1.f + fSceneDepthM / 8.f ); // make area bigger if distance more than 32 meters  float fDepthRangeScale = PS_NearFarClipDist.y / vSampleScale.z * 0.85f;		// convert from meters into SS units	vSampleScale.xy *= 1.0f / fSceneDepthM;	vSampleScale.z  *= 2.0f / PS_NearFarClipDist.y;  float fDepthTestSoftness = 64.f/vSampleScale.z;
Quote:Original post by jstroh
(do you know why they do this?)
rotSample = (2.0 * rotSample - 1.0);
This one is easy [smile]

As it is a texture sample, the values of the half3 will all lie in the 0 to 1 range. They are simply remapping that range to -1 to 1
Richard 'ViLiO' Thomasv.net | Twitter | YouTube
Quote:Original post by ViLiO
Quote:Original post by jstroh
(do you know why they do this?)
rotSample = (2.0 * rotSample - 1.0);
This one is easy [smile]

As it is a texture sample, the values of the half3 will all lie in the 0 to 1 range. They are simply remapping that range to -1 to 1


I'm not retarded I swear! Oo
but but but, no one wants to examine it? :(
I was looking at their shader again today and noticed:
// original depth targetsampler2D sceneDepthSampler = sampler_state{ Texture = $ZTarget; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; AddressU = Clamp; AddressV = Clamp;};// downscaled depth targetsampler2D sceneDepthSamplerAO = sampler_state{ Texture = $ZTargetScaled; MinFilter = POINT; MagFilter = POINT; MipFilter = POINT; AddressU = Clamp; AddressV = Clamp;};


So they actually accumulate from a downsampled depthbuffer and reconstruct points from the original depth buffer.


vert2fragSSAO Deferred_SSAO_Pass_VS(app2vertShadow IN){	vert2fragSSAO OUT;#ifndef OPENGL  	OUT = (vert2fragSSAO)0; #endif	OUT.HPosition = mul(CompMatrix, IN.Position);	OUT.ScreenTC.xy = IN.baseTC.xy;	OUT.ScreenTC.zw = IN.baseTC.xy*g_VS_ScreenSize.xy/4;	OUT.WS_ViewVect = IN.viewDir;	return OUT;}


[Edited by - jstroh on June 9, 2008 5:05:41 PM]

This topic is closed to new replies.

Advertisement