GPU文字渲染之周边功能实现
背景色¶
在片元着色器当中,如果覆盖率为0,则赋值为背景色即可
void main()
{
float alpha = 0;
//遍历字体的轮廓
Glyph glyph = loadGlyph(vertex_glyph_index);
for (int i = 0; i < glyph.count; i++)
{
Curve curve = loadCurve(glyph.start + i);
vec2 p0 = curve.p0 - vert_uv;
vec2 p1 = curve.p1 - vert_uv;
vec2 p2 = curve.p2 - vert_uv;
//计算覆盖率
alpha += computeCoverage(inverseDiameter.x, p0, p1, p2);
}
alpha = clamp(alpha, 0.0, 1.0);
if (alpha < 0.01)
{
frag_color = vert_backgroud_color;
}
else
{
frag_color = vert_color * alpha;
}
}
文字布局¶
假设,字符串在编排之后的坐标系如下所示:
- 坐标系原点在文字的左上角
- 文字所在Box在坐标系的第四象限
![[字符串编排.png]]
那么文字布局算法如下,在获得偏移量之后,构造一个偏移矩阵即可
//获取布局的偏移值
static Vec3f getAlignmentOffset(
const Box& box, //文字的Box
const Alignment& alignment //布局
)
{
//# 布局调整
Vec3f algin_offset{ 0,0,0 };
//y方向上偏移
auto y_len = box.max.y() - box.min.y();
switch (alignment)
{
case LEFT_BOTTOM:
case BOTTOM:
case RIGHT_BOTTOM:
//Bottom => 文字在放置点下方 => y小于等于0
// 当前就是,无需转换
break;
case LEFT:
case CENTER:
case RIGHT:
//文字中心与放置点重合 => 往上移一半
algin_offset.y() = +y_len/2;
break;
case LEFT_TOP:
case TOP:
case RIGHT_TOP:
//top => 上方
algin_offset.y() = +y_len;
break;
default:
break;
}
//x方向上的偏移
auto x_len = box.max.x() - box.min.x();
switch (alignment)
{
case RIGHT_TOP:
case RIGHT:
case RIGHT_BOTTOM:
//right => 原先就在右边,无需转换
break;
case TOP:
case CENTER:
case BOTTOM:
//中间 => 往左平移一半
algin_offset.x() = -x_len/2;
break;
case LEFT_TOP:
case LEFT:
case LEFT_BOTTOM:
//最左边 => 往左平移全部
algin_offset.x() = -x_len;
break;
default:
break;
}
return algin_offset;
}
粗体¶
FreeType本身支持粗体,只需在获取轮廓之前,调用FT_Outline_Embolden
函数即可。
if (key.bold)
{
if (_face->glyph->format == FT_GLYPH_FORMAT_OUTLINE)
{
FT_Outline_Embolden(&_face->glyph->outline, 10); //加粗
}
}
但是,一个字符要准备两个轮廓,即粗体时的轮廓、非粗体时的轮廓,而且两种轮廓要区分开。
这里需要注意的是,要更新字符描述信息的规格属性。因为这些属性不会因为调用了FT_Outline_Embolden
函数而改变,而实际上粗体之后,字体的宽、高是变化的。
// 字符的描述信息
struct Glyph
{
FT_UInt glyphIndex; //可由FT_Get_Char_Index获得
int32_t bufferIndex; //在m_GlyphBuffer中的索引
int32_t curveCount; //该字形的曲线个数
//此字符的规格
// Important glyph metrics in font units.
FT_Pos width, height;
FT_Pos bearingX;
FT_Pos bearingY;
FT_Pos advance;
};
具体做法是:
//在将轮廓转成二阶贝塞尔曲线时,统计其新的宽度
FT_Pos width = 0;
for (int i = 0; i < _face->glyph->outline.n_contours; i++)
{
// Note: The end indices in face->glyph->outline.contours are inclusive.
_convertContour(
//...
width //将宽度作为引用传入
);
//...
}
//按照原先的方式,获取文字规格
GlyphDescriptionValue glyph;
glyph.glyphIndex = glyphIndex;
glyph.bufferIndex = bufferIndex;
glyph.curveCount = bufferGlyph.count;
glyph.width = _face->glyph->metrics.width;
glyph.height = _face->glyph->metrics.height;
glyph.bearingX = _face->glyph->metrics.horiBearingX;
glyph.bearingY = _face->glyph->metrics.horiBearingY;
glyph.advance = _face->glyph->metrics.horiAdvance;
//字体加粗之后,字形的宽度其实发生了变换
// 但metrics内部是原先字体的规格,不会发生变换
// 因此需要特殊处理
auto oldWidth = _face->glyph->metrics.width;
if (width < oldWidth)
{
width = oldWidth;
}
glyph.width = width;
glyph.advance += (width - oldWidth)/2;
斜体¶
虽然FreeType支持斜体,但不建议使用。如果使用FreeType的斜体,那也要像粗体一样,多保存和维护一份轮廓了,如此数据量就大了。
我们可以直接使用二维的“错切”仿射变换即可,它用一个2x2的矩阵就可以解决
//斜体
if (text_desc.italic)
{
const static Matrixd shearMatrix(
1, 0, 0, 0,
0.30, 1, 0, 0, //tanθ=0.3
0, 0, 1, 0,
0, 0, 0, 1
); //沿X轴正方向错切θ角度
textMesh.matrix *= shearMatrix; //赋值给模型矩阵即可
}
如下图所示
- 左上角文字:加粗、红色背景色
- 右下角文字:斜体、红色背景色