Рисовать текст в OpenGL ES

131

В настоящее время я разрабатываю небольшую игру OpenGL для платформы Android, и мне интересно, есть ли простой способ визуализировать текст поверх визуализированного кадра (например, HUD со счетом игрока и т. Д.). Для текста также необходимо использовать собственный шрифт.

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

Любые идеи?

shakazed
источник
взгляните на этот проект: code.google.com/p/rokon
whunmr
Посмотрите, как libgdx делает это с помощью растровых шрифтов.
Роберт Массайоли

Ответы:

103

В Android SDK нет простого способа рисовать текст в представлениях OpenGL. Оставляя вам следующие варианты.

  1. Поместите TextView поверх SurfaceView. Это медленный и плохой, но самый прямой подход.
  2. Преобразуйте общие строки в текстуры и просто нарисуйте эти текстуры. Это, безусловно, самый простой и быстрый, но наименее гибкий.
  3. Создайте собственный код отрисовки текста на основе спрайта. Вероятно, второй лучший выбор, если 2 не вариант. Хороший способ намочить ноги, но обратите внимание, что, хотя он кажется простым (и основные функции таковы), он становится все сложнее и сложнее по мере добавления дополнительных функций (выравнивание текстур, обработка разрывов строк, шрифты переменной ширины и т. ) - если вы выберете этот маршрут, сделайте его настолько простым, насколько это вообще возможно!
  4. Используйте готовую библиотеку с открытым исходным кодом. Есть несколько вариантов, если вы охотитесь в Google, сложнее всего их интегрировать и запустить. Но, по крайней мере, как только вы это сделаете, у вас будет вся гибкость и зрелость, которые они обеспечивают.
Дейв
источник
3
Я решил добавить представление в свой GLView, возможно, это не самый эффективный способ сделать это, но представление обновляется не очень часто, плюс это дает мне возможность добавлять любой шрифт, который мне нужен. Спасибо за все отклики!
изменен
1
Как я могу визуализировать общие строки для текстур и просто рисовать эти текстуры? Спасибо.
VansFannel 07
1
VansFannel: просто используйте программу рисования, чтобы поместить все ваши строки в одно изображение, а затем в вашем приложении используйте смещения для визуализации только той части изображения, которая содержит строку, которую вы хотите отобразить.
Дэйв,
2
Или см. Ответ JVitela ниже, чтобы узнать о более программном способе достижения этой цели.
Дэйв,
4
Ответ JVitela лучше. Вот что я сейчас использую. Причина, по которой вы переходите со стандартного android vier + canvas на opengl, заключается (среди прочего) в скорости. Добавление текстового поля поверх вашего opengl отрицает это.
Shivan Dragon
166

Рендеринг текста в текстуру проще, чем в демонстрации Sprite Text, основная идея состоит в том, чтобы использовать класс Canvas для рендеринга в Bitmap, а затем передать Bitmap в текстуру 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();
JVitela
источник
5
Это помогло мне отобразить маленький счетчик кадров в секунду в моем приложении для отладки, спасибо!
Stealthcopter
2
Вы можете использовать это для создания всех букв и цифр в виде текстур, когда вы загружаете другие текстуры, а затем соединяете их вместе, чтобы сформировать слова или числа. Тогда она будет не менее эффективной, чем любая другая текстура gl.
twDuke 07
9
Это очень медленно, это убьет fps в игре для текста, который постоянно меняется (счет и т. Д.), Однако он хорошо работает для полустатических вещей (имя игрока, название уровня и т. Д.).
led42
3
Я хотел бы отметить, что код в этом ответе, вероятно, является всего лишь демонстрацией и не оптимизирован ни на что! Пожалуйста, оптимизируйте / кешируйте по-своему.
Sherif elKhatib
1
Не могли бы вы предоставить это для OpenGL ES 2.0?
безлимитный101,
36

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

Основное преимущество моего метода по сравнению с различными генераторами атласов шрифтов заключается в том, что вы можете поставлять небольшие файлы шрифтов (.ttf .otf) вместе с вашим проектом, вместо того, чтобы отправлять большие растровые изображения для каждого варианта и размера шрифта. Он может генерировать шрифты идеального качества в любом разрешении, используя только файл шрифта :)

В учебник включен полный код, который можно использовать в любом проекте :)

free3dom
источник
В настоящее время я изучаю это решение, и я уверен, что найду ответ вовремя, но использует ли ваша реализация какое-либо распределение времени выполнения?
Ник Хартунг,
@Nick - все выделения (текстуры, буферы вершин и т. Д.) Выполняются при создании экземпляра шрифта, рендеринг строк с использованием экземпляра шрифта не требует дополнительных выделений. Таким образом, вы можете создать шрифт «во время загрузки» без дополнительных выделений во время выполнения.
free3dom 04
Вау, отличная работа! Это действительно полезно, особенно в тех случаях, когда ваш текст часто меняется.
mdiener
Это лучший ответ с точки зрения производительности для приложений, которым приходится часто изменять текст во время выполнения. Еще не видел ничего лучше этого для Android. Однако он может использовать порт для OpenGL ES.
greeble31 01
1
Если вы не хотите иметь дело с выравниванием текста, разрывами строк и т. Д., Вы можете использовать TextView. TextView можно легко преобразовать в холст. С точки зрения производительности не должно быть больше, чем у данного подхода, вам нужен только один экземпляр TextView для рендеринга всех необходимых текстов. Таким образом, вы также бесплатно получаете простое форматирование HTML.
Гена Бацян
8

По этой ссылке:

http://code.neenbedankt.com/how-to-render-an-android-view-to-a-bitmap

Вы можете визуализировать любой вид в растровое изображение. Вероятно, стоит предположить, что вы можете разметить представление по своему усмотрению (включая текст, изображения и т. Д.), А затем отобразить его в растровое изображение.

Используя приведенный выше код JVitela, вы сможете использовать это Bitmap как текстуру OpenGL.

JWGS
источник
Да, я сделал это с помощью MapView в игре и привязал его к текстуре gl, так что комбинируйте карты и opengl. Так что да, все представления имеют onDraw (Canvas c), и вы можете передать любой холст и привязать любой холст к любому растровому изображению.
HaMMeReD
6

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

shakazed
источник
4

ИМХО есть три причины использовать OpenGL ES в игре:

  1. Избегайте различий между мобильными платформами, используя открытый стандарт;
  2. Чтобы иметь больший контроль над процессом рендеринга;
  3. Чтобы извлечь выгоду из параллельной обработки GPU;

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

Вы можете использовать платформу для создания растровых шрифтов из шрифтов TrueType и их рендеринга. Все структуры, которые я видел, работают одинаково: генерируют координаты вершин и текстуры для текста во время рисования. Это не самое эффективное использование OpenGL.

Лучший способ - выделить удаленные буферы (объекты буфера вершин - VBO) для вершин и текстур на ранней стадии кода, избегая операций ленивого переноса памяти во время отрисовки.

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

Итак, мое решение простое:

  1. Создавайте текстуры для общих надписей и предупреждений;
  2. Создайте текстуру для чисел 0-9, «:», «+» и «-». Одна текстура для каждого персонажа;
  3. Сгенерируйте удаленные VBO для всех позиций на экране. Я могу отображать статический или динамический текст в этих позициях, но VBO статичны;
  4. Создайте только один VBO текстуры, так как текст всегда отображается в одном направлении;
  5. Во время рисования я визуализирую статический текст;
  6. Для динамического текста я могу заглянуть в позицию VBO, получить текстуру символа и нарисовать ее, по символу за раз.

Операции рисования выполняются быстро, если вы используете удаленные статические буферы.

Я создаю XML-файл с позициями экрана (на основе процента диагонали экрана) и текстурами (статическими и символами), а затем загружаю этот XML перед рендерингом.

Чтобы получить высокий FPS, вам следует избегать создания VBO во время отрисовки.

cleuton
источник
Под «VOB» вы подразумеваете «VBO» (объект буфера вершин)?
Дэн Халм
3

Если вы настаиваете на использовании GL, вы можете визуализировать текст на текстурах. Предполагая, что большая часть HUD относительно статична, вам не нужно слишком часто загружать текстуры в память текстур.

Таль Прессман
источник
3

Взгляните на CBFGAndroid-порт кода загрузки / рендеринга. Вы должны иметь возможность вставить код в свой проект и сразу же использовать его.

  1. CBFG

  2. Загрузчик Android

У меня проблемы с этой реализацией. Он отображает только один символ, когда я пытаюсь изменить размер растрового изображения шрифта (мне нужны специальные буквы), вся отрисовка не выполняется :(

Aetherna
источник
2

Я искал это в течение нескольких часов, это была первая статья, которую я нашел, и, хотя она дает лучший ответ, самые популярные ответы, я думаю, не соответствуют действительности. Конечно для того, что мне было нужно. Ответы Вайкселя и Шаказеда были прямо на кнопке, но немного скрыты в статьях. Чтобы поставить вас прямо в проект. Здесь: просто создайте новый проект Android на основе существующего образца. Выберите ApiDemos:

Посмотрите в исходную папку

ApiDemos/src/com/example/android/apis/graphics/spritetext

И вы найдете все необходимое.

Джастин
источник
1

Для статического текста :

  • Создайте изображение со всеми словами, используемыми на вашем ПК (например, с помощью 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 для демонстрации (правда, с неправильной текстурой)

Пит
источник
0

В OpenGL ES 2.0 / 3.0 вы также можете комбинировать OGL View и UI-элементы Android:

public class GameActivity extends AppCompatActivity {
    private SurfaceView surfaceView;
    @Override
    protected void onCreate(Bundle state) { 
        setContentView(R.layout.activity_gl);
        surfaceView = findViewById(R.id.oglView);
        surfaceView.init(this.getApplicationContext());
        ...
    } 
}

public class SurfaceView extends GLSurfaceView {
    private SceneRenderer renderer;
    public SurfaceView(Context context) {
        super(context);
    }

    public SurfaceView(Context context, AttributeSet attributes) {
        super(context, attributes);
    }

    public void init(Context context) {
        renderer = new SceneRenderer(context);
        setRenderer(renderer);
        ...
    }
}

Создайте макет activity_gl.xml:

<?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
        tools:context=".activities.GameActivity">
    <com.app.SurfaceView
        android:id="@+id/oglView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <TextView ... />
    <TextView ... />
    <TextView ... />
</androidx.constraintlayout.widget.ConstraintLayout>

Чтобы обновить элементы из потока рендеринга, можно использовать Handler / Looper.

alexrnov
источник