Намалюйте текст у OpenGL ES


131

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

Я бачив приклад використання View в якості накладної, але не знаю, чи хочу це робити, оскільки я можу пізніше перенести гру на інші платформи.

Будь-які ідеї?


подивіться на цей проект: code.google.com/p/rokon
whunmr

Подивіться, як libgdx робить це за допомогою шрифтів Bitmap.
Роберт Массайолі

Відповіді:


103

Android SDK не пропонує простого способу малювання тексту на переглядах OpenGL. Залишаючи вас із наступних варіантів.

  1. Розмістіть TextView над SurfaceView. Це повільно і погано, але найбільш прямий підхід.
  2. Надайте загальні рядки текстурам і просто намалюйте їх. Це, безумовно, найпростіший і швидкий, але найменш гнучкий.
  3. Зчитування власного коду візуалізації тексту на основі спрайту. Можливо, другий найкращий вибір, якщо 2 - це не варіант. Хороший спосіб змочити ноги, але зауважте, що, хоча це здається простим (і основні функції), це стає складніше і складніше, оскільки ви додаєте більше функцій (вирівнювання текстури, обробка розривів рядків, шрифти змінної ширини тощо). ) - якщо ви скористаєтесь цим маршрутом, зробіть його таким же простим, як ви можете піти!
  4. Використовуйте бібліотеку, яка не є на полиці / з відкритим кодом. Є кілька навколо, якщо ви полюєте в Google, складний біт - це їх інтеграція та запуск. Але принаймні, як тільки ви це зробите, ви отримаєте всю гнучкість та зрілість, яку вони забезпечують.

3
Я вирішив додати погляд над своїм GLView, це може бути не найефективнішим способом зробити це, але представлення не оновлюється дуже часто, плюс це дає мені можливість додавати будь-який шрифт, який мені хотів би. Дякую за всі відповіді!
shakazed

1
Як я можу надати загальні рядки текстурам і просто намалювати ці текстури? Дякую.
VansFannel

1
VansFannel: просто використовуйте програму фарби, щоб розмістити всі свої рядки в одному зображенні, а потім у вашій програмі за допомогою компенсацій використовуйте лише частину зображення, яка містить потрібну рядок.
Дейв

2
Або дивіться відповідь JVitela нижче для більш програмного способу цього досягти.
Дейв

4
Відповідь JVitela краща. Саме цим я зараз користуюся. Причина, чому ви переходите з стандартного Android + Caner на полотно до opengl, - серед інших, - швидкість. Додавання текстового поля через ваш тип opengl заперечує це.
Дракон Шиван

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();

5
Це допомогло мені відобразити невеликий лічильник кадрів у кадрі в моєму додатку для налагодження, дякую!
stealthcopter

2
Ви можете використовувати це для створення всіх букв і цифр як текстур, коли ви завантажуєте інші текстури, а потім з'єднуєте їх для формування слів або цифр. Тоді це буде не менш ефективно, ніж будь-яка інша текстура gl.
twDuke

9
Це дуже повільно, це вбиватиме fps у грі для тексту, який завжди змінюється (рахунок тощо), однак він добре працює для напівстатичних матеріалів (ім'я гравця, назва рівня тощо).
вел42

3
Я хотів би зазначити, що код у цій відповіді, мабуть, є лише демонстраційним і не оптимізований як ніколи! Будь ласка, оптимізуйте / кешуйте по-своєму.
Шериф ель Хатиб

1
Чи можете ви надати це для OpenGL ES 2.0?
необмежено101

36

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

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

Підручник включає в себе повний код , який може бути використаний в будь-якому проекті :)


Зараз я розглядаю це рішення, і я впевнений, що знайду відповідь вчасно, але чи використовує ваша реалізація будь-які виділення часу на виконання?
Нік Хартунг

@ Nick - Усі розподіли (текстури, буфери вершин тощо) виконуються під час створення екземпляра шрифту, а для відображення рядків за допомогою екземпляра шрифту не потрібно більше виділяти. Таким чином, ви можете створювати шрифт під час "завантаження", без подальших виділень на час виконання.
free3dom

Вау, чудова робота! Це дуже корисно, особливо у випадках, коли ваш текст часто змінюється.
mdiener

Це найкраща відповідь, що відповідає продуктивності, для програм, які повинні часто змінювати текст під час виконання. Не бачив нічого іншого такого приємного, як це для Android. Однак він може використовувати порт для OpenGL ES.
greeble31

1
Якщо ви не хочете мати справу з вирівнюванням тексту, розривами рядків тощо - ви можете використовувати TextView. TextView легко винести на полотно. Ефективність не повинна бути важшою, ніж даний підхід, вам потрібен лише один екземпляр TextView, щоб візуалізувати всі необхідні вам тексти. Таким чином ви також отримуєте просте форматування HTML безкоштовно.
Гена Бацян

8

За цим посиланням:

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

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

Використовуючи код JVitela вище, ви повинні мати змогу використовувати цю растрову карту як текстуру OpenGL.


Так, я зробив це з MapView в грі і прив’язав його до текстури gl, тому поєднуйте карти та opengl. Отже, так, всі перегляди мають onDraw (Canvas c), і ви можете передавати будь-яке полотно і прив'язувати будь-яке полотно до будь-якої растрової карти.
HaMMeReD


6

Я переглянув приклад тексту-спрайту, і це виглядає жахливо складним для такого завдання, я також розглядав рендеринг текстури, але я переживаю за ефективність, яка може спричинити за собою. Можливо, мені доведеться просто піти з оглядом і переживати за перенесення, коли настав час перетнути цей міст :)



4

IMHO Існує три причини використовувати OpenGL ES у грі:

  1. Уникати різниць між мобільними платформами, використовуючи відкритий стандарт;
  2. Мати більше контролю над процесом візуалізації;
  3. Користуватися паралельною обробкою GPU;

Малювання тексту - це завжди проблема в ігровому дизайні, оскільки ви малюєте речі, тому ви не можете мати вигляд спільної діяльності з віджетами тощо.

Ви можете використовувати рамку для створення шрифтів Bitmap із шрифтів TrueType та їх рендерингу. Усі рамки, які я бачив, діють однаково: генерують координати вершин і текстур для тексту за час малювання. Це не найефективніше використання OpenGL.

Найкращим способом є виділення віддалених буферів (вершинних буферних об'єктів - VBO) для вершин і текстур на початку коду, уникаючи операцій з передачею лінивої пам’яті за час малювання.

Майте на увазі, що ігрові гравці не люблять читати текст, тому ви не будете писати довгий динамічно створений текст. Для міток можна використовувати статичні текстури, залишаючи динамічний текст за часом та оцінкою, і обидві мають числовий характер, має кілька символів.

Отже, моє рішення просте:

  1. Створіть текстуру для загальних етикеток та попереджень;
  2. Створіть текстуру для чисел 0-9, ":", "+" та "-". Одна текстура для кожного персонажа;
  3. Створіть віддалені VBO для всіх позицій на екрані. Я можу надати статичний чи динамічний текст у цих позиціях, але VBO - статичні;
  4. Створіть лише одну текстуру VBO, оскільки текст завжди відображається в один бік;
  5. За час малювання я виводжу статичний текст;
  6. Для динамічного тексту я можу зазирнути до позиції VBO, отримати текстуру символу та намалювати його, символ за один раз.

Операції малювання швидкі, якщо ви використовуєте віддалені статичні буфери.

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

Щоб отримати високий показник FPS, вам слід уникати генерації VBO під час витягування.


Під "VOB" ви маєте на увазі "VBO" (об'єкт вершинного буфера)?
Dan Hulme

3

Якщо ви наполягаєте на використанні GL, ви можете надати текст текстурам. Якщо припустити, що більшість HUD є відносно статичними, вам не доведеться занадто часто завантажувати текстури в пам'ять текстури.


3

Погляньте на CBFGпорт Android та код завантаження / візуалізації. Ви повинні мати можливість кинути код у свій проект та використати його відразу.

  1. CBFG

  2. Android завантажувач

У мене проблеми з цією реалізацією. Він відображає лише один символ, коли я намагаюся змінити розмір растрової картинки шрифту (мені потрібні спеціальні літери), весь малюнок не вдається :(


2

Я шукав це протягом декількох годин, це була перша стаття, яку я зіткнувся, і хоча вона має найкращу відповідь, найпопулярніші відповіді, я думаю, не вдається. Звичайно на те, що мені було потрібно. Відповіді weichsel і shakazed були прямо на кнопці, але трохи затьмарені у статтях. Щоб правильно ставитись до проекту. Тут: Просто створіть новий проект Android на основі наявного зразка. Виберіть ApiDemos:

Подивіться під вихідну папку

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

І ви знайдете все необхідне.


1

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

  • Створіть зображення із усіх слів, які використовуються на вашому ПК (наприклад, з GIMP).
  • Завантажте це як текстуру і використовуйте її як матеріал для площини.

Для довгого тексту, який потрібно оновлювати раз у раз:

  • Нехай андроїд малює на полотні растрових зображень (рішення 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);
        ...
    }
}

Створіть layout layout_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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.