Как отобразить текст в Юникоде в OpenGL?

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
16 865
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

Ответ принят как подходящий

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

Это не лучшее решение для азиатских языков, где исходное количество символов все еще довольно велико (несколько тысяч глифов), и в любом приложении GL вы, вероятно, будете использовать небольшую их часть. Подход с динамическим кешем глифов лучше.

Baxissimo 22.09.2008 04:41

Вы также должны проверить Библиотека FTGL.

FTGL is a free cross-platform Open Source C++ library that uses Freetype2 to simplify rendering fonts in OpenGL applications. FTGL supports bitmaps, pixmaps, texture maps, outlines, polygon mesh, and extruded polygon rendering modes.

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

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

Привет, я использую бесплатную библиотеку типов, и мне она очень нравится. Теперь, когда я визуализирую текст в виде кривых, производительность становится очень медленной, поэтому я использую метод шрифта для текстуры, чтобы отображать быстрее. Но таким образом я не могу использовать весь шрифт Unicode, так как визуализирую только ограниченное количество глифов. Есть ли какой-нибудь метод, который вы предлагаете в этом случае? В основном меня интересует поддержка английского и греческого языков.

user5141040 10.03.2017 20:24

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

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

Ваша ссылка мертва.

user5141040 10.03.2017 20:23

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

http://quesoglc.sourceforge.net/

Образец текста в Юникоде должен помочь вам начать работу.

Я бы порекомендовал 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;
}

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

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

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

(͡ ° ͜ʖ ͡ °)

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

...he com̡e̶s, ̕h̵i​s un̨ho͞ly radiańcé destro҉ying all enli̍̈́̂̈́ghtenment, HTML tags lea͠ki̧n͘g fr̶ǫm ̡yo​͟ur eye͢s̸ ̛l̕ik͏e liq​uid pain, the song of re̸gular exp​ression parsing will exti​nguish the voices of mor​tal man from the sp​here I can see it can you see ̲͚̖͔̙î̩́t̲͎̩̱͔́̋̀ it is beautiful t​he final snuffing of the lie​s of Man ALL IS LOŚ͖̩͇̗̪̏̈́T ALL I​S LOST the pon̷y he comes he c̶̮omes he comes the ich​or permeates all MY FACE MY FACE ᵒh god no NO NOO̼O​O NΘ stop the an​*̶͑̾̾​̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s ͎a̧͈͖r̽̾̈́͒͑e n​ot rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚​N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ

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

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

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

Юникод поддерживается в строке заголовка. Я только что попробовал это на Mac, и он должен работать и в другом месте. Если у вас есть (скажем) некоторые импортированные данные, включая текстовые метки, а некоторые метки могут содержать только Unicode, вы можете добавить инструмент, который повторяет метку в строке заголовка.

Это не лучшее решение, но сделать его очень просто.

Другие вопросы по теме