Friday, 26 September 2008

Pseudo gamma correction

As several lights add up in the scene, several accumulations happen in the background, flattening average contrast and luminance. In order to have the colorful scene back, we can tweak the accumulation buffer a bit using the GL_MULT function of glAccum API, and passing as multiplication factor the result of 1+γ*numberOfLightSources.

Here's a screenshot:


compared to the previous images, also with γ=.2 the improvement is pretty evident.

Wednesday, 24 September 2008

Put on the red light

Who said lights are just white? Introducing an almost coherent red light.

More then ever, hard shadows and light's cone edge appear too unnatural. Some kind of linear filtering is required here, maybe a PCF for shadows and a bit of blur for cone's boundaries.

Tuesday, 23 September 2008

N lights are better than one

Here we go! Accumulation buffers allowed an easy drawing of multiple light sources and relative shadows.

The trickiest aspect was the balance of exposition: every light features its accumulation factor and the light contributions are supposed to be under 1, in order to avoid overexposition.

It could be a cool effect, anyway. =)

Sunday, 21 September 2008

Accumulation buffers

Shadow mapping is working correctly, but only one (spot!) light at a time.

Nature is very different, so engine has to be changed a bit. How can one sum the contributions of several different lights, placed around the scene? Accumulation buffers can do the trick! These supplementary buffers will sum the RGB values of our different takes from any single light and return the final composite bitmap.

This screenshot shows a typical application of accumulation buffers: motion blur. With a very minimal modification of original dev-cpp's GLUT example we can achieve this interesting effect.

In pseudocode:

for (;;) {
glClear (GL_COLOR_BUFFER_BIT | GL_ACCUM_BUFFER_BIT)
const range = 10
for i=1 to range do {
render(i);
glAccum(GL_ACCUM, 1.0f/range)
}
glAccum(GL_RETURN, 1)
swapBuffers
}


Three brief comments.

First, the range means "how many different takes we wanna use for motion blur overlaps". This example is not optimized in any way and renders 10 times the scene for each pass.

Second, the render is bound to the i variable. The meaning is that we want to simulate movement, so it makes no sense to render 10 times the same static scene. In the example, I did the objects rotate "i degrees".

Third, the accumulation command takes a floating point value, which tells how much of the RGBA values we want to sum up in the buffer. Given that these values are clamped in the [0,1] range, and we're going to sum 10 shots, we can set these values in a way that the final count is 1: 10*(1/10) = 1.

The same philosophy shall work for multiple lights: we sum up the n contributions of n lights.

Friday, 19 September 2008

Young 3D engines suffer acne

As the engine grows up, I refactor some routine. Cleaner code allows me to use some higher level function, like a 3Ds objects loader. And here appears a well-know issue: z-fighting.

Even on a surface closest to the light source, you will always discover minor differences in the values associated with the R texture coordinate(...). This can result in "surface acne"(...). You can mitigate this problem by applying a depth offset when rendering in the shadow map:

...
glEnable (GL_POLYGON_OFFSET_FILL);
glPolygonOffset (factor, 0.0f);
...

(...)A balance need to be struck when it comes to polygon offset usage.

from OpenGL superbible 4th edition by Richard Wright (Wesley).
That balance is not so easy to achieve; as Nvidia says in its papers, it's really matter of art.

Wednesday, 17 September 2008

When shadows and light combine


Now it's not just primitives and smooth shading! Multitexturing is part of OpenGL since 1.2.1 specification and allows direct textures combining. Putting the shadow map into a separate texture unit (with glActiveTexture) permit us to spread it on our already textured polygons.

I don't know if this is the most performant way to achieve a correct result, but it works fine! =)

Tuesday, 9 September 2008

I can't see the light!


Now we're talking.

And guess what? Nothing was wrong. I tried running the code on my work's ATI x1300 and shadows correctly appeared.

I suppose my SIS 650 messes up with alpha testing. Disappointing.

And there's another useful information: OpenGL do not work through remote desktop. If you try to connect via terminal services, you'll see just flat shaded polygons. What I can't see is the logic behind this. Everything is computed on "server" side and I don't think there's a particular boost of network performances not shading and texturing. I'm pretty sure RDP protocol does a run length encoding optimization, but a spinning cube requires more updates on client's side than a static decaled teapot. Mysteries.

Monday, 8 September 2008

Neverending story


Something weird is going on here..! Shadows are supposed to be dark! Oo"

..at least they appear now!

"Cool and quiet" search box

It's cool (and a bit web2.0) to have a search box which works as soon as we cease typing, and displays new contents in the very page we're navigating.

The concept of "typing ceased" is tricky anyway; it's not cool (and not quiet!) to have the page refreshed soon after the onKeyUp event, because maybe we're still typing the whole word.

So, it takes a bit of javascripting:


function lookupStuff(flag) {
if (flag) {
do_things();
} else {
clearTimeout(timerid);
timerid = setTimeout("lookupStuff(true)", 1000);
}
}


If we bind this to the onKeyUp event of an text input form, we have do_things() called after 1 second, which is enough for typing anything; clearing the timer is useful for "jittered" key pressing.

This snippet has been tested successfully on every browser, and appears pretty usable.

Friday, 5 September 2008

Reinventing the wheel

Matrix multiplication is fundamental in computer graphics, expecially when you're dealing with shadow maps and texture projections.

Unfortunately, there are no standard functions for matrix multiplication, so it was time for DIY. Here comes my snippet:



float* matrixProduct(float* A, float* B) {

// we have to allocate space for a 4x4 floats matrix
float* pointer= (float *) calloc(sizeof(float), 16);

// in order to compute the resulting matrix, we have to use two nested fors
for (int r=0; r<4; r++) {
for (int c=0; c<4; c++) {

// actual multiplications
for (int i=0; i<4; i++) {
pointer[r*4+c] = pointer[r*4+c] + A[r*4+i]*B[c+i*4];
}
}
}

return
pointer;

}


This simple function returns a pointer to an handful 4x4 array of floats, without memory flaws. Maybe this snippet will be helpful for someone in the future.

Thursday, 4 September 2008

Reload a page with javascript

There's a lot of ways for completely reloading a web page via javascript.

Only a few permit to clean up any selection done to option forms.

Moreover, it turns out that there's only one robust way to do it, without weird confirmations popups (Internet Explorer) or strange behaviours (Firefox):

window.location.href=window.location.pathname;

Of course, works seemlessly in Chrome.

Monday, 1 September 2008

Work in progress


The answer to the previous question was: "it does work". I don't know how the framework I'm working on exactly works, but it does strange things with depth buffer.

It seems I'm at a good point. Shadow mapping consists in three steps (two, if the video cards supports the GL_ARB_shadow_ambient extension):
  1. depth buffer acquisition from light's point of view
  2. a "dim light" rendering from camera's point of view
  3. full light rendering w/ shadow map depths comparison
Screenshots prove that everything seems to works correctly.

New question is: why doesn't the shadow map show up..? ^^"
Damn!