18/3/2021 Наконец-то была закончена интеграция инструмента msdf-bmfont-xml для библиотеки openglobus. Текстовые метки стали выглядеть гораздо красивее! Мне помог инструмент msdf-bmfont-xml для создания атласов шрифтов и рендеринга текстур для (multichannel signed distance fields) MSDF.
![Пример по ссылке: https://openglobus.org/examples/fonts/fonts.html Пример по ссылке: https://openglobus.org/examples/fonts/fonts.html](https://habrastorage.org/getpro/habr/upload_files/7e6/fb2/870/7e6fb28707105ef2bdb83139ab753f8d.png)
msdf-bmfont-xml предлагает широкие возможности для формирования текстурных атласов из шрифтов в формате ttf. В нашем случае, текстура атласа представляет из себя многоканальную карту расстояний (multichannel signed distance fields), которая позволяет отображать острые углы букв, в отличии от оригинального signed distance field.
В этой статье я хочу рассказать, что из себя представляет атлас для хранения букв, и как отображать текст при помощи WebGL.
Текстурный атлас msdf-bmfont-xml
Текстурный атлас шрифта, это текстура с сохраненными на ней картинками символов. Каждая картинка имеет соответствующие текстурные координаты.
![Текстура 512x512 для атласа шрифта Roboto-Regular Текстура 512x512 для атласа шрифта Roboto-Regular](https://habrastorage.org/getpro/habr/upload_files/275/a31/adc/275a31adcf25a054e1ac3e00ffb735c5.png)
Для создания атласа я использую команду:
msdf-bmfont.cmd - reuse -i .\charset.txt -m 512,512 -f json -o %1.png -s 32 -r 8 -p 1 -t msdf %1
где %1 — имя файла шрифта в формате ttf, charset.txt — это файл с набором символов для которых строится атлас, про остальные параметры можно узнать на официальной страничке репозитория msdf-bmfont-xml.
При успешном выполнении команды создаются несколько файлов, нас будут интересовать текстура атласа в формате png и описание атласа в формате json.
В полученном файле описания json информация по символам хранится в разделе chars. Например, символ ‘q’ на картинке атласа расположен в координатах x = 131, y = 356, width = 22, height = 32. т.е. координаты левого правого угла [131, 356] и правого нижнего соответственно [131+22, 356+32]. Таким образом, если размер текстуры атласа равен 512 на 512 пикселей, значит текстурные координат символа ‘q’ соответственно будут равны [131/512, 356/512 ] и [153/512, 388/512]. Если передать эти текстурные координаты в шейдер, который рисует прямоугольник, то мы увидим в этом прямоугольнике наш символ. Кроме того, у нас имеется ширина и высота символа, согласно этим данным мы устанавливаем размер прямоугольника, чтобы символ выглядел пропорционально правильным.
{
id: 113,
char: "q",
width: 22,
height: 32,
xoffset: -3,
yoffset: 11,
xadvance: 18,
x: 131,
y: 356,
...
}
Другими важными параметрами для позиционирования символа являются:
xoffsеt — Смещение символа по горизонтали
yoffset — Смещение символа по вертикали
xadvance — Ширина символа; расстояние от левой границы символа до начала следующего символа в строке.
А также id символа по которому можно идентифицировать символ в таблице горизонтальных кернингов. Кернинг — это расстояние между двумя специфическими символами.
Пример: изображения строки “Wg!” шрифт Arial
Параметры символов:
W: width: 37, height: 31, xoffset: -4, yoffset: 4, xadvance: 30
g: width: 23, height: 32, xoffset: -3, yoffset: 10, xadvance: 18
! : width: 11, height: 31, xoffset: -1, yoffset: 4, xadvance: 9
Центром координат является левый верхний угол, изначально символы располагаются на одной прямой относительно верхней зеленой линии по вертикали и относительно левой зеленой линии по горизонтали, и имеют смещение относительно друг друга согласно параметрам width.
![](https://habrastorage.org/getpro/habr/upload_files/4bc/c88/6e4/4bcc886e4ca6d12d8d45e49812cb7996.png)
Следующая картинка показывает смещение символов относительно горизонтальной (центральной) оси по вертикали, параметр yoffsset.
![](https://habrastorage.org/getpro/habr/upload_files/0b9/6e6/b8b/0b96e6b8b2748b765851a1c684e7d3af.png)
Голубыми линиями обозначен параметр xadvance (расстояние до следующего символа), также каждый символ смещен по горизонтали согласно параметру xoffset.
![](https://habrastorage.org/getpro/habr/upload_files/9d8/83e/a5c/9d883ea5c195dbebb9a5f07dbf55b874.png)
Текущий вариант можно считать готовым, однако для лучшего качества следует применить параметры для кернинга, т.е. смещение относительно друг друга двух специфических символов. В этом примере я не показываю кернинги; кернинг учитывается в параметре xoffset во время отображения текста.
Рендеринг текста MSDF
Отрисовка массива вершин символа производится методом gl.drawArrays, где исходным буфером является буфер массива вершин для атрибута a_vertices:
vec2 a_vertices = [0, 0, 0, -1, 1, -1, 1, -1, 1, 0, 0, 0]
Основные параметры символов предварительно нормализуются при построении атласа шрифта:
imageSize = 512; //размер текстуры атласа
nWidth = width / imageSize; //нормализованная ширина символа
nHeight = height / imageSize; //нормализованная высота глифа
nAdvance = xadvance / imageSize; //нормализованный размер глифа до следующего символа в строке
nXOffset = xoffset / imageSize; //нормализованное смещение по горизонтали
nYOffset = 1.0 - yoffset / imageSize; //нормализованное смещение по вертикали
Шейдер GLSL
Исходник: https://github.com/openglobus/openglobus/blob/master/src/og/shaders/label.js
// Vertex shader:
// ...
vec2 v = screenPos + (a_vertices * a_gliphParam.xy + a_gliphParam.zw + vec2(advanceOffset, 0.0)) * a_size;
// Где:
// screenPos - экранные координаты строки
// a_vertices - Координаты вершин
// a_gliphParam - вектор с метриками символа, где:
// x - nWidth, y - nHeight, z - nXOffset, w - nYOffset
// advanceOffset - сумарное смещение по параметру nAdvance, каждого последующего символа в строке
// a_size - экранные размеры строки в пикселях
// Fragment shader:
// ...
const float imageSize = 512.0;
const float distanceRange = 8.0;
layout(location = 0) out vec4 outScreen;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
float getDistance() {
vec3 msdf = texture(fontTexture, v_uv).rgb;
return median(msdf.r, msdf.g, msdf.b);
}
void main () {
vec2 dxdy = fwidth(v_uv) * vec2(imageSize);
float dist = getDistance() + min(v_weight, 0.5 – 1.0 / DIST_RANGE) - 0.5;
float opacity = clamp(dist * distanceRange / length(dxdy) + 0.5, 0.0, 1.0);
outScreen = vec4(v_rgba.rgb, opacity * v_rgba.a);
}
// Где:
// fontTexture - текстура атласа шрифтов
// v_weight - ширина символа от 0 до 1, используется для окантовки, по умолчанию равен 0.
// Окантовка рисуется ПЕРВЫМ проходом с заданным v_weight.
// v_uv - текстурные координаты символа в атласе шрифтов
// v_rgba - цвет символа, или окантовки
// ...
Надеюсь, что я достаточно понятно объяснил, как работать с атласами шрифтов и как я использую msdf-bmfont-xml для своего проекта. Этот подход существенно улучшил качество текстовых меток на карте.
![Пример редактора планировщика маршрута БПЛА компании Microavia c использованием библиотеки Openglobus Пример редактора планировщика маршрута БПЛА компании Microavia c использованием библиотеки Openglobus](https://habrastorage.org/getpro/habr/upload_files/6a4/cd3/33b/6a4cd333b67018054b6c469ad2a7520f.png)
Пишите в комментариях, чем вы пользуетесь, для рендеринга шрифтов, и как на ваш взгляд можно улучшить качество текстовых меток?
Если у вас возникнут вопросы по применению моей рекомендации можете задать их на openglobus форуме https://groups.google.com/forum/#!forum/openglobus, и я обязательно отвечу!
Желаю Вам хорошего настроения!
raamid
Очень интересно, спасибо. Сам недавно решал похожую задачу, использовал canvas текстуру в THREE.js. Это когда обычный html canvas элемент превращается в текстуру и далее передается в шейдер.
Вот здесь есть пример, где все очень просто и ничего лишнего.
https://discourse.threejs.org/t/an-example-of-text-to-canvas-to-texture-to-material-to-mesh-not-too-difficult/13757
mgevlich Автор
Выриант с канвасом мне не подходил, по той простой причине, что в моем случае на карте может отображаться скажем 1000 текстовых меток, которые динамически меняют свои значения, в том числе масштабируются и крутятся в зависимости от расстояния до них. Для статики канвас(билборды), вполне годится я думаю, однако если хочется более качественный шрифт, а меток не сильно много, то еще можно div с текстом проецировать прямо на экран. Этот способ, конечно более затратный.