📜 ⬆️ ⬇️

Rendering UTF-8 text using an SDF font

We continue a series of articles about mobile game development. In this article I will tell you how to render UTF-8 text using SDF Bitmap fonts, how to create these fonts and how to use this technique for high-quality rendering of icons.



Content


Part 1. Mobile cross-platform engine
Part 2. Rendering UTF-8 text using an SDF font
Part 3. Rendering drops with transparency and reflections.




SDF ( Signed Distance Field ) is a grayscale image generated from a contrasting black and white image, in which the gray level indicates the distance to the nearest contrast border. It sounds confusing, but in fact everything is very simple.


The sdf font itself looks like this:



Let's take this image and change its levels (levels) in Photoshop or any other graphics editor.



Looks better already! We have a clear font with anti-aliasing on the edges.
We can also get a bold or thin outline. But get italic alas will not work .



The main advantage of the SDF is the ability to increase the font without noticeable artifacts.



In more detail about the SDF technique I recommend to read here .


How to create an SDF font?


First of all, you need to create the most common black and white bitmap font. This can be done in good old BMFont or in UBFG .


For a good result, generate a 400pt font with no anti-aliasing, with indents 45x45x45x45 and a picture size of 4096x4096. Merging at these sizes I advise you to disable most likely UBGF will hang.


We export the image to PNG without transparency, and the description format is desirable to choose BMFont (for greater compatibility).



Next we need ImageMagick and the following command:


convert font.png -filter Jinc (+ clone -negate -morphology Distance Euclidean -level 50%, - 50%) -morphology Distance Euclidean -compose Plus -composite -level 43%, 57% -resize 12.5% ​​font.png

At the output we get a picture of 512x512, which will give us a very good result as a result.
From the file with the description we will need to pull out the characters in unicode and their position / size (do not forget to divide the coordinates by 8 since we reduced the picture). Which characters need to be exported, I will tell a little lower in the section about UTF-8.


Just a minute, UBFG has a built-in Distance Field!
Yes there is. But the result is noticeably worse. Perhaps in the update, the authors of UBFG will fix this.


Shaders for text rendering


Vertex shader to display each letter, character by character:


#ifdef DEFPRECISION precision mediump float; #endif attribute mediump vec2 Vertex; uniform highp mat4 MVP; uniform mediump vec2 cords[4]; varying mediump vec2 outTexCord; void main(){ outTexCord=Vertex*cords[3]+cords[2]; gl_Position = MVP * vec4(Vertex*cords[1]+cords[0], 0.0, 1.0); } 

DEFPRECISION is needed for OpenGL ES.
In cords [1] and cords [0], we pass the position and scale of the character on the screen.
And in cords [2] and cords [3] - the coordinates of the character on the font texture.


Fragment Shader


 #ifdef DEFPRECISION precision mediump float; #endif varying mediump vec2 outTexCord; uniform lowp sampler2D tex0; uniform mediump vec4 color; uniform mediump vec2 params; void main(void){ float tx=texture2D(tex0, outTexCord).r; float a=min((tx-params.x)*params.y, 1.0); gl_FragColor=vec4(color.rgb,a*color.a); } 

In color we transfer the color and transparency of the letter.
And through the params adjust the thickness and smoothing of the edges of the font.


If you can adjust the thickness of the font, it means that you can also display a frame!
Fragment text shader with frame :


 #ifdef DEFPRECISION precision mediump float; #endif varying mediump vec2 outTexCord; uniform lowp sampler2D tex0; uniform mediump vec4 color; uniform mediump vec4 params; uniform mediump vec3 borderColor; void main(void){ float tx=texture2D(tex0, outTexCord).r; float b=min((tx-params.z)*params.w, 1.0); float a=clamp((tx-params.x)*params.y, 0.0, 1.0); gl_FragColor=vec4(borderColor+(color.rgb-borderColor)*a, b*color.a); } 

Additionally, we pass thickness, anti-aliasing in params.zw, and border color in borderColor .
This should be the result:



To get beautiful edges for both small and large text sizes, it is necessary to select different contrast / anti-aliasing parameters ( params ) for a small font and for a large one. Then interpolate them to the current size.


In my opinion, well suited for small sizes :



For large size :



Icons



In modern design, flat icons have become quite popular. Free vector icons are full . All we need to do is to collect a black and white textural texture atlas from the necessary icons and in the same way drive it through ImageMagick!


As a result, we can store icons in a fairly low resolution, but get a good result when scaling and rotating icons!


Bonus can be easily added to the gradient icons. To do this, just hang the colors on the vertices, and we get the gradient by interpolating between points. The radial gradient will have to be done pixel-by-pixel in a fragment shader.


UTF-8


In modern projects, no one uses single-byte encoding. All switched to UTF-8, wchar, unicode. For example, it is convenient for me to work with strings in the UTF-8 char *.
UTF-8 is easily decoded into unicode and fits perfectly with Java / String and NSString.


The function for converting UTF-8 to Unicode:


 static inline unsigned int UTF2Unicode(const unsigned char *txt, unsigned int &i){ unsigned int a=txt[i++]; if((a&0x80)==0)return a; if((a&0xE0)==0xC0){ a=(a&0x1F)<<6; a|=txt[i++]&0x3F; }else if((a&0xF0)==0xE0){ a=(a&0xF)<<12; a|=(txt[i++]&0x3F)<<6; a|=txt[i++]&0x3F; }else if((a&0xF8)==0xF0){ a=(a&0x7)<<18; a|=(a&0x3F)<<12; a|=(txt[i++]&0x3F)<<6; a|=txt[i++]&0x3F; } return a; } 

Bonus! Change the registry unicode character.
 static inline unsigned int uppercase(unsigned int a){ if(a>=97 && a<=122)return a-32; if(a>=224 && a<=223)return a-32; if(a>=1072 && a<=1103)return a-32; if(a>=1104 && a<=1119)return a-80; if((a%2)!=0){ if(a>=256 && a<=424)return a-1; if(a>=433 && a<=445)return a-1; if(a>=452 && a<=476)return a-1; if(a>=478 && a<=495)return a-1; if(a>=504 && a<=569)return a-1; if(a>=1120 && a<=1279)return a-1; } return a; } static inline unsigned int lowercase(unsigned int a){ if(a>=65 && a<=90)return a+32; if(a>=192 && a<=223)return a+32; if(a>=1040 && a<=1071)return a+32; if(a>=1024 && a<=1039)return a+80; if((a%2)==0){ if(a>=256 && a<=424)return a+1; if(a>=433 && a<=445)return a+1; if(a>=452 && a<=476)return a+1; if(a>=478 && a<=495)return a+1; if(a>=504 && a<=569)return a+1; if(a>=1120 && a<=1279)return a+1; } return a; } 

UTF-8 blocks


In most fonts, especially creative ones, there is only ascii and latin. What if we need, for example, currency symbols? Especially true for in-app payments, where any currencies only come across. I propose the following scheme, which has proven itself very well:



How to find out what characters are in the font?


Here comes to help us a strange thing from Adobe - tada! - empty font !
It can be used in CSS: font-family: Roboto, Adobe Blank;
This is how the plates are obtained from the image above. It remains only to copy the necessary pieces of characters and paste them into UBFG. As a result, we get several pictures 512x512, where each will contain as many characters as it will fit.


What kind of universal font?


There are not so many fonts containing most Unicode characters. I settled on Quivira . At least with the currency symbols he is doing well.


Let's say you added bitmaps for Arabic, Japanese, and Chinese. There will be quite a lot of pictures. Do not rush to download them all! Wait until you really get a symbol from this block and load the desired texture.


There is also a catch in that all the fonts are of different sizes and different baselines. When switching from font to font, the text will jump. Therefore, for each font, select the parameters of its relative scaling and Y shift. Take these parameters into account when rendering each character.


I promised buns!
Catch the ready SDF Quivira font already cut into blocks!


')

Source: https://habr.com/ru/post/282191/


All Articles