Hey guys,
I'm trying to implement SDF font for our game. My images are really wavy, as shown in the screenshot here. Here's the shader I'm using:
// vertex shader
#version 330 core
layout(location = 0) in vec4 in_position;
layout(location = 1) in vec2 in_coord;
out vec2 frag_coord;
uniform mat4 u_pvmMat;
void main()
{
frag_coord = in_coord;
gl_Position = u_pvmMat * in_position;
}
// fragment shader
#version 330 core
in vec2 frag_coord;
out vec4 out_color;
uniform vec4 u_color;
uniform sampler2D u_tex;
const float edgeDistance = 0.5;
void main()
{
float distance = texture(u_tex, frag_coord).r;"
float edgeWidth = 0.7 * length(vec2(dFdx(distance), dFdy(distance)));
float opacity = smoothstep(edgeDistance - edgeWidth, edgeDistance + edgeWidth, distance);
out_color = vec4(u_color.rgb, opacity);
}
I'm using FreeType2 to get the glyph data. I generate my glyph at a size of 24pt with a 4-pixel spread. This means that the glyph's final image size in the texture will have 8 pixels of padding for both dimensions. There's only 1 glyph in my texture atlas, so I just pad that image up to the nearest power of 2.
The way I generate the edge info is by first copying the FT2 bitmap data to the padded image buffer. While doing so, I check to see if the current sample is > 0. If it isn't, it stays as 0. If > 0, I check all adjacent pixels to that sample. If all adjacent pixels are > 0, then the sample is INSIDE the glyph, and gets a value of 255, otherwise, it's an EDGE and gets a value of 127. Next, I calculate the spread on the outside of the glyph. Here's my code for that:
unsigned char topSample = 0;
unsigned char bottomSample = 0;
unsigned char leftSample = 0;
unsigned char rightSample = 0;
// perform the first-pass over the glyph buffer to copy the bitmap into the padded glyph buffer
for(int y=0;y<slot->bitmap.rows;++y)
{
for(int x=0;x<slot->bitmap.width;++x)
{
unsigned char sample = slot->bitmap.buffer[(slot->bitmap.width * y) + x];
if(sample > 0)
{
// find adjacent samples
if(y > 0)
topSample = slot->bitmap.buffer[(slot->bitmap.width * (y - 1)) + x];
else
topSample = 0;
if(y < slot->bitmap.rows - 1)
bottomSample = slot->bitmap.buffer[(slot->bitmap.width * (y + 1)) + x];
else
bottomSample = 0;
if(x > 0)
leftSample = slot->bitmap.buffer[(slot->bitmap.width * y) + (x - 1)];
else
leftSample = 0;
if(x < slot->bitmap.width - 1)
rightSample = slot->bitmap.buffer[(slot->bitmap.width * y) + (x + 1)];
else
rightSample = 0;
// check if an adjacent sample is zero indicating an edge is found
if(!topSample || !bottomSample || !leftSample || !rightSample)
glyphBuffer[(imageWidth * (y + spread)) + (x + spread)] = 127;
else // otherwise, the sample is completely inside the glyph
glyphBuffer[(imageWidth * (y + spread)) + (x + spread)] = 255;
} else
glyphBuffer[(imageWidth * (y + spread)) + (x + spread)] = 0;
}
}
// perform the second-pass over the glyph to calculate the final pixel values
for(int y=0;y<glyphHeight;++y)
{
for(int x=0;x<glyphWidth;++x)
{
// get the sample, and make sure it's not an edge
unsigned char sample = glyphBuffer[(imageWidth * y) + x];
if(sample != 127)
{
// check if the sample is inside the glyph
if(sample == 255)
{
// leftover remnant logic...
} else {
// otherwise, find the minimum distance to the edge
unsigned char minDistance = 255;
// loop through all samples in the bitmap again
for(int y2=0;y2<glyphHeight;++y2)
{
for(int x2=0;x2<glyphWidth;++x2)
{
// find the test sample, and check if it's an edge
unsigned char testSample = glyphBuffer[(imageWidth * y2) + x2];
if(testSample == 127)
{
// find the distance from the current sample, and testSample (it's an edge)
short xDiff = x - x2;
short yDiff = y - y2;
unsigned char distance = (unsigned char)sqrtf((xDiff * xDiff) + (yDiff * yDiff));
// update the minDistance if the current distance is less
if(minDistance > distance)
minDistance = distance;
}
}
}
// make sure the minDistance isn't beyond the spread
if(minDistance <= spread)
glyphBuffer[(imageWidth * y) + x] = (unsigned char)((float)(spread - minDistance + 1) / (float)(spread + 1) * 127.0f);
}
}
}
}