Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (491)
Games in Android Showcase (112)
games submitted by our members
Games in WIP (556)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  [Solved] Signed-distance-field fonts look crappy at small pt sizes  (Read 2474 times)
0 Members and 1 Guest are viewing this topic.
Offline loom_weaver

JGO Coder


Medals: 17



« Posted 2014-06-20 02:16:24 »

Hi all, I've successfully implemented signed-distance-fields as described by the Valve paper:
http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf

As advertised it has created excellent looking characters at very large pt sizes.  However at small pt sizes (<12 or so) they look like crap.  Normal 64x64 glyph simply downscaled on the left, SDF on the right.



My distance fields fonts are 64x64 pixels per character calculated from a 1024x1024 pixel original using a brute-force exhaustive distance function.   Thus I figure that they encapsulate enough information to technically be able to render a good looking font event at small sizes.


(^ alpha SDF font is hard to see but provided to show that they are of high quality)

According to the creator of TextMesh Pro it is possible:

Quote
A big misconception about SDF Text Rendering is that large text looks great but small text looks bad. Well, that is not the case with TextMesh Pro's Advanced Signed Distance Field shaders. TextMesh Pro's SDF text looks awesome at large sizes and looks as good or better than bitmap text at small sizes.
http://forum.unity3d.com/threads/textmesh-pro-advanced-text-rendering-for-unity-beta-now-available-in-asset-store.227790/

Here's the source for my fragment shader which does the basic implementation:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
// GLSL 1.2 to correspond with OpenGL 2.0
#version 120

// predefined variables
uniform sampler2D tex0;

void main() {
    // retrieve distance from texture
   float dist = texture2D(tex0, gl_TexCoord[0].xy).a;

    // fwidth helps keep outlines a constant width irrespective of scaling
   float width = fwidth(dist);

    float alpha = smoothstep(0.5 - width, 0.5 + width, dist);

    // antialiased
   gl_FragColor = vec4(gl_Color.rgb, alpha);
}


Anyway have any of you worked in this area and have suggestions towards a custom fragment shader that may accomplish this?
Offline Roquen
« Reply #1 - Posted 2014-06-20 05:24:32 »

libgdx I think has SDF.  Forget that fwidth exists.  It was a bad idea, try doing the correct computation.

https://github.com/OpenGLInsights/OpenGLInsightsCode/tree/master/Chapter%2012%202D%20Shape%20Rendering%20by%20Distance%20Fields

I think this has a survey:
http://jcgt.org/published/0002/01/04/
Offline pitbuller
« Reply #2 - Posted 2014-06-20 06:47:22 »

Do you have mipmaps?
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline loom_weaver

JGO Coder


Medals: 17



« Reply #3 - Posted 2014-06-22 10:35:30 »

libgdx I think has SDF.  Forget that fwidth exists.  It was a bad idea, try doing the correct computation.

https://github.com/OpenGLInsights/OpenGLInsightsCode/tree/master/Chapter%2012%202D%20Shape%20Rendering%20by%20Distance%20Fields

I think this has a survey:
http://jcgt.org/published/0002/01/04/

It seems that fwidth() returns a number that maps to one pixel or less.  Trying it versus Stefan Gustavson's:

1  
float aa = 0.75 * length(vec2(dFdx(d), dFdy(d)));  // antialias


doesn't appear to make a perceptible difference.  Can you explain how calculating fwidth() properly will give other benefits?

Anyways an update on tests so far.  I've applied two improvements.  The first is a supersampling technique and the second is using a "thicker" original SDF texture.

Details about the second technique.  My original calculation used to create the alpha-channel texture was the canonical:

    val alpha: Double = 0.5 + 0.5 * (signedDistance / spread)

Where signedDistance = distance to an opposite bit and will be +ve if inside the glyph, -ve if outside; spread = 4

And in an attempt to make the original texture "thicker" I tried the following:

    val alpha: Double = 0.6 + (signedDistance / spread)


Original SDF (texture simply scaled on left, SDF shader w/ supersampling applied on right):


Versus thicker:


As you can see this gives me a much more solid-looking distance field glyph that also gives me better results with the shader esp. at small pt sizes. However, knowing that any alpha of > 0.5 (which becomes "distance" within the shader) is inside the glyph I figure I could using a more abrupt transition in the shader instead of recreating the alpha images.

I'm tending towards using the simple thicker texture because that means I can use the same texture for both SDF or simple scaling (when shaders aren't available).
Offline loom_weaver

JGO Coder


Medals: 17



« Reply #4 - Posted 2014-06-22 10:39:04 »

Do you have mipmaps?

Thought about it but putting in different code and different shaders depending on the font size is too unwieldy for my tastes.  Dynamically switching could affect performance too as I would have to split up all my VBOs by pt size.

Using a single shader + source texture for all text is the most elegant.
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 783
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #5 - Posted 2014-06-22 10:41:58 »

So, are you using mipmaps or not? You should. You'd still have the same code, a single shader and a single texture.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline loom_weaver

JGO Coder


Medals: 17



« Reply #6 - Posted 2014-06-22 10:47:03 »

So, are you using mipmaps or not? You should. You'd still have the same code, a single shader and a single texture.

If you mean by using the GL commands and the work it does behind the scenes I am:

1  
2  
3  
4  
5  
6  
7  
8  
// Using MipMaps instead of glTextImage2D() and GL_NEAREST
// gives us much better scaled down textures.
//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16*64, 16*64, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)

gluBuild2DMipmaps(GL_TEXTURE_2D, 4, sheet.width, sheet.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR)


But I don't actually have pre-rendered SDF textures for multiple point sizes.
Offline loom_weaver

JGO Coder


Medals: 17



« Reply #7 - Posted 2014-06-22 10:53:02 »

I'm also using a supersampling technique suggested by "glacialthinker" that also gives improvement in the small pixel details.  My current shader source:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
// GLSL 1.2 to correspond with OpenGL 2.0
#version 120

// predefined variables
uniform sampler2D tex0;

float contour(in float d, in float w) {
    // smoothstep(lower edge0, upper edge1, x)
   return smoothstep(0.5 - w, 0.5 + w, d);
}

float samp(in vec2 uv, float w) {
    return contour(texture2D(tex0, uv).a, w);
}

void main(void) {

    // retrieve distance from texture
   vec2 uv = gl_TexCoord[0].xy;
    float dist = texture2D(tex0, uv).a;

    // fwidth helps keep outlines a constant width irrespective of scaling
   // GLSL's fwidth = abs(dFdx(uv)) + abs(dFdy(uv))
   float width = fwidth(dist);
    // Stefan Gustavson's fwidth
   //float width = 0.7 * length(vec2(dFdx(dist), dFdy(dist)));

// basic version
   //float alpha = smoothstep(0.5 - width, 0.5 + width, dist);

// supersampled version

    float alpha = contour( dist, width );
    //float alpha = aastep( 0.5, dist );

    // ------- (comment this block out to get your original behavior)
   // Supersample, 4 extra points
   float dscale = 0.354; // half of 1/sqrt2; you can play with this
   vec2 duv = dscale * (dFdx(uv) + dFdy(uv));
    vec4 box = vec4(uv-duv, uv+duv);

    float asum = samp( box.xy, width )
               + samp( box.zw, width )
               + samp( box.xw, width )
               + samp( box.zy, width );

    // weighted average, with 4 extra points having 0.5 weight each,
   // so 1 + 0.5*4 = 3 is the divisor
   alpha = (alpha + 0.5 * asum) / 3.0;

    // -------

    gl_FragColor = vec4(gl_Color.rgb, alpha);
}
Offline Roquen
« Reply #8 - Posted 2014-06-22 13:12:35 »

http://www.opengl.org/wiki/Common_Mistakes#Legacy_Generation
http://www.opengl.org/wiki/Common_Mistakes#gluBuild2DMipmaps
(box filter = shit)

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR)
is illegal. 
GL_LINEAR


Offline loom_weaver

JGO Coder


Medals: 17



« Reply #9 - Posted 2014-06-25 01:24:49 »

One interesting note that I learned from the person (thanks glacialthinker@reddit!) who showed me supersampling.  If using the supersampling approach in the shader you actually don't want to use mipmapping as you'll lose some of the detail that the supersampler would otherwise pick up on.

Supersampling shader without mipmaps (left is downscaling and right is SDF):



And going back to the original non-thick SDF font:



To conclude this thread SDF fonts can look good at small pt sizes if you use supersampling and no mipmapping.

Reminder of how crappy the SDF looked when this saga began:

Pages: [1]
  ignore  |  Print  
 
 

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Nickropheliac (15 views)
2014-08-31 22:59:12

TehJavaDev (23 views)
2014-08-28 18:26:30

CopyableCougar4 (29 views)
2014-08-22 19:31:30

atombrot (41 views)
2014-08-19 09:29:53

Tekkerue (39 views)
2014-08-16 06:45:27

Tekkerue (35 views)
2014-08-16 06:22:17

Tekkerue (25 views)
2014-08-16 06:20:21

Tekkerue (36 views)
2014-08-16 06:12:11

Rayexar (72 views)
2014-08-11 02:49:23

BurntPizza (49 views)
2014-08-09 21:09:32
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!