Рисование текста в OpenGL ES
-
20-09-2019 - |
Вопрос
В настоящее время я разрабатываю небольшую игру OpenGL для платформы Android, и мне интересно, есть ли простой способ отрисовки текста поверх отрисованного фрейма (например, HUD с оценкой игроков и т.д.).В тексте также должен был бы использоваться пользовательский шрифт.
Я видел пример использования представления в качестве наложения, но я не знаю, хочу ли я это делать, поскольку позже, возможно, захочу портировать игру на другие платформы.
Есть какие-нибудь идеи?
Решение
В Android SDK нет простого способа рисования текста в представлениях OpenGL.Оставляя вам следующие варианты.
- Поместите TextView поверх SurfaceView. Это медленный и плохой, но самый прямой подход.
- Преобразуйте общие строки в текстуры и просто рисуйте эти текстуры. Это, безусловно, самый простой и быстрый, но наименее гибкий вариант.
- Создайте собственный код рендеринга текста на основе спрайта. Вероятно, второй лучший выбор, если 2 не вариант.Хороший способ намочить ноги, но учтите, что, хотя он кажется простым (а базовые функции таковыми), он становится все сложнее и сложнее по мере добавления дополнительных функций (выравнивание текстур, работа с разрывами строк, шрифты переменной ширины и т. д.). ) — если вы выберете этот путь, сделайте его настолько простым, насколько это возможно!
- Используйте готовую библиотеку с открытым исходным кодом. Их несколько, если вы ищете в Google, сложнее всего их интегрировать и запустить.Но, по крайней мере, как только вы это сделаете, у вас появится вся гибкость и зрелость, которые они предоставляют.
Другие советы
Рендеринг текста в текстуру проще, чем то, как это выглядит в демонстрации Sprite Text. Основная идея состоит в том, чтобы использовать класс Canvas для рендеринга в растровое изображение, а затем передать растровое изображение в текстуру OpenGL:
// Create an empty, mutable bitmap
Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_4444);
// get a canvas to paint over the bitmap
Canvas canvas = new Canvas(bitmap);
bitmap.eraseColor(0);
// get a background image from resources
// note the image format must match the bitmap format
Drawable background = context.getResources().getDrawable(R.drawable.background);
background.setBounds(0, 0, 256, 256);
background.draw(canvas); // draw the background to our bitmap
// Draw the text
Paint textPaint = new Paint();
textPaint.setTextSize(32);
textPaint.setAntiAlias(true);
textPaint.setARGB(0xff, 0x00, 0x00, 0x00);
// draw the text centered
canvas.drawText("Hello World", 16,112, textPaint);
//Generate one texture pointer...
gl.glGenTextures(1, textures, 0);
//...and bind it to our array
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
//Create Nearest Filtered Texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
//Use the Android GLUtils to specify a two-dimensional texture image from our bitmap
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
//Clean up
bitmap.recycle();
Я написал книгу Учебник это дополняет ответ, опубликованный Живителя.По сути, он использует ту же идею, но вместо рендеринга каждой строки в текстуру, он рендерит все символы из файла шрифта в текстуру и использует это для обеспечения полного динамического рендеринга текста без дальнейших замедлений (после завершения инициализации).
Основное преимущество моего метода по сравнению с различными генераторами font atlas заключается в том, что вы можете отправлять файлы небольших шрифтов (.ttf .otf) в свой проект вместо того, чтобы отправлять большие растровые изображения для каждого варианта шрифта и размера.Он может генерировать шрифты идеального качества в любом разрешении, используя только файл шрифта :)
Тот Самый Учебник включает полный код, который может быть использован в любом проекте :)
По этой ссылке:
http://code.neenbedankt.com/how-to-render-an-android-view-to-a-bitmap
Вы можете визуализировать любой вид в растровое изображение.Вероятно, стоит предположить, что вы можете расположить представление по своему усмотрению (включая текст, изображения и т. д.), а затем преобразовать его в растровое изображение.
Использование кода JVitela выше вы сможете использовать это растровое изображение в качестве текстуры OpenGL.
Взгляните на CBFG и Android-порт кода загрузки/рендеринга.У вас должна быть возможность добавить код в свой проект и сразу же использовать его.
КБФГ - http://www.codehead.co.uk/cbfg
Андроид-загрузчик - http://www.codehead.co.uk/cbfg/TexFont.java
Я посмотрел на пример текста спрайта, и он выглядит ужасно сложным для такой задачи. Я тоже рассматривал возможность рендеринга в текстуру, но меня беспокоит снижение производительности, которое может вызвать.Вместо этого мне, возможно, придется просто заняться просмотром и беспокоиться о портировании, когда придет время пересечь этот мост :)
Посмотрите на образец «Текст спрайта» в Примеры GLSurfaceView.
ИМХО есть три причины использовать OpenGL ES в игре:
- Избегайте различий между мобильными платформами, используя открытый стандарт;
- Чтобы иметь больше контроля над процессом рендеринга;
- Чтобы получить выгоду от параллельной обработки графического процессора;
Рисование текста всегда является проблемой в игровом дизайне, потому что вы рисуете вещи, поэтому у вас не может быть внешнего вида обычного действия с виджетами и так далее.
Вы можете использовать платформу для создания растровых шрифтов из шрифтов TrueType и их рендеринга.Все фреймворки, которые я видел, работают одинаково:генерировать координаты вершин и текстур для текста во время отрисовки.Это не самое эффективное использование OpenGL.
Лучший способ — выделить удаленные буферы (объекты буфера вершин — VBO) для вершин и текстур на ранних этапах кода, избегая ленивых операций передачи памяти во время отрисовки.
Имейте в виду, что игроки не любят читать текст, поэтому вы не будете писать длинный динамически генерируемый текст.Для меток вы можете использовать статические текстуры, оставляя динамический текст для времени и оценки, и оба они являются числовыми и имеют длину несколько символов.
Итак, мое решение простое:
- Создайте текстуру для общих надписей и предупреждений;
- Создайте текстуру для цифр 0–9, «:», «+» и «-».Одна текстура для каждого персонажа;
- Сгенерируйте удаленные VBO для всех позиций на экране.Я могу отображать статический или динамический текст в этих позициях, но VBO статичны;
- Создайте только одну текстуру VBO, поскольку текст всегда отображается в одном направлении;
- Во время рисования я визуализирую статический текст;
- Для динамического текста я могу посмотреть позицию VBO, получить текстуру персонажа и нарисовать ее по одному символу.
Операции рисования выполняются быстро, если вы используете удаленные статические буферы.
Я создаю XML-файл с позициями экрана (в зависимости от процента диагонали экрана) и текстурами (статическими и символьными), а затем загружаю этот XML перед рендерингом.
Чтобы получить высокий показатель FPS, вам следует избегать создания VBO во время отрисовки.
Если вы настаиваете на использовании GL, вы можете визуализировать текст в текстурах.Предполагая, что большая часть HUD относительно статична, вам не придется слишком часто загружать текстуры в текстурную память.
Взгляните на CBFG
и порт Android для загрузки / рендеринга
кода.Вы должны быть в состоянии поместить код в свой проект и сразу же использовать его
.
У меня есть проблемы с этой реализацией.Он отображает только один символ, когда я пытаюсь изменить размер растрового изображения шрифта (мне нужны специальные буквы), весь отрисовка завершается неудачей:(
Я искал это несколько часов, это была первая статья, на которую я наткнулся, и хотя в ней есть лучший ответ, самые популярные ответы, я думаю, не соответствуют действительности.Конечно, для того, что мне было нужно.Ответы weichsel и shakazed были точными, но немного неясными в статьях.Чтобы познакомить вас с проектом.Здесь:Просто создайте новый проект Android на основе существующего образца.Выберите ApiDemos:
Посмотрите в исходной папке
ApiDemos/src/com/example/android/apis/graphics/spritetext
И вы найдете все, что вам нужно.
Для статический текст:
- Создайте изображение со всеми словами, используемыми на вашем компьютере (например, с помощью GIMP).
- Загрузите это как текстуру и используйте как материал для плоскости.
Для длинный текст который необходимо время от времени обновлять:
- Позвольте Android рисовать на растровом холсте (решение JVitela).
- Загрузите это как материал для самолета.
- Используйте разные координаты текстуры для каждого слова.
Для число (в формате 00.0):
- Создайте изображение со всеми числами и точкой.
- Загрузите это как материал для самолета.
- Используйте шейдер ниже.
В событии onDraw обновляйте только переменную значения, отправленную в шейдер.
precision highp float; precision highp sampler2D; uniform float uTime; uniform float uValue; uniform vec3 iResolution; varying vec4 v_Color; varying vec2 vTextureCoord; uniform sampler2D s_texture; void main() { vec4 fragColor = vec4(1.0, 0.5, 0.2, 0.5); vec2 uv = vTextureCoord; float devisor = 10.75; float digit; float i; float uCol; float uRow; if (uv.y < 0.45) { if (uv.x > 0.75) { digit = floor(uValue*10.0); digit = digit - floor(digit/10.0)*10.0; i = 48.0 - 32.0 + digit; uRow = floor(i / 10.0); uCol = i - 10.0 * uRow; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.5) / devisor, uRow / devisor) ); } else if (uv.x > 0.5) { uCol = 4.0; uRow = 1.0; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.0) / devisor, uRow / devisor) ); } else if (uv.x > 0.25) { digit = floor(uValue); digit = digit - floor(digit/10.0)*10.0; i = 48.0 - 32.0 + digit; uRow = floor(i / 10.0); uCol = i - 10.0 * uRow; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-0.5) / devisor, uRow / devisor) ); } else if (uValue >= 10.0) { digit = floor(uValue/10.0); digit = digit - floor(digit/10.0)*10.0; i = 48.0 - 32.0 + digit; uRow = floor(i / 10.0); uCol = i - 10.0 * uRow; fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-0.0) / devisor, uRow / devisor) ); } else { fragColor = vec4(0.0, 0.0, 0.0, 0.0); } } else { fragColor = vec4(0.0, 0.0, 0.0, 0.0); } gl_FragColor = fragColor; }
Приведенный выше код работает для атласа текстур, где числа начинаются с 0 в 7-м столбце 2-й строки атласа шрифтов (текстуры).
Ссылаться на https://www.shadertoy.com/view/Xl23Dw для демонстрации (правда, с неправильной текстурой)