Wednesday, February 15, 2012

PSP Water

Today, a post that has nothing to do with my game project :)

I've been asked several times how we do our water effect on the PSP:
http://www.youtube.com/watch?v=QtIHRqt9dV0&feature=g-all-f&context=G2f3806aFAAAAAAAABAA

As the PSP is ageing now and slowly moving to the exclusives realms of homebrew and demo makers, I thought it wouldn't hurt to explain how we do.


It's a bit tricky so I hope I can make myself clear enough :) Here it goes:

The idea is first to have an animated map representing the waves moving. To do that I used some procedural texture generation tool to create each frame of the moving waves. Here is a frame of the animation.



You have to pack all the frames of the animation into a single 512x512 texture (the largest supported by the PSP) and then later do the animation by moving the UV coordinates over each frame.

I choose 102x102 pixels for each frame so i could pack 25 frames of animation into the 512x512 texture (five rows of five frames). I know it sounds weird.

I could have used 16 128x128 frames, but that would have been a short loop @ 60 frames per second.
Or I could have used 64 64x64 frames, but those would have been pretty low def.
Hence the peculiar resolution choice.

Then I used nVidia normal map filter pluggin for photoshop in order to generate a normal texture.


Then you need to quantize your texture using a very specific 256 colors palette. To create the palette, think of it as 16 row of 16 columns. In each column you can store the X component of a normal spanning an entire hemisphere, and on each row you can store the Y component.

Basically that gives you all the possible normals over an hemisphere stored into the palette:


Now when you quantize the texture with this palette, that means each of the texels now uses one of the 256 possible normals. It's a little rough, but when it's moving you barely see the quantization. Here's one frame from the quantized texture (cropped).


To render the reflection effect,you just have for each palette entry (256 entries) to compute the reflected vector from the eye vector around the normal vector that you know is stored in that palette entry (it's just a XY gradient so you can do that procedurally without actually using the palette).

That gives you a new vector that you can use as texture coordinates to sample an hemispheric environment texture. We use a small 32x32 texture of a sky with clouds and sun. The reflection and sampling with bilinear filtering is done in software using the VFPU (implemented by my partner @ Fresh3d Yann Robert), so it's very fast (a few microseconds).

Here's the environment texture:



Last you have to replace the palette entry used by the texture with this sampled color. So basically 256 vector reflection and color sampling per frame.

For every texel using this entry (normal) in the texture the reflected color will be displayed. And voila.

I think it's a neat method because of its fixed cost and no use of multiple pass fill-rate hungry methods.

There's a drawback in that the reflection is not in perspective, it's parallel, but it's hard to say if you don't know it. Furthermore this can be hidden by some billboard fake global, low freq, specular effect on top of it (that's what we do) to modulate the hi-freq wave effect.

Told ya it would be tricky :)

There's a envMap() lua function that applies to a color lookup table (palette) to directly compute all this and do the palette update. For those of you lucky enough to be able to use the engine :) Check this on the engine documentation here: http://freshengine.net/FreshEngineCommunity/documentation/scripting-reference-guide

Look for clut (color lookup table) in the effect section.

3 comments:

  1. I love your posts.

    So, if you apply this trick to any texture, for exaple a 64x64 tileable texture on a sphere... will this look like a real shader?

    Can I use your water texture to test some demos?

    Thanks!

    ReplyDelete
    Replies
    1. Hi Mills and thanks for the kind comments.

      Applying this trick to a sphere won't work. The surface needs to be flat so that the normals from one tile of the texture are pointing in the same direction as the normals from another tile of the same texture. This is what makes the per-index lighting computation work because the result will be valid anywhere on the same surface. Of course the rule can be bent a little, I've done this for a more complex effect with actual wave displacement (might do a post about it sometimes) and it still looks ok because you cannot really figure out if the reflections are 100% correct or not. In the end it's just the effect that matters.

      Yes you can use the textures for your demos no problem.

      Franck.

      Delete
  2. Very interesting effect, you can use it on any surface, like grass, sand... as long as the surface is not too bumpy.

    Thanks a lot

    ReplyDelete