Skip to main content
GameDev.net gamedev.net
🔒 Locked

GPU particles

Started by Telanor Jun 18, 2013 at 11:35 PM 38 replies 15.4k views
Original Post
Telanor
Telanor
So I've been reading posts and articles about GPU particle systems and the general consensus for stateful systems seems to be to create 2 buffers, bind one as input, read from it in the vertex shader, then output to the other. And then in a second pass, you read from that second buffer to do your rendering.

In all the samples I've looked at (like the ones in the directx SDK), they create a couple hundred particles to start off with and then just reuse those same ones over and over. The particles never die off and new ones are never spawned. In one of the demos they teleport the particles back to the spawn point to make it look like they're dying off and being respawned.

While that works in a demo, I don't see how that can work in practice when you can have dozens of different emitters emitting different types of particles at different rates. Essentially the problem of choosing when and where to spawn particles has been completely avoided by all the samples I've seen. So my question is: how would you handling spawning particles when you have many different emitters with different properties?

Also, on the topic of gpu particles, I've seen some systems that use directcompute instead. Am I correct in assuming that only works on feature level 11 and up?
lwm
lwm

A few day ago, I created a little GPU particle sample for SharpDX. The buffers start off completely empty. To spawn new particles, I copy them to a third vertex buffer. After the pass to update the particles from last frame, I just "draw" the third buffer to append the new particles to the buffer for the current frame as well.

Any properties that are "sufficiently random" like the particle's color or various flags, I put directly into the vertex. I didn't include this in the sample to avoid bloating it, but to handle properties that a lot of particles share, every particle also has a type ID that indexes into an array of particle types in the geometry shader.

current project: Roa
Telanor
Telanor
Very nice sample. So basically it has a small, 128 element buffer that the cpu can dump new particles into and calling the shader with that as the input results in it appending them to the existing list.

I was hoping to avoid the geometry shader since a lot of people have been saying it has poor performance, but I'm not sure I can see a way of doing something similar using vertex texture fetch instead. Your GS setup is so straightforward and simple though that I might just go with that.

Off-topic question: How do you disable vsync for sharpdx toolkit? I want to see what kind of performance this has.
ATEFred
ATEFred




Also, on the topic of gpu particles, I've seen some systems that use directcompute instead. Am I correct in assuming that only works on feature level 11 and up?

Yes, that's correct. Or possibly feature level 10 depending on how you do it.

Last weekend I was knocking a little cs based gpu particle system up actually. it's not very hard.

you have 2 buffers which you use as append and consume buffers. I made them relatively large (a million particle wide in my test). Perform sim consuming from one and appending to the other. Then go through emitters and emit by append into the current buffer. Then to render use drawIsntancedIndirect using the size of the current buffer as indirect draw arguments, and expand in GS.

on my 580, sim and drawing of half a million particles ends up taking less than a ms. Not appropriate for everything, as all particle systems using the setup end up with the same sim code, diven by a few parameters, so if you want wildly different behaviour you might also want another approach for those. Also, it kind of gets hard to order particles with alpha objects in your scene, as they are all rendered in one batch. For this I will use it only for specific effects that need a large number of very small particles, and run a traditional cpu based particle sim for more standard fuzzy particles effects.

Here 's a quick video of what it looks like without textures / anything fancy:


lwm
lwm

Off-topic question: How do you disable vsync for sharpdx toolkit? I want to see what kind of performance this has.

this.graphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
this.IsFixedTimeStep = false;

On my system (Nvidia GTX 460), updating and drawing 16k (1024 * 16) particles takes about 0.3 milliseconds.

current project: Roa
kauna
kauna




I was hoping to avoid the geometry shader since a lot of people have been saying it has poor performance,

Actually, on some cards at least 1:4 geometry expansion (typical particle scenario) is specifically handled so that it doesn't result as poor performance.

Cheers!

Jason Z
Jason Z

I was hoping to avoid the geometry shader since a lot of people have been saying it has poor performance,

Actually, on some cards at least 1:4 geometry expansion (typical particle scenario) is specifically handled so that it doesn't result as poor performance.

Cheers!

In the particle storm sample in Hieroglyph 3 I use 1:4 expansion in the GS, and it still runs pretty darn fast. It isn't perfect, but even on older DX11 hardware it handles 100k+ particles with no problems...

Jason Zink :: DirectX MVP   Direct3D 11 engine on CodePlex: Hieroglyph 3 Direct3D Books: 
MJP
MJP

Yeah the point->quad expansion has special-case handling in GPU's because it's so common. If you really want to avoid GS you can also use instancing to accomplish the same thing.

Telanor
Telanor
It's actually the GS-based update pass I was hoping to avoid more so than the point->quad expansion. There were some posts I read here on gamedev about how using the GS ended up being slower than some other method. I don't exactly remember what it said, I'd have to go find the link again. In any case, I'm in the process of porting the sample to my project. Even if it uses the GS it'll be faster than the CPU implementation I have now.
Ashaman73
Ashaman73




While that works in a demo, I don't see how that can work in practice when you can have dozens of different emitters emitting different types of particles at different rates.

Particle pooling works quite nicely. Just use some metadata like number of active paricles etc. and a usage factor or similar. Once you approach the upper limit of active particles(e.g. 80%), your emitters will start to emit less particles. This will auto-adjust quite nicely as long as you don't have some extreme situtations.




There were some posts I read here on gamedev about how using the GS ended up being slower than some other method.

Yes, the GS is not the best idea to instanciate larger models (trees, rocks ..). In this case standard instancing works better. But for very low expansion (1:3/1:4), the geometry shader seems to work much better, especially when considering what MJP said, that the GPU hardware supports this special case.

Particle systems often suffer more from pixel overdraw (alpha blending, no early-z etc.) than from vertex performance. Still it depends on lot of factors (blend mode, particle size, attributes: when simulating the particle on the GPU, you will have lot of attributes per vertex). Considering this, trimming the particle like what humus sugguested in one of his presentations (Graphics Gems for Games - Findings from Avalanche Studios) might give you an additional performance boost ( if needed smile.png )

Telanor
Telanor
Well... I ported it over but I can't see any particles. The DX debug layer is giving me a warning when I try to run the VS graphics debugger:

Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

D3D11 WARNING: ID3D11DeviceContext::Begin: Begin is being invoked on a Query, where the previous results have not been obtained with GetData. This is valid; but unusual. The previous results are being abandoned, and new Query results will be generated. [ EXECUTION WARNING #408: QUERY_BEGIN_ABANDONING_PREVIOUS_RESULTS]
I'm not even using queries. If I turn off the debug layer, it stops on the line above it:

Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

"Additional information: External component has thrown an exception."

Full code:
[spoiler]
using Craft.Camera;
using Craft.Main;
using Craft.Misc;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Runtime.InteropServices;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;

namespace Craft.Game.Particles
{
	public struct ParticleVertex
	{
		public static readonly InputElement[] VertexDeclaration = new[]
		{
			new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
			new InputElement("NORMAL", 0, Format.R32G32B32_Float, 0),
			new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 0),
			new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0),
			new InputElement("TEXCOORD", 1, Format.R32_UInt, 0),
			new InputElement("TEXCOORD", 2, Format.R32G32_Float, 0)
		};

		public static readonly int SizeInBytes = Marshal.SizeOf(typeof(ParticleVertex));

		public Vector3 Position;
		public Vector3 Velocity;
		public Vector4 Color;
		public Vector2 TimerLifetime;
		public uint Flags;
		public Vector2 SizeStartEnd;

		public ParticleVertex(Vector3 position, Vector3 velocity, Vector4 color, Vector2 timerLifetime, uint flags, Vector2 sizeStartEnd) : this()
		{
			Position = position;
			Velocity = velocity;
			Color = color;
			TimerLifetime = timerLifetime;
			Flags = flags;
			SizeStartEnd = sizeStartEnd;
		}
	}

	public static class GPUParticles
	{
		private const int MaxParticles = 1048576;
		private const int MaxNew = 128;

		[Flags]
		enum ParticleFlags
		{
			Constrained = 1,
			FastFade = 2
		}

		private static Buffer spawnBuffer;
		private static Buffer drawFromBuffer;
		private static Buffer streamToBuffer;

		private static Effect effect;
		private static EffectPass updatePass;
		private static EffectPass renderPass;
		private static InputLayout layout;

		private static EffectMatrixVariable evView;
		private static EffectMatrixVariable evProjection;
		private static EffectMatrixVariable evLookAt;
		private static EffectScalarVariable evTime;
		private static EffectVectorVariable evCamDir;
		private static EffectVectorVariable evGravity;
		private static EffectShaderResourceVariable evTexture;
		private static ParticleVertex[] newParticles;
		private static int numNew;

		internal static void Initialize()
		{
			newParticles = new ParticleVertex[MaxNew];

			effect = ContentLoader.LoadEffect(@"Content\Effects\GPUParticles.fxo");
			updatePass = effect.GetTechniqueByName("UpdateTeq").GetPassByIndex(0);
			renderPass = effect.GetTechniqueByName("RenderTeq").GetPassByIndex(0);
			layout = ContentLoader.CreateInputLayout(updatePass, ParticleVertex.VertexDeclaration);

			spawnBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxNew, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0));
			drawFromBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));
			streamToBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));

			evView = effect.GetVariableByName("View").AsMatrix();
			evProjection = effect.GetVariableByName("Projection").AsMatrix();
			evLookAt = effect.GetVariableByName("LookAt").AsMatrix();
			evTime = effect.GetVariableByName("Time").AsScalar();
			evCamDir = effect.GetVariableByName("CamDir").AsVector();
			evGravity = effect.GetVariableByName("Gravity").AsVector();
			evTexture = effect.GetVariableByName("Texture").AsShaderResource();

			evTexture.SetResource(ContentLoader.LoadTexture(@"Content\Textures\particles\sparkle.png"));
		}

		public static void Spawn(Vector3 position, Vector3 velocity, Color color, float sizeStart, float sizeEnd, float lifetime, bool constrained, bool fastFade)
		{
			// discard particle if buffer is full
			if (numNew >= MaxNew)
				return;

			// create particle struct
			var v = new ParticleVertex(position, velocity, color.ToVector4(), new Vector2(0, lifetime), 0, new Vector2(sizeStart, sizeEnd));

			// set the particle's flags
			if (constrained) v.Flags |= (uint) ParticleFlags.Constrained;
			if (fastFade) v.Flags |= (uint) ParticleFlags.FastFade;

			// append to buffer
			newParticles[numNew++] = v;
		}

		internal static void Draw(ICamera camera)
		{
			var cameraForward = camera.Forward;
			var lookAtMatrix = Matrix.Billboard(Vector3.Zero, -cameraForward, Vector3.UnitY, cameraForward);

			evView.SetMatrix(camera.View);
			evProjection.SetMatrix(camera.Projection);
			evLookAt.SetMatrix(lookAtMatrix);
			evTime.Set((float) Core.GameTime.TotalGameTime.TotalSeconds);
			evCamDir.Set(cameraForward);
			evGravity.Set(9.81f);

			Advance();
			Render();
		}

		private static void Advance()
		{
			// Use particleDrawFrom as input and stream output to particleStreamTo
			Engine.Context.InputAssembler.InputLayout = layout;
			Engine.Context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));
			Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

			updatePass.Apply(Engine.Context);

			// Update particles on the gpu
			Engine.Context.DrawAuto();

			// See if we have any newly-spawned particles waiting
			if (numNew > 0)
			{
				// Copy new particles to vertex buffer and draw them to append them to stream output target
				DataStream stream;
				Engine.Context.MapSubresource(spawnBuffer, MapMode.WriteDiscard, MapFlags.None, out stream);
				stream.WriteRange(newParticles);
				Engine.Context.UnmapSubresource(spawnBuffer, 0);

				Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(spawnBuffer, ParticleVertex.SizeInBytes, 0));
				Engine.Context.Draw(numNew, 0);
				numNew = 0;
			}

			Engine.Context.StreamOutput.SetTargets(null);

			// Swap vertex buffers
			Utilities.Swap(ref drawFromBuffer, ref streamToBuffer);
		}

		private static void Render()
		{
			// Since we just swapped the vertex buffers, particleDrawFrom contains the current state
			Engine.Context.OutputMerger.SetBlendState(DeviceStates.BlendStateAdditive);
			Engine.Context.OutputMerger.SetDepthStencilState(DeviceStates.DepthStencilStateNone);
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

			renderPass.Apply(Engine.Context);
			Engine.Context.DrawAuto();

			Engine.Context.GeometryShader.Set(null);
		}

		internal static void Shutdown()
		{
			spawnBuffer.Dispose();
			drawFromBuffer.Dispose();
			streamToBuffer.Dispose();
		}
	}
}
[/spoiler]
Bummel
Bummel

Is there some official information about the point->quat expansion special-case handling?

kalle_h
kalle_h

http://directtovideo.wordpress.com/2009/10/06/a-thoroughly-modern-particle-system/

Quite good reading about gpu particle systems using just dx9.

Just creating lot of particles is fun but not that usefull. Making particles look good and fitting for scene you usually want shading/shadowing match the rest of the scene. Also you want simulate some collisions and maybe even particle to particle forces. Also some sorting is must for good looking alpha blending.

ATEFred
ATEFred

http://directtovideo.wordpress.com/2009/10/06/a-thoroughly-modern-particle-system/

Quite good reading about gpu particle systems using just dx9.

Just creating lot of particles is fun but not that usefull. Making particles look good and fitting for scene you usually want shading/shadowing match the rest of the scene. Also you want simulate some collisions and maybe even particle to particle forces. Also some sorting is must for good looking alpha blending.

For particle lighting, I generate a volume texture mapped to the camera projection with lighting information in each voxel (I use HL2 basis) which I can then sample with a few texture fetches in my particle PS to resolve reasonably accurate lighting. When I have too much overdraw/particles, I revert to just sampling a fixed approximated lighting value instead of resolving directional lighting. Works well, and is really quite fast, once you pay the cost to generate the map. (atm takes 2-3 ms in cs with 500 lights visible). Maybe it would be possible to move the generation to CPU while the gbuffer / whatnot is being rendered?

Tracekill
Tracekill

Well... I ported it over but I can't see any particles. The DX debug layer is giving me a warning when I try to run the VS graphics debugger:


Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

D3D11 WARNING: ID3D11DeviceContext::Begin: Begin is being invoked on a Query, where the previous results have not been obtained with GetData. This is valid; but unusual. The previous results are being abandoned, and new Query results will be generated. [ EXECUTION WARNING #408: QUERY_BEGIN_ABANDONING_PREVIOUS_RESULTS]
I'm not even using queries. If I turn off the debug layer, it stops on the line above it:


Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

"Additional information: External component has thrown an exception."
Full code:
[spoiler]

using Craft.Camera;
using Craft.Main;
using Craft.Misc;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Runtime.InteropServices;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;

namespace Craft.Game.Particles
{
	public struct ParticleVertex
	{
		public static readonly InputElement[] VertexDeclaration = new[]
		{
			new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
			new InputElement("NORMAL", 0, Format.R32G32B32_Float, 0),
			new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 0),
			new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0),
			new InputElement("TEXCOORD", 1, Format.R32_UInt, 0),
			new InputElement("TEXCOORD", 2, Format.R32G32_Float, 0)
		};

		public static readonly int SizeInBytes = Marshal.SizeOf(typeof(ParticleVertex));

		public Vector3 Position;
		public Vector3 Velocity;
		public Vector4 Color;
		public Vector2 TimerLifetime;
		public uint Flags;
		public Vector2 SizeStartEnd;

		public ParticleVertex(Vector3 position, Vector3 velocity, Vector4 color, Vector2 timerLifetime, uint flags, Vector2 sizeStartEnd) : this()
		{
			Position = position;
			Velocity = velocity;
			Color = color;
			TimerLifetime = timerLifetime;
			Flags = flags;
			SizeStartEnd = sizeStartEnd;
		}
	}

	public static class GPUParticles
	{
		private const int MaxParticles = 1048576;
		private const int MaxNew = 128;

		[Flags]
		enum ParticleFlags
		{
			Constrained = 1,
			FastFade = 2
		}

		private static Buffer spawnBuffer;
		private static Buffer drawFromBuffer;
		private static Buffer streamToBuffer;

		private static Effect effect;
		private static EffectPass updatePass;
		private static EffectPass renderPass;
		private static InputLayout layout;

		private static EffectMatrixVariable evView;
		private static EffectMatrixVariable evProjection;
		private static EffectMatrixVariable evLookAt;
		private static EffectScalarVariable evTime;
		private static EffectVectorVariable evCamDir;
		private static EffectVectorVariable evGravity;
		private static EffectShaderResourceVariable evTexture;
		private static ParticleVertex[] newParticles;
		private static int numNew;

		internal static void Initialize()
		{
			newParticles = new ParticleVertex[MaxNew];

			effect = ContentLoader.LoadEffect(@"Content\Effects\GPUParticles.fxo");
			updatePass = effect.GetTechniqueByName("UpdateTeq").GetPassByIndex(0);
			renderPass = effect.GetTechniqueByName("RenderTeq").GetPassByIndex(0);
			layout = ContentLoader.CreateInputLayout(updatePass, ParticleVertex.VertexDeclaration);

			spawnBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxNew, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0));
			drawFromBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));
			streamToBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));

			evView = effect.GetVariableByName("View").AsMatrix();
			evProjection = effect.GetVariableByName("Projection").AsMatrix();
			evLookAt = effect.GetVariableByName("LookAt").AsMatrix();
			evTime = effect.GetVariableByName("Time").AsScalar();
			evCamDir = effect.GetVariableByName("CamDir").AsVector();
			evGravity = effect.GetVariableByName("Gravity").AsVector();
			evTexture = effect.GetVariableByName("Texture").AsShaderResource();

			evTexture.SetResource(ContentLoader.LoadTexture(@"Content\Textures\particles\sparkle.png"));
		}

		public static void Spawn(Vector3 position, Vector3 velocity, Color color, float sizeStart, float sizeEnd, float lifetime, bool constrained, bool fastFade)
		{
			// discard particle if buffer is full
			if (numNew >= MaxNew)
				return;

			// create particle struct
			var v = new ParticleVertex(position, velocity, color.ToVector4(), new Vector2(0, lifetime), 0, new Vector2(sizeStart, sizeEnd));

			// set the particle's flags
			if (constrained) v.Flags |= (uint) ParticleFlags.Constrained;
			if (fastFade) v.Flags |= (uint) ParticleFlags.FastFade;

			// append to buffer
			newParticles[numNew++] = v;
		}

		internal static void Draw(ICamera camera)
		{
			var cameraForward = camera.Forward;
			var lookAtMatrix = Matrix.Billboard(Vector3.Zero, -cameraForward, Vector3.UnitY, cameraForward);

			evView.SetMatrix(camera.View);
			evProjection.SetMatrix(camera.Projection);
			evLookAt.SetMatrix(lookAtMatrix);
			evTime.Set((float) Core.GameTime.TotalGameTime.TotalSeconds);
			evCamDir.Set(cameraForward);
			evGravity.Set(9.81f);

			Advance();
			Render();
		}

		private static void Advance()
		{
			// Use particleDrawFrom as input and stream output to particleStreamTo
			Engine.Context.InputAssembler.InputLayout = layout;
			Engine.Context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));
			Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

			updatePass.Apply(Engine.Context);

			// Update particles on the gpu
			Engine.Context.DrawAuto();

			// See if we have any newly-spawned particles waiting
			if (numNew > 0)
			{
				// Copy new particles to vertex buffer and draw them to append them to stream output target
				DataStream stream;
				Engine.Context.MapSubresource(spawnBuffer, MapMode.WriteDiscard, MapFlags.None, out stream);
				stream.WriteRange(newParticles);
				Engine.Context.UnmapSubresource(spawnBuffer, 0);

				Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(spawnBuffer, ParticleVertex.SizeInBytes, 0));
				Engine.Context.Draw(numNew, 0);
				numNew = 0;
			}

			Engine.Context.StreamOutput.SetTargets(null);

			// Swap vertex buffers
			Utilities.Swap(ref drawFromBuffer, ref streamToBuffer);
		}

		private static void Render()
		{
			// Since we just swapped the vertex buffers, particleDrawFrom contains the current state
			Engine.Context.OutputMerger.SetBlendState(DeviceStates.BlendStateAdditive);
			Engine.Context.OutputMerger.SetDepthStencilState(DeviceStates.DepthStencilStateNone);
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

			renderPass.Apply(Engine.Context);
			Engine.Context.DrawAuto();

			Engine.Context.GeometryShader.Set(null);
		}

		internal static void Shutdown()
		{
			spawnBuffer.Dispose();
			drawFromBuffer.Dispose();
			streamToBuffer.Dispose();
		}
	}
}
[/spoiler]

I'm currently having this exact same issue in my SlimDX code. Did you ever find a solution to that problem?

GuoLei007
GuoLei007

A few day ago, I created a little GPU particle sample for SharpDX. The buffers start off completely empty. To spawn new particles, I copy them to a third vertex buffer. After the pass to update the particles from last frame, I just "draw" the third buffer to append the new particles to the buffer for the current frame as well.

Any properties that are "sufficiently random" like the particle's color or various flags, I put directly into the vertex. I didn't include this in the sample to avoid bloating it, but to handle properties that a lot of particles share, every particle also has a type ID that indexes into an array of particle types in the geometry shader.

Hello! I have see your sample ! It's very Good But I use SharpDX11 to do this, not Tookit! The Shader your sample can't recognize about

"StreamOutput = "SV_POSITION.xyz; NORMAL.xyz; COLOR.xyzw; TEXCOORD0.xy; TEXCOORD1.x; TEXCOORD2.xy";

StreamOutputRasterizedStream = 0;"

I can't find right way to express this in Shader model5.0 of Technique11!

Oh! Sorry ! My English is bad! hople you can understand me! if you can help me I am Very thanks to you!

GuoLei007
GuoLei007

Well... I ported it over but I can't see any particles. The DX debug layer is giving me a warning when I try to run the VS graphics debugger:


Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

D3D11 WARNING: ID3D11DeviceContext::Begin: Begin is being invoked on a Query, where the previous results have not been obtained with GetData. This is valid; but unusual. The previous results are being abandoned, and new Query results will be generated. [ EXECUTION WARNING #408: QUERY_BEGIN_ABANDONING_PREVIOUS_RESULTS]
I'm not even using queries. If I turn off the debug layer, it stops on the line above it:


Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

"Additional information: External component has thrown an exception."
Full code:
[spoiler]

using Craft.Camera;
using Craft.Main;
using Craft.Misc;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Runtime.InteropServices;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;

namespace Craft.Game.Particles
{
	public struct ParticleVertex
	{
		public static readonly InputElement[] VertexDeclaration = new[]
		{
			new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
			new InputElement("NORMAL", 0, Format.R32G32B32_Float, 0),
			new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 0),
			new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0),
			new InputElement("TEXCOORD", 1, Format.R32_UInt, 0),
			new InputElement("TEXCOORD", 2, Format.R32G32_Float, 0)
		};

		public static readonly int SizeInBytes = Marshal.SizeOf(typeof(ParticleVertex));

		public Vector3 Position;
		public Vector3 Velocity;
		public Vector4 Color;
		public Vector2 TimerLifetime;
		public uint Flags;
		public Vector2 SizeStartEnd;

		public ParticleVertex(Vector3 position, Vector3 velocity, Vector4 color, Vector2 timerLifetime, uint flags, Vector2 sizeStartEnd) : this()
		{
			Position = position;
			Velocity = velocity;
			Color = color;
			TimerLifetime = timerLifetime;
			Flags = flags;
			SizeStartEnd = sizeStartEnd;
		}
	}

	public static class GPUParticles
	{
		private const int MaxParticles = 1048576;
		private const int MaxNew = 128;

		[Flags]
		enum ParticleFlags
		{
			Constrained = 1,
			FastFade = 2
		}

		private static Buffer spawnBuffer;
		private static Buffer drawFromBuffer;
		private static Buffer streamToBuffer;

		private static Effect effect;
		private static EffectPass updatePass;
		private static EffectPass renderPass;
		private static InputLayout layout;

		private static EffectMatrixVariable evView;
		private static EffectMatrixVariable evProjection;
		private static EffectMatrixVariable evLookAt;
		private static EffectScalarVariable evTime;
		private static EffectVectorVariable evCamDir;
		private static EffectVectorVariable evGravity;
		private static EffectShaderResourceVariable evTexture;
		private static ParticleVertex[] newParticles;
		private static int numNew;

		internal static void Initialize()
		{
			newParticles = new ParticleVertex[MaxNew];

			effect = ContentLoader.LoadEffect(@"Content\Effects\GPUParticles.fxo");
			updatePass = effect.GetTechniqueByName("UpdateTeq").GetPassByIndex(0);
			renderPass = effect.GetTechniqueByName("RenderTeq").GetPassByIndex(0);
			layout = ContentLoader.CreateInputLayout(updatePass, ParticleVertex.VertexDeclaration);

			spawnBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxNew, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0));
			drawFromBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));
			streamToBuffer = new Buffer(Engine.Device, new BufferDescription(ParticleVertex.SizeInBytes * MaxParticles, ResourceUsage.Default, BindFlags.VertexBuffer | BindFlags.StreamOutput, CpuAccessFlags.None, ResourceOptionFlags.None, 0));

			evView = effect.GetVariableByName("View").AsMatrix();
			evProjection = effect.GetVariableByName("Projection").AsMatrix();
			evLookAt = effect.GetVariableByName("LookAt").AsMatrix();
			evTime = effect.GetVariableByName("Time").AsScalar();
			evCamDir = effect.GetVariableByName("CamDir").AsVector();
			evGravity = effect.GetVariableByName("Gravity").AsVector();
			evTexture = effect.GetVariableByName("Texture").AsShaderResource();

			evTexture.SetResource(ContentLoader.LoadTexture(@"Content\Textures\particles\sparkle.png"));
		}

		public static void Spawn(Vector3 position, Vector3 velocity, Color color, float sizeStart, float sizeEnd, float lifetime, bool constrained, bool fastFade)
		{
			// discard particle if buffer is full
			if (numNew >= MaxNew)
				return;

			// create particle struct
			var v = new ParticleVertex(position, velocity, color.ToVector4(), new Vector2(0, lifetime), 0, new Vector2(sizeStart, sizeEnd));

			// set the particle's flags
			if (constrained) v.Flags |= (uint) ParticleFlags.Constrained;
			if (fastFade) v.Flags |= (uint) ParticleFlags.FastFade;

			// append to buffer
			newParticles[numNew++] = v;
		}

		internal static void Draw(ICamera camera)
		{
			var cameraForward = camera.Forward;
			var lookAtMatrix = Matrix.Billboard(Vector3.Zero, -cameraForward, Vector3.UnitY, cameraForward);

			evView.SetMatrix(camera.View);
			evProjection.SetMatrix(camera.Projection);
			evLookAt.SetMatrix(lookAtMatrix);
			evTime.Set((float) Core.GameTime.TotalGameTime.TotalSeconds);
			evCamDir.Set(cameraForward);
			evGravity.Set(9.81f);

			Advance();
			Render();
		}

		private static void Advance()
		{
			// Use particleDrawFrom as input and stream output to particleStreamTo
			Engine.Context.InputAssembler.InputLayout = layout;
			Engine.Context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));
			Engine.Context.StreamOutput.SetTarget(streamToBuffer, 0);

			updatePass.Apply(Engine.Context);

			// Update particles on the gpu
			Engine.Context.DrawAuto();

			// See if we have any newly-spawned particles waiting
			if (numNew > 0)
			{
				// Copy new particles to vertex buffer and draw them to append them to stream output target
				DataStream stream;
				Engine.Context.MapSubresource(spawnBuffer, MapMode.WriteDiscard, MapFlags.None, out stream);
				stream.WriteRange(newParticles);
				Engine.Context.UnmapSubresource(spawnBuffer, 0);

				Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(spawnBuffer, ParticleVertex.SizeInBytes, 0));
				Engine.Context.Draw(numNew, 0);
				numNew = 0;
			}

			Engine.Context.StreamOutput.SetTargets(null);

			// Swap vertex buffers
			Utilities.Swap(ref drawFromBuffer, ref streamToBuffer);
		}

		private static void Render()
		{
			// Since we just swapped the vertex buffers, particleDrawFrom contains the current state
			Engine.Context.OutputMerger.SetBlendState(DeviceStates.BlendStateAdditive);
			Engine.Context.OutputMerger.SetDepthStencilState(DeviceStates.DepthStencilStateNone);
			Engine.Context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(drawFromBuffer, ParticleVertex.SizeInBytes, 0));

			renderPass.Apply(Engine.Context);
			Engine.Context.DrawAuto();

			Engine.Context.GeometryShader.Set(null);
		}

		internal static void Shutdown()
		{
			spawnBuffer.Dispose();
			drawFromBuffer.Dispose();
			streamToBuffer.Dispose();
		}
	}
}
[/spoiler]

Do you solve your problem! I have use your code in another XOBOX forum The same you say I can't see any partile If you solve the problem please Hlep I will very thanks to you! Sorry My english is very bad! hople you can understand me!

lwm
lwm

I can't find right way to express this in Shader model5.0 of Technique11!

Here is MSDN's description of the Stream Out Syntax. You might also want to take a look at the ParticlesGS sample from the DirectX SDK.

current project: Roa
GuoLei007
GuoLei007

I can't find right way to express this in Shader model5.0 of Technique11!

Here is MSDN's description of the Stream Out Syntax. You might also want to take a look at the ParticlesGS sample from the DirectX SDK.

Yes I has see it But it does't work

The modified code is?

GeometryShader pGSComp = CompileShader(gs_5_0, GS_Update());
GeometryShader gsStreamOut = ConstructGSWithSO( pGSComp, "0:SV_POSITION.xyz;1:NORMAL.xyz;2:COLOR.xyzw;3:TEXCOORD0.xy;4:TEXCOORD1.x;5:TEXCOORD2.xy;",NULL,NULL,0);
technique11 UpdateTeq
{
pass Pass0
{
SetVertexShader( CompileShader( vs_5_0, VS_Passthrough() ) );
SetGeometryShader(gsStreamOut);
SetPixelShader( NULL);
}
}
It senms to right ! but VS Prompt Me!
D:\Coding\???\ResourceEditer(2013.07.15DX11????)\ResourceEditer\ResourceEditer\bin\Debug\MoonLight.Material.ParticleEffect.fx(91,31): error X3000: parameter count mismatch (ConstructGSWithSO)
I don't konw why! because MSDN write like this!! Oh ! I am very happy you can reply Me!
GuoLei007
GuoLei007

I can't find right way to express this in Shader model5.0 of Technique11!

Here is MSDN's description of the Stream Out Syntax. You might also want to take a look at the ParticlesGS sample from the DirectX SDK.

I use the same modified code in your sample It also can't work! I feel it's a simle error ! But I has solve it for 2 days...Now,Still failed!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.