[XNA] Particle rendering

Started by
6 comments, last by deadlydog 15 years, 2 months ago
HI, I am having a slight problem rendering my particle system. I am rendering the particles as primitives but the seem rendering on top of the scene.. So the models seem to be rendered underneath the particles even though I am rendering the particles first. I tried changing the z position of the particles to something ridiculous but that didn't fix it either. Here is the code that I use to render: Particle Manager - rendering code

public void render(){
            graphics.GraphicsDevice.RenderState.PointSize = 2;
            graphics.GraphicsDevice.VertexDeclaration = basicEffectVertexDeclaration;
            graphics.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);

            // This code would go between a device 
            // BeginScene-EndScene block.
            int tmpN = totalParticles();
            if (tmpN > 0)
            {
                basicEffect.Begin();
                foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList, pointList, 0, tmpN);
                    pass.End();
                }

                basicEffect.End();
            }
        }

Here is the model rendering code

public void Draw(Matrix view, Matrix projection)
        {
            Matrix translateMatrix = Matrix.CreateTranslation(Position);
            Matrix worldMatrix = translateMatrix;

            foreach (ModelMesh mesh in Model.Meshes){
                foreach (BasicEffect effect in mesh.Effects){

                    effect.World = worldMatrix;
                    effect.View = view;
                    effect.Projection = projection;

                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;
                    
                }
                mesh.Draw();
            }
        }

Here is the code in game that calls the rendering routines from the different classes

protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            // TODO: Add your drawing code here
            pm.render();

            
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                aspectRatio, 1.0f, 10000.0f);
            player.Draw(viewMatrix, projection );

            base.Draw(gameTime);
        }


Seems like I must be missing something trivial but I can't figure out what it is. Any help would be appreciated. Thanks!
Advertisement
Don't know exactly what the problem is, but when you render particles you typically need to check, but not update the z-buffer.
My Apps:Putt Nutz

Are you setting an appropriate view & projection matrix on the BasicEffect of the particle system? It's been a while since I trinkered with point sprites, but as I recall they're just rendered in world space and thus need these matrices to show up correctly.

Also, please define 'something ridiculous' [smile]
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Sorry, I wasn't specific enough in my initial post. I am making a 2d side scrolling space shooter, but I am doing it in 3d to leverage the effects of 3d. So my models that I render will be in the foreground while the particles will be effectively in the background.

As far as ridiculous I initialize the particles to have a position in the Z axis of 9000, where as the models are something on the order of 100 I believe.
I am currently not using point sprites but rendering pixels that correlate to the vertex points.

I will provide the full source since there are only a couple of classes currently

Particle.cs
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Graphics;namespace CowboyBebop{    class Particle{        public float angle;        public float velocity;        //public float velocity0;        //public float velocityy;        public Vector3 position;        public Vector3 position0;        public Color color;        public int life;        public double time0;        public int type;        //public int pointSize;        public enum ptype        {            STARFIELD,            EXPLOSION,            PROJECTILE        }        public static float gravity = 9.81f;        public Particle(){            this.angle = 0.0f;            //this.velocity0 = 1.0f;            //this.velocityy = this.velocity0;            this.velocity = 1.0f;            this.position = new Vector3(0.0f, 0.0f, 0.0f);            this.position0 = new Vector3(0.0f, 0.0f, 0.0f);            this.color = Color.White;            this.life = 0;            this.time0 = 0.0f;            this.type = (int)ptype.EXPLOSION;            //this.pointSize = 1;                    }        public Particle(float xpos, float ypos, float zpos, Color c1, float v, float a){            this.angle = a;            //this.velocity0 = v;            //this.velocityy = v;            this.velocity = v;            this.color = c1;            this.position = new Vector3(xpos, ypos, zpos);            this.position0 = new Vector3(xpos, ypos, zpos);            this.life = 0;            this.time0 = 0.0f;            this.type = (int)ptype.EXPLOSION;            //this.pointSize = 1;        }        public void age(){            if (this.life > 0){                this.life--;            }                    }        public void kill(){            this.life = 0;        }        public Boolean isAlive(){            if (this.life > 0){return true;}            else{return false;}        }        public void move(){            this.position.X = this.position.X + ((float)Math.Sin(MathHelper.ToRadians(angle))) * this.velocity;            this.position.Y = this.position.Y + ((float)Math.Cos(MathHelper.ToRadians(angle))) * this.velocity;        }        public void projectileMove(double time){            this.position0 = this.position;            float currentTime = (float)(time - this.time0);            this.position.Y = this.position0.Y + (((this.velocity * (float)Math.Cos(angle)) * currentTime) + ((Particle.gravity * (currentTime * currentTime))) / 2.0f);            this.position.X = this.position0.X + ((this.velocity * (float)Math.Sin(angle)) * currentTime);        }    }}


ParticleManager.cs
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Microsoft.Xna.Framework.Graphics;using Microsoft.Xna.Framework;namespace CowboyBebop{    class ParticleManager{        private Particle[] particleList;        private int totalP;        private Random random;        private GraphicsDeviceManager graphics;        private VertexPositionColor[] pointList;        private int width;        private int height;        private Matrix worldMatrix;        private Matrix viewMatrix;        private Matrix projectionMatrix;        private VertexDeclaration basicEffectVertexDeclaration;        private VertexBuffer vertexBuffer;        private BasicEffect basicEffect;                public ParticleManager(GraphicsDeviceManager g)        {            this.graphics = g;            this.width = graphics.PreferredBackBufferWidth;            this.height = graphics.PreferredBackBufferHeight;            this.totalP = 1000;            this.pointList = new VertexPositionColor[this.totalP];            this.particleList = new Particle[this.totalP];            this.random = new Random();            for (int i = 0; i < this.totalP; i++){                this.particleList = new Particle();            }            initGraphics();        }        public ParticleManager(int total, GraphicsDeviceManager g)        {            this.graphics = g;            this.width = graphics.PreferredBackBufferWidth;            this.height = graphics.PreferredBackBufferHeight;            this.totalP = total;            this.pointList = new VertexPositionColor[this.totalP];            this.particleList = new Particle[totalP];            this.random = new Random();            for (int i = 0; i < this.totalP; i++){                this.particleList = new Particle();            }            initGraphics();        }        private void initGraphics(){            float tilt = MathHelper.ToRadians(0f);  // 0 degree angle            // Use the world matrix to tilt the cube along x and y axes.            worldMatrix = Matrix.CreateRotationX(tilt) *                Matrix.CreateRotationY(tilt);            viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 10000.0f),Vector3.Zero,Vector3.Up);            projectionMatrix = Matrix.CreateOrthographicOffCenter(                0,                (float)graphics.GraphicsDevice.Viewport.Width,                (float)graphics.GraphicsDevice.Viewport.Height,                0,                1.0f, 10000.0f);            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);            basicEffect.Alpha = 1.0f;            basicEffect.VertexColorEnabled = true;                        basicEffect.World = worldMatrix;            basicEffect.View = viewMatrix;            basicEffect.Projection = projectionMatrix;            vertexBuffer = new VertexBuffer(graphics.GraphicsDevice,                VertexPositionColor.SizeInBytes * this.totalP,                BufferUsage.None            );            vertexBuffer.SetData<VertexPositionColor>(pointList);                        basicEffectVertexDeclaration = new VertexDeclaration(            graphics.GraphicsDevice, VertexPositionColor.VertexElements);        }        public void update(double time){            boundsCheck();            age();            updatePointList();            move(time);        }        private void move(double time){            int count = 0;            int type = (int)Particle.ptype.EXPLOSION;            for (int i = 0; i < this.totalP; i++){                if (this.particleList.isAlive()){                    type = this.particleList.type;                    switch (type){                        case (int)Particle.ptype.PROJECTILE:                            this.particleList.projectileMove(time);                            break;                        case (int)Particle.ptype.EXPLOSION:                            this.particleList.move();                            break;                        case (int)Particle.ptype.STARFIELD:                            this.particleList.move();                            break;                        default:                            this.particleList.move();                            break;                    }                    this.pointList[count].Position.X = this.particleList.position.X;                    this.pointList[count].Position.Y = this.particleList.position.Y;                    this.pointList[count].Position.Z = this.particleList.position.Z;                    count++;                }            }        }        private void age(){            for (int i = 0; i < this.totalP; i++){                if (this.particleList.isAlive()){                    if (this.particleList.type != (int)Particle.ptype.STARFIELD){                        this.particleList.age();                    }                }            }        }        public void killAll(){            for (int i = 0; i < this.totalP; i++){                this.particleList.kill();            }        }        public void addExplosion(int total, float x, float y, double time)        {            int count = 0;            for (int i = 0; i < this.totalP; i++){                if (!this.particleList.isAlive()){                    if (count < total){                        this.particleList.position0.X = x;                        this.particleList.position0.Y = y;                        this.particleList.position0.Z = 0;                        this.particleList.position.X = x;                        this.particleList.position.Y = y;                        this.particleList.position.Z = 0;                        this.particleList.time0 = time;                        this.particleList.angle = (float)(this.random.NextDouble() * 359);                        this.particleList.velocity = (float)(this.random.NextDouble() * 10) + 0.1f;                        this.particleList.color = getExlosionColor();                        this.particleList.life = this.random.Next(50);                        this.particleList.type = (int)Particle.ptype.EXPLOSION;                        count++;                    }                }            }        }        public void addProjectileCloud(int total, float x, float y, double time)        {            int count = 0;            for (int i = 0; i < this.totalP; i++)            {                if (!this.particleList.isAlive())                {                    if (count < total)                    {                        this.particleList.position0.X = x;                        this.particleList.position0.Y = y;                        this.particleList.position0.Z = 9000;                        this.particleList.position.X = x;                        this.particleList.position.Y = y;                        this.particleList.position.Z = 9000;                        this.particleList.time0 = time;                        this.particleList.angle = (float)(this.random.NextDouble() * 359);                        this.particleList.velocity = (float)(this.random.NextDouble() * 3) + 0.1f;                        this.particleList.color = getExlosionColor();                        this.particleList.life = this.random.Next(200);                        this.particleList.type = (int)Particle.ptype.PROJECTILE;                        count++;                    }                }            }        }        public void initializeStarField(int totalStars){            int count = 0;            for (int i = 0; i < this.totalP; i++){                if (!this.particleList.isAlive()){                    if (count < totalStars){                        this.particleList.position0.X = (float)(this.random.NextDouble() * this.width);                        this.particleList.position0.Y = (float)(this.random.NextDouble() * this.height);                        this.particleList.position0.Z = 9000;                        this.particleList.position.X = this.particleList.position0.X;                        this.particleList.position.Y = this.particleList.position0.Y;                        this.particleList.position.Z = 9000;                        this.particleList.time0 = 0.0f;                        this.particleList.angle = 270.0f;                        this.particleList.velocity = (float)(this.random.NextDouble() * 10) + 0.1f;                        this.particleList.color = Color.White;                        this.particleList.life = 1;                        this.particleList.type = (int)Particle.ptype.STARFIELD;                        count++;                    }                }            }        }        private Color getExlosionColor(){            int tmpN = random.Next(4);            Color c = new Color();            switch (tmpN){                case 0:                    c = Color.White;                    break;                case 1:                    c = Color.Red;                    break;                case 2:                    c = Color.Orange;                    break;                case 3:                    c = Color.Yellow;                    break;            }            return c;        }        public void render(){            graphics.GraphicsDevice.RenderState.PointSize = 2;            graphics.GraphicsDevice.VertexDeclaration = basicEffectVertexDeclaration;            graphics.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);            // This code would go between a device             // BeginScene-EndScene block.            int tmpN = totalParticles();            if (tmpN > 0)            {                basicEffect.Begin();                foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)                {                    pass.Begin();                    graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList, pointList, 0, tmpN);                    pass.End();                }                basicEffect.End();            }        }        private void boundsCheck(){            for (int i = 0; i < this.totalP; i++){                if (this.particleList.isAlive()){                    if ((this.particleList.position.X < 0) || (this.particleList.position.X > this.width)){                        this.particleList.kill();                        if (this.particleList.type == (int)Particle.ptype.STARFIELD){                            this.particleList.position0.X = (float)this.width;                            this.particleList.position0.Y = (float)(this.random.NextDouble() * this.height);                            this.particleList.position0.Z = 9000;                            this.particleList.position.X = this.particleList.position0.X;                            this.particleList.position.Y = this.particleList.position0.Y;                            this.particleList.position.Z = 9000;                            this.particleList.life = 1;                        }                    }                    if ((this.particleList.position.Y < 0) || (this.particleList.position.Y > this.height)){                        this.particleList.kill();                    }                }            }        }        private void updatePointList(){            int tmpN = totalParticles();            int count = 0;            this.pointList = new VertexPositionColor[tmpN];            for (int i = 0; i < this.totalP; i++)            {                if (this.particleList.isAlive())                {                    this.pointList[count].Position.X = this.particleList.position.X;                    this.pointList[count].Position.Y = this.particleList.position.Y;                    this.pointList[count].Position.Z = this.particleList.position.Z;                    this.pointList[count].Color = this.particleList.color;                                        count++;                }            }        }        public int totalParticles(){            int tmpNum = 0;            for (int i = 0; i < this.totalP; i++){                if (this.particleList.isAlive()){                    tmpNum++;                }            }            return tmpNum;        }    }}


GameObject.cs
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Content;using Microsoft.Xna.Framework.Graphics;using Microsoft.Xna.Framework.Input;namespace CowboyBebop{    class GameObject{        public Model Model { get; set; }        public Vector3 Position; //{ get; set; }        public bool IsActive { get; set; }        public BoundingSphere BoundingSphere { get; set; }        public GameObject()        {            Model = null;            Position = Vector3.Zero;            IsActive = false;            BoundingSphere = new BoundingSphere();        }        public void LoadContent(ContentManager content, string modelName){            Model = content.Load<Model>(modelName);        }        public void Draw(Matrix view, Matrix projection)        {            Matrix translateMatrix = Matrix.CreateTranslation(Position);            Matrix worldMatrix = translateMatrix;            foreach (ModelMesh mesh in Model.Meshes){                foreach (BasicEffect effect in mesh.Effects){                    effect.World = worldMatrix;                    effect.View = view;                    effect.Projection = projection;                    effect.EnableDefaultLighting();                    effect.PreferPerPixelLighting = true;                                    }                mesh.Draw();            }        }    }}


Player.cs-In the process of moving all the rendering an position matrices into this class but haven't implemented it yet
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Microsoft.Xna.Framework;namespace CowboyBebop{    class Player : GameObject{                public float Zoom = 200;        public float RotationY = 0.0f;        public float RotationX = 0.0f;        public Matrix viewMatrix;        public Matrix projectionMatrix;        public Player(float apsectRatio) : base(){            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), apsectRatio, 1.0f, 10000.0f);            viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, Zoom), Vector3.Zero, Vector3.Up);        }    }}


Game.cs
using System;using System.Collections.Generic;using System.Linq;using Microsoft.Xna.Framework;using Microsoft.Xna.Framework.Audio;using Microsoft.Xna.Framework.Content;using Microsoft.Xna.Framework.GamerServices;using Microsoft.Xna.Framework.Graphics;using Microsoft.Xna.Framework.Input;using Microsoft.Xna.Framework.Media;using Microsoft.Xna.Framework.Net;using Microsoft.Xna.Framework.Storage;namespace CowboyBebop{    /// <summary>    /// This is the main type for your game    /// </summary>    public class Game1 : Microsoft.Xna.Framework.Game    {        GraphicsDeviceManager graphics;        SpriteBatch spriteBatch;        //Add our particle manager        ParticleManager pm;        MouseState old_mouse;                private Player player;                private float Zoom = 200;        private float RotationY = 0.0f;        private float RotationX = 0.0f;        //private Matrix gameWorldRotation;        private Matrix viewMatrix;        private float aspectRatio;        public Game1()        {            graphics = new GraphicsDeviceManager(this);            IsMouseVisible = true;            Content.RootDirectory = "Content";        }        /// <summary>        /// Allows the game to perform any initialization it needs to before starting to run.        /// This is where it can query for any required services and load any non-graphic        /// related content.  Calling base.Initialize will enumerate through any components        /// and initialize them as well.        /// </summary>        protected override void Initialize()        {            // TODO: Add your initialization logic here            viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, Zoom), Vector3.Zero, Vector3.Up);            aspectRatio = graphics.GraphicsDevice.Viewport.Width / graphics.GraphicsDevice.Viewport.Height;            player = new Player(aspectRatio);            base.Initialize();        }        /// <summary>        /// LoadContent will be called once per game and is the place to load        /// all of your content.        /// </summary>        protected override void LoadContent()        {            // Create a new SpriteBatch, which can be used to draw textures.            spriteBatch = new SpriteBatch(GraphicsDevice);            // TODO: use this.Content to load your game content here            pm = new ParticleManager(graphics);            pm.initializeStarField(100);            player.LoadContent(this.Content, "Models\\swordfish");                    }        /// <summary>        /// UnloadContent will be called once per game and is the place to unload        /// all content.        /// </summary>        protected override void UnloadContent()        {            // TODO: Unload any non ContentManager content here        }        /// <summary>        /// Allows the game to run logic such as updating the world,        /// checking for collisions, gathering input, and playing audio.        /// </summary>        /// <param name="gameTime">Provides a snapshot of timing values.</param>        protected override void Update(GameTime gameTime)        {            // Allows the game to exit            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)                this.Exit();            // TODO: Add your update logic here            double t = gameTime.TotalGameTime.TotalSeconds;            updateMouse(t);            updateKeyboard(t);            pm.update(t);                                    base.Update(gameTime);        }        protected void updateMouse(double time)        {            MouseState current_mouse = Mouse.GetState();            if (current_mouse.LeftButton == ButtonState.Pressed)            {                if (old_mouse.LeftButton != ButtonState.Pressed)                {                    //pm.addExplosion(100, (float)current_mouse.X, (float)current_mouse.Y, time);                    pm.addProjectileCloud(100, (float)current_mouse.X, (float)current_mouse.Y, time);                }            }            old_mouse = current_mouse;        }        protected void updateKeyboard(double time){            KeyboardState newState = Keyboard.GetState();            // Is the SPACE key down?            if (newState.IsKeyDown(Keys.A)){                Zoom++;            }            if (newState.IsKeyDown(Keys.Z))            {                Zoom--;            }            if (newState.IsKeyDown(Keys.Right)){                player.Position.X++;            }            if (newState.IsKeyDown(Keys.Left)){                player.Position.X--;            }            if (newState.IsKeyDown(Keys.Down)){                player.Position.Y--;            }            if (newState.IsKeyDown(Keys.Up)){                player.Position.Y++;            }                        //gameWorldRotation = Matrix.CreateRotationX(MathHelper.ToRadians(RotationX)) *        //Matrix.CreateRotationY(MathHelper.ToRadians(RotationY));            viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, Zoom), Vector3.Zero, Vector3.Up);            if (newState.IsKeyDown(Keys.Escape)){                Exit();            }        }        /// <summary>        /// This is called when the game should draw itself.        /// </summary>        /// <param name="gameTime">Provides a snapshot of timing values.</param>        protected override void Draw(GameTime gameTime)        {            GraphicsDevice.Clear(Color.Black);            // TODO: Add your drawing code here            pm.render();                        Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),                aspectRatio, 1.0f, 10000.0f);            player.Draw(viewMatrix, projection );            base.Draw(gameTime);        }    }}
I was able to find someone else that had the exact same problem on the XNA forums but it doesn't seem like there was a resolution to his problem either :(

http://forums.xna.com/forums/p/14077/73787.aspx#73787

Hmm, I don't see anything wrong with the code at first glance, but you might want to try setting the view & projection matrices on the effect explicitly in your ParticleManager.render() method. This is the typical approach and that way you know fo sure that the correct matrices are set up each frame.

Other than that, the zfar may be a bit far off with 10000.0f, which reduces the usable resolution of the zbuffer and may cause depth artifacts. With the differences in z-coord of the player and the particles (200/9000 iirc) that shouldn't matter too much, but it still might be a good idea to reduce z-far to say 1000. If you then leave the particles at z=9000 and they still show up over the player char (or at all!), you'll know either the matrices aren't getting set correctly, or something really fishy is going on.
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Thanks Remi,

that was the problem, I passed the view and the projection matrix from the game into the constructor for my my particle manager and everything works great!
You should check out the Dynamic Particle System Framework. It's an XNA framework you build your particle systems on top of, and it takes care of the updating and rendering for you. So if you plan on adding more particle systems this should make it easier. Check it out at http://dpsf.freeforums.org
-Dan- Can't never could do anything | DansKingdom.com | Dynamic Particle System Framework for XNA

This topic is closed to new replies.

Advertisement