Thursday, 28 June 2012

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...


Posted Mon, Oct 15 2007 8:51 PM by Charles Humphrey | Add post to favorites | Add blog to favorites
Filed under: Randomchaos 3D Engine, Fire, XNA 1.0 [Edit Tags]

No comments:

Post a comment