XNA Lens flare
Lens就是镜头的意思,flare就是闪光可闪耀的意思,Lens flare即是光晕。在3D游戏中,无论是室外场景的太阳还是室内场景中的灯光效果,都有要用到Lens flare的效果。
经过两天多的努力终于把这个效果做出来了,虽然还有点小问题,但总的来说已经实现了。
Lens就是镜头的意思,flare就是闪光可闪耀的意思,Lens flare即是光晕。在3D游戏中,无论是室外场景的太阳还是室内场景中的灯光效果,都有要用到Lens flare的效果。
经过两天多的努力终于把这个效果做出来了,虽然还有点小问题,但总的来说已经实现了。
首先还是老样的,完成后的效果(也是目标效果):
为了节省时间,这个Demo中不少的组件都是以前做的,新加的东西也都做成了组件。镜头的控制也是通过以前写的CameraLib.DLL控制。
先是引用了CameraLib和Terrain。后测试效果为
之后,加入一个SkyBox类:
代码如下:
Code
public class Skybox :DrawableGameComponent
{
Model skybox;
Effect skyboxeffect;
TextureCube textu;
Camera camera;
Vector3 position;
Matrix world = Matrix.Identity;
public Skybox(Game game,Camera camera): base(game)
{
this.camera = camera;
}
protected override void LoadContent()
{
skybox = Game.Content.Load<Model>("skybox");
skyboxeffect = Game.Content.Load<Effect>("skyboxeffect");
textu = Game.Content.Load<TextureCube>("SkyboxTex");
base.LoadContent();
}
public override void Initialize()
{
position = Vector3.Zero;
base.Initialize();
}
public override void Update(GameTime gameTime)
{
position = camera.mposi;
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
skyboxeffect.CurrentTechnique = skyboxeffect.Techniques[0];
skyboxeffect.Parameters["World"].SetValue(world * Matrix.CreateScale(85));
skyboxeffect.Parameters["View"].SetValue(camera.View);
skyboxeffect.Parameters["Projection"].SetValue(camera.Projection);
skyboxeffect.Parameters["eyePosition"].SetValue(camera.mposi);
skyboxeffect.Parameters["textu"].SetValue(textu);
foreach (ModelMesh mesh in skybox.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = skyboxeffect;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
测试效果如下:
最后就是主要的部分了——LensFlare,为了以后使用方便,我还是将产生LensFlare的类封装成组件以方便以后需要的时候再调用。
其中主要有两个类,一个是Flare.代码如下:
Code
namespace LensFlareLib
{
public class Flare
{
public float Position;
public float Scale;
public Color Color;
public string TextureName;
public Texture2D Texture;
public Flare(float position,float scale,Color color,string textureName)
{
Position = position;
Scale = scale;
Color = color;
TextureName = textureName;
}
}
}
另一个就是LensFlare的主要代码:
代码如下:
Code
public class Lens : DrawableGameComponent
{
float glowSize = 400;
float querySize = 100;
public Matrix View;
public Matrix Projection;
public Vector3 LightDirection = Vector3.Normalize(new Vector3(-1, -0.1f, 0.3f));
// Graphics objects.
Texture2D glowSprite;
SpriteBatch spriteBatch;
BasicEffect basicEffect;
VertexDeclaration vertexDeclaration;
VertexPositionColor[] queryVertices;
// An occlusion query is used to detect when the sun is hidden behind scenery.
OcclusionQuery occlusionQuery;
bool occlusionQueryActive;
float occlusionAlpha;
Flare[] flares =
{
new Flare(-0.5f, 0.7f, new Color( 50, 25, 50), "flare1"),
new Flare( 0.3f, 0.4f, new Color(100, 255, 200), "flare1"),
new Flare( 1.2f, 1.0f, new Color(100, 50, 50), "flare1"),
new Flare( 1.5f, 1.5f, new Color( 50, 100, 50), "flare1"),
new Flare(-0.3f, 0.7f, new Color(200, 50, 50), "flare2"),
new Flare( 0.6f, 0.9f, new Color( 50, 100, 50), "flare2"),
new Flare( 0.7f, 0.4f, new Color( 50, 200, 200), "flare2"),
new Flare(-0.7f, 0.7f, new Color( 50, 100, 25), "flare3"),
new Flare( 0.0f, 0.6f, new Color( 25, 25, 25), "flare3"),
new Flare( 2.0f, 1.4f, new Color( 25, 50, 100), "flare3"),
};
Camera camera;
public Lens(Game game, Camera camera): base(game)
{
this.camera = camera;
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
glowSprite = Game.Content.Load<Texture2D>("glow");
foreach (Flare flare in flares)
{
flare.Texture = Game.Content.Load<Texture2D>(flare.TextureName);
}
basicEffect = new BasicEffect(GraphicsDevice, null);
basicEffect.View = Matrix.Identity;
basicEffect.VertexColorEnabled = true;
vertexDeclaration = new VertexDeclaration(GraphicsDevice,VertexPositionColor.VertexElements);
// Create vertex data for the occlusion query polygons.
queryVertices = new VertexPositionColor[4];
queryVertices[0].Position = new Vector3(-querySize / 2, -querySize / 2, -1);
queryVertices[1].Position = new Vector3(querySize / 2, -querySize / 2, -1);
queryVertices[2].Position = new Vector3(querySize / 2, querySize / 2, -1);
queryVertices[3].Position = new Vector3(-querySize / 2, querySize / 2, -1);
// Create the occlusion query object.
occlusionQuery = new OcclusionQuery(GraphicsDevice);
base.LoadContent();
}
public override void Update(GameTime gameTime)
{
Projection = camera.Projection;
base.Update(gameTime);
}
#region Draw
public override void Draw(GameTime gameTime)
{
Matrix infiniteView = View;
infiniteView.Translation = Vector3.Zero;
// Project the light position into 2D screen space.
Viewport viewport = GraphicsDevice.Viewport;
Vector3 projectedPosition = viewport.Project(-LightDirection, Projection, infiniteView, Matrix.Identity);
if ((projectedPosition.Z < 0) || (projectedPosition.Z > 1))
return;
Vector2 lightPosition = new Vector2(projectedPosition.X, projectedPosition.Y);
UpdateOcclusion(lightPosition);
// If it is visible, draw the flare effect.
if (occlusionAlpha > 0)
{
DrawGlow(lightPosition);
DrawFlares(lightPosition);
}
RestoreRenderStates();
}
void UpdateOcclusion(Vector2 lightPosition)
{
if (!occlusionQuery.IsSupported)
return;
if (occlusionQueryActive)
{
if (!occlusionQuery.IsComplete)
return;
const float queryArea = querySize * querySize;
occlusionAlpha = Math.Min(occlusionQuery.PixelCount / queryArea, 1);
}
RenderState renderState = GraphicsDevice.RenderState;
renderState.DepthBufferEnable = true;
renderState.DepthBufferWriteEnable = false;
renderState.AlphaTestEnable = false;
renderState.ColorWriteChannels = ColorWriteChannels.None;
Viewport viewport = GraphicsDevice.Viewport;
basicEffect.World = Matrix.CreateTranslation(lightPosition.X, lightPosition.Y, 0);
basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, 1);
basicEffect.Begin();
basicEffect.CurrentTechnique.Passes[0].Begin();
GraphicsDevice.VertexDeclaration = vertexDeclaration;
occlusionQuery.Begin();
GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleFan, queryVertices, 0, 2);
occlusionQuery.End();
basicEffect.CurrentTechnique.Passes[0].End();
basicEffect.End();
// Reset renderstates.
renderState.ColorWriteChannels = ColorWriteChannels.All;
renderState.DepthBufferWriteEnable = true;
occlusionQueryActive = true;
}
void DrawGlow(Vector2 lightPosition)
{
Vector4 color = new Vector4(1, 1, 1, occlusionAlpha);
Vector2 origin = new Vector2(glowSprite.Width, glowSprite.Height) / 2;
float scale = glowSize * 2 / glowSprite.Width;
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(glowSprite, lightPosition, null, new Color(color), 0,origin, scale, SpriteEffects.None, 0);
spriteBatch.End();
}
void DrawFlares(Vector2 lightPosition)
{
Viewport viewport = GraphicsDevice.Viewport;
Vector2 screenCenter = new Vector2(viewport.Width, viewport.Height) / 2;
Vector2 flareVector = screenCenter - lightPosition;
spriteBatch.Begin(SpriteBlendMode.Additive);
foreach (Flare flare in flares)
{
Vector2 flarePosition = lightPosition + flareVector * flare.Position;
Vector4 flareColor = flare.Color.ToVector4();
flareColor.W *= occlusionAlpha;
Vector2 flareOrigin = new Vector2(flare.Texture.Width,flare.Texture.Height) / 2;
spriteBatch.Draw(flare.Texture, flarePosition, null,new Color(flareColor), 1, flareOrigin, flare.Scale, SpriteEffects.None, 0);
}
spriteBatch.End();
}
#endregion
}
测试程序,只是效果有点不理想,但还过后去,以后有时间了再修正。