Advection - sahilshahpatel/fluid-sim GitHub Wiki

This is the first chapter where we will start implementing the physically-based rules to govern our simulation. Advection is the process by which the fluid moves according to its own velocity.

That means that the velocity field itself should move over time. It also means that our dye should move. By the end of this chapter you should see something like this:

Advection GIF

Now that you are practiced with connecting our GLSL shaders into JavaScript with WebGL, this chapter will include a lot less code and a lot more math. It will be up to you to translate the algorithm into code.

Section 1: Understanding the algorithm

Let's start this section by looking at the most obvious way to think about advection. Imagine that each cell of our data is a single particle. Then we should move that particle forward according to its velocity, right?

Well, let's think about what happens in two different cases:

  1. Quantized velocity Quantity from one cell goes directly to another

  2. Non-quantized velocity It isn't clear to move the quantity from the source cell when it doesn't fall exactly in a single destination

You can see how when the velocity points exactly into another cell (referred to as "quantized velocity" above), it's easy to transfer that value. But when the velocity vector isn't quantized, there isn't an easy way to distribute the source value.

This is especially true for GPUs, where each fragment/cell must depend on certain values. You can't distribute current values to multiple other cells in a fragment shader! In other words, the above method would be a 1-to-many program, but fragment shaders can only be many-to-1 at most.

Our solution is to work backwards. In the fragment shader mindset, we need to figure out a cell's next value from it's current value. If we assume a relatively smooth velocity field, we can reverse our own velocity to find the location where our next value should come from (approximately). If that location is in between cells that's ok -- we can use bilinear interpolation to get an average of sorts.

Backwards advection

Recall that GLSL does bilinear interpolation for you (because of how we set up our textures). So if you calculate the right source location, convert it to UV coordinates, and then sample from there, you will get the interpolated value automatically!

Section 2: Implementation hints

That's actually all you need to know to implement advection, but to help you further, here are some implementation hints. (You may skip this section if you want to try it fully on your own!)

Remember that we are going to fill in the advect function in JavaScript as a wrapper for our shader. It can help to think of our shader as just a function too where inputs are uniforms.

Our inputs then, are

uniform sampler2D data; // Quantity to advect (dye or velocity)
uniform sampler2D vel;  // Velocity field
uniform float dt;       // Time delta of frame
uniform vec2 res;       // Resolution of textures

Don't forget to multiply velocity by time to find the source position! You'll also need the texture resolution because your velocities are in XY units but the texture function requires UV units.

Once your shader is done, go ahead and write your JavaScript wrapper just like we did before. Make sure that you begin and end the same way as applyForces -- that is, you should be rendering using the this.quad geometry and using render-to-texture targeting this.outputTexture.

In between those blocks of code you will have to send in your uniforms. In this shader there is one complication: your data and vel textures might be the same! This will happen when you perform the self-advection of the velocity. In this case you can assign both uniforms to the same texture unit (using gl.uniform1i) and then assign that unit the correct texture just once. You'll want to do this in your wrapper by checking for the case where data === vel.

Finally, edit the update function to call advect twice -- once for the velocity and then for the dye. By the end your output should look something like this:

Advection GIF

Section 3: Observations

Let's examine what's happening here to make sure we understand it. At first, this should look wrong to you. Why is the velocity field (the green-ish section) not moving to the right? Well, remember that our assumption about advection was a smooth velocity field. Just past the green bubbles' boundary the velocity field is 0 (black), so when that cell is run through our shader, it will calculate its source to be itself! You may be thinking, then, that our assumption was a bad one. Truthfully, it is right now, but the next two steps of our algorithm will smooth out the velocity field.

So what advection can we observe at the moment? Check out the dye section (red bubble). You can see it start as a long-ish blob where I clicked. Where it intersects with the velocity the color white appears instead of red, but that still signifies dye. You can see the white section moving according to the direction of the velocity. That is advection at work.

"Reading" our data from this RGB plot of sorts takes some getting used to, but is an amazing skill to have. As our debugging page explains, you can't use typical tools like breakpoints or print statements to debug a shader. Often you'll need to write a few lines to output some temporary data to screen and return from the shader prematurely. By looking at the resulting image, a skilled programmer can then tell what might be going wrong.

That's it for this chapter! In the next chapter we will add diffusion to our simulation.