Dynamic font effects using distance fields

Interestingly, the distance fields technique from the previous recipe can be exploited to achieve font effects dynamically, such as outlines, glows, and drop shadows. Can this technique get any more awesome?

No need to generate yet another font texture with prebaked effects; you can turn them off or on as well as tweak and tween their parameters completely at runtime. Consequently, you will be able to achieve a much richer and engaging experience. Moreover, the performance penalty is negligible—you can go crazy!

Just keep in mind that every time you set a shader on a SpriteBatch object, the latter is automatically flushed with the consequent draw call.

In this recipe, we will start off with an existing distance field font and write a configurable fragment shader to obtain outline and glow effects.

Getting ready

This time, we will use the data/fonts/pacific-distance.fnt file as well as the data/fonts/font-effects.frag and data/fonts/font-effects.vert shaders. Additionally, you need the samples projects to be in your workspace.

How to do it…

First, let's get our fragment shader sorted; make sure to check data/fonts/font-effects.frag for the full source. We are going to control the outline and glow parameters from the code, so we need to define a few uniform variables, as follows:

uniform vec4 u_outlineColor;
uniform vec4 u_glowColor;
uniform int u_enableOutline;
uniform int u_enableGlow;
uniform vec2 u_outline;
uniform vec2 u_glow;

The u_outline and u_glow vector uniform variables contain the minimum and maximum distance values for which those effects should be visible; this way, we can configure their position and width. We require u_outline to be closer to the center than u_glow for the shader to work correctly.

Then, we get the distance value from the texture and initialize our alpha variable. Remember that a distance of 1.0 means right inside the font, while 0.0 marks the greatest distance possible:

float distance = texture2D(u_texture, v_texCoord).a;
float alpha = 1.0;

When the Outline effect is enabled, we check whether the distance for the current fragment falls within our configured outline boundaries. If so, we need to check whether the fragment is either in the inner or outer falloff area and use the same smoothing function to calculate the alpha value, as we did in our previous recipe:

if (u_enableOutline > 0 && distance >= u_outline.x && distance <= u_outline.y) {
   
   if (distance <= u_outline.x + smoothing) {
      alpha = smoothstep(u_outline.x - smoothing, u_outline.x + smoothing, distance);
   }
   else {
      alpha = smoothstep(u_outline.y + smoothing, u_outline.y - smoothing, distance);
   }
   
   gl_FragColor = vec4(u_outlineColor.rgb, alpha * v_color.a * u_outlineColor.a);
}

When the Outline effect is disabled but the glow effect is enabled, we need to readjust the boundaries to avoid a gap between the font edge and the start of the glowing area:

vec2 glow = u_glow;
glow.y = max(u_outline.y, glow.y);

Then, we check whether the glow effect is enabled and the fragment is within the correct range. If so, we get the appropriate alpha and fragment color using the glow effect color:

if (u_enableGlow > 0 && distance >= glow.x && distance < glow.y) {
	alpha = smoothstep(glow.x - smoothing, glow.y + smoothing, distance);
	gl_FragColor = vec4(u_glowColor.rgb, alpha * u_glowColor.a);
}

Finally, if either no effects are enabled or the current fragment belongs to the actual text, we use the same approach as in the distance fields recipe:

else {
   alpha = smoothstep(threshold - smoothing, threshold + smoothing, distance);
   gl_FragColor = vec4(v_color.rgb, alpha * v_color.a);
}

We are now done with the fragment shader; let's move on to the Java side of this sample, which is located inside the DistanceFieldEffectsSample.java file. First, we define a few constants to configure the color and size of our effects:

private static final Color OUTLINE_COLOR = new Color(0x00222b);
private static final Color GLOW_COLOR = new Color(0xffe680);
private static final Vector2 OUTLINE = new Vector2(0.45f, 0.55f);
private static final Vector2 GLOW = new Vector2(0.00f, 0.45f);

Naturally, we need a camera, a viewport, a batch, our font, and a shader object. Both the create() and dispose() methods are very similar to the ones in the previous recipe:

public void create() {	
   …
   font = new BitmapFont(Gdx.files.internal("data/fonts/pacifico-distance.fnt"));
   font.setColor(new Color(0x5fbcd3));
   font.setScale(3.0f);
Texture texture = font.getRegion().getTexture();
   texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
   fontShader = new ShaderProgram(Gdx.files.internal("data/fonts/font-effects.vert"), Gdx.files.internal("data/fonts/font-effects.frag"));
   
}

We are going to render several pieces of text with different effects enabled. We need to pass the uniform values to the shader:

fontShader.setUniformf("u_glow", GLOW);
fontShader.setUniformf("u_outline", OUTLINE);
fontShader.setUniformf("u_glowColor", GLOW_COLOR);
fontShader.setUniformf("u_outlineColor", OUTLINE_COLOR);

Before calling the draw() method of the bitmap font, we enable or disable the effects at will. If the next draw call uses different shader parameters, we need to make sure that the batch sends the current draw instructions to the GPU; otherwise, the parameters will be overwritten. In order to do so, we call its flush() method. Be careful though; flushing the batch is a costly operation, so you should group draw calls that use the same shader parameters together:

fontShader.setUniformi("u_enableGlow", 1);
fontShader.setUniformi("u_enableOutline", 1);
font.draw(batch, "Outline and glow", 20.0f, VIRTUAL_HEIGHT - 500.0f);
batch.flush();

Note

Remember that you can set uniforms by index rather than name, which is considerably faster. Querying a uniform index is easy; just call getUniformLocation().

The results are quite good; here is some text with an outline:

How to do it…

We can also render glowing text:

How to do it…

Finally, it is also possible to combine both the effects:

How to do it…

Brilliant! Now you can introduce awesome, dynamically generated font effects in your game.

How it works…

As you saw in the previous section, this follows the same principles as explained in the Scaling friendly font rendering with distance fields recipe. We determine whether or not we should color the fragment using the distance field and 0.5 as the threshold.

The step that we take to add effects is simple; it is possible to use different thresholds and colors to render specific areas of the letters in a particular way. Just before the edge of the font, the outline color kicks in, whereas, the glow color does the same from the outer edge onwards, progressively fading away.

There's more…

Drop shadows is an additional effect that you can achieve using the distance field information. Instead of sampling the texture once in the fragment shader, you will have to do it twice, with an offset for the second one. Feel free to experiment and try to implement this effect yourself.

Since we are configuring our shader using uniforms, we can make the color and size of the effects vary over time in interesting ways.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.210.205