Note: This work was started the week before the California wildfires and is unrelated to that disaster.
I decided to start with only grass fires that spread across the terrain mesh but don't spread to other scene objects. The existing uniform mesh x-y grid structure seemed like a fine place to store fire related data. Each grid cell contains the following fire fields:
- Hitpoints: The amount of fire/heat damage this cell can take before it catches on fire; this is a function of humidity (grass wetness), current precipitation (rain/snow), and some randomness
- Available Fuel: The amount of grass fuel available for the fire to burn; consumed over time as the fire burns; this is a function of grass density (blade count)
- Burn Intensity: The intensity of the fire from 0.0 to 1.0; this controls fuel consumption rate, heat/damage generated, spreading rate, and flame density + height
Here are some screenshots of fires burning in a scene consisting of dense grass with light tree cover. The fires burn the grass and flowers but not the trees. Fire leaves behind burnt black grass blades and gray ash covered ground where the green grass used to be.
|Grass fire burning a circular area between the trees. Oops, looks like flowers aren't burning.|
|Ring of fire, after flowers were fixed so that they burn as well.|
|Fire burning the grass and flowers nearby, with heat haze producing rippling effects on the screen.|
Fire won't burn areas that are over dirt or rock, or cells that are underwater. Also, once the grass has been burned, it remains black and can't be set on fire again. This image shows multiple fires. If you look closely, there is a shallow pool of water under the grassy area in the bottom right, which is why it's not burning. In addition, the steep slopes are covered in dirt and don't burn. The source of the nearest fire was an explosion in bottom center of the image where the grass is pushed away in a circle.
|Multiple fires burning in this scene. The puddle near the bottom right produces wet grass that doesn't burn.|
The rate and direction of fire spreading is affected by wind speed and direction. The fire will spread more quickly in the direction of strong winds. In the absence of strong wind, fire spreads approximately uniformly in all directions that contain grass, forming a circular burn area and a doughnut shaped ring of active fire. The fire at the center of the circle has consumed all of the grass fuel and has burned out.
This fire implementation is integrated into the physics and gameplay system of 3DWorld through the following effects:
- Green grass blades are slowly turned black at randomly varying rates
- Green terrain texture is temporarily turned black and will fade to gray ash over time
- Light black/gray smoke is produced that will float toward the sky
- The player takes damage while walking over fire in gameplay mode
- Objects tagged as explode-able explode when fire contacts them (see video below)
- A burning sound loop is played when the player is near a fire
- Heat from nearby fire produces a wavy screen space effect (see image above)
- Heat sensors can detect the heat from fires
|Fire from the plasma cannon spreading in the back yard.|
Given enough time, the fire will eventually spread to all the grass around the house, but it currently won't set anything else on fire.
I recorded two videos of the grass fire effect. The first video shows how a fire spreads, destroying an exploding tank in its path. Near the end I stand in the fire and take damage until I burn to death. Sorry, there's no sound this time.
The second video shows fire spreading in a dense grassy meadow with sparse tree cover. It will climb the hills and reach any area connected by a path through the grass until the entire scene is burned black. You can see how the fire produces emissive lighting when I cycle to night time. At the end of the video, I enable rain, which wets the grass and quickly extinguishes all fires.
This system is very efficient. The entire scene can be burning and I still get about 40 frames per second. More reasonable fires will give me at least 100 FPS. Most of the frame time is taken by the grass and terrain texture updates, as these require new data to be sent to the GPU each frame in many small batches. To reduce the time spent in these operations, grid cells are split into batches and updated sparsely. Each cell is assigned a random number fro 0 to 31 every frame, with the following update cycle:
0: Update grass color (green => black)
1: Update terrain texture (grass => dirt or rock, depending on elevation)
2: Update terrain color modulation (white => black, temporary)
3: Generate a smoke puff that rises
4: Damage nearby objects such as exploding tanks
Any other number does no update. Therefore, expensive updates are only done for 1/32 of the active fire cells each frame. This is enough to keep cell update time around 1ms in most cases.
Fire is drawn as a collection of up to 6 variable sized, camera facing quads (billboards) per active cell, for the cells within the camera's view frustum. The number of quads is a function of burning intensity with some random variation. Each quad is textured with a sequence of 16 images from a texture atlas that gives a burning flame animation. They're drawn with additive blending and emissive lighting to produce bright, intense colors. Enabling additive blending and disabling depth testing allows thousands of fire billboards to be drawn without having to sort them back to front for alpha blending. I also use the depth buffer from the previous frame to blend the bottom edges of the flame quads to transparent where they meet the ground, which avoids ugly artifacts at locations where the quads intersect the terrain mesh under them.
For future work, I would like to extend this system to non-grass types of fires. For example, fire could be made to spread to trees and other scene objects. This is more difficult because it's a 3D effect rather than a 2D affect applied to a uniform grid on the ground. It's not clear if my billboard rendering will look good when the fire is up in the air on a tree branch. It's also unclear how to make other scene objects appear burned. I'll be sure to post more screenshots and videos if I ever figure this out.