Three.js: FBO Particles in Three.js

Created on 25 Jan 2012  路  18Comments  路  Source: mrdoob/three.js

Just throwing some ideas here to see how we could add use particles simulation using FBO in three.js

Basically what this means is that particles positions are simulated/calculated in webgl shaders, so particles positions never need to be passed via JS, which potentially means its possible to run millions of particles in realtime.

here's 2 poc, but they are not really integrated with three.js yet since things broke when i got into a little mess trying hack the webglrenderer.

http://jabtunes.com/labs/particles/experiments_fbo.html
http://jabtunes.com/labs/particles/experiments_fbo2.html

perhaps we could something like this?

init() {
  particles = new THREE.FBOParticles();
  particles.simulation = new THREE.Composer(); //...
  particles.renderer = new THREE.ShaderMaterial(); //...

  particles.position.set(x,y,z);

  scene.add(particles)
}

render() {
  particles.updateSimulation();
  renderer.render(scene, camera);
}
Suggestion

Most helpful comment

Update of FBO particle experiments links with three.js for anyone who might find examples useful :)

http://nucleal.com
http://mrdoob.com/lab/javascript/webgl/particles/particles_zz85.html
http://mrdoob.com/144/Magic_dust

All 18 comments

It looks like http://www.neveroccurs.com/lab/three.js/gpu_particles/index.html?particles=256 has a better example of how using textures with particles in Three.js. I'm trying to think if that code could be even more simplified..

this has been taking me very long, but here's a sneak preview...

while @alteredq suggested the possibility of a RenderPlugin like ShadowMap, its probably easier to deal with it outside the renderer with a class of its own (like the initial example), since dealing with FBO particles is likely going to be more advanced, it might be better for users to deal with it at a lower level, but with some abstraction. Probably might make it an example instead :)

very nice to see particles experiments from @mrdoob o/*\o
certainly running it on the shaders would give huge performance improvements. its running less than 15fps for me now.
i think i can only release the FBO particle simulation wrapper in next month at least, but it'd be simple to port your examples to it, if you can wait :D

I can wait, I can wait.
I have it a bit on hold, but eventually I'll finish it. I want to have this stuff under control ;)

Here is something I hacked up last December, inspired by @zz85's FBO experiments, that implements GPGPU pretty-much-entirely within the framework of Three.js.

http://dl.dropbox.com/u/63997063/Three/GPGPU/Three.JS.GPGPU.Snowfall.html

The reason I say pretty-much is that I had to hack in two calls to swap textures to get things started.

// the secret sauce, reassign the read-from textures (notice the cross)
materialPos1.uniforms.tPos.texture.__webglTexture = rtTexture2.__webglTexture;
materialPos2.uniforms.tPos.texture.__webglTexture = rtTexture1.__webglTexture;

These calls are made only once -- everything else is straight-forward Three.js.

What the demo does is find the zeroes of a function defined inside the unit cube. That is, it finds the _surfaces_, inside the cube, where the 3D function is zero. Yes, not that interesting, but I was more interested in implementing the GPGPU part. :-)

Here is the most significant thing I learned. You will run into trouble if, inside the GPU, you try to read from, and write to, the same texture within the same thread. For this reason, I had to double-buffer the textures. It works like a charm.


Notebook: Just to elaborate on the double-buffering issue a bit. I used as another source of inspiration this:

http://www.neveroccurs.com/lab/three.js/gpu_particles/index1.html

It turns out that if you hack the neveroccurs code so the particles aren't flying all over the place, but instead move in a deterministic manner, you will see unexpected results, such as particles 'lost' and particles whose positions are not updating every frame. Double-buffering solves that problem.

yup, keeping it under control is important. its sometimes hard to debug and have to be very careful of how texture are swapped for GPGPU. i wonder how many lost nights i spent debugging and it turned that out render to textures were not swapped correctly.

so sometimes, its nice to have a CPU/JS simulation that you can compare results with.

can't remember if it was neveroccurs, or http://gpuattractors.ecoulon.com/ i remember spotting some of those bugs too. but probably didn't matter much as there were so many particles and the visual effect was intended as it was

speaking of old examples, here's one that does simple particle emitter spawing and physics in the shader
http://jabtunes.com/labs/particles/experiments_fbo6b.html
http://jabtunes.com/labs/particles/experiments_fbo6g.html

@zz85

Are you using the webgl inspector should help a lot to debug??

http://benvanik.github.com/WebGL-Inspector/

Here is the most significant thing I learned. You will run into trouble if, inside the GPU, you try to read from, and write to, the same texture within the same thread. For this reason, I had to double-buffer the textures. It works like a charm.

Yup, we learned this in ro.me ;). We have been getting weird artefacts in postprocessing chain.

That's why EffectComposer does double-buffering:

https://github.com/mrdoob/three.js/blob/master/examples/js/postprocessing/EffectComposer.js

What's interesting is that these artefacts were system specific - some systems weren't having any, some were having just slight ones, some very strong ones. I guess this depends on particular GPU / driver.

I remember reading somewhere official (specs or some other materials on Khronos site) that reading and writing to the same texture leads to undefined results. It makes sense, this is the price for high GPU parallelism.

I remember reading somewhere official (specs or some other materials on Khronos site) that reading and writing to the same texture leads to undefined results.

I remember reading (but not understanding) a fairly-detailed explanation here: http://www.gpgpu.org/wiki/FAQ (Section 11).

I certainly understand the concept of concurrency, when two threads interleave their read/write operations to/from a single texel.

What I do not understand, however, is how undefined results can occur when, within a single frame, each texel is read from, and then written to, by just one thread only.

Apparently, that is not what is actually happening, and the above link explains why.

Perhaps some kind sole can explain in terms a non-comp-sci person such as myself can understand. :-)

@gero3 not sure why, webgl inspector doesn't to work for me a couple of chrome updates ago... i make it a point to render out textures myself sometimes to inspect if they are simulating correctly.

@alteredq @WestLangley had an impression reading something similar too, i think it was applying to opengl as well, that there might be some unexpected behaviour when rendering to a same texture (perhaps also due to different drivers and cards too) because parallelism may not be happening all at once.

that is, if there are 1024x1024 = 1,048,576 pixels in a render target, we might expect the fragment shader to execute everything at once, (read from the source, then write to the target), but while gpu would probably execute the shader in parallel, it might not be reading and writing 1,048,576 pixels at one go, which means, certain pixels might be written first then read into another executing fragment code, creating unexpected results. really couldn't remember where i was reading it, but had this impression that some (nvidia?) cards/drivers does allow you to render to a same texture without these side effects. anyways, not a CG or comp sci student too, so this is just my guess ;)

@WestLangley The way I understood it - it would work like you say if each pixel was processed independently and sequentially.

But this is apparently not how it works. Pixels are processed in parallel in cached blocks and order can be random. So you may get some pixels read one state (what was at the beginning of frame) and some pixels read another state (various degrees of "feedback" effect where you have a cascades of state changes).

Let's say pixel (0,0) asks for texel (0,0). This causes GPU to fetch a block of let's say 4x4 texels. Now each of subsequent texture fetches from (0,0) to (3,3) block will use these cached values independently of what was written there in meanwhile (write values go to write cache, which is flushed into GPU memory independently of read cache).

Now imagine this going on in parallel for many many blocks.

If you do just reads, all cached values will be from the same state (beginning of the frame).

If you do just writes (in a separate texture), all writes will be in one coherent state.

But if you mix reads and writes (from and to the same texture), there will be chaos - in a single frame some patches of pixels will use original state as their input, some patches will use result of what would be already next frame, some other patches will use inputs from that state and so on, so you may get wildly desynchronized simulation state across the texture.

heehee... managed to get @mrdoob experiment moved to the shaders.

before was running < 5fps on my macbook air, now its running at 30fps :)

@zz85 sweet! ^^

Update of FBO particle experiments links with three.js for anyone who might find examples useful :)

http://nucleal.com
http://mrdoob.com/lab/javascript/webgl/particles/particles_zz85.html
http://mrdoob.com/144/Magic_dust

Awesome!! Thanks!

this is really awesome great work ...... I was wondering how can i make a effect like this http://www.jam3.com/ or maybe http://dl.dropboxusercontent.com/u/7508542/sandbox/particles/particles.html this just with hover event ..
you can give me a tip thanks so much ..

@tomashernandez

As stated in the guidelines, help requests should be directed to stackoverflow. This board is for bugs and feature requests.

Was this page helpful?
0 / 5 - 0 ratings