GM:S 1.4 Efficient text outline

Discussion in 'Programming' started by Liam Jacobs, Jul 10, 2019 at 11:49 AM.

  1. Liam Jacobs

    Liam Jacobs Member

    Joined:
    Jul 1, 2016
    Posts:
    151
    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 :).
     
  2. Lonewolff

    Lonewolff Member

    Joined:
    Jan 8, 2018
    Posts:
    1,084
    I don't see why there would be a difference. They are both just textured quads.
     
  3. Liam Jacobs

    Liam Jacobs Member

    Joined:
    Jul 1, 2016
    Posts:
    151
    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.
     
  4. Lonewolff

    Lonewolff Member

    Joined:
    Jan 8, 2018
    Posts:
    1,084
    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.
     
  5. Liam Jacobs

    Liam Jacobs Member

    Joined:
    Jul 1, 2016
    Posts:
    151
    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?
     
  6. Lonewolff

    Lonewolff Member

    Joined:
    Jan 8, 2018
    Posts:
    1,084
    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.
     
  7. Liam Jacobs

    Liam Jacobs Member

    Joined:
    Jul 1, 2016
    Posts:
    151
    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: Jul 10, 2019 at 1:02 PM
  8. Lonewolff

    Lonewolff Member

    Joined:
    Jan 8, 2018
    Posts:
    1,084
    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.
     
  9. Liam Jacobs

    Liam Jacobs Member

    Joined:
    Jul 1, 2016
    Posts:
    151

    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.
     
  10. Lonewolff

    Lonewolff Member

    Joined:
    Jan 8, 2018
    Posts:
    1,084
    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.
     
  11. Liam Jacobs

    Liam Jacobs Member

    Joined:
    Jul 1, 2016
    Posts:
    151
    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.
     
  12. Lonewolff

    Lonewolff Member

    Joined:
    Jan 8, 2018
    Posts:
    1,084
    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();
    
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice