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.