FireSim

CS184 | Final Project

Final Report



Abstract

Our project simulates real-time, 3D fire by using a grid-based discretization of fluid dynamics using the Navier-Stokes equations for incompressible flow. The fire is represented by a fuel quantity which is advected by the simulation fluid, with fuel depletion controlled by a reaction coordinate. To render the fire, the program ray-traces through a 3D texture to accumulate temperature values along each ray and uses the output temperature to calculate color. We sampled images of real fire to obtain equations representing how red, green, and blue values vary with temperature. These methods allowed us to achieve relatively realistic looking fire effects in real-time at a fairly low performance cost.

Technical Approach

Set-Up

We set up the base application by importing GLFW to use with the OpenGL library to manage I/O functions and GLAD to manage the cumbersome work of loading the relevant OpenGL functions, which can vary depending on the OpenGL version used. We also implemented basic camera controls that allowed the user to zoom in and out and tested if our setup worked by opening an application window and getting a triangle to render to the screen. (LearnOpenGL)

Fluid Sim in 2D

We first modeled our simulation as an incompressible, homogeneous fluid discretized into a 2D grid. For each cell in the grid, the program tracks terms such as velocity and pressure by solving the Navier-Stokes equations, which can be broken down into the processes of advection, diffusion, external forces, and projection (Harris). Applying advection, diffusion, and external forces can yield a fluid with non-zero divergence, which must be corrected for using the projection step, derived from the Helmholtz-Hodge Decomposition Theorem, which subtracts the gradient of the fluid's pressure to obtain a field with divergence-free velocity. Both diffusion and pressure in this implementation are solved for using Jacobi iteration, which we can afford to run less iterations for because fire does not require as much precision to model convincingly as liquids like water.

As each process is performed on the fluid, we visualized and tested them, first with arrows to indicated the velocity direction of that cell, and later by adding a passive dye scalar which is advected alongside the velocity.

Arrow Representation of Advection Function.
Velocity texture over time, with RG corresponding to XY (normalized for visual clarity)
Dye advecting out from center (Color is a function of time)

GPU Implementation

After completing and testing the Navier-Stokes fluid solver on CPU, we began moving the implementation into GPU to increase performance. We stuck with OpenGL because we already had the libraries for it, it was more stable, and it would allow our program to be cross-platform rather than limited to Nvidia graphics cards.

Variables such as velocity, pressure, and divergence were stored as 2D textures, and each step of the fluid solver was written as a separate GLSL frag shader and drawn into a framebuffer to perform the calculation. As a result, our program's performance increased significantly: while the CPU solver was only able to simulate up to a 100x100 grid without noticeable lag, the GPU solver could handle grid sizes of over 1500x1500.

We replaced the dye with a texture representing the fuel quantity of the flame, which represents the reaction coordinate. To simulate the fuel being "consumed" by the combustion reaction, we decrease it linearly at every cell. We also "refill" the fuel's "source" (a circle at the base of the flame) at every time step for demo purposes, but this implementation could easily be modified to model the consumption of a limited fuel source over time.


Fuel Representation

To increase the realism of our fire, we also switched our implicit Stam advection scheme to the MacCormack advection scheme in order to increase the detail of the simulation without having to increase resolution (Crane). We also applied a pseudo-randomized Gaussian splat to our flame to achieve a more realistic fire effect from random external forces.

Velocity field for original stam advection scheme
Velocity field for improved MacCormack advection scheme with less smoothing

Conversion from 2D to 3D

At this point, we began translating our 2D fluid computations to 3D. This involved adding an extra z-coordinate and tweaking our calculations in the GLSL frag shaders slightly to account for the extra dimension. Because of the limitations of framebuffer drawing, each shader can only operate on a single "slice", or a 2D cross-section, of our 3D texture at a time, so we iterate over each slice until the entire texture has been drawn.

Fuel amount in 3D
Visual representation of velocity in 3D

Raytracing

In order to render the 3D textures, we raytraced through the 3D temperature texture to estimate the radiance at each pixel (Nguyen et al). Since our model strives to merely mimic the appearance of fire rather than simulate realistic physics, we used a simplified version of Nguyen's approach. We chose to measure temperature values, where each ray would accumulate the total temperature over the slices of the 3D texture that it intersected. We also skipped the radiance step of the equation, as our color conversions did not need an accurate radiance estimate.

To implement raytracing, we first created a new 2D texture that represents the viewing plane and calculated each ray as originating from the camera and intersecting with its respective grid position. Unlike rendering programs that perform intensive calculations to output an accurate but still image, our raycasting function had to operate in real time, so we simplified and parallelized the process by writing a GLSL frag shader that would handle the ray at each point of the grid in a single pass. Because texture coordinates are always fixed from 0 to 1, the rotating effect was simulated by rotating the camera and viewing plane instead of the texture, allowing for an easier conversion from world space to texture space at the tangent point.

Temperature to Color Conversion

We observed many images of fire for this project to reference our desired look and color. In particular, we referenced candles, as those flames were often more controlled and matched the shape of our small ball of flame. We noticed that the colors for these fires often smoothly transitioned based off of distance from the fuel source, and we came up with the idea to calculate red, green, and blue as polynomial equations that took in a scaled temperature value. Hotter temperature areas would be lighter, occasionally bluer, while cooler parts of the flame would be more red in appearance.

We studied multiple image references of candles and recorded the colors we observed. To calculate our equations, we took over 150 hexidecimal color samples and stored them in an Excel spreadsheet that plotted the red, green, and blue values of the colors separately. We coded the Excel spreadsheet to calculate three line of fit equations based off of our results for each color channel, and modified our color shader to output the final color based off the polynomials we found.

Early color results were slightly off color. We manipulated the data to account for white cutoff, and continued to tweak our equations and take addtional samples until we got a flame that matched our desired look. We also tried out different color equation combinations to get interesting results.

Early attempt
Cel Shading effect for more stylized 3D animation.
Blue Fire Effect

Problems Encountered

Setting up the project to be cross-platform and easily compilable was a major first hurdle, which required several hours of CMake tweaking. We also spent a lot of time looking through OpenGL documentation to find the best ways to implement our ideas as well as learning the features and limitations of OpenGL. Since both the simulation step and the rendering step of our program require GPU reads and writes, the process was difficult to abstract away without introducing unexpected bugs and required careful coordination of GPU-related functions across classes.

Since none of us were familiar with the physics of fire simulation prior to this project, we experienced some setbacks trying to understand and implement some of the equations in the paper. For example, we originally tried to compute color by measuring radiance and converting it to color using Planck's equation, but the documentation that we could find on how it was meant to be implemented was sparse. We were able to improve our overall comprehension through good team work and frequent communication, and we held frequent meetings and worked together to research and teach each other what we understood. When we were collectively stuck, we would discuss alternative approaches we could take that we could implement instead.

Lessons Learned

This project was a challenging learning experience for all of us, and helped us better understand fluid sim, GPU parallelization, and fire physics. We learned how to optimize our graphics program to run in real time and make use of shaders and textures to achieve the results we want. The scope of the project made it crucial that all team members were on the same page, and it stressed the need to help each other so that nobody would be left behind. Ultimately it was a difficult, but rewarding experience.

Results

Our final program is a 3D fire simulation that can run in real time without noticeable lag. On an Nvidia GEForce GTX 960, it can easily handle a 3D grid resolution of 100 x 100 x 100. For demo purposes, we also added click-and-drag fuel generation and camera rotation as well as the ability to toggle the color of the flame. We set up two scenes, one which generates a ball of fire, and one which feeds random amounts of fuel to the base of the simulation space.

Fire with randomly seeded fuel values
Different color effects
Interaction with the cursor

References

Contributions

Gracie Li

Yuan Zhou

Chris Zhang