2015-06-24 128 views
3

這真是太遺憾了,我從來沒有在我的應用程序中從未獲得過堅實的字體渲染系統。所以我決定結束這個。實現距離場字體渲染

雖然我在尋找字體渲染的例子,但我碰巧進入了這個每個人都稱讚的valve paper

但是我缺乏經驗和知識來完成這個。

這裏是什麼,我做了總結:

首先,使用對FreeType2庫,我裝一個字符(「A」)到存儲爲位圖。

unsigned char u8Character = 'A'; 
int32_t u32Character = 0; 
utf8::utf8to32(&u8Character, &u8Character + 1, &u32Character); 
FT_Set_Char_Size(face, 0, 64 * 64, 300, 300);  
FT_Load_Char(face, u32Character, FT_LOAD_RENDER); 

其次,我計算距離場從位圖來生成浮點數地圖的每個像素切比雪夫距離。

注意:老實說,我不知道該怎麼做。下面的算法是完整的猜測。

template <typename input_type, typename output_type> 
output_type chebyshev_distance(input_type from_x 
           , input_type from_y 
           , input_type to_x 
           , input_type to_y) 
{ 
    input_type dx = std::abs(to_x - from_x); 
    input_type dy = std::abs(to_y - from_y); 
    return static_cast<output_type>(dx > dy ? dx : dy); 
} 
void GenerateSigendDistanceFieldFrom(const unsigned char* inputBuffer 
            , int width 
            , int height 
            , float* outputBuffer 
            , bool normalize = false) 
{ 
    for (int iy = 0; iy < height; ++iy) 
    { 
     for (int ix = 0; ix < width; ++ix) 
     { 
      int index = iy*width + ix; 
      unsigned char value = inputBuffer[index]; 
      int indexMax = width*height; 
      int indexMin = 0; 
      int far = width > height ? width : height; 
      bool found = false; 
      for (int distance = 1; distance < far; ++distance) 
      { 
       int xmin = (ix - distance) >= 0 ? ix - distance : 0; 
       int ymin = (iy - distance) >= 0 ? iy - distance : 0; 
       int xmax = (ix + distance) < width ? ix + distance+1 : width; 
       int ymax = (iy + distance) < height ? iy + distance+1 : height; 
       int x = xmin; 
       int y = ymin; 
       auto fCompareAndFill = [&]() -> bool 
       { 
        if (value != inputBuffer[y*width + x]) 
        { 
         outputBuffer[index] = chebyshev_distance<int, float>(ix, iy, x, y); 
         if (value < 0xff/2) outputBuffer[index] *= -1; 
         //outputBuffer[index] = distance; 
         return true; 
        } 
        return false; 
       }; 
       while (x < xmax) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        ++x; 
       } 
       --x; 
       if (found == true){ break; } 
       while (y < ymax) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        ++y; 
       } 
       --y; 
       if (found == true){ break; } 
       while (x >= xmin) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        --x; 
       } 
       ++x; 
       if (found == true){ break; } 
       while (y >= ymin) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        --y; 
       } 
       if (found == true){ break; } 

      } // for(int distance = 1; distance < far; ++distance) 

     } // for(int ix = 0; ix < width; ++ix) 

    } // for(int iy = 0; iy < height; ++iy) 

    if(normalize) 
    { 
     float min = outputBuffer[0]; 
     float max = outputBuffer[0]; 
     for(int i = 0; i < width*height; ++i) 
     { 
      if(outputBuffer[i] < min) 
       min = outputBuffer[i]; 
      if(outputBuffer[i] > max) 
       max = outputBuffer[i]; 
     } 
     float denominator = (max - min); 
     float newMin = min/denominator; 
     for(int i = 0; i < width*height; ++i) 
     { 
      outputBuffer[i] /= denominator; 
      outputBuffer[i] -= newMin; 
     } 
    } 

} // GenerateSigendDistanceFieldFrom 

第三,作爲一個測試,我將這個字符的紋理渲染到800x600的整個屏幕上,看看它們是如何展開的。紋理採樣「GL_LINEAR」結果很糟糕。

!1!將Alpha渲染爲紅色值。

const GLchar fssource[] = 
    "#version 440 \n" 
    "out vec4 v_color;" 
    "in vec2 v_uv;" 
    "uniform sampler2D u_texture;" 
    "void main()" 
    "{" 
    " float mask = texture(u_texture, v_uv).a;" 
    " v_color = vec4(mask,0,0,1);" 
    "}" 
; 

enter image description here

!2!渲染文本。閾值用於α-是0.5

const GLchar fssource[] = 
    "#version 440 \n" 
    "out vec4 v_color;" 
    "in vec2 v_uv;" 
    "uniform sampler2D u_texture;" 
    "void main()" 
    "{" 
    " vec4 result = vec4(1,1,1,1);" 
    " float mask = texture(u_texture, v_uv).a;" 
    " if(mask >= 0.5) { result.a = 1; }\n" 
    " else { result.a = 0; }\n" 
    " v_color = result;" 
    "}" 

enter image description here

!3!渲染文本。閾值用於α-是0.7

const GLchar fssource[] = 
    "#version 440 \n" 
    "out vec4 v_color;" 
    "in vec2 v_uv;" 
    "uniform sampler2D u_texture;" 
    "void main()" 
    "{" 
    " vec4 result = vec4(1,1,1,1);" 
    " float mask = texture(u_texture, v_uv).a;" 
    " if(mask >= 0.7) { result.a = 1; }\n" 
    " else { result.a = 0; }\n" 
    " v_color = result;" 
    "}" 

enter image description here

的字體看起來顛簸,顯然距離場太亮。算法應該用0.5閾值工作。 不僅結果不正確,距離場的產生花費太多時間。因此,我無法使用高分辨率圖像作爲輸入。

在這裏,我正在做一些明顯錯誤的事情,但似乎我自己想出如何生成正確的結果。

但是,請幫助我,如果你知道我做錯了什麼。

下面是整個源文件:

#include <iostream> 
#include <iomanip> 
#include <algorithm> 
#include <fstream> 
#include <vector> 
#include <cstdint> 
#include <climits> 
#include "ft2build.h" 
#include FT_FREETYPE_H 
#include "../utf8_v2_3_4/Source/utf8.h" 
#include <SDL.h> 
#include "../Glew/glew.h" 
#include <gl/GL.h> 
#undef main 
template <typename input_type, typename output_type> 
output_type chebyshev_distance(input_type from_x 
           , input_type from_y 
           , input_type to_x 
           , input_type to_y) 
{ 
    input_type dx = std::abs(to_x - from_x); 
    input_type dy = std::abs(to_y - from_y); 
    return static_cast<output_type>(dx > dy ? dx : dy); 
} 
void GenerateSigendDistanceFieldFrom(const unsigned char* inputBuffer 
            , int width 
            , int height 
            , float* outputBuffer 
            , bool normalize = false) 
{ 
    for (int iy = 0; iy < height; ++iy) 
    { 
     for (int ix = 0; ix < width; ++ix) 
     { 
      int index = iy*width + ix; 
      unsigned char value = inputBuffer[index]; 
      int indexMax = width*height; 
      int indexMin = 0; 
      int far = width > height ? width : height; 
      bool found = false; 
      for (int distance = 1; distance < far; ++distance) 
      { 
       int xmin = (ix - distance) >= 0 ? ix - distance : 0; 
       int ymin = (iy - distance) >= 0 ? iy - distance : 0; 
       int xmax = (ix + distance) < width ? ix + distance+1 : width; 
       int ymax = (iy + distance) < height ? iy + distance+1 : height; 
       int x = xmin; 
       int y = ymin; 
       auto fCompareAndFill = [&]() -> bool 
       { 
        if (value != inputBuffer[y*width + x]) 
        { 
         outputBuffer[index] = chebyshev_distance<int, float>(ix, iy, x, y); 
         if (value < 0xff/2) outputBuffer[index] *= -1; 
         //outputBuffer[index] = distance; 
         return true; 
        } 
        return false; 
       }; 
       while (x < xmax) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        ++x; 
       } 
       --x; 
       if (found == true){ break; } 
       while (y < ymax) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        ++y; 
       } 
       --y; 
       if (found == true){ break; } 
       while (x >= xmin) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        --x; 
       } 
       ++x; 
       if (found == true){ break; } 
       while (y >= ymin) 
       { 
        if (fCompareAndFill()) 
        { 
         found = true; 
         break; 
        } 
        --y; 
       } 
       if (found == true){ break; } 

      } // for(int distance = 1; distance < far; ++distance) 

     } // for(int ix = 0; ix < width; ++ix) 

    } // for(int iy = 0; iy < height; ++iy) 

    if(normalize) 
    { 
     float min = outputBuffer[0]; 
     float max = outputBuffer[0]; 
     for(int i = 0; i < width*height; ++i) 
     { 
      if(outputBuffer[i] < min) 
       min = outputBuffer[i]; 
      if(outputBuffer[i] > max) 
       max = outputBuffer[i]; 
     } 
     float denominator = (max - min); 
     float newMin = min/denominator; 
     for(int i = 0; i < width*height; ++i) 
     { 
      outputBuffer[i] /= denominator; 
      outputBuffer[i] -= newMin; 
     } 
    } 

} // GenerateSigendDistanceFieldFrom 


namespace 
{ 
    SDL_Window* window = NULL; 
    SDL_Surface* screenSurface = NULL; 
    FT_Library freetype; 
    FT_Face face; 
    SDL_GLContext glContext; 
    GLuint glProgram = 0; 
    GLuint vbo = 0; 
    GLuint vao = 0; 
    GLuint glTexture = 0; 
} 

GLuint MakeShader(GLenum shaderType, const char* source, int slen) 
{ 
    auto shader = glCreateShader(shaderType); 
    glShaderSource(shader, 1, (const GLchar**)&source, &slen); 
    glCompileShader(shader); 
    GLint success; 
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success); 
    if (success == GL_FALSE) 
    { 
     std::vector<GLchar> glInfoLogBuffer; 
     int len; 
     glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); 
     glInfoLogBuffer.resize(len + 1); 
     GLsizei outlen; 
     glGetShaderInfoLog(shader, glInfoLogBuffer.size(), &outlen, glInfoLogBuffer.data()); 
     glInfoLogBuffer.back() = 0; 
     std::cout << glInfoLogBuffer.data() << std::endl; 
     return 0; 
    } 
    return shader; 
} 

GLuint MakeProgram(GLuint vshader, GLuint fshader) 
{ 
    auto program = glCreateProgram(); 
    glAttachShader(program, vshader); 
    glAttachShader(program, fshader); 
    glLinkProgram(program); 
    GLint success; 
    glGetProgramiv(program, GL_LINK_STATUS, &success); 
    if(success == GL_FALSE) 
    { 
     int len; 
     glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); 
     std::vector<GLchar> buffer; 
     buffer.resize(len+1); 
     buffer.back() = 0; 
     glGetProgramInfoLog(program, buffer.size(), &len, buffer.data()); 
     std::cout << buffer.data() << std::endl; 
     return 0; 
    } 
    return program; 
} 

int Initialize() 
{ 
    if (SDL_Init(SDL_INIT_VIDEO) < 0) 
    { 
     std::cout << "SDL could not initialize!"; 
     return -1; 
    } 

    window = SDL_CreateWindow("My Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); 
    if (!window) 
    { 
     std::cout << "Window could not be created!"; 
     return -1; 
    } 
    screenSurface = SDL_GetWindowSurface(window); 
    SDL_FillRect(screenSurface, 0, SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF)); 
    SDL_UpdateWindowSurface(window); 


    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); 
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 

    glContext = SDL_GL_CreateContext(window); 

    SDL_GL_SetSwapInterval(1); 

    GLenum glError = glewInit(); 
    if(glError != GLEW_OK) 
    { 
     std::cout << "Failed to initialize glew" << std::endl; 
     return -1; 
    } 

    // ---------------------- 
    // Vertex Shader 
    const GLchar vssource[] = 
     "#version 440 \n" 
     "layout(location=0) in vec3 a_position;" 
     "layout(location=1) in vec2 a_uv;" 
     "out vec2 v_uv;" 
     "void main()" 
     "{" 
     " gl_Position = vec4(a_position,1);" 
     " v_uv = a_uv;" 
     "}\n" 
    ; 
    auto vshader = MakeShader(GL_VERTEX_SHADER, vssource, _countof(vssource)); 
    // -------------------- 
    // Fragment Shader 
    //const GLchar fssource[] = 
    // "#version 440 \n" 
    // "out vec4 v_color;" 
    // "in vec2 v_uv;" 
    // "uniform sampler2D u_texture;" 
    // "void main()" 
    // "{" 
    // " float mask = texture(u_texture, v_uv).a;" 
    // " v_color = vec4(mask,0,0,1);" 
    // "}" 
    //; 
    const GLchar fssource[] = 
     "#version 440 \n" 
     "out vec4 v_color;" 
     "in vec2 v_uv;" 
     "uniform sampler2D u_texture;" 
     "void main()" 
     "{" 
     " vec4 result = vec4(1,1,1,1);" 
     " float mask = texture(u_texture, v_uv).a;" 
     " if(mask >= 0.7) { result.a = 1; }\n" 
     " else { result.a = 0; }\n" 
     " v_color = result;" 
     "}" 
    ; 
    auto fshader = MakeShader(GL_FRAGMENT_SHADER, fssource, _countof(fssource)); 

    // -------------------- 
    // Shader Program 
    glProgram = MakeProgram(vshader, fshader); 

    // -------------------- 
    // Vertex Buffer Object 
    float vb[] = 
    { 
     -1, -1, 0, 
     1, -1, 0, 
     -1, 1, 0, 

     1, -1, 0, 
     1, 1, 0, 
     -1, 1, 0, 

     0, 0, 
     1, 0, 
     0, 1, 

     1, 0, 
     1, 1, 
     0, 1, 
    }; 
    glGenBuffers(1, &vbo); 
    glBindBuffer(GL_ARRAY_BUFFER, vbo); 
    glBufferData(GL_ARRAY_BUFFER, sizeof(vb), vb, GL_STATIC_DRAW); 
    glBindBuffer(GL_ARRAY_BUFFER, 0); 

    // -------------------- 
    // Vertex Array Object 
    glGenVertexArrays(1, &vao); 
    glBindVertexArray(vao); 
    glEnableVertexAttribArray(0); 
    glEnableVertexAttribArray(1); 
    glBindBuffer(GL_ARRAY_BUFFER, vbo); 
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); 
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)(sizeof(float)*18)); 
    glBindBuffer(GL_ARRAY_BUFFER, 0); 
    glBindVertexArray(0); 

    // 
    // Freetype 
    // 

    FT_Error error = FT_Init_FreeType(&freetype); 
    if (error) 
    { 
     std::cout << "FreeType: error occured with error code: " << error << std::endl; 
    } 
    error = FT_New_Face(freetype, "C:/Windows/Fonts/Arial.ttf", 0, &face); 
    if (error) 
    { 
     std::cout << "FreeType: error occured with error code: " << error << std::endl; 
    } 
    error = FT_Set_Char_Size(face, 0, 64 * 64, 300, 300); 
    if (error) 
    { 
     std::cout << "FreeType: error occured with error code: " << error << std::endl; 
    } 

    unsigned char u8Character = 'A'; 
    int32_t u32Character = 0; 
    utf8::utf8to32(&u8Character, &u8Character + 1, &u32Character); 
    error = FT_Load_Char(face, u32Character, FT_LOAD_RENDER); 
    if (error) 
    { 
     std::cout << "FreeType: error occured with error code: " << error << std::endl; 
    } 

    auto bitmap = face->glyph->bitmap; 
    const int width = bitmap.width; 
    const int height = bitmap.rows; 
    const int size = width*height; 
    std::vector<float> outputBuffer; 
    outputBuffer.resize(size); 
    GenerateSigendDistanceFieldFrom(face->glyph->bitmap.buffer, width, height, outputBuffer.data(), true); 

    std::ofstream ofs("testout.txt"); 
    for (int i = 0; i < height; ++i) 
    { 
     for (int j = 0; j < width; ++j) 
     { 
      ofs << bitmap.buffer[i*width + j] << ' '; 
     } 
     ofs << std::endl; 
    } 
    ofs << std::endl; 
    for (int i = 0; i < height; ++i) 
    { 
     for (int j = 0; j < width; ++j) 
     { 
      ofs << std::setw(6) << std::setprecision(2) << std::fixed << outputBuffer[i*width + j]; 
     } 
     ofs << std::endl; 
    } 

    // ---- 


    // -------------------- 
    // Texture 
    std::vector<float> invertY; 
    invertY.reserve(size); 
    for(int i = height-1; i >= 0; --i) 
    { 
     for(int j = 0; j < width; ++j) 
     { 
      invertY.push_back(outputBuffer[i*width+j]); 
     } 
    } 
    glGenTextures(1, &glTexture); 
    glActiveTexture(GL_TEXTURE0); 
    glBindTexture(GL_TEXTURE_2D, glTexture); 
    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_FLOAT, invertY.data()); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
    glGenerateMipmap(GL_TEXTURE_2D); 


    glClearColor(0.5f,0.5f,1.0f,1.0f); 

    return 0; 
} 
void Release() 
{ 
    SDL_DestroyWindow(window); 

    SDL_Quit(); 
} 
int main(int argc, char* argv[]) 
{ 
    Initialize(); 
    bool quit = false; 
    while(!quit) 
    { 
     SDL_Event e; 
     if(SDL_PollEvent(&e) != 0) 
     { 
      if (e.type == SDL_QUIT) 
      { 
       quit = true; 
      } 
     } // if(SDL_PollEvent(&e) != 0) 

     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 


     glEnable(GL_BLEND); 
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
     glUseProgram(glProgram); 
     GLint loc = glGetUniformLocation(glProgram, "u_texture"); 
     glUniform1i(loc, 0); 
     glBindVertexArray(vao); 
     glDrawArrays(GL_TRIANGLES, 0, 6); 
     glBindVertexArray(0); 
     glUseProgram(0); 

     SDL_GL_SwapWindow(window); 
    } // while(!quit) 
    Release(); 
    return 0; 
} 

回答

1

如果我理解所有正確地設置該位圖炭尺寸爲64(= 64.64在Freetype的26.6定點型),但這時就需要伸展位圖以一個更大的尺寸,因此縮放。

我建議你將字符大小(FT_Set_Char_Size)設置爲等於或大於最終大小的尺寸。然後剩下的SW應該保持原來的位圖,最終縮小它的大小。這並不意味着質量的損失,而放大(將尺寸爲x的柵格圖像轉變爲更大的尺寸)會將您帶到觀察到的問題。然後爲了縮小比例,任何插值方案都會給你帶來不錯的結果。

+0

感謝您的回覆。的確,使用更高分辨率的輸入有助於獲得更好的視覺效果而不會出現鋸齒。根據freetype教程,FT_Set_Char_Size的大小以像素單位(1/64像素)爲單位,但實際上,我得到的實際寬度,高度範圍在100到200之間...這是另一個問題。但我仍然需要更快的算法來計算距離場來縮放圖像。 – user2883715

0

爲了獲得平滑的邊緣,您需要在邊緣周圍使用smoothstep,也許從0.5 alpha到0.6。 對於需要太多時間的簽名距離字段,您不應該在運行時生成紋理,而應事先生成紋理,然後將其加載並保存所有必要的數據。