10/17/07

Real Time Ambient Occlusion of Spheres

Well, with SSAO (see article below), this trick is a little redundant. However, I'll put it here for my own records as much as anything else.

I was looking for a trick to squeeze ambient occlusion in real time into 1k. It turns out that the occlusion of a sphere on another sphere can be calculated analytically. Similarly a sphere and plane can occlude each other and the result is analytic. This means it can be calculated using a simple equation for every pixel on the screen.

Fine, so a fragment shader would do?
Yes. And no. I couldnt get it to below 1k. 4k yes, but 1k no. So I started to examine the equations. To my amazement, traditional OpenGL lighting can be set up to model ambient occlusion of spheres on spheres and planes on spheres!

Inside each sphere we place a dark light, a light with negative colour! This is a point light source. Then we set the light attenuation, how the light fades with respect to distance, to exactly that required by the ambient occlusion equations. Lastly we assume all spheres have radius of 1, which simplifies the setup and equations.

Now draw all the spheres cast darkness onto each other correctly. Oh one trick to watch for is to switch off the light inside a sphere when drawing that sphere - a sphere does not occlude itself.

Here is the image:



Not bad. Notice all the shadows. We need one light per sphere and OpenGL allows us 8 lights so we can have 8 spheres. The spheres can also occlude a plane:


Lastly, the plane can also occlude the spheres. This is trickier. In essence one light source is used for the plane and as each sphere is drawn, this light source is moved onto the plane directly under the sphere. I used a point light source which is incorrect (the plane is infinite and requires a directional light but this is not then attenuated correctly). Here is the final result:



Notice the dark patches at the bottom of spheres now from the plane below them. Lastly here is an image where I've used the technique for real. The image was created for Rebels demogroup, but I dont think they will use it. M:ET gave permission to use the Rebels logo here, originally by H2O. The background image is a greeble image created by alien^pdx. I used two passes, the first to draw ambient occlusion, the second to draw the glossy surface. It could be done in one pass, but really 7 or 8 spheres are not hard for PCs now.



(Click any image to see a larger version)

Finally some code to implement the dynamic sphere occlusion...


const float white[] = { 0.75, 0.75, 0.75, 1 };
const float black[] = { 0, 0, 0, 1 }; //doubles as position of lights
const float darklight[] = { -2.0f, -2.0f, -2.0f, 2.0f };

void drawAOSpheres(){
int i;

// we skip the first light as this has its own initial rules
// by ignoring it, the code gets smaller...half size infact
glDisable(GL_LIGHT0);
// everything in the scene will receive white ambient light
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, white);
// set the lights in position and switch them on
for (i=1; i < 8; i++) {
moveSphere(i);
// lights are set with NEGATIVE colour!
glLightfv(GL_LIGHT0+i,GL_DIFFUSE,darklight);
// black is 0,0,0,1, which are the values we need for
// a point light at the origin.
glLightfv (GL_LIGHT0+i, GL_POSITION, black);
// a quadratic falloff of light power is needed
// attenuation is 1/(constant + linear*dist + quadratic*dist)
// we want 1,0,1, default is 1,0,0
glLightf(GL_LIGHT0+i, GL_QUADRATIC_ATTENUATION, 1.0f);
glEnable (GL_LIGHT0+i);
}
// draw the spheres being careful not to draw a sphere occluding itself
for (i=1; i < 8; i++) {
//glColor4f (1.0f-(i/5.0f),0.7f+(i/20.0f),1.0f,1.0f);
// a sphere should not occlude itself so switch off its own light
glDisable (GL_LIGHT0+i);
moveSphere(i);
mySphere(1,60); //gluSphere(q,1,60,60);
glEnable (GL_LIGHT0+i);
}
}

13 lines of code and some constants!!

5 comments:

  1. Anonymous17/10/07

    wow, a brand new article every day? awesome

    pohar

    ReplyDelete
  2. :-) not really pohar. I had some stuff to say, which I've said. Things slow down now.

    ReplyDelete
  3. Cool trick, auld! I didn't know it works to create lights with negative color. Does this work even with older hardware?

    ReplyDelete
  4. Hi tonic. Yep it works on anything.. negative colours are only clamped at fragment creation time (ie after all the lighting part of the pipeline). So its all good solid legal opengl 1.0 :-)

    ReplyDelete
  5. Anonymous6/11/07

    Yeah O_o--b cool article!
    :aha

    ReplyDelete