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 replies on “SSAO in stage 3D (source added)”
Where did you get Batman model?
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!
Great !
do you think about release the code source ? 🙂
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.
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!
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.
Thanks a lot!
Your post is always impressive for me.
I will try again with ur helper class.
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.
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
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:)
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:)