I'm using the particles sample from create.msdn.com/en-US/education/catalog/sample/particle_3d which by default, has all the particles look straight at the camera. I'm trying to modify it so that the particles will still look at the camera but won't rotate up and down. I think these are called billboard particles but I'm not really sure.


This is what I have in the shader now. It seems like its working but the particles are really tiny. What did I do wrong?

float4x4 billboardRotation = CreateConstrainedBillboard(output.Position, CameraPosition, float3(0, 1, 0));

output.Position.xy += mul(input.Corner, rotation);
output.Position = mul(output.Position, billboardRotation);
output.Position = mul(mul(output.Position, View), Projection);
output.Position.xy += size * ViewportScale;
  • \$\begingroup\$ Small correction: Rotating up/down (x axis) is called pitch. Rotating left/right (y axis) is called yaw. \$\endgroup\$ Dec 15, 2011 at 13:19
  • \$\begingroup\$ Your right, ill fix that \$\endgroup\$
    – Telanor
    Dec 16, 2011 at 1:15

3 Answers 3


Sprite billboards can be of two types:

  • Spherical: they look always at the camera.
  • Cylindrical/constrained: you define a constraint axis and they always try to face the camera, but rotating along that axis. This is the kind you are aiming for.

So, instead of using Matrix.CreateBillboard when creating the world matrix for your particles, use Matrix.CreateConstrainedBillboard. The only difference is that you need to specify the object's rotation axis instead of the camera up vector, and the optional cameraForwardVector and objectForwardVector to smooth possible deformations when the camera is too close.

  • \$\begingroup\$ The sample he mentioned is set up a bit differently though, so making those changes won't be that trivial. For instance, the sample does not apply any matrix to billboard the particles. Instead it stores a position and a corner offset for each vertex, and adds the corner offset to the vertex position in clip space (i.e. after view and projection transforms) which is why they're always facing the camera. He'll need to change the sample so that the corner offset is applied in world space followed by a custom billboard matrix, all before applying the view and projection matrices. \$\endgroup\$ Dec 15, 2011 at 15:25
  • \$\begingroup\$ +1 But still, your method is the usual way to do it. And the OP may still want to use the Matrix.CreateConstrainedBillboard method and pass that value into the shader to make things easier. \$\endgroup\$ Dec 15, 2011 at 15:28

I haven't had the time to test this, but after looking through the sample's code I believe you'll need to do a few changes for it to work. Here's what I have in mind, although I'm not sure it will work as it is:

The Sample

For starters, the way the particles are being billboarded at the moment doesn't allow for much variation. Each vertex basically carries the particle position and a 2D corner offset such as (-1,-1) or (1,1).

The GPU does all of the particle animation, applies the view and projection matrices to get it into clip space, and then adds the corner offset multiplied by the particle's size to the clip space XY position. Since this sum is done in clip space, the resulting quad will always be facing you. No billboard matrix anywhere.

The Solution

You'll need to change your shader in order to apply the billboards in a more traditional way using a matrix. Using the method r2d2rigo mentioned (Matrix.CreateConstrainedBillboard) you'll need to calculate a billboard matrix for each particle and pass it to the shader when drawing.

Then you'll need to change the method in the shader that is currently applying the View and Projection matrices and leave that part out, because you still need to do a few other things before converting to clip space.

With the position still in world space, that's when you will need to apply the corner offset, along with the billboard matrix you created before to make it face the camera.

Finally just apply the View and Projection matrices as it was done originally in the sample.


Start from the billboard sample instead and adapt it to your particle needs. That might turn out to be easier.

  • \$\begingroup\$ Is there a way to calculate that matrix in the shader? Calculating it individually for each particle would probably kill the performance \$\endgroup\$
    – Telanor
    Dec 16, 2011 at 1:28
  • \$\begingroup\$ Yep. The way I'd do it would be to get dotPeek or some other .NET decompiler and look into Matrix.CreateConstrainedBillboard's source code. Then just convert that into HLSL. \$\endgroup\$ Dec 16, 2011 at 2:29
  • \$\begingroup\$ Ok I got the source, ported it to HLSL, but I'm a little confused on how to use the result. I removed the view/projection multiply in the ComputeParticlePosition function. See my edit for what I've tried \$\endgroup\$
    – Telanor
    Dec 18, 2011 at 3:27
  • \$\begingroup\$ I'm not sure really, but try placing that multiplication by size/ViewportSize in some other places. E.g. directly on the corner before rotation, or after rotation. Or is there a chance you could isolate this portion of your code and posting it? Would be easier to find out the problem by fiddling with it. \$\endgroup\$ Dec 18, 2011 at 3:36
  • \$\begingroup\$ I can post my modifications to the shader file but you'd have to modify the sample a bit to provide a camera location to the shader. pastebin.com/k0gbv29x. Ive tried moving the size part around with various degrees of weirdness being produced. I might have done more than one thing wrong. \$\endgroup\$
    – Telanor
    Dec 18, 2011 at 3:53

I guess it's a little late to post this answer, but, I was struggeling for a few days with the problem as well.

I started from the gpu particle sample from Microsoft and wanted to make it rain. This was one difficult task, But in the end I was surprised that just a few things need to be changed after all.

All I did was changing the CalculateParticlePosition like you did. I just return the plain Position. No View or Projection multiplications involved.

Then I changed some things in the VertexShader and now it runs correctly.

The last Line of the ComputeParticlePosition-Line has to look something like this.

return float4(position, 1);

Here are the changes I made to the VertexShader

VSO_Particles VS_Particles(VSI_Particles input)
VSO_Particles output;

// Calculate the Particle's age
float lAge = CurrentTime - input.Time;

// Randomize the Age
lAge *= 1 + input.Random.x * DurationRandomness;

// Normalize the Age 
float lNormalizedAge = saturate(lAge / Duration);

// Calculate the World Position for this Particle Vertex
output.Position = CalculateParticlePosition(input.Position, input.Velocity, lAge, lNormalizedAge);

// Calculate the Size and rotation for this Particle
float lSize = CalculateParticleSize(input.Random.y, lNormalizedAge);
float2x2 lRotation = CalculateParticleRotation(input.Random.w, lAge);

// These Lines are new, you need to pass the CameraPosition to the shader
float3 ParticleToCamera = output.Position.xyz - CameraPosition;
float3 ParticleUp = float3(0, 1, 0);
float3 ParticleRight = normalize(cross(ParticleToCamera, ParticleUp));

// Apply the Particle's Rotation to the current corner
float2 CurrentCorner = mul(input.Corner, lRotation);

// Then you need to place the Particle's vertices according to their Up and side Vector
output.Position.xyz += ((CurrentCorner.x * lSize * ViewportScale.x) * ParticleRight);
output.Position.xyz += ((CurrentCorner.y * lSize * ViewportScale.y) * ParticleUp);

// and shift to Screen Space
output.Position = mul(mul(output.Position, View), Projection);

output.Color = CalculateParticleColor(output.Position, input.Random.z, lNormalizedAge);
output.UV = (input.Corner + 1) / 2;

// Pass a copy of the ScreenPosition
output.ScreenPosition = output.Position;

return output;

And that's that. This works for me to get constrained Billboard Particles.

I hope this will help at least somebody who's dealing with the 3D Particle Sample by Microsoft for XNA 4.0.

Cheers Dekurian


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .