使用 True Type 和 OpenGL 核心 (3.3) 的轮廓字体
Outline fonts with true type and opengl core (3.3)
我在这里使用了 learnopengl.com 中的文本渲染示例:
https://learnopengl.com/In-Practice/Text-Rendering
我正在尝试更改此示例以渲染轮廓,但我在调用所有函数之间有点迷茫,而且着色器代码似乎有点棘手。
这是我的尝试:
void inittext_stroked() {
cout << "initializing text, stroken" << endl;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Compile and setup the shader
shader = Shader("text.vs", "text.fs");
//Shader shader("text.vs", "text.fs");
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(WIDTH), 0.0f, static_cast<GLfloat>(HEIGHT));
shader.use();
//shader.
glUniformMatrix4fv(glGetUniformLocation(shader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// FreeType
FT_Library ft;
// All functions return a value different than 0 whenever an error occurred
if (FT_Init_FreeType(&ft))
std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
string fontfile = base::config.getstr("fontfile");
cout << fontfile << endl;
// Load font as face
FT_Face face;
// if (FT_New_Face(ft, "DejaVuSansMono.ttf", 0, &face))
if (FT_New_Face(ft, fontfile.c_str(), 0, &face))
std::cout << "ERROR::FREETYPE: Failed to load font " << fontfile << std::endl;
// stroke, added
// void * m_stroker;
FT_Stroker stroker;
if (FT_Stroker_New(static_cast<FT_Library>(ft), &stroker) != 0)
{
cerr << "Failed to load font \"" << fontfile << "\" (failed to create the stroker)" << std::endl;
FT_Done_Face(face);
return;
}
// m_stroker = stroker;
// m_face = face;
// end added
// Set size to load glyphs as
int fontsize;
//cfg.SET(fontsize);
// fontsize = 16;
fontsize = base::config.getvar<int>("fontsize");
FT_Set_Pixel_Sizes(face, 0, fontsize);
//FT_Set_Pixel_Sizes(face, 0, 48);
// Disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Load first 128 characters of ASCII set
for (GLubyte c = 0; c < 128; c++)
{
// Load character glyph
if (FT_Load_Char(face, c,
//FT_LOAD_RENDER|
FT_LOAD_NO_BITMAP|FT_LOAD_TARGET_NORMAL
)) // modified, added FT_LOAD_NO_BITMAP (also 2 more so 3)
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
continue;
}
// added outlining _________________
float outlineThickness = 10.0f;
// Retrieve the glyph
FT_Glyph glyphDesc;
if (FT_Get_Glyph(face->glyph, &glyphDesc) != 0)
{
cerr << "bad return from FT_Get_Glyph" << endl;
return;
}
{
// FT_Stroker stroker = static_cast<FT_Stroker>(m_stroker);
FT_Stroker_Set(stroker, static_cast<FT_Fixed>(outlineThickness * static_cast<float>(1 << 6)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
FT_Glyph_Stroke(&glyphDesc, stroker, true);
}
FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1);
// FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>(glyphDesc)->bitmap;
// added end _________________
// Generate texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
//GL_RED,
GL_RG16,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
//GL_RED,
GL_RG16,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
// Set texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Now store character for later use
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x
};
Characters.insert(std::pair<GLchar, Character>(c, character));
}
glBindTexture(GL_TEXTURE_2D, 0);
// Destroy FreeType once we're finished
FT_Done_Face(face);
FT_Done_FreeType(ft);
// Configure VAO/VBO for texture quads
glGenVertexArrays(1, &VAO_text);
glGenBuffers(1, &VBO_text);
glBindVertexArray(VAO_text);
glBindBuffer(GL_ARRAY_BUFFER, VBO_text);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
texts["popo"] = "haha";
}
和着色器代码:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
uniform vec3 outlinecolor;
void main()
{
// old shader code:
// vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
// color = vec4(textColor, 1.0) * sampled;
vec2 tex = texture2D(text, TexCoords).rg;
color = vec4(textColor, tex.g)
+vec4(outlinecolor, tex.g)
;
}
如您所见,我使用 GL_RG16 而不是 RG_RED 来设置一个通道用于填充,一个通道用于轮廓。我不理解所有那些 freetype 调用,也不熟悉面、字形和笔划的概念,而且我不确定我的着色器代码是否足够。
最简单的方法是再次绘制每个字母,并在同一位置进行一定的缩放和不同的颜色。只要确保你以正确的顺序绘制它们(首先是按比例放大的,然后是正常的)
你想做的事没那么容易。第一个误解是 FT_Glyph_To_Bitmap
不会像您预期的那样创建 2 个通道,一个用于笔画轮廓,另一个用于填充主体。
您必须从字形创建 2 个位图,并且必须手动将它们合并为一个具有 2 个通道的位图。
一个问题是,位图的大小甚至都不一样。 "outline" 位图略高,然后是 "filled"。
首先创建轮廓位图:
FT_Error err_code = FT_Load_Char( face, i, FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_NORMAL );
if ( err_code != 0 )
{
// error handling
}
FT_Glyph glyphDescStroke;
err_code = FT_Get_Glyph( face->glyph, &glyphDescStroke );
static double outlineThickness = 2.0;
FT_Stroker_Set( stroker, static_cast<FT_Fixed>(outlineThickness * static_cast<float>(1 << 6)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0 );
if ( err_code == 0 )
err_code = FT_Glyph_Stroke( &glyphDescStroke, stroker, true );
if ( err_code == 0 )
err_code = FT_Glyph_To_Bitmap( &glyphDescStroke, FT_RENDER_MODE_NORMAL, 0, 1);
FT_BitmapGlyph glyph_bitmap;
FT_Bitmap *bitmap_stroke = nullptr;
if ( err_code == 0 )
{
glyph_bitmap = (FT_BitmapGlyph)glyphDescStroke;
bitmap_stroke = &glyph_bitmap->bitmap;
}
创建一个具有 2 个颜色通道的临时缓冲区,并将轮廓位图复制到缓冲区的第二个颜色通道:
#include <vector>
unsigned int cx = 0, cy = 0, ox = 0, oy = 0;
std::vector<unsigned char> buffer
if ( error_code == 0 && bitmap_stroke )
{
cx = bitmap_stroke->width;
cy = bitmap_stroke->rows;
ox = glyph_bitmap->left;
oy = glyph_bitmap->top;
buffer = std::vector<unsigned char>(cx * cy * 2, 0); // * 2 -> 2 color channels (red and green)
for ( unsigned int i = 0; i < cx * cy; ++ i)
buffer[i*2 + 1] = bitmap_stroke->buffer[i]; // + 1 -> 2nd color channel
}
FT_Done_Glyph( glyphDescStroke );
创建 "filled" 位图:
FT_Glyph glyphDescFill;
err_code = FT_Get_Glyph( face->glyph, &glyphDescFill );
if ( err_code == 0 )
err_code = FT_Glyph_To_Bitmap( &glyphDescFill, FT_RENDER_MODE_NORMAL, 0, 1);
FT_Bitmap *bitmap_fill = nullptr;
if ( err_code == 0 )
{
FT_BitmapGlyph glyph_bitmap = (FT_BitmapGlyph)glyphDescFill;
bitmap_fill = &glyph_bitmap->bitmap;
}
将"filled"位图添加到缓冲区的第一个颜色通道:
if ( err_code == 0 && bitmap_fill )
{
unsigned int cx_fill = bitmap_fill->width;
unsigned int cy_fill = bitmap_fill->rows;
unsigned int offset_x = (cx - cx_fill) / 2; // offset because the bitmap my be smaller,
unsigned int offset_y = (cy - cy_fill) / 2; // then the former
for ( unsigned int y = 0; y < cy_fill; ++ y )
{
for ( unsigned int x = 0; x < cx_fill; ++ x )
{
unsigned int i_source = y * cx_fill + x;
unsigned int i_target = (y + offset_y) * cx + x + offset_x;
buffer[i_target*2 + 0] = bitmap_fill->buffer[i_source]; // + 0 -> 1st color channel
}
}
}
FT_Done_Glyph( glyphDescFill );
当您将缓冲区加载到纹理对象时,您必须设置 GL_UNPACK_ALIGNMENT
参数,因为字形位图 buffer
是紧密排列的并且位图的一行可能未对齐到 4 个字节。参见 glPixelStore
。
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStore( GL_UNPACK_ALIGNMENT, 1 ); // of course 2 will work too (2 channels)
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RG8, // 2 channels each with 8 bits
cx,
cy,
GL_RG, // "GL_RG" - "GL_RG16" is not a valid format
GL_UNSIGNED_BYTE,
buffer.data() );
glPixelStore( GL_UNPACK_ALIGNMENT, 4 );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
将字符数据添加到数据结构中:
Character character = {
texture,
glm::ivec2(cx, cy),
glm::ivec2(ox, oy),
face->glyph->advance.x
};
Characters.insert(std::pair<GLchar, Character>(c, character));
GLSL 代码使用 GLSL 函数 mix
绘制轮廓和填充内部颜色不同的字形,可能如下所示:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
uniform vec3 outlinecolor;
void main()
{
vec3 fill_col = vec3(0.0, 0.0, 1.0); // e.g blue
vec3 outline_col = vec3(1.0, 0.0, 0.0); // e.g red
vec2 tex = texture2D(text, TexCoords).rg;
float fill = tex.r;
float outline = tex.g;
float alpha = max( fill, outline );
vec3 mix_color = mix( mix(vec3(0.0), fill_col, fill), outline_col, outline );
color = vec4(mix_color, alpha);
}
使用 pixlim(2).ttf 字体预览,来自您在评论中链接的存储库:
我在这里使用了 learnopengl.com 中的文本渲染示例:
https://learnopengl.com/In-Practice/Text-Rendering
我正在尝试更改此示例以渲染轮廓,但我在调用所有函数之间有点迷茫,而且着色器代码似乎有点棘手。
这是我的尝试:
void inittext_stroked() {
cout << "initializing text, stroken" << endl;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Compile and setup the shader
shader = Shader("text.vs", "text.fs");
//Shader shader("text.vs", "text.fs");
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(WIDTH), 0.0f, static_cast<GLfloat>(HEIGHT));
shader.use();
//shader.
glUniformMatrix4fv(glGetUniformLocation(shader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// FreeType
FT_Library ft;
// All functions return a value different than 0 whenever an error occurred
if (FT_Init_FreeType(&ft))
std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
string fontfile = base::config.getstr("fontfile");
cout << fontfile << endl;
// Load font as face
FT_Face face;
// if (FT_New_Face(ft, "DejaVuSansMono.ttf", 0, &face))
if (FT_New_Face(ft, fontfile.c_str(), 0, &face))
std::cout << "ERROR::FREETYPE: Failed to load font " << fontfile << std::endl;
// stroke, added
// void * m_stroker;
FT_Stroker stroker;
if (FT_Stroker_New(static_cast<FT_Library>(ft), &stroker) != 0)
{
cerr << "Failed to load font \"" << fontfile << "\" (failed to create the stroker)" << std::endl;
FT_Done_Face(face);
return;
}
// m_stroker = stroker;
// m_face = face;
// end added
// Set size to load glyphs as
int fontsize;
//cfg.SET(fontsize);
// fontsize = 16;
fontsize = base::config.getvar<int>("fontsize");
FT_Set_Pixel_Sizes(face, 0, fontsize);
//FT_Set_Pixel_Sizes(face, 0, 48);
// Disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Load first 128 characters of ASCII set
for (GLubyte c = 0; c < 128; c++)
{
// Load character glyph
if (FT_Load_Char(face, c,
//FT_LOAD_RENDER|
FT_LOAD_NO_BITMAP|FT_LOAD_TARGET_NORMAL
)) // modified, added FT_LOAD_NO_BITMAP (also 2 more so 3)
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
continue;
}
// added outlining _________________
float outlineThickness = 10.0f;
// Retrieve the glyph
FT_Glyph glyphDesc;
if (FT_Get_Glyph(face->glyph, &glyphDesc) != 0)
{
cerr << "bad return from FT_Get_Glyph" << endl;
return;
}
{
// FT_Stroker stroker = static_cast<FT_Stroker>(m_stroker);
FT_Stroker_Set(stroker, static_cast<FT_Fixed>(outlineThickness * static_cast<float>(1 << 6)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
FT_Glyph_Stroke(&glyphDesc, stroker, true);
}
FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1);
// FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>(glyphDesc)->bitmap;
// added end _________________
// Generate texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
//GL_RED,
GL_RG16,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
//GL_RED,
GL_RG16,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
// Set texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Now store character for later use
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x
};
Characters.insert(std::pair<GLchar, Character>(c, character));
}
glBindTexture(GL_TEXTURE_2D, 0);
// Destroy FreeType once we're finished
FT_Done_Face(face);
FT_Done_FreeType(ft);
// Configure VAO/VBO for texture quads
glGenVertexArrays(1, &VAO_text);
glGenBuffers(1, &VBO_text);
glBindVertexArray(VAO_text);
glBindBuffer(GL_ARRAY_BUFFER, VBO_text);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
texts["popo"] = "haha";
}
和着色器代码:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
uniform vec3 outlinecolor;
void main()
{
// old shader code:
// vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
// color = vec4(textColor, 1.0) * sampled;
vec2 tex = texture2D(text, TexCoords).rg;
color = vec4(textColor, tex.g)
+vec4(outlinecolor, tex.g)
;
}
如您所见,我使用 GL_RG16 而不是 RG_RED 来设置一个通道用于填充,一个通道用于轮廓。我不理解所有那些 freetype 调用,也不熟悉面、字形和笔划的概念,而且我不确定我的着色器代码是否足够。
最简单的方法是再次绘制每个字母,并在同一位置进行一定的缩放和不同的颜色。只要确保你以正确的顺序绘制它们(首先是按比例放大的,然后是正常的)
你想做的事没那么容易。第一个误解是 FT_Glyph_To_Bitmap
不会像您预期的那样创建 2 个通道,一个用于笔画轮廓,另一个用于填充主体。
您必须从字形创建 2 个位图,并且必须手动将它们合并为一个具有 2 个通道的位图。
一个问题是,位图的大小甚至都不一样。 "outline" 位图略高,然后是 "filled"。
首先创建轮廓位图:
FT_Error err_code = FT_Load_Char( face, i, FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_NORMAL );
if ( err_code != 0 )
{
// error handling
}
FT_Glyph glyphDescStroke;
err_code = FT_Get_Glyph( face->glyph, &glyphDescStroke );
static double outlineThickness = 2.0;
FT_Stroker_Set( stroker, static_cast<FT_Fixed>(outlineThickness * static_cast<float>(1 << 6)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0 );
if ( err_code == 0 )
err_code = FT_Glyph_Stroke( &glyphDescStroke, stroker, true );
if ( err_code == 0 )
err_code = FT_Glyph_To_Bitmap( &glyphDescStroke, FT_RENDER_MODE_NORMAL, 0, 1);
FT_BitmapGlyph glyph_bitmap;
FT_Bitmap *bitmap_stroke = nullptr;
if ( err_code == 0 )
{
glyph_bitmap = (FT_BitmapGlyph)glyphDescStroke;
bitmap_stroke = &glyph_bitmap->bitmap;
}
创建一个具有 2 个颜色通道的临时缓冲区,并将轮廓位图复制到缓冲区的第二个颜色通道:
#include <vector>
unsigned int cx = 0, cy = 0, ox = 0, oy = 0;
std::vector<unsigned char> buffer
if ( error_code == 0 && bitmap_stroke )
{
cx = bitmap_stroke->width;
cy = bitmap_stroke->rows;
ox = glyph_bitmap->left;
oy = glyph_bitmap->top;
buffer = std::vector<unsigned char>(cx * cy * 2, 0); // * 2 -> 2 color channels (red and green)
for ( unsigned int i = 0; i < cx * cy; ++ i)
buffer[i*2 + 1] = bitmap_stroke->buffer[i]; // + 1 -> 2nd color channel
}
FT_Done_Glyph( glyphDescStroke );
创建 "filled" 位图:
FT_Glyph glyphDescFill;
err_code = FT_Get_Glyph( face->glyph, &glyphDescFill );
if ( err_code == 0 )
err_code = FT_Glyph_To_Bitmap( &glyphDescFill, FT_RENDER_MODE_NORMAL, 0, 1);
FT_Bitmap *bitmap_fill = nullptr;
if ( err_code == 0 )
{
FT_BitmapGlyph glyph_bitmap = (FT_BitmapGlyph)glyphDescFill;
bitmap_fill = &glyph_bitmap->bitmap;
}
将"filled"位图添加到缓冲区的第一个颜色通道:
if ( err_code == 0 && bitmap_fill )
{
unsigned int cx_fill = bitmap_fill->width;
unsigned int cy_fill = bitmap_fill->rows;
unsigned int offset_x = (cx - cx_fill) / 2; // offset because the bitmap my be smaller,
unsigned int offset_y = (cy - cy_fill) / 2; // then the former
for ( unsigned int y = 0; y < cy_fill; ++ y )
{
for ( unsigned int x = 0; x < cx_fill; ++ x )
{
unsigned int i_source = y * cx_fill + x;
unsigned int i_target = (y + offset_y) * cx + x + offset_x;
buffer[i_target*2 + 0] = bitmap_fill->buffer[i_source]; // + 0 -> 1st color channel
}
}
}
FT_Done_Glyph( glyphDescFill );
当您将缓冲区加载到纹理对象时,您必须设置 GL_UNPACK_ALIGNMENT
参数,因为字形位图 buffer
是紧密排列的并且位图的一行可能未对齐到 4 个字节。参见 glPixelStore
。
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStore( GL_UNPACK_ALIGNMENT, 1 ); // of course 2 will work too (2 channels)
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RG8, // 2 channels each with 8 bits
cx,
cy,
GL_RG, // "GL_RG" - "GL_RG16" is not a valid format
GL_UNSIGNED_BYTE,
buffer.data() );
glPixelStore( GL_UNPACK_ALIGNMENT, 4 );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
将字符数据添加到数据结构中:
Character character = {
texture,
glm::ivec2(cx, cy),
glm::ivec2(ox, oy),
face->glyph->advance.x
};
Characters.insert(std::pair<GLchar, Character>(c, character));
GLSL 代码使用 GLSL 函数 mix
绘制轮廓和填充内部颜色不同的字形,可能如下所示:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
uniform vec3 outlinecolor;
void main()
{
vec3 fill_col = vec3(0.0, 0.0, 1.0); // e.g blue
vec3 outline_col = vec3(1.0, 0.0, 0.0); // e.g red
vec2 tex = texture2D(text, TexCoords).rg;
float fill = tex.r;
float outline = tex.g;
float alpha = max( fill, outline );
vec3 mix_color = mix( mix(vec3(0.0), fill_col, fill), outline_col, outline );
color = vec4(mix_color, alpha);
}
使用 pixlim(2).ttf 字体预览,来自您在评论中链接的存储库: