One final thing I wanted to achieve with smoke effects is to port the 2D simulation to 3D. Early results in pure AS3 were pretty disappointing, so again I turned to Pixel Bender and its speedy goodness. Still, the main obstacle was of course performance. The first step was to optimize the original 2D version by doing more in a single filter, throwing out some repetitive calculations performed per pixel, etc. And obviously, adding an extra dimension means more calculations. This system is grid-based, so the amount of nodes increases a lot! As a result, the grid size is reduced to 18x32x12 , which is leaning dangerously close to the realm of visual crud. Still, as an experiment, I consider it acceptable, and it’s using some tricks that might be worth sharing. Perhaps someone can do something better with this :)
I’ll explain a bit on how it works since I usually forget that part (if you’re not interested, just scroll down a bit). The actual fluid solver is based on the work of Jos Stam, and a lot has been written on the subject already.
3D textures
Pixel Shaders for 3D graphics such as GLSL have support for 3D textures. Since Pixel Bender is made for 2D, we have to find a different way to map a 3D grid to work with Pixel Bender. The solution is so straightforward that it’s hardly worth mentioning, but since I haven’t seen it done before, I will explain it anyway. All slices of the texture/grid along the z-axis are simply placed next to eachother, as pictured below:
Analogous to the representation of a 2D texture in a 1D array, the 2D coordinates are simply found by coord2D(x,y,z) = (x+gridWidth*z, y), or in a 1D array as coord1D(x, y, z) = (x+y*gridWidth*gridDepth+z*gridWidth). Told you it’s pretty pointless :)
Rendering
My first thought was to consider the grid as a set of voxels and use a raymarching algorithm to render it, although I knew that would end up being way too slow. Luckily, I found a much simpler solution that worked well enough. Depending on the world axis that is closest to the view vector, the grid is sliced up in bitmaps aligned to the grid’s local axes. Looking at the “box” head on, it’s simply divided in planes (parallel to the local XY plane) from back to front, when looking more from the left, it’s divided from left to right (parallel to the local YZ plane) and so forth. Using these slices, they’re simply placed on the stage as Bitmap objects using FP10’s 3D functionality. Luckily, the lack of depth sorting does not seem like such an important issue to create the illusion of a volume filled with smoke.
Finally
I’ll close up with some good news: I think I’m done with smoke for now! It’s not a promise, though… Moving on:
- The demo – The box is invisible at first, in the middle of the screen. Fill it up with smoke and it’ll become visible.
- The source – To get performance up a bit, there’s some guerilla-style coding. Enter at own risk!
Thanks to Joa for running his AS3V-tool on it – I can’t wait for it to go public! I don’t think I caught all violations, tho ;)
I don’t see anything :)
Hmm, the Flash isn’t loading (might be a version issue, it needs 10.0.22) or it is but you can only see the background? In that case, move the mouse around the middle of the flash movie and it should appear.
Cool example :)
The effect is fantastic, and the real-time performance is very good on my old P4.
It is my opinion though, that you are writing this code for the wrong crowd. Don’t take my word for it, but a plugin like this for Cinema 4D or 3DS Max, written in C (or C.O.F.F.E.E., Python, whatever), would be very welcome, and probably could make you a lot wealthier than doing this in Actionscript or Pixelbender (although this might have some use in After Effects maybe) :)
Naturally this Navier-Stokes magic isn’t for the faint of heart, and i must say i’m deeply impressed with your results.
@Willem: Thanks! I think there already are plenty of fluid/smoke simulators for those 3D packages, which do a much better job at it, too :) Aside from that, I just like doing it for Flash, just to see how far I can push the platform and try to make it perform in real-time. Of course the use is limited for now, but as Flash gets faster with future versions, some day we could see things like these on a larger scale. That’s what I keep telling myself anyway :D
Looks cool but it’s gonna take some more demos till we’ll have a 3D smoke for production.
Meanwhile we got to stick with faked smoked http://www.twistanddrink.com/en/island/ (roll over cottage)
Really nice example. Agree with David and Milan, but great effect nonetheless
@Milan: True enough, for now particle-based fake smoke is the way to go for good production values. Even most video games still use that approach (tho it slowly seems to be changing :) )
@Tejas: Thanks. Just for the record: David, that’s me, the author of the post ;)
I have just imported the class Fluids3DPB.as inside flash 10, but I got three migration issue:
Warning: 1090: Migration issue: The onKeyDown event handler is not triggered automatically by Flash Player at run time in ActionScript 3.0. You must first register this handler for the event using addEventListener ( ‘keyDown’, callback_handler).
Warning: 1090: Migration issue: The onMouseDown event handler is not triggered automatically by Flash Player at run time in ActionScript 3.0. You must first register this handler for the event using addEventListener ( ‘mouseDown’, callback_handler).
How do I have to modiffy this function ?
private function onKeyDown(event : KeyboardEvent) : void
{
switch (event.keyCode) {
case Keyboard.SPACE:
for (var i : int = 0; i < BUFFER_SIZE_4; ++i)
_velocityDensityField[i] = 0;
break;
case Keyboard.ENTER:
_container.rotationX = _container.rotationY = _container.rotationZ;
break;
}
}
Ah, good old Flash IDE warnings! It just does that because the listeners registered to the KeyboardEvent.KEY_DOWN etc have the same name as they used to have in AS2. They’re registered with addEventListener, so you shouldn’t pay attention to those warnings. If you really want to get rid of them, rename the method to handleKeyDown or so, and replace all the references to it with the new name.
Thanks for the answer David…
I am not sure I understand
I have to replace
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown) ?
or
private function onKeyDown(event : KeyboardEvent) : void ?
In fact, you shouldn’t have to do anything, unless you really want to get rid of the warning. In that case replace the “onKeyDown” with “handleKeyDown” (or anything) in both lines.
Hope that helps!