Please email me with any comments or questions about this work. I would be happy to help you in any way I can!
There is an ever-increasing need for more realism in today's movies and video games to immerse viewers and players in virtual worlds. Sometimes, artists can cause virtual characters to mimic behaviors observed in the real world. However, to achieve true realism, one must simulate the physics involved in underlying physical phenomena. Phenomena that are often simulated instead of directly animated include collisions, wind, water, and smoke.
This project investigated smoke simulation, and implemented a simulation based on solutions to the discrete Navier-Stokes equations. Because simulations are typically very computationally intensive, I thought that I could get more performance out of the GPU as compared to a CPU-only implementation. Therefore this project contains both CPU- and GPU-based smoke simulators.
Using the GPU to perform calculations is advantageous because the algorithm's calculations are independent of each other, and the GPU can perform many calculations simultaneously. The GPU is also optimized for vector and matrix operations, which are common when simulating physical phenomena in 2-dimensional or 3-dimensional space.
The GPU simulation achieves roughly 16x better performance than the CPU simulation, based on a simple framerate vs. resolution metric.
There are several methods used to simulate smoke and fluids in general, and this simulator uses the incompressible Navier-Stokes method. The Navier-Stokes equations were developed by Claude-Louis Navier and George Gabriel Stokes in the 19th century (working separately). The equations seen below are the Navier-Stokes equations with the added constraint of fluid incompressibility, where u is the fluid velocity, ρ is the fluid's density, p is the fluid pressure, ν is the fluid's kinematic viscosity, and f is the sum of external forces.
Similar (in fact, nearly identical) equations govern the motion of particulate matter, such as smoke particles, that move around within the fluid.
The fluid solver operates on discrete grid, where each grid cell has a velocity and smoke density.
The solver works by finding a discrete solution to the above equations on the grid over some small timestep, and does so in six phases:
The simulator works in real-time, using the OS' native high-performance timer. Each frame, the simulator calculates how much time has elapsed since the previous frame and then simulates that amount of time. Users can add smoke at chosen locations as well as define their own forces (see "Running the Simulator" below).
The CPU simulator uses simple 2D arrays of floats to hold values in the velocity and density grids, user sources, and user forces. It isn't very interesting :).
To display the smoke, the simulator renders a quad for every cell, setting the color to a grayscale value proportional to the smoke density in the cell.
The GPU simulator uses textures to hold the velocity and density grids, as well as user sources and forces and the results of several intermediate calculations.
The simulator uses 32-bit float textures as defined by the ARB_texture_float OpenGL extension. Vector and scalar grid values are stored in the textures' RGBA components; for example, the velocity and density fields are stored in a single texture: R -> velocityx, G -> velocityy, B -> velocityz, and A -> density.
Calculations are performed using fragment shaders. Current grid values and intermediate results are specified as input textures to the shader, and the EXT_framebuffer_object extension is used to allow the shader to render to an output texture. This texture is then fed into the next phase of the simulation.
Here is a diagram of the render path for the GPU simulation:
To get user input, the GPU simulator also has simple 2D arrays of floats that it uses to store user sources and user forces, just like the CPU simulator. At the beginning of each frame, the GPU simulator uses glTexImage2D() to turn the 2D arrays of floats into a texture that the shaders can use.
For display, the GPU simulator renders a single quad to the screen and uses yet another fragment shader to render the smoke color in proportion to the density in each cell.
smokeSim [-s] [res]