Урок 11. 2D Текст


В этом уроке мы будем учиться выводить 2D текст поверх 3D картинки. Для примера, это будет просто таймер:

API

Будем реализовывать следующий интерфейс(который объявлен в common\text2D.h):
void initText2D(const char * texturePath);
void printText2D(const char * text, int x, int y, int size);
void cleanupText2D();

Для того, чтобы наш код работал на любых разрешениях, X и Y будут у нас в [0-800][0-600], а уже вершинный шейдер будет адаптировать все до необходимого размера на экране.
Полную имплементацию можете найти в файле common/text2D.cpp.

Текстура

initText2D просто читает текстуру и необходимые для шрифта шейдеры. В самой загрузке нет ничего особенного, а вот в текстуре есть:

Эта текстура была сгенерирована с помощью утилиты CBFG. Это одна из многих утилит для генерации текстур из шрифтов. Я её открыл в Paint.Net и залил прозрачные участки красным цветом для того, чтобы вам было видно, что будет прозрачным, а что нет.
Целью функции printText2D есть генерация прямоугольников по необходимому положению и с необходимыми текстурными координатами.

Рисование

Нам нужно заполнить эти буферы:
std::vector<glm::vec2> vertices;
std::vector<glm::vec2> UVs;

Для каждого символа мы рассчитываем координаты четырех вершин которые будут определять прямоугольник этого символа, но так как с прямоугольниками OpenGL не работает, то добавляем два треугольника:

for ( unsigned int i=0 ; i<length ; i++ ){
    glm::vec2 vertex_up_left    = glm::vec2( x+i*size, y+size );
    glm::vec2 vertex_up_right   = glm::vec2( x+i*size+size, y+size );
    glm::vec2 vertex_down_right = glm::vec2( x+i*size+size, y);
    glm::vec2 vertex_down_left  = glm::vec2( x+i*size, y);
    vertices.push_back(vertex_up_left   );
    vertices.push_back(vertex_down_left );
    vertices.push_back(vertex_up_right  );
                                                                   
    vertices.push_back(vertex_down_right);
    vertices.push_back(vertex_up_right);
    vertices.push_back(vertex_down_left);

Теперь рассчитываем UV координаты. Левая верхняя координата каждого символа рассчитывается так:
char character = text[i];
float uv_x = (character%16)/16.0f;
float uv_y = (character/16)/16.0f;

Это работает, так как в таблице ASCII кодов у символа А код 65.
65%16 = 1, поэтому А находится в столбце 1(начало в 0).

65/16 = 4, поэтому А на строчке 4(это у нас целочисленное деление, поэтому тут не будет 4, 0625)
Результирующее значение делится на 16.0 чтобы впихнуть его в интервал [0.0 – 1.0] для UV координат.

А теперь нужно сделать почти то же, но для вершин:
glm::vec2 uv_up_left    = glm::vec2( uv_x, 1.0f - uv_y );
glm::vec2 uv_up_right   = glm::vec2( uv_x+1.0f/16.0f, 1.0f-uv_y);
glm::vec2 uv_down_right = glm::vec2( uv_x+1.0f/16.0f, 1.0f-(uv_y + 1.0f/16.0f));
glm::vec2 uv_down_left  = glm::vec2( uv_x, 1.0f - (uv_y + 1.0f/16.0f));
UVs.push_back(uv_up_left   );
UVs.push_back(uv_down_left );
UVs.push_back(uv_up_right  );
UVs.push_back(uv_down_right);
UVs.push_back(uv_up_right);
UVs.push_back(uv_down_left);
}

Все остальное как всегда, забиндить буфера, заполнить их, выставить шейдер, забиндить текстуру, включить/забиндить/сконфигурировать атрибуты вершин, включить блендинг и вызвать glDrawArrays. И Вуаля! Мы все сделали.

Важное замечание – координаты генерируются в интервале [0,800][0,600]. Другими словами, нам не нужны никакие матрицы тут. Вершинный шейдер должен просто выставить его в интервал [-1,1][-1,1] выполнив простые арифметические действия(но при желании их можно сделать в С++):
void main(){
// Результирующая позиция в пространстве отсечения
// нужно преобразовать [0..800][0..600] в [-1..1][-1..1]
vec2 vertexPosition_homoneneousspace = vertexPosition_screenspace - vec2(400,300); // [0..800][0..600] -> [-400..400][-300..300]
vertexPosition_homoneneousspace /= vec2(400,300);
gl_Position =  vec4(vertexPosition_homoneneousspace,0,1);
// UV текстурные координаты. Никаких особых преобразований тут не нужно.
UV = vertexUV;
}

Фрагментный шейдер тоже не делает ничего выдающегося:

void main(){
color = texture( myTextureSampler, UV );
}

Кстати, не используйте этот код в продакшене, так как он может работать лишь с латинским алфавитом. Или не продавайте ничего в Индию, Китай, Японию(даже в Германию, так как там, например, есть буква ß которой нет у нас в текстуре шрифта). Будьте осторожными при применении готовых библиотек по выводу текста в OpenGL, так как большинство из них используют OpenGL 2. К сожалению я не могу вам посоветовать ни одной хорошей библиотеки которая бы хорошо работала с Юникодом.


Комментариев нет:

Отправить комментарий