Как отобразить текст Unicode в OpenGL? - PullRequest
6 голосов
/ 11 августа 2008

Есть ли хороший способ для отображения текста Unicode в OpenGL под Windows? Например, когда вам приходится иметь дело с разными языками. Наиболее распространенный подход, как

#define FONTLISTRANGE 128
GLuint list;
list = glGenLists(FONTLISTRANGE);
wglUseFontBitmapsW(hDC, 0, FONTLISTRANGE, list);

просто не подойдет, потому что вы не можете создать достаточно списков для всех символов Юникода.

Ответы [ 7 ]

15 голосов
/ 23 августа 2008

Вам также следует проверить библиотеку FTGL .

FTGL - бесплатный кроссплатформенный Open Исходная библиотека C ++, использующая Freetype2 упростить рендеринг шрифтов в OpenGL Приложения. FTGL поддерживает растровые изображения, растровые изображения, карты текстур, контуры, полигональная сетка и экструдированный полигон режимы рендеринга.

Этот проект некоторое время не использовался, но в последнее время находится в стадии разработки. Я не обновил свой проект для использования последней версии, но вы должны проверить его.

Позволяет использовать любой шрифт True Type через библиотеку шрифтов FreeType .

6 голосов
/ 16 сентября 2008

Я рекомендую прочитать это Руководство по шрифту OpenGL . Это для языка программирования D, но это хорошее введение в различные вопросы, связанные с реализацией системы кеширования глифов для рендеринга текста с помощью OpenGL. Учебное пособие посвящено методам соответствия Unicode, сглаживания и кернинга.

D довольно понятен любому, кто знает C ++, и большая часть статьи посвящена общим методам, а не языку реализации.

3 голосов
/ 26 января 2009

Я бы порекомендовал FTGL, как уже рекомендовалось выше, однако я сам реализовал рендерер freetype / OpenGL и подумал, что вам может пригодиться код, если вы захотите заново изобрести это колесо самостоятельно. Я бы действительно порекомендовал FTGL, так как его использование намного проще. :)

* glTextRender class by Semi Essessi
 *
 * FreeType2 empowered text renderer
 *
 */

#include "glTextRender.h"
#include "jEngine.h"

#include "glSystem.h"

#include "jMath.h"
#include "jProfiler.h"
#include "log.h"

#include <windows.h>

FT_Library glTextRender::ftLib = 0;

//TODO::maybe fix this so it use wchar_t for the filename
glTextRender::glTextRender(jEngine* j, const char* fontName, int size = 12)
{
#ifdef _DEBUG
    jProfiler profiler = jProfiler(L"glTextRender::glTextRender");
#endif
    char fontName2[1024];
    memset(fontName2,0,sizeof(char)*1024);
    sprintf(fontName2,"fonts\\%s",fontName);

    if(!ftLib)
    {
#ifdef _DEBUG
        wchar_t fn[128];
        mbstowcs(fn,fontName,strlen(fontName)+1);
        LogWriteLine(L"\x25CB\x25CB\x25CF Font: %s was requested before FreeType was initialised", fn);
#endif
        return;
    }

    // constructor code for glTextRender
    e=j;

    gl = j->gl;

    red=green=blue=alpha=1.0f;

    face = 0;

    // remember that for some weird reason below font size 7 everything gets scrambled up
    height = max(6,(int)floorf((float)size*((float)gl->getHeight())*0.001666667f));
    aHeight = ((float)height)/((float)gl->getHeight());

    setPosition(0.0f,0.0f);

    // look in base fonts dir
    if(FT_New_Face(ftLib, fontName2, 0, &face ))
    {
        // if we dont have it look in windows fonts dir
        char buf[1024];
        GetWindowsDirectoryA(buf,1024);
        strcat(buf, "\\fonts\\");
        strcat(buf, fontName);

        if(FT_New_Face(ftLib, buf, 0, &face ))
        {
            //TODO::check in mod fonts directory
#ifdef _DEBUG
            wchar_t fn[128];
            mbstowcs(fn,fontName,strlen(fontName)+1);
            LogWriteLine(L"\x25CB\x25CB\x25CF Request for font: %s has failed", fn);
#endif
            face = 0;
            return;
        }
    }

    // FreeType uses 64x size and 72dpi for default
    // doubling size for ms 
    FT_Set_Char_Size(face, mulPow2(height,7), mulPow2(height,7), 96, 96);

    // set up cache table and then generate the first 256 chars and the console prompt character
    for(int i=0;i<65536;i++) 
    {
        cached[i]=false;
        width[i]=0.0f;
    }

    for(unsigned short i = 0; i < 256; i++) getChar((wchar_t)i);
    getChar(CHAR_PROMPT);

#ifdef _DEBUG
    wchar_t fn[128];
    mbstowcs(fn,fontName,strlen(fontName)+1);
    LogWriteLine(L"\x25CB\x25CB\x25CF Font: %s loaded OK", fn);
#endif
}

glTextRender::~glTextRender()
{
    // destructor code for glTextRender
    for(int i=0;i<65536;i++)
    {
        if(cached[i])
        {
            glDeleteLists(listID[i],1);
            glDeleteTextures(1,&(texID[i]));
        }
    }

    // TODO:: work out stupid freetype crashz0rs
    try
    {
        static int foo = 0;
        if(face && foo < 1)
        {
            foo++;
            FT_Done_Face(face);
            face = 0;
        }
    }
    catch(...)
    {
        face = 0;
    }
}


// return true if init works, or if already initialised
bool glTextRender::initFreeType()
{
    if(!ftLib)
    {
        if(!FT_Init_FreeType(&ftLib)) return true;
        else return false;
    } else return true;
}

void glTextRender::shutdownFreeType()
{
    if(ftLib)
    {
        FT_Done_FreeType(ftLib);
        ftLib = 0;
    }
}

void glTextRender::print(const wchar_t* str)
{
    // store old stuff to set start position
    glPushAttrib(GL_TRANSFORM_BIT);
    // get viewport size
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);
    glPopAttrib();

    float color[4];
    glGetFloatv(GL_CURRENT_COLOR, color);

    glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT  | GL_ENABLE_BIT | GL_TRANSFORM_BIT); 
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    glEnable(GL_TEXTURE_2D);
    //glDisable(GL_DEPTH_TEST);

    // set blending for AA
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glTranslatef(xPos,yPos,0.0f);

    glColor4f(red,green,blue,alpha);

    // call display lists to render text
    glListBase(0u);
    for(unsigned int i=0;i<wcslen(str);i++) glCallList(getChar(str[i]));

    // restore old states
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glPopAttrib();

    glColor4fv(color);

    glPushAttrib(GL_TRANSFORM_BIT);
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glPopAttrib();
}

void glTextRender::printf(const wchar_t* str, ...)
{
    if(!str) return;

    wchar_t*    buf = 0;
    va_list  parg;
    va_start(parg, str);

    // allocate buffer
    int len = (_vscwprintf(str, parg)+1);
    buf = new wchar_t[len];
    if(!buf) return;
    vswprintf(buf, str, parg);
    va_end(parg);

    print(buf);

    delete[] buf;
}

GLuint glTextRender::getChar(const wchar_t c)
{
    int i = (int)c;

    if(cached[i]) return listID[i];

    // load glyph and get bitmap
    if(FT_Load_Glyph(face, FT_Get_Char_Index(face, i), FT_LOAD_DEFAULT )) return 0;

    FT_Glyph glyph;
    if(FT_Get_Glyph(face->glyph, &glyph)) return 0;

    FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);

    FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyph;
    FT_Bitmap& bitmap = bitmapGlyph->bitmap;

    int w = roundPow2(bitmap.width);
    int h = roundPow2(bitmap.rows);

    // convert to texture in memory
    GLubyte* texture = new GLubyte[2*w*h];

    for(int j=0;j<h;j++)
    {
        bool cond = j>=bitmap.rows;

        for(int k=0;k<w;k++)
        {
                texture[2*(k+j*w)] = 0xFFu;
                texture[2*(k+j*w)+1] = ((k>=bitmap.width)||cond) ? 0x0u : bitmap.buffer[k+bitmap.width*j];
        }
    }

    // store char width and adjust max height
    // note .5f
    float ih = 1.0f/((float)gl->getHeight());
    width[i] = ((float)divPow2(face->glyph->advance.x, 7))*ih;
    aHeight = max(aHeight,(.5f*(float)bitmap.rows)*ih);

    glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT  | GL_ENABLE_BIT | GL_TRANSFORM_BIT);

    // create gl texture
    glGenTextures(1, &(texID[i]));

    glEnable(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, texID[i]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, texture);

    glPopAttrib();

    delete[] texture;

    // create display list
    listID[i] = glGenLists(1);

    glNewList(listID[i], GL_COMPILE);

    glBindTexture(GL_TEXTURE_2D, texID[i]);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    // adjust position to account for texture padding
    glTranslatef(.5f*(float)bitmapGlyph->left, 0.0f, 0.0f);
    glTranslatef(0.0f, .5f*(float)(bitmapGlyph->top-bitmap.rows), 0.0f);

    // work out texcoords
    float tx=((float)bitmap.width)/((float)w);
    float ty=((float)bitmap.rows)/((float)h);

    // render
    // note .5f
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f);
        glVertex2f(0.0f, .5f*(float)bitmap.rows);
        glTexCoord2f(0.0f, ty);
        glVertex2f(0.0f, 0.0f);
        glTexCoord2f(tx, ty);
        glVertex2f(.5f*(float)bitmap.width, 0.0f);
        glTexCoord2f(tx, 0.0f);
        glVertex2f(.5f*(float)bitmap.width, .5f*(float)bitmap.rows);
    glEnd();

    glPopMatrix();

    // move position for the next character
    // note extra div 2
    glTranslatef((float)divPow2(face->glyph->advance.x, 7), 0.0f, 0.0f);

    glEndList();

    // char is succesfully cached for next time
    cached[i] = true;

    return listID[i];
}

void glTextRender::setPosition(float x, float y)
{
    float fac = ((float)gl->getHeight());
    xPos = fac*x+FONT_BORDER_PIXELS; yPos = fac*(1-y)-(float)height-FONT_BORDER_PIXELS;
}

float glTextRender::getAdjustedWidth(const wchar_t* str)
{
    float w = 0.0f;

    for(unsigned int i=0;i<wcslen(str);i++)
    {
        if(cached[str[i]]) w+=width[str[i]];
        else
        {
            getChar(str[i]);
            w+=width[str[i]];
        }
    }

    return w;
}
2 голосов
/ 01 декабря 2013

Вам следует рассмотреть возможность использования библиотеки рендеринга Unicode (например, Pango ) для рендеринга материала в растровое изображение и помещения его в экран или в текстуру.

Рендеринг текста в юникоде не прост. Таким образом, вы не можете просто загрузить 64K прямоугольных глифов и использовать их.

Символы могут перекрываться. Например, в этом смайлике:

(͡ ° ͜ʖ ͡ °)

Некоторые кодовые точки складывают акценты на предыдущем символе. Посмотрите на этот отрывок из этого заметного сообщения :

... он признает, что Единородный радиант уничтожает все enli̍̈́̂̈́ghtenment, HTML-теги lea͠ki̧n͘g fr̶ǫm ̡yo ͟ur eye͢s̸ ̛l̕ik͏e Жидкая боль, песня регулярного разбора вытащить голоса мертальца из зр здесь я вижу это Можете ли вы увидеть ̋̀î̩́t̲͎̩̱͔́̋̀ это прекрасно, что окончательный нюхает ложь человека ВСЕ ЕСТЬ ЛОЖЬ ВСЕ Я ПОТЕРЯЛ пони, он приходит он приходит, он приходит или пронизывает все мое лицо мое лицо НЕТ NOO̼O O NΘ остановить * an ͇̫͛͆̾ͫ̑͆ ͇̫͛͆̾ͫ̑͆g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s Na̧͈͖r̽̾̈́͒͑e n от rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚ N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̹̭̯ͧ̾ͬC̷̙̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔

Если вы действительно хотите правильно отображать Unicode, вы также должны иметь возможность правильно отображать и этот.

ОБНОВЛЕНИЕ: посмотрел на этот двигатель Панго, и это случай банана, гориллы и целых джунглей. Во-первых, это зависит от Glib, потому что он использовал GObjects, во-вторых, он не может визуализироваться напрямую в байтовый буфер. У него есть бэкэнды Cario и FreeType, поэтому вы должны использовать один из них для рендеринга текста и в конечном итоге экспортировать его в растровые изображения. Пока это выглядит не очень хорошо.

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

2 голосов
/ 11 августа 2008

Возможно, вам придется сгенерировать собственный «кэш глифов» в памяти текстур по ходу работы, возможно, с помощью некоторой политики LRU, чтобы избежать разрушения всей памяти текстур. Не так просто, как ваш текущий метод, но может быть единственным способом, учитывая количество символов в юникоде

1 голос
/ 18 сентября 2008

Queso GLC отлично подходит для этого, я использовал его для визуализации китайских и кириллических символов в 3D.

http://quesoglc.sourceforge.net/

Пример текста Unicode, с которым он поставляется, должен помочь вам начать работу.

0 голосов
/ 11 августа 2008

Вы также можете сгруппировать символы по языку. Загружайте каждую языковую таблицу по мере необходимости, а когда вам нужно переключить языки, выгрузите предыдущую языковую таблицу и загрузите новую.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...