Ok, found it. It sits in shader inputs, here is the shader that works:
float4x4 Projection;
Texture2D FontTexture;
sampler TextureSampler = sampler_state
{
texture = <FontTexture>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = clamp;
AddressV = clamp;
};
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 Texture : TEXCOORD0;
float4 Color : COLOR0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 Texture : TEXCOORD0;
float4 Color : COLOR0;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
output.Texture = input.Texture;
output.Position = mul(input.Position, Projection);
output.Color = input.Color;
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float4 originalColor = tex2D(TextureSampler, input.Texture);
return originalColor*input.Color;
}
technique Technique1
{
pass Pass1
{
VertexShader = compile vs_2_0 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
UPDATE: as I subsequently learned, the shader can be further simplified like this:
Texture2D FontTexture;
sampler TextureSampler = sampler_state
{
texture = <FontTexture>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = clamp;
AddressV = clamp;
};
float4 main(float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 originalColor = tex2D(TextureSampler, texCoord);
return originalColor*color;
}
technique Technique1
{
pass Pass1
{
PixelShader = compile ps_2_0 main();
}
}
See Custom SpriteEffects Sample for further info.