Jump to content
  • Advertisement
Sign in to follow this  

R&D Poor Signed Distance Font Quality When Drawn Small

Recommended Posts

My SDF font looks great at large sizes, but not when I draw it at smaller sizes. I have my orthogonal projection matrix setup so that each unit is a 1x1 pixel. The text is rendered from Freetype2 to a texture atlas @ 56px with a spread of 8 pixels (the multiplier is 8x and scaled down). I'm drawing @ 18px in the screenshot attached to this post. The way I calculate the size of the text quads is by dividing the desired size (18px in the screenshot) by the size of the glyphs in the atlas (56px in this case), and scaling the glyph sprite by that factor. So: 18/56 = ~0.32, and I multiply the rect's size vector by that when it comes to vertex placement (this obviously doesn't apply to the vertices' texture coords). Now, I made sure that all metrics stored in my SDF font files are whole numbers (rect position/size, bearing amounts, advance, etc), but when I scale the font, vertex positions are almost always not going to be whole numbers. I increase the "edge" smoothstep shader parameter for smaller text as well, but it doesn't seem to help all that much.

Screen Shot 2017-12-11 at 9.13.20 PM.png

Share this post

Link to post
Share on other sites
1 hour ago, Hodgman said:

How do you generate the mip-maps for your SDF texture?

I forgot to post that. I'm doing basic mipmapping. Here's the member definition that creates the OpenGL texture object, and uploads the data to its memory space:

uint32_t Font::CreateTexture(size_t width, size_t height, const void* buffer)
	uint32_t handle;
	glGenTextures(1, &handle);
	glBindTexture(GL_TEXTURE_2D, handle);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, (GLsizei)width, (GLsizei)height, 0, GL_RED, GL_UNSIGNED_BYTE, buffer);

	return handle;

Here's how I call it:

texture_ = CreateTexture(atlasWidth_, atlasHeight_, (void*)((uint8_t*)buffer + offset));

Here's my text shader source as well:

static const std::string TextVertSource =
"uniform mat4 u_transMat;"
"attribute vec2 a_pos;"
"attribute vec2 a_coord;"
"varying vec2 v_coord;"
"void main()"
"	v_coord = a_coord;"
"    gl_Position = u_transMat * vec4(a_pos, 0.0, 1.0);"

static const std::string TextFragSource =
"uniform vec4 u_params;" // fillWidth, fillEdge, strokeWidth, strokeEdge
"uniform vec4 u_colors[2];" // fillColor, strokeColor
"uniform sampler2D u_tex;"
"varying vec2 v_coord;"
"void main()"
"	float distance = 1.0 - texture2D(u_tex, v_coord).r;"
"	float fillAlpha = 1.0 - smoothstep(u_params.x, u_params.x + u_params.y, distance);"
"	float strokeAlpha = 1.0 - smoothstep(u_params.z, u_params.z + u_params.w, distance);"
"	float a = fillAlpha + (1.0 - fillAlpha) * strokeAlpha;"
"	vec4 color = mix(u_colors[1], u_colors[0], fillAlpha / a);"
"	gl_FragColor = color;"
"	gl_FragColor.a = a;"


Edited by Vincent_M

Share this post

Link to post
Share on other sites

I'm not sure whether standard mipmap generation (averaging) is a good/correct one for signed distance fields (correct me if I'm wrong, it's 5:22 am and I'm in front of computer for more than 20 hours ... so I'm not really thinking straight).

Can you show how your distance fields (incl. higher pyramid levels - miplevels) look like?

Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!