0
\$\begingroup\$

I've recently found a nice way to render text in XNA with custom font AND custom shader.

Here is the game code**:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Reflection;

namespace Custom_SpriteFont_Test
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont Font1;
        SpriteFont Font2;
        Effect FontEffect;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferWidth = 1024;
            graphics.PreferredBackBufferHeight = 768;
        }


        protected override void LoadContent()
        {
            Font1 = Content.Load<SpriteFont>("1");
            Font2 = Content.Load<SpriteFont>("2");
            FontEffect = Content.Load<Effect>("FontEffect");

            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void Draw(GameTime gameTime)
        {
            float fontScaleModifier = 1;

            GraphicsDevice.Clear(Color.CornflowerBlue);

            FontEffect.Parameters["Projection"].SetValue(Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 0, 1));

            spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone, FontEffect);

            FontEffect.Parameters["Color1"].SetValue(Color.Red.ToVector4());
            FontEffect.Parameters["Color2"].SetValue(Color.Blue.ToVector4());

            spriteBatch.DrawString(Font1, "Hello world ~!(*)     $100", new Vector2(0, 0), Color.Red, 0, Vector2.Zero, 0.6f * fontScaleModifier, SpriteEffects.None, 0);

            FontEffect.Parameters["Color1"].SetValue(Color.Green.ToVector4());
            FontEffect.Parameters["Color2"].SetValue(Color.Blue.ToVector4());

            spriteBatch.DrawString(Font2, "Hello world ~!(*)     $100", new Vector2(0, 100), Color.Red, 0, Vector2.Zero, 0.2f * fontScaleModifier, SpriteEffects.None, 0);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

And here is the shader code:

float4x4 Projection;
float4 Color1, Color2;
float FontHeight;
float2 Location;

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;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 Texture : TEXCOORD0;
    float2 OriginalPosition : TEXCOORD1;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    output.OriginalPosition = input.Position;
    output.Texture = input.Texture;
    output.Position = mul(input.Position, Projection);

    return output;
}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    //float4 fontColor = lerp(Color1, Color2, (1.0 / FontHeight) * (input.OriginalPosition.y - Location.y));
    //return tex2D(TextureSampler, input.Texture)*  fontColor;
    float4 originalColor = tex2D(TextureSampler, input.Texture);
    float4 fontColor = lerp(Color1, Color2, originalColor.r)*originalColor.a;
    return fontColor;
}

technique Technique1
{
    pass Pass1
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

**Mind: you will need two font files "1" and "2" in you content folder to run it.

If you launch it you will immediately see the problem: only the second pair of colors is being applied for every line of text rendered. Calling Effect.Passes[0].Apply() will not help. If you write text without the custom shader, you can write two lines of text within the same spritebatch.Begin()..spritebatch.End() construction with TWO different colors (as specified in spriteBatch.DrawString() method) - and that is precisely what I want.

You might have noticed that the font texture here is being mapped automatically (two different fonts will render properly without specifying the font texture into the custom shader), so I am sure there is some way to map the colors as well.

Ideally I want this:

        spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone, FontEffect);

        spriteBatch.DrawString(Font1, "Hello world ~!(*)     $100", new Vector2(0, 0), Color.Red, 0, Vector2.Zero, 0.6f * fontScaleModifier, SpriteEffects.None, 0);

        spriteBatch.DrawString(Font2, "Hello world ~!(*)     $100", new Vector2(0, 100), Color.Green, 0, Vector2.Zero, 0.2f * fontScaleModifier, SpriteEffects.None, 0);

        spriteBatch.End();

And the output text should be rendered with Red and Green color respectively, only with my custom shader.

P.S. I might have been unclear with my shader accepting two colors and not one: it would be enough if I could map just one color for my shader.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.