Thank you for the demo!
Iāve looked at the demo. Thanks for sharing, I learned something new today.
Are SDF fonts not overkill for my effect?
I would have to create specific .png images and write my own .txt files for the font because of the double outlines/shadows. So I canāt just generate it with a tool, or is there another method/way?
imho SDF fonts are not only overkill, but you wonāt get the effect youāre trying to get.
SDF looks absolutely stunning but you have no control over how the shadow will look other than a float value used to decide the width of the border (but not the shape, in example you could get the border of the letter R ok, but get the border of the letter O slightly rounded)
While this is usually desirable for fonts, youāre probably going for a āpixel lookā font, and SDF borders may not work for that.
Thanks for the reply @KakCAT.
Thatās what I was thinking. I hope thereās another way to achieve this effect.
And yes, Iām going for a pixel look font.
While I love distance field fonts, even their best variant still have issues when rendering at very small sizes. It is great for use in world space text like nameplates and dynamic assets, but for GUI, especially gui with small/fine fonts it is still quite problematic.
Btw thatās case even for superior multi channel signed distance field fonts.
Also sorry, but that example actually starts to fall apart at large sizes as well.
There is example with mutli channel signed fonts: https://github.com/roy-t/MSDF however, while providing better quality, it creates interesting āfont backgroundā artifact at lower sizes. So I agree with you, I think it is overkill for your case and if you really want to do it realtime and you are not afraid about performance (honestly unless rest of the game is performance hungry or you draw ridiculous amount of text with overdraw) then use normal sprite fonts and drag it through one pass 3x3 kernel, provided you need just single pixel outline (which can be ofc upscaled up for pixel art look).
Iām just about to head to bed but I figured Iād post this and let you play with it. Iāve never tried this with text beforeā¦ lol. Anyway, I created this shader for putting an outline around sprites. In theory, it should work just fine on text. If you want to add shadow, youād have to adapt it.
Pass in as parameters the colour of the outline (xOutlineColour) and the size of the texture it will process (xTextureSize). Iām actually not sure what this will be for text rendering. You might actually have to render all your text to a RenderTarget layer and then run this over it? Is this the problem you were running into in your original post?
#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0_level_9_1
#define PS_SHADERMODEL ps_4_0_level_9_1
#endif
Texture2D SpriteTexture;
float2 xTextureSize : VPOS;
float4 xOutlineColour = float4(128, 0, 0, 255);
sampler2D InputSampler = sampler_state
{
Texture = <SpriteTexture>;
};
struct VertexShaderOutput
{
float4 Position : SV_POSITION;
float4 Color : COLOR0;
float2 UV : TEXCOORD0;
};
float4 MainPS(VertexShaderOutput input) : COLOR
{
float4 currentPixel = tex2D(InputSampler, input.UV) * input.Color;
float4 output = currentPixel;
if (currentPixel.a == 0.0f)
{
float2 uvPix = float2(1 / xTextureSize.x, 1 / xTextureSize.y);
if (false
|| tex2D(InputSampler, float2((1 * uvPix.x) + input.UV.x, (0 * uvPix.y) + input.UV.y)).a > 0
|| tex2D(InputSampler, float2((0 * uvPix.x) + input.UV.x, (1 * uvPix.y) + input.UV.y)).a > 0
|| tex2D(InputSampler, float2((-1 * uvPix.x) + input.UV.x, (0 * uvPix.y) + input.UV.y)).a > 0
|| tex2D(InputSampler, float2((0 * uvPix.x) + input.UV.x, (-1 * uvPix.y) + input.UV.y)).a > 0
)
{
output = xOutlineColour;
}
}
return output;
}
technique SpriteOutline
{
pass P0
{
PixelShader = compile PS_SHADERMODEL MainPS();
}
};
Thanks for the shader, but the problem is that each character in a drawstring operation gets drawn in a separate draw rectangle. An example of a shader that transforms all transparant pixels to red pixels can be seen below to show this.
Iām also thinking about something like this. I canāt seem to create the desired effect with just a drawstring call and a hlsl shader.
Yea, I figured. Thatās a bummer for sure.
It sounds like itās a performance trade-off between multiple sprite batch renders per text to achieve your effect, or rendering to a full screen render target to do some post processing.
Iām curious as to which ends up performing better!
Thatās a bug in your rendering.
You cannot render SDF fonts at integer pixel values, you need floating point accuracy.
Look in SDFFont.cs you have a function to draw a string which uses a glyph batch to render all the chars.
Itās one of the things that caught me out when I first wrote the code (a long time ago )
Also make sure the sampler states are correct on the font texture, that can make it look awful as well.
@Jelle If you rendered first all the outlines and afterwards the text, would you achieve the desired effect? If so, thatād probably be only 2 drawcalls with SpriteBatch.Deferred
The number of drawstrings is probably not relevant, only the stuff put inside the spritebatch, but I canāt confirm that because DrawString was the first XNA function I replaced with my custom renderer. Hate at first sight
Iāll try using a render target and post the results here if I can replicate my font outlines.
@Jelle If you rendered first all the outlines and afterwards the text, would you achieve the desired effect? If so, thatād probably be only 2 drawcalls with SpriteBatch.Deferred
Probably. Iām still looking for an optimal solution
Iām making progress.
Iām using a full screen rendertarget like you said. I draw the text to the rendertarget and apply a custom hlsl shader to it.
Havenāt tested the fps on both methods yet.
EDIT, full shader:
I will test the shader against the drawstring method tomorrow. Feel free to ask for the shader if you want, I donāt know if itās very efficient though.
EDIT 2: Fast test showed that the hlsl shader & full screen rendertarget method is faster. Iāll post more details later.
Hey you, open secret how did you achive such high FPS?
I used a small resolution for the shader tests (1100 x 900) and nothing else gets drawn to the screen except the font, shader and tile texture. I usually get around 500 fps when the screen is full of tiles (~33k) on a 3440 x 1440 resolution though.
You can unlock your fps limit with
graphics.SynchronizeWithVerticalRetrace = false; // vsync IsFixedTimeStep = false; // Don't force equal timestep updates
in your Game1 constructor.
I tested the current shader performance with a bunch of test strings.
The performance of the pixel shader doesnāt really drop much with more strings because the shader already checks the whole screen on a downscaled rendertarget.
Shader method: ~ 400 - 500 fps
Drawstring method: ~ 100 - 200 fps
There are probably some improvements I can make but Iām already happy with the results.
Fun side effect of rendering to a smaller render target (and drawing with a higher scale): All custom text effects are now pixel perfect, example:
(green wiggle effect)
Hey @Jelle ! Iāve been using Monogame for a couple months now and have been trying to achieve something like this for awhile and yours looks exactly like what Iām going for! I"m so glad I searched and found your post, and that itās so recent! I feel like I end up finding posts from 2013 or old XNA stuff from 2006 so often haha.
You mentioned you were willing to share this shader. Would you mind sending it to me?
Hi @lentil
You can find the current shader here: hlsl shader optimization
The shader is less flexible now, but probably a bit faster. If you need more help, do not hesitate to contact me!
Hi @Jelle
Also getting started and this is pretty cool is the wiggle also done with shaders? Do you have an example I could see?
Thanks!
Hi @brandc87
The character effects like color, wiggle, ā¦ are all done using C# code. I made custom text rendering classes that deconstruct and render strings.
Example:
Hello Ā§bWorld, Ā§r1red wiggle test
Is a string that I need to render. This string will be split into a list of strings: [Hello, World, red wiggle text]. I use āĀ§ā as a special character that announces a color for a part of the string, or a color and an effect.
I then calculate the character/string positions depending on the effect and draw them. The wiggle effect splits a string into characters and changes each Y value of each character according to a sinus value, bound to a timer.
I end up with fully animated/colored strings at the end of this process. I then draw all the text to a render target and apply the pixel shader for the extra outlines.