It has been a while since our last post, we have been working a lot though! Now we can share all the new stuff we have. We just finished building the technical prototype for Havre, it features dynamic wind, clouds, rain and thunder that are displayed through raymarching into a 3D texture representing the weather. In the coming posts we will try to lay out the inner workings of these systems and what we plan to improve. We will focus mainly on the fluid simulation itself and on the methods we used to make it visible.
As such we need to start by looking at the fluid simulation supporting Havre’s atmosphere. The world of Havre is made of storms, clear nights, rainy sun sets, thunder and so much more. Its weather evolves and changes, all the time. To achieve this we quickly came to realize that we needed some kind of dynamical wind. Indeed, wind carries humidity which can aggregate into clouds that make rain and thunder possibles. It also diffuses heat and cold and moves magnetism through the cylinder. Without a wind simulation it would have been very hard to create the kind of emergent climatic world we had aimed for. We want to have storms that move through the cylinder, are cut in half by mountains and generate thunder under the right conditions.
We then set out to research fluid simulations and quickly discovered Jos Stam’s 2003 GDC paper about stable fluid simulations for games. You can find it here: http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf
I found this talk pretty useful too: http://www.dgp.utoronto.ca/%7Estam/reality/Talks/FluidsTalk/FluidsTalkNotes.pdf
The funny thing is, when I started programming videogames I told myself that I would never work with fluids or anything like this as it seemed way too complex to be of actual use, or fun. Turns out, 5 years later I find myself reading a research paper on fluid simulation. Though, I must admit I had not envisioned the possibility of using fluids for simulating weather, which is, in my humble opinion, terribly exciting.
Anyway, the wind can effectively be represented as a fluid, though not a very viscous one. Jos Stam’s work is based on the Navier-Stokes equations, and it seemed to be the case for pretty much everyone working on fluid simulations.
If you are eager to know more about fluid simulations I can only advise you to read Jos Stam’s paper, it is really well written. I will try to synthesize how the algorithm works to the best of my capabilities but it is a rather complex algorithm and my math skills are not what they should be, I may be wrong or inaccurate at times.
Jos Stam’s implementation uses a vector field. It is basically a grid of values. If you are doing 2D fluid you might build a 20x20 grid which would amount to 400 cells, or rather 400 values. Each value corresponding to the fluid force at the particular cell position. But this is not all, the fluid forces are mostly here to make any kind of substance move. Thus, there is another value at each cell which corresponds to the density of the specific substance you want to simulate. The density value will follow the fluid force and conserve its mass so that it looks like a big blob moving, stretching and distorting.
The fluid simulation is modeled based on theses two equations:
They are called the Navier-Stokes equations and aim to describe the behavior of fluids. The first equation describes the evolution of velocity and the second one how density propagates in the velocity field. Density being a representation of a fluid.
The terms of the equations are as follows:
u is the velocity vector.
ρ is the density.
v is the viscosity of the fluid because oil should behave differently than air.
k represents the diffusion of the fluid, how much it loses density over time.
S are the sources of density such as boiling water for instance
f are the fluid forces applied to the field such as from a fan.
In the second equation we can see that density follows velocity, that it diffuses according to a diffusion rate (k) and that it receives external density sources (S).
The first equation behaves similarly but in this case velocity follows velocity. It also changes based on a viscosity parameter (v) and receives external forces (f).
These operations are executed at each time step and for all the cells of the grid.
Let’s have a look at some of the tests I have made during development. It is shown in 2D from a top down point of view. Showing it in 3D is pretty useless.
These represent the density, the fluid itself:
These represent the forces of the grid, what moves the density:
Weather forecasts use a simplified version of the Navier-Stokes equations called the primitive equations by neglecting vertical motion and some other stuff. We would very much like to do this too but it is hard to find a more computer science kind of approach to this. I should probably start to find some adult math and physics classes, I don’t know.
The main feature that drawn us to this implementation is that it is stable, which means that it is possible to use a large delta time without breaking the simulation. For instance, the simulation do not blow up if we use a 2 seconds time step, the fluid stay consistent. It is then possible to update the simulation every 2 seconds and get the same result than a simulation that ran for 2 seconds at a rate of 10 steps per seconds. It is a major feature for two reasons: the first one is that it is a very CPU intensive algorithm. It loops through the grid many times during a step, the bigger the grid the longer it takes for a step to complete. And boy, do we have a huge grid. As of now, and it is subject to change of course, it is 92x8x162 which amounts to 119232 cells to iterate through. Each cell is 20x20x20 meters. Our world is then 1800 meters wide 160 meters high and 3200 meters long. The ideal would be to double the resolution for a better accuracy around obstacles and reach 10m³ cells which would double the number of cells, you guessed it. We could as well make use of smaller cells just around obstacles to keep improving accuracy. But the sure thing is, we cannot expect to iterate through the whole simulation at every frame. Being able to update the simulation every 100 frames or so is our only chance.
The second reason is that, unlike water or fire, wind is of a much bigger scale. Comparatively, it is slow, even at its fastest. A very strong wind of 118 km/h (according to the Beaufort wind force scale), or 32 m/s, will propagate of roughly a cell and a half per seconds if the grid’s cells are 20m³. This means that, at its fastest, the simulation will move any kind of substance at 1.5 cells/seconds. It is not much. We do not even need to make any calculations we just have to look at the sky and see that clouds are pretty slow from where we are. Updating our simulation at every frame would be a waste of resources.
Since we update the simulation less often than the rendering we will need to interpolate between steps in order to give the impression of a continuous movement. And we will also need some kind of a data structure that can be used by our set of shaders. Gaël will have a more in-depth look at the rendering part in a subsequent post.
Now we know that we can update our fluid simulation every few seconds, but it is still a very long task which would block any kind of game loop for too long if we used it in the same thread. Hence the use of a second thread dedicated to the fluid simulation to free the main thread of the burden of running a step that can last 2 seconds at times. This poses a few concurrent access troubles but nothing we cannot handle.
Once we had all that, we just needed to make the algorithm work with a 3D grid, add obstacle recognition, make the wind warp around the edges, we are in a cylinder remember? We also added various forces to represent wind elevation, the effect of the perpetual rotation of the cylinder and that was it!
I am pretty sure I still have a lot of work ahead of me if we want to have a complex weather simulation with many different behaviors and parameters but this big chunk here is already pretty amazing.