Google
 

Saturday, July 21, 2007

Engine Design - Fire

Right,

Here is the code for my fire, this fire effect is not to robust really, I need to do a little more work on it so I can have true volumetric fire, also I don't like the multi mesh approach I am using either so will probably play with the shader to get it to run on a single mesh. In short I am not happy with it yet, but I am posting it here so you can at least see what it does and it might give you some ideas of how to do your own. When I started out playing with this shader my knowledge of shaders was very limited, now I have a bit more experience I will more than likely improve on this, if I do I will post it her.

So, the code...
RCFire


public class RCLayeredFire : RCObject, IRCChildRenderer, IRCLoadable
   {
       public bool Colapsed;
       private RCModel[] flame;
       private RCModel[] flame2;
       private string flameModelAsset;
       private string flameTextureAsset;
       private int flameLayers;
       private float animSpeed;
       private float tick;
       private float flameOffset;

       private Vector3 camerasLastPosition = new Vector3(0,0,0);
       
       private Texture2D flameTexture;

       public float AnimationSpeed
       {
           get { return animSpeed; }
           set { animSpeed = value; }
       }

       public float FlameOffSet
       {
           get { return flameOffset; }
           set { flameOffset = value; }
       }
       
       public RCLayeredFire(string modelAsset,string textureAsset,int layers,string Name) : base(Name)
       {    
           flameModelAsset = modelAsset;
           flameTextureAsset = textureAsset;
           flameLayers = layers;
           flame = new RCModel[flameLayers];
           flame2 = new RCModel[flameLayers];
           base.Rotate(new Vector3(1, 0, 0), MathHelper.PiOver2);

           for (int f = 0; f < flameLayers; f++)
           {
               flame[f] = new RCModel(flameModelAsset, "flame" + f.ToString());
               flame[f].Rotation = myRotation;

               flame2[f] = new RCModel(flameModelAsset, "flame" + f.ToString());
               flame2[f].Rotation = myRotation;
               flame2[f].Rotate(new Vector3(0, 0, 1), MathHelper.Pi);
           }            
       }

       public void LoadGraphicsContent(GraphicsDevice myDevice, ContentManager myLoader)
       {
           flameTexture = myLoader.Load<Texture2D>(flameTextureAsset);
           for (int f = 0; f < flameLayers; f++)
           {  
               flame[f].Rotation = myRotation;
               // Now collapsed to a sigle position to ease rotation.
               flame[f].Position = new Vector3(myPosition.X, myPosition.Y, myPosition.Z + (((float)f) / 10f));
               //flame[f].Position = myPosition;
               flame[f].SetShader(myShader);                
               flame[f].LoadGraphicsContent(myDevice, myLoader);
               flame[f].UseBasicRender = false;

               flame2[f].Rotation = myRotation;
               flame2[f].Rotate(new Vector3(0, 0, 1), MathHelper.Pi);
               flame2[f].Position = new Vector3(myPosition.X, myPosition.Y, myPosition.Z - (((float)f) / 10f));                
               flame2[f].SetShader(myShader);
               flame2[f].LoadGraphicsContent(myDevice, myLoader);
           }
       }
       
       public void RenderChildren(GraphicsDevice myDevice)
       {
           CullMode cull = myDevice.RenderState.CullMode;
           bool depthBuffer = myDevice.RenderState.DepthBufferEnable;
           bool Zwrite = myDevice.RenderState.DepthBufferWriteEnable;
           bool AlphaEnable = myDevice.RenderState.AlphaBlendEnable;
           BlendFunction blendOp = myDevice.RenderState.BlendFunction;
           Blend srcBlend = myDevice.RenderState.SourceBlend;
           Blend destblend = myDevice.RenderState.DestinationBlend;

           //if (cull != CullMode.None)
           //    myDevice.RenderState.CullMode = CullMode.None;
           if (depthBuffer != true)
               myDevice.RenderState.DepthBufferEnable = true;
           if (Zwrite != true)
               myDevice.RenderState.DepthBufferWriteEnable = true;
           if (AlphaEnable != true)
               myDevice.RenderState.AlphaBlendEnable = true;
           if (blendOp != BlendFunction.Add)
               myDevice.RenderState.BlendFunction = BlendFunction.Add;
           if (srcBlend != Blend.One)
               myDevice.RenderState.SourceBlend = Blend.One;
           if (destblend != Blend.One)
               myDevice.RenderState.DestinationBlend = Blend.One;

           if (RCCameraManager.ActiveCamera.Position != camerasLastPosition)
           {
               Vector3 tminusp = myPosition - RCCameraManager.ActiveCamera.Position;
               tminusp.Normalize();
               float angle = (float)Math.Acos(Vector3.Dot(tminusp, Vector3.Forward));
               //this.Rotate(new Vector3(0, 0, 1), angle);
               //this.Revolve(myPosition, new Vector3(0, 0, 1), angle);
               camerasLastPosition = RCCameraManager.ActiveCamera.Position;
           }            

           Effect effect = RCShaderManager.GetShader(myShader).Effect;
           if (effect.Parameters["flameTexture"] != null)
               effect.Parameters["flameTexture"].SetValue(flameTexture);
           if (effect.Parameters["ticks"] != null)
               effect.Parameters["ticks"].SetValue(tick += animSpeed);

           for (int f = 0; f < flameLayers; f++)
           {
               flame[f].Rotation = myRotation;
               flame[f].Scaling = myScaling;              
               //flame[f].AnimationSpeed = animSpeed;

               flame2[f].Rotation = myRotation;
               flame2[f].Rotate(new Vector3(0, 0, 1), MathHelper.Pi);
               flame2[f].Scaling = myScaling;
               flame2[f].AnimationSpeed = animSpeed;

               if (effect.Parameters["Index"] != null)
                   effect.Parameters["Index"].SetValue(flameOffset + (float)Convert.ToDouble(f) / 10);
               
               flame[f].Draw(myDevice);
               //flame2[f].Draw(myDevice);
           }


           if (cull != myDevice.RenderState.CullMode)
               myDevice.RenderState.CullMode = cull;
           if (depthBuffer != myDevice.RenderState.DepthBufferEnable)
               myDevice.RenderState.DepthBufferEnable = depthBuffer;
           if (Zwrite != myDevice.RenderState.DepthBufferWriteEnable)
               myDevice.RenderState.DepthBufferWriteEnable = Zwrite;
           if (AlphaEnable != myDevice.RenderState.AlphaBlendEnable)
               myDevice.RenderState.AlphaBlendEnable = AlphaEnable;
           if (blendOp != myDevice.RenderState.BlendFunction)
               myDevice.RenderState.BlendFunction = blendOp;
           if (srcBlend != myDevice.RenderState.SourceBlend)
               myDevice.RenderState.SourceBlend = srcBlend;
           if (destblend != myDevice.RenderState.DestinationBlend)
               myDevice.RenderState.DestinationBlend = destblend;
       }        
   }






So using this class in my engine for the screen shot above I set it up like this.

           RCLayeredFire fire = new RCLayeredFire("Content/Models/Plane", "Content/Textures/flame", 6, "f");
           fire.Position = new Vector3(0, -9f, 54);
           fire.Scaling = new Vector3(3, 1, 5);
           fire.AnimationSpeed = 0.1f;
           fire.SetShader("FLA");
           fire.FlameOffSet = .2f;
           game.Scene.AddObject(fire, "f");



I have also found a bug (one of many) the mesh you are applying this to needs to have a texture with it or you will get a flame like this



As you can see there are a few bits of code commented out, this is because I am still working on this class, so little time, so much to do.

Flame.fx
Here is the shader (thanks again NVIDIA)

/*
   Volumetric flame effect
   based on Yury Uralsky's "Volumetric Fire"
   http://www.cgshaders.org/shaders/show.php?id=39
   $Id: //sw/devtools/SDK/9.5/SDK/MEDIA/HLSL/Flame.fx#2 $

   This revolves a cross section of a flame image around the Y axis to
   produce a cylindrical volume, and then perturbs the texture coordinates
   with 4 octaves of animated 3D procedural noise to produce the flame effect.
   
   Mod History: [Oriional shader from the NVIDIA 9.5 SDK]    
   
   23//03/2007 C. Humphrey charles.humphrey53@yahoo.co.uk http://www.randomchaos.co.uk
   Modified so that the shader can be placed on an object that has
   its position other than at 0,0,0 and made compatible with XNA.
*/

string XFile <string UIWidget="None";> = "slices_10y.x";
string description = "3D Flame";

// Added by C.Humphrey
float Index
<
   string UIWidget = "slider";
   float UIMin = 0.0;
   float UIMax = 1.0;
   float UIStep = 0.1;
>;

float Script : STANDARDSGLOBAL <
   string UIWidget = "none";
   string ScriptClass = "object";
   string ScriptOrder = "standard";
   string ScriptOutput = "color";
   string Script = "Technique=ps20;";
> = 0.8;

float ticks : Time
<
string units = "sec";
string UIWidget="None";
>;

/************* TWEAKABLES **************/

float noiseFreq
<
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.01;
> = .10;

float noiseStrength
<
   string UIWidget = "slider";
   float UIMin = 0.0; float UIMax = 5.0; float UIStep = 0.01;
> = 1.0;

float timeScale
<
   string UIWidget = "slider";
   string UIName = "Speed";
   float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.01;
> = 1.0;

float3 noiseScale = { 1.0, 1.0, 1.0 };
float3 noiseAnim = { 0.0, -0.1, 0.0 };

float4 flameColor <string UIName = "flame color"; string UIWidget="Color";> = { 0.2, 0.2, 0.2, 1.0 };
float3 flameScale <string UIName = "flame scale";> = { 1.0, -1.0, 1.0 };
float3 flameTrans <string UIName = "flame offset";> = { 0.0, 0.0, 0.0 };

// Textures /////////////////

#define VOLUME_SIZE 32

texture noiseTexture
<
//    string Name = "noiseL8_32x32x32.dds";
   string ResourceType = "3D";
   string function = "GenerateNoise1f";
   float3 Dimensions = { VOLUME_SIZE, VOLUME_SIZE, VOLUME_SIZE};
>;

texture flameTexture
<
   string ResourceName = "flame.png";
   string ResourceType = "2D";
>;

// Vector-valued noise
float4 GenerateNoise4f(float3 Pos : POSITION) : COLOR
{
   float4 c;
   float3 P = Pos*VOLUME_SIZE;
   c.r = noise(P);
   c.g = noise(P + float3(11, 17, 23));
   c.b = noise(P + float3(57, 93, 65));
   c.a = noise(P + float3(77, 15, 111));
//    return c*0.5+0.5;
   return abs(c);
}

// Scalar noise
float GenerateNoise1f(float3 Pos : POSITION) : COLOR
{
   float3 P = Pos*VOLUME_SIZE;
//    return noise(P)*0.5+0.5;
   return abs(noise(P));
}

// Tracked matricies
float4x4 wvp : WorldViewProjection <string UIWidget="WVP";>;
float4x4 world : World <string UIWidget="World";>;

//////////////////////////////

// Structures
struct appdata {
   float3 Position    : POSITION;
   float4 UV        : TEXCOORD0;
   float4 Tangent    : TANGENT0;
   float4 Binormal    : BINORMAL0;
   float4 Normal    : NORMAL;
};

struct vertexOutput {
   float4 HPosition    : POSITION;
   float3 NoisePos     : TEXCOORD0;
   float3 FlamePos     : TEXCOORD1;
   float2 UV           : TEXCOORD2;
};

// Vertex shader
vertexOutput flameVS(appdata IN,
                   uniform float4x4 WorldViewProj,
                   uniform float4x4 World,
                   uniform float3 noiseScale,                    
                   uniform float noiseFreq,
                   uniform float3 noiseAnim,
                   uniform float3 flameScale,
                   uniform float3 flameTrans,
                   uniform float timeScale
                   )
{
   vertexOutput OUT;
   float4 objPos = float4(IN.Position.x,IN.Position.y,IN.Position.z,1.0);
   float3 worldPos = mul(objPos, World).xyz;

   OUT.HPosition = mul(objPos, WorldViewProj);
   float time = fmod(ticks, 10.0);    // avoid large texcoords
   OUT.NoisePos = worldPos*noiseScale*noiseFreq + time*timeScale*noiseAnim;
   OUT.FlamePos = worldPos*flameScale + flameTrans;
   
   // Mod by C.Humphrey so flame can be anywhere in the scene.    
   IN.Position.y += Index;    
   OUT.FlamePos.xz = IN.Position.xy;
   OUT.FlamePos.y = IN.Position.z;        
   // End Mod
   
   OUT.UV = IN.UV;
   return OUT;
}

// Pixel shaders
half4 noise3D(uniform sampler3D NoiseMap, float3 P)
{
   //return tex3D(NoiseMap, P)*2-1;
   return tex3D(NoiseMap, P);
}

half4 turbulence4(uniform sampler3D NoiseMap, float3 P)
{
   half4 sum = noise3D(NoiseMap, P)*0.5 +
                noise3D(NoiseMap, P*2)*0.25 +
                noise3D(NoiseMap, P*4)*0.125 +
               noise3D(NoiseMap, P*8)*0.0625;
   return sum;
}

half4 flamePS(vertexOutput IN,
             uniform sampler3D NoiseMap,
             uniform sampler2D FlameTex,
             uniform half noiseStrength,
             uniform half4 flameColor
             ) : COLOR
{
//    return tex3D(NoiseMap,IN.NoisePos) * flameColor;
//  return turbulence4(NoiseMap, IN.NoisePos) * flameColor;

   half2 uv;
   uv.x = length(IN.FlamePos.xz);    // radial distance in XZ plane    
   uv.y = IN.FlamePos.y;    
   
   //uv.y += turbulence4(NoiseMap, IN.NoisePos) * noiseStrength;
   uv.y += turbulence4(NoiseMap, IN.NoisePos) * noiseStrength / uv.x;    
       
   return tex2D(FlameTex, uv) * flameColor;
}

/****************************************************/
/********** SAMPLERS ********************************/
/****************************************************/

sampler3D noiseTextureSampler = sampler_state
{
   Texture = <noiseTexture>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
};

sampler2D flameTextureSampler = sampler_state
{
   Texture = <flameTexture>;
   MinFilter = Linear;
   MagFilter = Linear;
   MipFilter = Linear;
   AddressU = Clamp;
   AddressV = Clamp;    
};


/****************************************************/
/********** TECHNIQUES ******************************/
/****************************************************/

technique ps20 <string Script = "Pass=p1d;";>
{
   pass p1d <string Script = "Draw=geometry;";>
   {        
       VertexShader = compile vs_1_1 flameVS(wvp, world,noiseScale, noiseFreq, noiseAnim, flameScale, flameTrans, timeScale);
/*
       ZEnable = true;
       ZWriteEnable = true;
       CullMode = None;

       AlphaBlendEnable = true;
       BlendOp = Add;        
       SrcBlend = One;
       DestBlend = One;
*/    
       PixelShader = compile ps_2_0 flamePS(noiseTextureSampler, flameTextureSampler, noiseStrength, flameColor);
   }
}

/***************************** eof ***/


I suggest you don't use this code or shader as it is really not of much use in a 3D world. Once I have some spare time I will make this fully functional. I guess if nothing else you can have a play with the shader and learn from it...

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.