Legacy GM Efficient text outline

MeBoingus

Member
Hi all,


I'm currently working on a project in which I need to apply an outline to a lot of text upon drawing.

My first idea was to do what everyone normally does, and draw the text in black, at different offsets around the sprite. However, due to the amount of text being displayed, this causes horrendous lag.

I then decided that the best way would be to use a shader. Sadly, I know nothing about shaders, and every outline shader (that I can find) is for sprites, not text.


Is there an efficient way to draw outlined text?
I'm pretty sure a shader would be preferable, for lag-reasons.


Thanks :).
 

MeBoingus

Member
I don't see why there would be a difference. They are both just textured quads.
Here's what I'm using:

The shader Fragment itself

Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float pixelH;
uniform float pixelW;

void main()
{
    vec2 offsetx;
    offsetx.x = pixelW;
    vec2 offsety;
    offsety.y = pixelH;
  
    float alpha = texture2D(gm_BaseTexture, v_vTexcoord).a;
  
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord + offsetx).a);
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord - offsetx).a);
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord + offsety).a);
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord - offsety).a);

    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.a = alpha;
}

And then I'm using a script called "draw_text_outline," which does this (in the draw event, obviously):
Code:
var tX = argument0;
var tY = argument1;
var t = argument2;
var tC = argument3;

tC = c_white;

shader_set(shOutline);
draw_text_colour(tX, tY, t, tC, tC, tC, tC, 1)
shader_reset();

This results in white, perfectly normal text.
 
L

Lonewolff

Guest
The fragment shader there looks like it will just spread the alpha around. I don't see anything there that would give you an outline.
 

MeBoingus

Member
The fragment shader there looks like it will just spread the alpha around. I don't see anything there that would give you an outline.
Well... that makes a lot more sense. Absolutely no idea why someone shared this as an outline shader, but it's also my fault for blindly trusting someone else's code that I don't understand.

Any ideas on how this affect could be achieved?
 
L

Lonewolff

Guest
Just on my iPad right now so this may be full of errors, as I can't test it, but probably something more like this.

Code:
vec2 offset;
offset.x = pixelW;
offset.y = pixelH;

vec4 outline;
outline = texture2D(gm_BaseTexture, v_vTexcoord + vec2(-offset.x, -offset.y));
outline += texture2D(gm_BaseTexture, v_vTexcoord + vec2(-offset.x, offset.y));
outline += texture2D(gm_BaseTexture, v_vTexcoord + vec2(offset.x, -offset.y));
outline += texture2D(gm_BaseTexture, v_vTexcoord + vec2(offset.x, offset.y));
outline.rgb = vec3(0.0, 0.0, 0.0);        // make coloured area black
 
gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord) * outline;
Hopefully this will at least get you started in the right direction.

pixelW and H, will no doubt need some tweaking as the fragment shader works in % of the texture page size. So if the current texture page is 512 wide then one pixel will be 1/512 of the texture page.
 

MeBoingus

Member
Just on my iPad right now so this may be full of errors, as I can't test it, but probably something more like this.

Code:
vec2 offset;
offset.x = pixelW;
offset.y = pixelH;

vec4 outline;
outline = texture2D(gm_BaseTexture, v_vTexcoord + vec2(-offset.x, -offset.y));
outline += texture2D(gm_BaseTexture, v_vTexcoord + vec2(-offset.x, offset.y));
outline += texture2D(gm_BaseTexture, v_vTexcoord + vec2(offset.x, -offset.y));
outline += texture2D(gm_BaseTexture, v_vTexcoord + vec2(offset.x, offset.y));
outline.rgb = vec3(0.0, 0.0, 0.0);        // make coloured area black
 
gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord) * outline;
Hopefully this will at least get you started in the right direction.

pixelW and H, will no doubt need some tweaking as the fragment shader works in % of the texture page size. So if the current texture page is 512 wide then one pixel will be 1/512 of the texture page.
I ran into the same issue with this as I did with a few other shaders I was testing. It simply draws black text as if I were using draw_text normally.


The result of the shader:
ExampleA.png

The result of using draw_text multiple times:
ExampleB.png





This is the updated code I'm using to draw the text (with the shader):
Code:
shader_set(shOutline);
draw_text_colour(tX, tY, t, tC, tC, tC, tC, 1);
shader_reset();


I'm almost certain I'm not understanding something. I.E. Whether or not I'm supposed to set "pixelW" and "pixelH."





EDIT:

I also tried using the following code, but it yields the same outcome:

Code:
var pixelW = shader_get_uniform(shOutline, "pixelW");
var pixelH = shader_get_uniform(shOutline, "pixelH");
shader_set(shOutline);

shader_set_uniform_f(pixelW, 500);
shader_set_uniform_f(pixelH, 500);

draw_text_colour(tX, tY, t, tC, tC, tC, tC, 1);
shader_reset();
 
Last edited:
L

Lonewolff

Guest
Yep, you'll most certainly need to set those variables.

You can hard code them in to the shader (for either test purposes or permanently).

You can adjust the shader like this as a test

Code:
// 10 pixels on a 512 pixel texture page.
offset.x = 10.0 / 512.0;    // pixelW;
offset.y = 10.0 / 512.0;    // pixelH;
Sorry I can't be of more help, being on my iPad. I can take a deeper look in their morning though.

This should certainly give some sort of effect on your text, one way or another.
 

MeBoingus

Member
Yep, you'll most certainly need to set those variables.

You can hard code them in to the shader (for either test purposes or permanently).

You can adjust the shader like this as a test

Code:
// 10 pixels on a 512 pixel texture page.
offset.x = 10.0 / 512.0;    // pixelW;
offset.y = 10.0 / 512.0;    // pixelH;
Sorry I can't be of more help, being on my iPad. I can take a deeper look in their morning though.

This should certainly give some sort of effect on your text, one way or another.

Not a problem, you've been a fantastic help :).

Even with the values hard-coded in, there is no 'outline.' The text changes alphas in seemingly random locations, but it's never any larger, smaller, or offset in any direction.


The text affected by the shader:
ExampleA.png


Drawing regular text on top of that:
ExampleB.png



As you can see, the text being drawn with the shader is the exact same size as the text drawn via draw_text.
 
L

Lonewolff

Guest
I've quite possibly used addition where I should have used multiplication or vice versa.

I'll be able to have a deeper look in roughly ten hours or so.
 

MeBoingus

Member
I've quite possibly used addition where I should have used multiplication or vice versa.

I'll be able to have a deeper look in roughly ten hours or so.
Not a problem :) Will reply/edit with any "major" breakthroughs, just in case someone else has any ideas.


I updated the shader fragment to:

Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    vec2 offset;
    offset.x = 10.0 / 512.0;
    offset.y = 10.0 / 512.0;
    
    vec4 outline;
    outline = texture2D(gm_BaseTexture, v_vTexcoord * vec2(-offset.x, -offset.y));
    outline += texture2D(gm_BaseTexture, v_vTexcoord * vec2(-offset.x, offset.y));
    outline += texture2D(gm_BaseTexture, v_vTexcoord * vec2(offset.x, -offset.y));
    outline += texture2D(gm_BaseTexture, v_vTexcoord * vec2(offset.x, offset.y));
    outline.rgb = vec3(0.0, 0.0, 0.0);        // make coloured area black
    
    gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord) * outline;
}
The text is now drawn entirely black, exactly the same way as you'd expect draw_text to draw it.
 
L

Lonewolff

Guest
Looks like the following will work. But, it looks like you need to draw the text to a surface first as each character is drawn to the edge of the quad (each character has it's own quad), so the shader has nowhere to draw to in a lot of cases.

Drawing the text to a surface first gives the shader room to move, so to speak.


Frag shader

Code:
    vec4 col = texture2D(gm_BaseTexture, v_vTexcoord);
    if (col.a > 0.5)
        gl_FragColor = col;
    else {
        float a = texture2D(gm_BaseTexture, vec2(v_vTexcoord.x + offset, v_vTexcoord.y)).a;
            a += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y - offset)).a;
            a += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x - offset, v_vTexcoord.y)).a;
            a += texture2D(gm_BaseTexture, vec2(v_vTexcoord.x, v_vTexcoord.y + offset)).a;
        if (col.a < 1.0 && a > 0.0)
            gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
        else
            gl_FragColor = col;
    }

Draw event

Code:
surface_set_target(surf);
    draw_clear_alpha(c_black, 0);
    draw_text(100, 100, "TEST STRING");
surface_reset_target();


shader_set(shader0);
    draw_surface(surf, 0, 0);
shader_reset();
 
Top