Fire Shader

The purpose of this project was to write a surface shader that would give a simple object the appearance of being an animated flame. As with the previous assignments, the emphasise was on the vital need to carefully observe and document a natural phenomena before creating a digital version of it.

Content on this page requires a newer version of Adobe Flash Player.

Get Adobe Flash player

Reference Movie

Content on this page requires a newer version of Adobe Flash Player.

Get Adobe Flash player

Shader

The shader was created by writing a 2D procedual Texture and then using a cylindrical mapping to apply it to a series of 100 planes in order to create a volumetric effect.

2D texture

final2dTexture

The basic shape of these flames can be represented by a 2d texture that is wrapped around a cylinder. This is a procedual texture that was completel coded in RSL. The 2D texture has 2 main parts: large blue flames and small cyan flames flame.

Flame Big   Small Flame Comp

Right Big Flame. Left Small Flame.

 

Big Flames

height

CircDistance   

To create the general shape of the flames I used the distance function to create circular gradients (Left). The height of these gradients is modified by a noise wave (top). I then clamped these values to the range of zero to one and then remapped the values to create the base shape of the large flames (Right). It is important that you clamp values that range out side of zero to one, because if you let values outside of that range into your shader it can have disasterous results. For example if your opacity becomes a negative number your colors will look inverted.

 

To give the flames a sort of halo I applied the smoothstep function to the height varible to produce the above image.

mix flame1 and aura

These images were then mixed together to create the final alpha for the larger flames.

Flame Color Base    Flame Big

For the color of the large flames I wanted blue with streaks of red randomly apearing. To create these sparse streaks I simply used two dimensional noise that was remapped so that only values greater than .75 would show up.

Small Flames

 

flame2

The alpha for the small flames was created similarly to the larger flames except that they are more opace at the center.

line

I then created a simple linear gradient.

Small Flame Comp

I then added the two values together and multiplied them by cyan to get the above texture.

Volumetric Effects.

I quickly realized than a thin surface would not be able to replicate the kind of flames I was trying to produce. Instead I decided to create a volumetric effect using a series of 100 planes.

to create my volumetric effects I created a series of 100 planes

100 planes

Rendering of 100 planes using ambient occlusion

Having 100 planes that are arranged facing the camera can create a great volumetric effect. However the illusion is lost if you move the camera so that the planes become foreshortened. The solution to this is to have the planes always face towards the camera via a aim constraint in maya and have your shaders orientation be independent of the facing of the object it is asigned to.

 

By including the code: #include "maps.sl" at the beginning of my shader I was able to use cylindrical mapping. The following code takes the sc and tc variable and asigns the values based on a cylindrical mapping. flamespace is a variable for the space that the cylinder is attached to.


 #include  "maps.sl"

...
surface shader statement
...


float sc, tc;
point pp = transform(flamespace,P);
cylinmap(pp, sc, tc, point (0,0,0), point(1,0,0), point(0,0,1), point(0,1,0));
 As can be scene below the cylindrical projection alone does not look good because it fills the entiere width of the planes.

flame planes

To make my textures only show up in a tube like area. I used the following code to create a tube shaped mask.

float xzDist = distance(pp, point(0,pp[1],0));
  
float mask = smoothstep(radiusNew - thickness/2,radiusNew,xzDist)
    * (1 - smoothstep(radiusNew,radiusNew + thickness/2,xzDist));

Flames Cylinder

By varing the the radius of the cylinder along its height I was able to get the sflames to take on an appropriate shape.

flame shape

By animating the radius of cylinder and the height of the flames with noise function I was able to create convencing movement.

 

Shader Code

#include "maps.sl"
  
  
surface
StoveFlame(float    Kfb = .15, // intensity, set to .15 for 100 planes
                    noiseFreq = .25, // frequency of noise for base height
                    radius = .305, // radius of base of flame
                    thickness = .1, // thickness of the flames
                    fireHeight = .6, //height fo the large flames
                    flame2height = .20, //height of smaller flames
                    yOffset = 0, //decrease this at about 10 units per second for animation
                    freq = 4, //radius animation frequency
                    flame2freq=10,
                    radiusAmp = .1,
                    redIntensity = 4, // Intensity of the red streaks
                    redFlashFreq = 1; // Frequency of the red streaks
           color flameColor1 = (.1, .2, 1),
                 flameColor2 = (0.168,0.529,0.984),
                 flameColor3 = (1.0,0.2,0.0);
           string flamespace = "object")
{
float sc, tc;
point pp = transform(flamespace,P);
//use a cylinder projection for  sc and tc cordinates
cylinmap(pp, sc, tc, point (0,0,0), point(1,0,0), point(0,0,1), point(0,1,0));
  
tc = tc*2;
// Red Streaks
float RedAlpha = (clamp(noise(sc*redFlashFreq*20,tc*redFlashFreq+yOffset*5),.75,1) -.75)*4*redIntensity;
color flameColorBase = mix(flameColor1,flameColor3,RedAlpha);
  
float ss, tt;
color surfcolor;
// repeat ss times 20
ss = mod(sc*20,1);
float flameNum = floor(sc*20);
float flameHeight = (.5 + (noise(flameNum*noiseFreq)-.5)*3) * fireHeight;
flameHeight *= 1 + (noise(flameNum*noiseFreq*3, yOffset)-.5)*1;
float height = (.5 + (noise(sc*20*noiseFreq) -.5)*3) * fireHeight;
height *= 1 + (noise(sc*20*noiseFreq*3, yOffset) -.5)*1;
  
// Large Flames
tt = tc / flameHeight;
float flame1Size = .6;
float circ1dist = distance(point(0.5,0,0),point(ss/1,tt/2.5,0));
float flame1 = clamp(flame1Size - circ1dist,0,1);
flame1 = mix(clamp(sin(flame1*PI*2),0,1),clamp(flame1,0,.2),.5);
float aura = smoothstep(0,1,(height*1.5-tc)*3);
flame1 = mix(flame1,1,aura/2);
color flameBig = flame1;
  
// Small Flames
float flame2Size = .4;
float s1= ss + noise(t*flame2freq + yOffset,s*flame2freq*2 +10)*.1;
float t2= tt + noise(t*flame2freq + yOffset,s*flame2freq*2)*.1;
float circ2dist = distance(point(0.5,0,0),point(s1,t2*1.5,0));
float flame2 = clamp(flame1Size - circ2dist,0,1);
if (flame2 < .1)
    flame2 = smoothstep(0,.1,flame2);
else
    flame2 = (1-smoothstep(.1,.3,flame2)*.6);
  
float line1 = clamp(1-abs(-tc)*10,0,1);
  
  
// xzDist is the distance from the y axis
float xzDist = distance(pp, point(0,pp[1],0));
float radiusNoise = noise(tc * freq + yOffset, sc * freq/2)
    * sin(tc*freq+yOffset*3) * tc;
  
// Modify the radius to create a bowl shape and varry it as yoffset is increased
float radiusMod =  sin(clamp(tc,0,1/6)*PI*3)*.1;
float radiusNew = radius + radiusMod - radiusNoise * radiusAmp;
float radiusNew2 = radius + radiusMod/2 - radiusNoise * radiusAmp;
  
float mask = smoothstep(radiusNew - thickness/2,radiusNew,xzDist)
    * (1 - smoothstep(radiusNew,radiusNew + thickness/2,xzDist));
  
float maskthin = smoothstep(radiusNew2 - thickness/5,radiusNew2,xzDist)
    * (1 - smoothstep(radiusNew2,radiusNew2 + thickness/5,xzDist));
  
// Final compositing for flames
flameBig = flameBig * mask * flameColorBase;
color flameSmall = (flame2 + line1) * maskthin * flameColor2;
color flameComp = (flameBig*flameColor1 + flameSmall*2)/2;
flameComp = (flameSmall/2 + flameBig/2);
  
surfcolor = flameComp;
surfcolor *= smoothstep(0,.01,tc);
  
/* STEP 1 - set the apparent surface opacity */
Oi = 0;
  
/* STEP 2 - calculate the apparent surface color */
Ci = surfcolor * Kfb;
}