Rysuj tekst w OpenGL ES

131

Obecnie tworzę małą grę OpenGL na platformę Android i zastanawiam się, czy istnieje łatwy sposób renderowania tekstu na wyrenderowanej ramce (jak HUD z wynikiem gracza itp.). Tekst musiałby również używać niestandardowej czcionki.

Widziałem przykład używający widoku jako nakładki, ale nie wiem, czy chcę to zrobić, ponieważ mógłbym później przenieść grę na inne platformy.

Jakieś pomysły?

wstrząśnięty
źródło
spójrz na ten projekt: code.google.com/p/rokon
whunmr
Spójrz na sposób, w jaki libgdx robi to za pomocą czcionek bitmapowych.
Robert Massaioli

Odpowiedzi:

103

Android SDK nie zapewnia łatwego sposobu rysowania tekstu w widokach OpenGL. Pozostawiając Ci następujące opcje.

  1. Umieść TextView na swoim SurfaceView. To jest powolne i złe, ale najbardziej bezpośrednie podejście.
  2. Renderuj wspólne ciągi tekstur i po prostu narysuj te tekstury. Jest to zdecydowanie najprostsze i najszybsze, ale najmniej elastyczne.
  3. Roll-your-own text rendering code based on a sprite. Prawdopodobnie drugi najlepszy wybór, jeśli 2 nie wchodzi w grę. Dobry sposób na zmoczenie stóp, ale pamiętaj, że chociaż wydaje się to proste (a podstawowe funkcje są), staje się trudniejsze i trudniejsze, gdy dodajesz więcej funkcji (wyrównanie tekstur, radzenie sobie z łamaniem linii, czcionkami o zmiennej szerokości itp. ) - jeśli wybierzesz tę trasę, zrób to tak prosto, jak tylko możesz!
  4. Użyj gotowej biblioteki / open-source. Jest ich kilka, jeśli polujesz w Google, najtrudniejsze jest zintegrowanie ich i uruchomienie. Ale przynajmniej, kiedy to zrobisz, będziesz miał całą elastyczność i dojrzałość, które zapewniają.
Dave
źródło
3
Zdecydowałem się dodać widok do mojego GLView, może to nie być najbardziej efektywny sposób, ale widok nie jest aktualizowany zbyt często, a ponadto daje mi elastyczność dodawania dowolnej czcionki, którą chcę. Dzięki za wszystkie odpowiedzi!
shakazed
1
Jak mogę renderować typowe ciągi tekstur i po prostu narysować te tekstury? Dzięki.
VansFannel
1
VansFannel: po prostu użyj programu do rysowania, aby umieścić wszystkie ciągi znaków w jednym obrazie, a następnie w aplikacji użyj przesunięć, aby renderować tylko część obrazu zawierającą żądany ciąg.
Dave,
2
Lub zobacz odpowiedź JVitela poniżej, aby uzyskać bardziej programowy sposób osiągnięcia tego celu.
Dave,
4
Odpowiedź JVitela jest lepsza. Właśnie tego obecnie używam. Powodem przejścia ze standardowego Androida Vier + Canvas na OpenGL jest (między innymi) szybkość. Dodanie pola tekstowego nad twoim opengl w pewnym sensie neguje to.
Shivan Dragon
168

Renderowanie tekstu do tekstury jest prostsze niż to, jak wygląda demo Sprite Text, podstawową ideą jest użycie klasy Canvas do renderowania do mapy bitowej, a następnie przekazanie mapy bitowej do tekstury 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
źródło
5
Pomogło mi to wyświetlić mały licznik fps w mojej aplikacji do debugowania, dzięki!
stealthcopter
2
Możesz użyć tego do generowania wszystkich liter i cyfr jako tekstur, kiedy ładujesz inne tekstury, a następnie łączysz je w słowa lub cyfry. Wtedy będzie nie mniej wydajny niż jakakolwiek inna konsystencja gl.
twDuke
9
Jest to bardzo wolne, zabija fps w grze w przypadku tekstu, który zawsze się zmienia (wynik itp.), Jednak działa dobrze w przypadku elementów półstatycznych (nazwa gracza, nazwa poziomu itp.).
led42
3
Chciałbym zauważyć, że kod w tej odpowiedzi jest prawdopodobnie tylko wersją demonstracyjną i nie jest zoptymalizowany w ogóle! Zoptymalizuj / buforuj na swój własny sposób.
Sherif elKhatib
1
Czy możesz to zapewnić dla OpenGL ES 2.0?
nieograniczony
36

Napisałem samouczek, który rozszerza odpowiedź opublikowaną przez JVitela . Zasadniczo używa tego samego pomysłu, ale zamiast renderować każdy ciąg do tekstury, renderuje wszystkie znaki z pliku czcionki na teksturę i wykorzystuje to, aby umożliwić pełne dynamiczne renderowanie tekstu bez dalszych spowolnień (po zakończeniu inicjalizacji) .

Główną zaletą mojej metody, w porównaniu z różnymi generatorami atlasów czcionek, jest to, że możesz dostarczać z projektem małe pliki czcionek (.ttf .otf), zamiast wysyłać duże mapy bitowe dla każdej odmiany i rozmiaru czcionki. Potrafi generować czcionki doskonałej jakości w dowolnej rozdzielczości, używając tylko pliku czcionki :)

Poradnik zawiera pełny kod, który może być używany w każdym projekcie :)

free3dom
źródło
Obecnie przyglądam się temu rozwiązaniu i jestem pewien, że znajdę odpowiedź na czas, ale czy Twoja implementacja używa jakichkolwiek przydziałów czasu wykonywania?
Nick Hartung,
@Nick - wszystkie alokacje (tekstury, bufory wierzchołków itp.) Są wykonywane podczas tworzenia wystąpienia czcionki, renderowanie ciągów za pomocą wystąpienia czcionki nie wymaga dalszych alokacji. Możesz więc utworzyć czcionkę w „czasie ładowania” bez dalszych alokacji w czasie wykonywania.
free3dom
Wow, świetna robota! Jest to bardzo pomocne, szczególnie w przypadkach, gdy tekst często się zmienia.
mdiener
Jest to najlepsza odpowiedź pod względem wydajności dla aplikacji, które muszą często zmieniać tekst w czasie wykonywania. Nie widziałem nic tak fajnego jak to na Androida. Może jednak używać portu do OpenGL ES.
greeble31
1
Jeśli nie chcesz zajmować się wyrównaniem tekstu, podziałami wierszy itp. - możesz użyć TextView. TextView można łatwo renderować na kanwie. Wydajność nie powinna być cięższa niż podane podejście, potrzebujesz tylko jednej instancji TextView do renderowania wszystkich potrzebnych tekstów. W ten sposób otrzymujesz również bezpłatne proste formatowanie HTML.
Gena Batsyan
8

Zgodnie z tym linkiem:

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

Możesz renderować dowolny widok do mapy bitowej. Prawdopodobnie warto założyć, że możesz ułożyć widok zgodnie z wymaganiami (w tym tekst, obrazy itp.), A następnie wyrenderować go na mapę bitową.

Przy użyciu kodu JVitela za powyżej powinny być w stanie używać tej bitmapy jako OpenGL tekstury.

JWGS
źródło
Tak, zrobiłem to z MapView w grze i związałem go z teksturą gl, więc połącz mapy i opengl. Więc tak, wszystkie widoki mają onDraw (Canvas c) i możesz przekazać dowolne płótno i powiązać je z dowolną bitmapą.
HaMMeReD
6

Przyjrzałem się przykładowi tekstu sprite'a i wygląda to na strasznie skomplikowane jak na takie zadanie, rozważałem również renderowanie do tekstury, ale martwię się o spadek wydajności, który może spowodować. Być może będę musiał po prostu pójść z widokiem i martwić się o przeniesienie, kiedy nadejdzie czas na przekroczenie tego mostu :)

wstrząśnięty
źródło
4

IMHO Istnieją trzy powody, dla których warto używać OpenGL ES w grze:

  1. Unikaj różnic między platformami mobilnymi, stosując otwarty standard;
  2. Aby mieć większą kontrolę nad procesem renderowania;
  3. Aby skorzystać z równoległego przetwarzania GPU;

Rysowanie tekstu jest zawsze problemem w projektowaniu gier, ponieważ rysujesz rzeczy, więc nie możesz mieć wyglądu i stylu typowego działania, z widżetami i tak dalej.

Możesz użyć struktury do generowania czcionek bitmapowych z czcionek TrueType i renderowania ich. Wszystkie struktury, które widziałem, działają w ten sam sposób: generuj współrzędne wierzchołka i tekstury dla tekstu w czasie rysowania. Nie jest to najbardziej efektywne wykorzystanie OpenGL.

Najlepszym sposobem jest przydzielenie zdalnych buforów (obiektów buforów wierzchołków - VBO) dla wierzchołków i tekstur na wczesnym etapie kodu, unikając leniwych operacji transferu pamięci w czasie rysowania.

Pamiętaj, że gracze nie lubią czytać tekstu, więc nie napiszesz długiego, dynamicznie generowanego tekstu. W przypadku etykiet można używać tekstur statycznych, pozostawiając tekst dynamiczny na czas i punktację, a oba są numeryczne i składają się z kilku znaków.

Moje rozwiązanie jest więc proste:

  1. Utwórz teksturę dla typowych etykiet i ostrzeżeń;
  2. Utwórz teksturę dla liczb 0-9, „:”, „+” i „-”. Jedna tekstura dla każdej postaci;
  3. Generuj zdalne VBO dla wszystkich pozycji na ekranie. Potrafię renderować statyczny lub dynamiczny tekst w tych pozycjach, ale VBO są statyczne;
  4. Wygeneruj tylko jedną teksturę VBO, ponieważ tekst jest zawsze renderowany w jeden sposób;
  5. W czasie rysowania renderuję tekst statyczny;
  6. W przypadku tekstu dynamicznego mogę zerknąć na pozycję VBO, pobrać teksturę postaci i narysować ją, po jednej postaci.

Operacje rysowania są szybkie, jeśli używasz zdalnych buforów statycznych.

Tworzę plik XML z pozycjami ekranu (na podstawie procentu przekątnej ekranu) i teksturami (statyczne i znaki), a następnie ładuję ten XML przed renderowaniem.

Aby uzyskać wysoki współczynnik FPS, należy unikać generowania VBO w czasie rysowania.

cleuton
źródło
Przez „VOB” masz na myśli „VBO” (obiekt bufora wierzchołków)?
Dan Hulme,
3

Jeśli nalegasz na używanie GL, możesz renderować tekst na teksturach. Zakładając, że większość HUD jest stosunkowo statyczna, nie powinieneś zbyt często ładować tekstur do pamięci tekstur.

Tal Pressman
źródło
3

Spójrz na CBFGport Androida w kodzie ładowania / renderowania. Powinieneś móc upuścić kod do projektu i od razu go używać.

  1. CBFG

  2. Moduł ładujący Androida

Mam problemy z tą realizacją. Wyświetla tylko jeden znak, kiedy próbuję zmienić rozmiar bitmapy czcionki (potrzebuję specjalnych liter), całe rysowanie kończy się niepowodzeniem :(

Aetherna
źródło
2

Szukałem tego od kilku godzin, był to pierwszy artykuł, z którym się spotkałem i chociaż zawiera najlepszą odpowiedź, najpopularniejsze odpowiedzi, moim zdaniem, są chybione. Z pewnością na to, czego potrzebowałem. Odpowiedzi weichsel i shakazed były tuż przy przycisku, ale w artykułach były nieco niejasne. Aby skierować Cię prosto do projektu. Tutaj: Po prostu utwórz nowy projekt systemu Android na podstawie istniejącej próbki. Wybierz ApiDemos:

Zajrzyj do folderu źródłowego

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

Znajdziesz wszystko, czego potrzebujesz.

Justin
źródło
1

W przypadku tekstu statycznego :

  • Wygeneruj obraz ze wszystkimi słowami używanymi na komputerze (na przykład z GIMP).
  • Załaduj to jako teksturę i użyj jako materiału na samolot.

W przypadku długich tekstów, które wymagają od czasu do czasu aktualizacji:

  • Niech Android rysuje na kanwie mapy bitowej (rozwiązanie JVitela).
  • Załaduj to jako materiał na samolot.
  • Użyj różnych współrzędnych tekstury dla każdego słowa.

W przypadku liczby (w formacie 00.0):

  • Wygeneruj obraz ze wszystkimi liczbami i kropką.
  • Załaduj to jako materiał na samolot.
  • Użyj poniżej modułu cieniującego.
  • W zdarzeniu onDraw aktualizuj tylko zmienną wartość wysłaną do modułu cieniującego.

    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;
    
    }
    

Powyższy kod działa dla atlasu tekstur, w którym liczby zaczynają się od 0 w siódmej kolumnie drugiego rzędu atlasu czcionek (tekstury).

Zapoznaj się z https://www.shadertoy.com/view/Xl23Dw, aby uzyskać demonstrację (chociaż z niewłaściwą teksturą)

Pete
źródło
0

W OpenGL ES 2.0 / 3.0 możesz także łączyć OGL View i elementy UI Androida:

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);
        ...
    }
}

Utwórz układ 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>

Aby zaktualizować elementy z wątku renderowania, możesz użyć Handler / Looper.

alexrnov
źródło