GraphicsSeptember 29, 2005 5:50 pm
Bumbling on with the particles engine has led me to pixel shaders in C#, using HLSL. I even have a book. Up until today however, I’d just fiddled with existing examples.
The code already had the alpha blending and coloring of the particles as a very, very simple pixel shader. It needed more per particle variables to find their way down to the shader (e.g. particle lifetime, coloring function etc.) - this requires using a custom vertex type and a corresponding vertex shader that mangles it into a sensible input for the pixel shader.
This is what I ended up with (the odd variables at the top are for EffectEdit, an application that comes with the DirectX SDK and compiles and displays effects. Definitely worth using to find mistakes in the shaders!):
// Stuff for EffectEdit
string XFile = "tiger\\tiger.x"; // model
int BCLR = 0xff202080; // background
float4x4 gTransform : WORLDVIEWPROJECTION;
struct VS_OUTPUT
{
float4 Pos : POSITION;
float4 Color : COLOR0;
};
VS_OUTPUT VS(float4 position : POSITION, float4 color : COLOR0)
{
VS_OUTPUT Out = (VS_OUTPUT)0;
Out.Pos = mul(position, gTransform);
Out.Color = color;
return Out;
}
float4 PS(VS_OUTPUT input) : COLOR
{
float4 result = { input.Color.r, input.Color.g, input.Color.b, 0.5 };
return result;
}
technique Monkey
{
pass P0
{
// shaders
VertexShader = compile vs_1_0 VS();
PixelShader = compile ps_1_1 PS();
}
}
Then the C# code is just:
m_effect = Effect.FromFile(m_device, "../../effect.fx", null, null, ShaderFlags.None, null);
m_device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);
m_device.BeginScene();
m_effect.Technique = "Monkey";
m_effect.SetValue("gTransform", m_device.Transform.World * m_device.Transform.View * m_device.Transform.Projection);
m_effect.Begin(FX.None);
m_effect.BeginPass(0);
// Draw stuff
m_effect.EndPass();
m_effect.End();
m_device.EndScene();
m_device.Present();
And yeah yeah, it’ll just render something in a slightly wimpier color than the color specified for the vertex (due to the 0.5 alpha). The idea’s there though - more can be added to the VS_OUTPUT and vertex structure and the appropriate calculations can be done in the appropriate shader. With hindsight, many calculations that are per-particle can be done at the vertex shader level. Oh well.
As for the stuff that’s actually drawn, well the call to DrawUserPrimitives now uses a custom vertex array (all 6 vertices!):
struct MyVertex
{
public Vector3 Position;
public Vector2 TuTv;
public int Color;
}
private MyVertex[] m_quad_verts = new MyVertex[6];
The only slight faff is setting up the VertexDeclaration (not the VertexFormat, I believe that’s for the fixed pipeline, not shaders). This is done in the renderer construction, around the same time as constructing effects, fonts and the like:
VertexElement[] decl = new VertexElement[] {
new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default,
DeclarationUsage.Position, 0),
new VertexElement(0, 12, DeclarationType.Float2, DeclarationMethod.Default,
DeclarationUsage.TextureCoordinate, 0),
new VertexElement(0, 20, DeclarationType.Color, DeclarationMethod.Default,
DeclarationUsage.Color, 0),
VertexElement.VertexDeclarationEnd
};
m_device.VertexFormat = 0;
m_device.VertexDeclaration = new VertexDeclaration(m_device, decl);
Slightly scary looking, but it’s just setting up offsets into a datastructure. The important thing is that the VertexDeclaration matches the layout in memory as MyVertex.