Контурные шрифты с верным шрифтом и ядром opengl (3.3) - PullRequest
0 голосов
/ 30 августа 2018

Я использовал пример рендеринга текста с 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, и я не знаком с понятием лица, глифа и штриховщика, и я не уверен, что мой код шейдера также адекватен.

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

То, что вы хотите сделать, не так просто. Первое неправильное понимание состоит в том, что 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;
}

Создайте временный буфер с 2 цветными каналами и скопируйте растровое изображение схемы во 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 );

Создание «заполненного» растрового изображения:

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 из репозитория, который вы указали в комментариях:

text

0 голосов
/ 30 августа 2018

Самый простой способ сделать это - нарисовать каждую букву во второй раз с некоторым масштабированием и другим цветом в одной и той же позиции. Просто убедитесь, что вы нарисовали их в правильном порядке (сначала в увеличенном, а затем в обычном порядке)

...