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)
// 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;
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.