SSAO in stage 3D (source added)

I had deveoped my own, simple implementation of SSAO (Screen Space Ambient Occlusion)  and thought I would try some other implementations to get better resukts.

I’ll use this post to share video’s of progress and demos to follow as well.

 

Video 1:

SSAO pre blur with 8 samples per pixel (hits the agal instruction limit real fast). Still some kinks to iron out for sure, depth buffer is currently really short to help with the accuracy but should be able to improve this I have been able to encode it across 4 channels now not 1.

Runs pretty quick, 60fps is still achievable.

 

 

 

 

 

related/useful links:

SSAO

http://www.gamerendering.com/category/lighting/ssao-lighting/
http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/a-simple-and-practical-approach-to-ssao-r2753
http://www.john-chapman.net/content.php?id=8
http://blog.nextrevision.com/?p=76
http://www.gamerendering.com/2008/09/28/linear-depth-texture/
http://www.theorangeduck.com/page/pure-depth-ssao

DEPTH ENCODING

http://www.gamedev.net/topic/516146-how-to-encode-the-depth-value-in-a-32bit-rgba-texture/
http://www.gamedev.net/topic/486847-encoding-16-and-32-bit-floating-point-value-into-rgba-byte-texture/
http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/
http://aras-p.info/blog/2007/03/03/a-day-well-spent-encoding-floats-to-rgba/
http://asgl.googlecode.com/svn-history/r490/trunk/ASGL_FP11/src/asgl/shaders/agal/AGALCodec.as
http://www.gamerendering.com/2008/09/25/packing-a-float-value-in-rgba/
http://www.gamedev.net/topic/585330-packing-a-generic-float-into-a-rgba-texture/     <– nice function at the end of this one
http://www.gamedev.net/topic/442138-packing-a-float-into-a-a8r8g8b8-texture-shader/

SOURCE CODE::

Here it is folks, later than I would have liked but I have been busier than badger in Spring.
I have done my best to comment what is going on.
It took a lot of playing around with to get it working and muchos muchos trial and error.

PLEASE do take this and try and make it better I am sure there is room for improvement!!!

Also feel free to ask any questions at all and I will try and answer them.
Cheers Daniel Holden (orange duck for the code from which this is ported)
pure depth ssao
(Although I use a normal texture to save on operations)

 
var flags:String = (smooth) ? "linear" : "nearest";
 
//varying registers
var uv_in:String = "v0";
 
//samplers
var tex_normal:String = "fs1";
var tex_depth:String = "fs2";
var tex_noise:String = "fs3";
 
//float3 - float4
var colour:String = "ft0";
var normal:String = "ft1";
var position:String = "ft2";
var random:String = "ft3";
var ray:String = "ft4";
var hemi_ray:String = "ft5";
 
//float1
var depth:String = "ft6.x";
var radiusDepth:String = "ft6.y";
var occlusion:String = "ft6.z";
var occ_depth:String = "ft6.w";
var difference:String = "ft7.x";
var temp:String = "ft7.y";
var temp2:String = "ft7.z";
var temp3:String = "ft7.w";			//NOT USED
var fallOff:String = "ft0.x";		//NOT USED
 
//float2
var uv:String = "ft0";
 
//constants
var radius:String = "fc0.z";
var scale:String = "fc0.w";
var decoder:String = "fc2.z";
var zero:String = "fc0.x";
var one:String = "fc0.y";
var two:String = "fc1.z";			//NOT USED
var thresh:String = "fc1.w";
var neg_one:String = "fc2.y";		//NOT USED
 
var depth_decoder:String = "fc3.xyzw";
 
var area:String = "fc1.x";			//NOT USED
var falloff:String = "fc1.y";
var total_strength:String = "fc2.x";
var base:String = "fc4.x";
 
var invSamples:String = "fc2.w";
 
 
//SHADER OF DOOOOOOOOOM
AGAL.init();
//sample normal at current fragment, and decode
AGAL.tex(normal, uv_in, tex_normal, "2d", "clamp", flags);//ex ft0, v0, fs0 <2d,wrap,linear> \n"+
AGAL.decode(normal, normal, decoder);
//sample deopth at current fragment, and decode
AGAL.tex(colour, uv_in, tex_depth, "2d", "clamp", flags);//ex ft0, v0, fs0 <2d,wrap,linear> \n"+
AGAL.decodeFloatFromRGBA(depth, colour, depth_decoder);
 
//use this instead if depth is not encoded
//AGAL.mov("oc.a", one);
//AGAL.mov("oc", depth);//col+".xyz");
 
//sample random vector
AGAL.mov(uv, uv_in);
AGAL.mul(uv, uv, scale);					
AGAL.tex(random, uv, tex_noise, "2d", "wrap", flags);
//AGAL.mul(random+".z", random+".z", neg_one);		//not sure if negation needed?
 
//position
AGAL.mov(position+".xy", uv_in+".xy");
AGAL.mov(position+".z", depth);
 
//radiusDepth
AGAL.div(radiusDepth, radius, depth);
 
//occlusion
AGAL.mov(occlusion, zero);					
 
for(var i:int=0; i < 8; i++)
{
	//reflect the random normal against the current normal and size accoring to depth, further should be larger
	AGAL.reflect(ray,"fc"+(5+(i*2)), random);	//could just add but will look crap?
	AGAL.mul(ray, ray, radiusDepth);
 
	//dot the ray against normal
	AGAL.dp3(hemi_ray, ray, normal);
	AGAL.sign(hemi_ray, hemi_ray, temp);
	AGAL.mul(hemi_ray, hemi_ray, ray);
	AGAL.add(hemi_ray, hemi_ray, position+".xyz");
 
	//use position to sample from 
	AGAL.sat(hemi_ray+".xy", hemi_ray+".xy");
	AGAL.tex(colour, hemi_ray+".xy", tex_depth, "2d", "clamp", flags);
	AGAL.decodeFloatFromRGBA(occ_depth, colour, depth_decoder);
 
	//gets the difference in depth between the current depth and sampled depth
	AGAL.sub(difference, depth, occ_depth);				
	AGAL.sge(temp, difference, thresh);	// 1 if difference is bigger than the threshold, 0 otherwise
	AGAL.slt(temp2, difference, falloff);	// 1 if difference is less than the falloff, 0 otherwise
 
	//set difference to range 0 - 1 (and clamp)
	AGAL.div(difference, difference, falloff);
	AGAL.mul(difference, temp, difference);						
	AGAL.mul(difference, temp2, difference);
 
	//accumulate the occusion
	AGAL.add(occlusion, occlusion, difference);
}
//bring back into range 0-1
AGAL.mul(occlusion, occlusion, invSamples);
//apply any multiplier
AGAL.mul(occlusion, occlusion, total_strength);
//add it to a base value
AGAL.add(occlusion, occlusion, base);
//invert and boom headshot
AGAL.sub("oc", one, occlusion);
 
var fragmentShader:String = AGAL.code;

here are the constants used:: (some of them didn’t end up being used so they can be left out – too busy/lazy to do that myself yet)

//uv sample offset
var radius:Number = 0.002;		//you should derive from texture size
//noise uv scale
var scaler : Number = 24;		//much smaller and the noise blocks become more apparent
//unused at the mo
var falloff : Number = 0.05;		//not using this so ignore it / remove it
//unused at the mo
var area : Number = 5;			//not using this so ignore it / remove it
//the depth difference threshold
var depthThresh:Number = 0.0001;
//strength of the effect
var total_strength : Number = 1;
//base value for the effect
var base : Number = 0;
 
var sample_sphere:Vector.<Number> = new Vector.<Number>();
 
sample_sphere.push( 0.5381, 0.1856,-0.4319, 0);
sample_sphere.push( 0.1379, 0.2486, 0.4430, 0);
sample_sphere.push( 0.3371, 0.5679,-0.0057, 0); 
sample_sphere.push(-0.6999,-0.0451,-0.0019, 0);
sample_sphere.push( 0.0689,-0.1598,-0.8547, 0); 
sample_sphere.push( 0.0560, 0.0069,-0.1843, 0);
sample_sphere.push(-0.0146, 0.1402, 0.0762, 0); 
sample_sphere.push( 0.0100,-0.1924,-0.0344, 0);
sample_sphere.push(-0.3577,-0.5301,-0.4358, 0); 
sample_sphere.push(-0.3169, 0.1063, 0.0158, 0);
sample_sphere.push( 0.0103,-0.5869, 0.0046, 0); 
sample_sphere.push(-0.0897,-0.4940, 0.3287, 0);
sample_sphere.push( 0.7119,-0.0154,-0.0918, 0); 
sample_sphere.push(-0.0533, 0.0596,-0.5411, 0);
sample_sphere.push( 0.0352,-0.0631, 0.5460, 0); 
sample_sphere.push(-0.4776, 0.2847,-0.0271, 0);
 
//...
 
context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([0, 1, radius, scaler]));
context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([area, falloff, 2, depthThresh]));
context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, Vector.<Number>([total_strength, -1, 0.5, 1/8]));
context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, Vector <Number>[1/(255*255*255), 1/(255*255), 1/255, 1]);
context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 4, Vector.<Number>([base, 0, 0, 0]));
context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 5, sample_sphere);

Enjoy! On-line demo to follow soonish, and please to feedback with any comments or improvements.

IMPORTANT NOTE:
the values in this shader are very VERY important, tiny tweaks/mistakes can throw the whole thing off so do be carefull and don’t be supprised if the world explodes when you play around with it.

:)

b

11 Responses to “SSAO in stage 3D (source added)”

  1. etucapio says:

    Where did you get Batman model?

  2. bwhiting says:

    Most of my models I get from:

    http://www.turbosquid.com/
    http://artist-3d.com/
    http://archive3d.net/
    and lots of cg forums etc…

    Am not entirely sure about the batman one although I think I found a link to him on youtube!

  3. @Seraf_NSS says:

    Great !
    do you think about release the code source ? :)

  4. bwhiting says:

    Yes I will do! It is by no means complete but I would be happy to release it in its current state. Someone else might be able to tweak it and get it working even better!

    It’s all simple AGAL code at the moment and not that much of it either so should be easy to plug into something else.

    If I get some time today I’ll put up a quick interactive demo and attach the shader source.

  5. GoroMatsumoto says:

    Hi. Thank you for sharing nice article : )

    Which class do you use as AGAL helper?
    asgl? or your original helper class?

    I can’t find “decode” method in each of them.

    Thank you in advance!

  6. bwhiting says:

    So far I have always been using my original AGAL helper, just haven’t updated the google code version for a while.

    You only need to decode if you have encoded the depth across all the channels (rgba), if you have depth stored in just one channel (or the same in all channels) then you can get the value right back out easily (but the precision will not be as good).

    the code for the decode is very simple:
    AGAL.dp4(dest, rgba, constants); //where constants is [1/(256*256*256), 1/(256*256), 1/256, 1] – as long as this works with your chosen encode method

    hope that helps, will update the AGAL helper again soon.

  7. GoroMatsumoto says:

    Thanks a lot!

    Your post is always impressive for me.
    I will try again with ur helper class.

  8. _kihu says:

    Hey, very nice research!

    How do you acquire the depth map for your calculations? As in tex_depth in lines below:

    //sample deopth at current fragment, and decode
    AGAL.tex(colour, uv_in, tex_depth, “2d”, “clamp”, flags);//ex ft0, v0, fs0 \n”+
    AGAL.decodeFloatFromRGBA(depth, colour, depth_decoder);

    I can’t find a way to get to Stage3D’s depth buffer.

  9. bwhiting says:

    At the moment you have to render the scene to a texture and output the depth to that, for any sort of usable accuracy you will need to encode it across more than one channel.

    So in a nutshell, set render to a texture. and render your scene with a shader that outputs the depth. You then use this texture to extract the depth from in any subsequent passes.

    b

  10. _kihu says:

    Thank you for the answer! I’m still struggling to get this to work but your code is very helpful, and it’s also – I think – the only example of SSAO written in AS3. If I ever finish this, I’ll share the results and insights:)

  11. Jason Huang says:

    Hi, I think the position and normal buffer should be in the world view space.
    Before the flash 11.7 comes out, there is no such a thing called “floating texture”.
    So the only thing you can do for the buffers is reconstructing them in the SSAO shader.
    which are useing the quad uv.xy and the depth buffer and an interted projection matrix for
    the position buffer, Normal buffer is much simple though, simply * 2-1 which means make it
    back to range -1 ~ 1;
    I will be finishing the SSAO using Flare3d framework today, adding a blur pass and removing
    the artifacts as much as possible:)

Leave a Reply