Здесь я использовал пример отрисовки текста с сайта learnnopengl.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 каналами.
Проблема в том, что растровые изображения даже не будут иметь одинаковый размер. "Контурное" растровое изображение будет немного выше, чем "заполненное".
Сначала создайте растровое изображение контура:
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;
}
Создайте временный буфер с двумя цветовыми каналами и скопируйте растровое изображение контура во второй цветовой канал буфера:
#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 );
Создайте "заполненное" растровое изображение:
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;
}
Добавьте "заполненное" растровое изображение к 1-му цветовому каналу буфера:
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 из репозитория, на который вы указали ссылки в комментариях:
@jokoon Да, я тестировал, но не эту часть кода. Я протестировал часть, которая создает растровые изображения и шейдер, в существующем примере в моем наборе тестов. Я посмотрю на вашу проблему.
@jokoon Настройка структуры Character
также должна быть адаптирована. Смотрите изменения в ответе.
Текст теперь выровнен! К сожалению, он вылетает внутри ftobjs.c в строке 2955, когда я загружаю шрифт TTF, который я сделал ...
@jokoon Я тестировал код с помощью FreeSans.ttf, 37043_SYMBOL.ttf (греческие буквы) и Pacifico.ttf (скрипт), и он работал нормально.
@jokoon pixlim (2) .ttf, кажется, работает для меня. Может быть, это зависит от версии библиотеки. Пользуюсь версией 2.9
SFML использует FT_LOAD_FORCE_AUTOHINT, и я видел, как другой шейдер шрифтов делает что-то вроде fill = clamp(fill * 2.0, 0.0, 1.0);
wildfiregames.com/forum/index.php?/topic/…. На вашем скриншоте пространство для заполнения результата кажется хорошим ... Возможно, мне что-то не хватает ... Какое значение контура / размер шрифта вы используете?
Хотя это не похоже на проблему кернинга, это просто текстуры, по какой-то причине перекрывающие альфу ...
К вашему сведению, если вы хотите отобразить пример строки со всеми буквами, найдите панграмму, например «Быстрая коричневая лиса перепрыгивает через ленивую собаку».
Вы тестировали этот код? Мне удалось заставить его работать, но символы не выровнены i.imgur.com/HNM82IA.png Использование другого действительного TTF, чем шрифты deja, вылетает.