Gambar teks dalam OpenGL ES

131

Saat ini saya sedang mengembangkan game OpenGL kecil untuk platform Android dan saya ingin tahu apakah ada cara mudah untuk membuat teks di atas frame yang diberikan (seperti HUD dengan skor pemain, dll.). Teks juga perlu menggunakan font khusus.

Saya telah melihat contoh menggunakan Tampilan sebagai hamparan, tetapi saya tidak tahu apakah saya ingin melakukan itu karena saya mungkin ingin memport game ke platform lain nanti.

Ada ide?

diguncang
sumber
lihat proyek ini: code.google.com/p/rokon
whunmr
Lihatlah cara libgdx melakukan ini melalui Bitmap Fonts.
Robert Massaioli

Jawaban:

103

Android SDK tidak dilengkapi dengan cara mudah untuk menggambar teks pada tampilan OpenGL. Meninggalkan Anda dengan opsi berikut.

  1. Tempatkan TextView di atas SurfaceView Anda. Ini lambat dan buruk, tetapi pendekatan yang paling langsung.
  2. Berikan string umum ke tekstur, dan cukup gambar tekstur itu. Sejauh ini yang paling sederhana dan tercepat, tetapi paling tidak fleksibel.
  3. Gulung kode rendering teks Anda sendiri berdasarkan sprite. Mungkin pilihan terbaik kedua jika 2 bukan pilihan. Cara yang baik untuk membuat kaki Anda basah, tetapi perhatikan bahwa walaupun tampaknya sederhana (dan fitur dasarnya), itu menjadi lebih sulit dan lebih menantang saat Anda menambahkan lebih banyak fitur (pelurusan tekstur, berurusan dengan penahan garis, font lebar variabel, dll. ) - jika Anda mengambil rute ini, buat sesederhana mungkin!
  4. Gunakan perpustakaan off-the-shelf / open-source. Ada beberapa di sekitar jika Anda berburu di Google, bagian yang rumit adalah membuatnya terintegrasi dan berjalan. Tetapi setidaknya, begitu Anda melakukannya, Anda akan memiliki semua fleksibilitas dan kematangan yang mereka berikan.
Dave
sumber
3
Saya telah memutuskan untuk menambahkan pandangan pada GLView saya, ini mungkin bukan cara yang paling efisien untuk melakukannya tetapi tampilan tidak terlalu sering diperbarui, ditambah lagi memberi saya fleksibilitas untuk menambahkan font apa pun yang saya suka. Terima kasih atas semua balasan!
shakazed
1
Bagaimana saya bisa membuat string umum ke tekstur, dan hanya menggambar tekstur itu? Terima kasih.
VansFannel
1
VansFannel: cukup gunakan program cat untuk meletakkan semua string Anda dalam satu gambar, kemudian di aplikasi Anda gunakan offset untuk hanya membuat bagian gambar yang berisi string yang Anda inginkan. Ditampilkan.
Dave
2
Atau lihat jawaban JVitela di bawah ini untuk cara yang lebih terprogram untuk mencapai ini.
Dave
4
Jawaban JVitela lebih baik. Itulah yang saya gunakan saat ini. Alasan mengapa Anda beralih dari android standar vier + kanvas ke OpenGL adalah (antara lain) untuk kecepatan. Menambahkan kotak teks di atas semacam pembuka Anda meniadakan itu.
Shivan Dragon
166

Rendering teks ke tekstur lebih sederhana dari apa yang tampak seperti demo Teks Sprite, ide dasarnya adalah menggunakan kelas Canvas untuk merender ke Bitmap dan kemudian meneruskan Bitmap ke tekstur 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
sumber
5
Ini membantu saya menampilkan di penghitung fps kecil di aplikasi saya untuk debugging, terima kasih!
stealthcopter
2
Anda bisa menggunakan ini untuk membuat semua huruf dan angka sebagai tekstur saat Anda memuat tekstur lainnya dan kemudian menyatukannya untuk membentuk kata atau angka. Maka itu akan tidak kalah efisien daripada tekstur gl lainnya.
twDuke
9
Ini sangat lambat, itu akan membunuh fps pada game untuk teks yang selalu berubah (skor, dll), namun itu berfungsi dengan baik untuk hal-hal semi-statis (nama pemain, nama level, dll).
led42
3
Saya ingin mencatat bahwa kode dalam jawaban ini mungkin hanya demo dan tidak dioptimalkan apa-yang-pernah-pernah! Silakan lakukan optimasi / cache dengan cara Anda sendiri.
Sherif elKhatib
1
Bisakah Anda memberikan ini untuk OpenGL ES 2.0?
unlimited101
36

Saya telah menulis tutorial yang memperluas jawaban yang diposting oleh JVitela . Pada dasarnya, ia menggunakan ide yang sama, tetapi alih-alih merender setiap string ke tekstur, ia merender semua karakter dari file font ke tekstur dan menggunakannya untuk memungkinkan rendering teks dinamis penuh tanpa perlambatan lebih lanjut (setelah inisialisasi selesai) .

Keuntungan utama metode saya, dibandingkan dengan berbagai generator atlas font, adalah Anda dapat mengirimkan file font kecil (.ttf .otf) dengan proyek Anda alih-alih harus mengirimkan bitmap besar untuk setiap variasi dan ukuran font. Itu dapat menghasilkan font kualitas sempurna pada resolusi apa pun hanya menggunakan file font :)

The tutorial termasuk kode lengkap yang dapat digunakan dalam setiap proyek :)

free3dom
sumber
Saat ini saya sedang mencari solusi ini, dan saya yakin saya akan menemukan jawabannya tepat waktu, tetapi apakah implementasi Anda menggunakan alokasi waktu berjalan?
Nick Hartung
@Nick - Semua alokasi (tekstur, simpul verteks, dll) dilakukan ketika membuat turunan font, rendering string menggunakan turunan font tidak memerlukan alokasi lebih lanjut. Jadi, Anda dapat membuat font pada "waktu buka" tanpa alokasi lebih lanjut pada waktu berjalan.
free3dom
Wow, kerja bagus! Ini sangat membantu terutama dalam kasus di mana teks Anda sering berubah.
mdiener
Ini adalah jawaban terbaik, berdasarkan kinerja, untuk aplikasi yang harus sering mengganti teks saat runtime. Belum melihat hal lain sebaik ini untuk Android. Itu bisa menggunakan port ke OpenGL ES.
greeble31
1
Jika Anda tidak ingin berurusan dengan perataan teks, pemisah baris, dll - Anda bisa menggunakan TextView. TextView dapat dengan mudah dirender menjadi kanvas. Kinerja bijaksana tidak boleh lebih berat dari pendekatan yang diberikan, Anda hanya perlu satu contoh TextView untuk membuat semua teks yang Anda butuhkan. Dengan cara ini Anda juga mendapatkan pemformatan HTML sederhana gratis.
Gena Batsyan
8

Menurut tautan ini:

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

Anda dapat merender Tampilan apa pun ke bitmap. Mungkin layak untuk mengasumsikan bahwa Anda dapat mengatur tampilan sesuai kebutuhan (termasuk teks, gambar, dll.) Dan kemudian merendernya menjadi Bitmap.

Menggunakan kode JVitela di atas Anda harus dapat menggunakan Bitmap itu sebagai tekstur OpenGL.

JWGS
sumber
Ya, saya melakukannya dengan MapView dalam game dan mengikatnya ke tekstur gl jadi gabungkan peta dan opengl. Jadi ya, semua view memiliki onDraw (Canvas c) dan Anda dapat melewatkan setiap kanvas dan mengikat setiap kanvas ke bitmap apa pun.
HaMMeReD
6

Saya melihat contoh teks sprite dan itu terlihat sangat rumit untuk tugas seperti itu, saya mempertimbangkan rendering ke tekstur juga, tapi saya khawatir tentang kinerja yang mungkin menyebabkan hit. Saya mungkin hanya harus pergi dengan pemandangan dan khawatir tentang porting ketika tiba saatnya untuk menyeberangi jembatan itu :)

diguncang
sumber
4

IMHO ada tiga alasan untuk menggunakan OpenGL ES dalam game:

  1. Hindari perbedaan antara platform seluler dengan menggunakan standar terbuka;
  2. Untuk memiliki kontrol lebih besar terhadap proses render;
  3. Untuk mendapatkan manfaat dari pemrosesan paralel GPU;

Menggambar teks selalu menjadi masalah dalam desain game, karena Anda menggambar sesuatu, jadi Anda tidak dapat memiliki tampilan dan nuansa aktivitas yang umum, dengan widget dan sebagainya.

Anda dapat menggunakan kerangka kerja untuk menghasilkan font Bitmap dari font TrueType dan merendernya. Semua kerangka kerja yang saya lihat beroperasi dengan cara yang sama: menghasilkan koordinat titik dan tekstur untuk teks dalam waktu menggambar. Ini bukan penggunaan OpenGL yang paling efisien.

Cara terbaik adalah dengan mengalokasikan buffer jarak jauh (objek buffer vertex - VBO) untuk simpul dan tekstur di awal kode, menghindari operasi pemindahan memori yang malas dalam waktu penarikan.

Ingatlah bahwa pemain game tidak suka membaca teks, jadi Anda tidak akan menulis teks yang dibuat secara dinamis lama. Untuk label, Anda dapat menggunakan tekstur statis, meninggalkan teks dinamis untuk waktu dan skor, dan keduanya numerik dengan panjang beberapa karakter.

Jadi, solusi saya sederhana:

  1. Buat tekstur untuk label dan peringatan umum;
  2. Buat tekstur untuk angka 0-9, ":", "+", dan "-". Satu tekstur untuk setiap karakter;
  3. Hasilkan VBO jarak jauh untuk semua posisi di layar. Saya dapat membuat teks statis atau dinamis di posisi itu, tetapi VBO statis;
  4. Hasilkan hanya satu Tekstur VBO, karena teks selalu diterjemahkan satu arah;
  5. Dalam waktu menggambar, saya membuat teks statis;
  6. Untuk teks dinamis, saya bisa mengintip posisi VBO, mendapatkan tekstur karakter dan menggambarnya, karakter sekaligus.

Operasi draw cepat, jika Anda menggunakan buffer statis jarak jauh.

Saya membuat file XML dengan posisi layar (berdasarkan persentase diagonal layar) dan tekstur (statis dan karakter), dan kemudian saya memuat XML ini sebelum rendering.

Untuk mendapatkan tingkat FPS yang tinggi, Anda harus menghindari menghasilkan VBO pada waktu undian.

cleuton
sumber
Dengan "VOB", maksud Anda "VBO" (objek vertex buffer)?
Dan Hulme
3

Jika Anda bersikeras menggunakan GL, Anda dapat merender teks menjadi tekstur. Dengan anggapan bahwa sebagian besar HUD relatif statis, Anda tidak perlu terlalu sering memuat tekstur ke memori tekstur.

Tal Pressman
sumber
3

Lihatlah CBFGdan port Android dari kode pemuatan / rendering. Anda harus dapat memasukkan kode ke proyek Anda dan langsung menggunakannya.

  1. CBFG

  2. Android loader

Saya memiliki masalah dengan implementasi ini. Ini hanya menampilkan satu karakter, ketika saya mencoba mengubah ukuran bitmap font (saya perlu huruf khusus) gagal total :(

Aetherna
sumber
2

Saya telah mencari ini selama beberapa jam, ini adalah artikel pertama yang saya temui dan meskipun memiliki jawaban terbaik, jawaban paling populer menurut saya tidak tepat sasaran. Tentu untuk apa yang saya butuhkan. Jawaban weichsel dan shakazed tepat pada tombolnya tetapi sedikit dikaburkan dalam artikel. Untuk menempatkan Anda di proyek. Di sini: Cukup buat proyek Android baru berdasarkan sampel yang ada. Pilih ApiDemos:

Lihat di bawah folder sumber

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

Dan Anda akan menemukan semua yang Anda butuhkan.

Justin
sumber
1

Untuk teks statis :

  • Hasilkan gambar dengan semua kata yang digunakan di PC Anda (Misalnya dengan GIMP).
  • Muat ini sebagai tekstur dan gunakan sebagai bahan untuk pesawat.

Untuk teks panjang yang perlu diperbarui sesekali:

  • Biarkan Android menggambar di atas kanvas bitmap (solusi JVitela).
  • Muat ini sebagai bahan untuk pesawat.
  • Gunakan koordinat tekstur yang berbeda untuk setiap kata.

Untuk angka (diformat 00.0):

  • Hasilkan gambar dengan semua angka dan satu titik.
  • Muat ini sebagai bahan untuk pesawat.
  • Gunakan shader di bawah ini.
  • Dalam acara onDraw Anda, hanya perbarui variabel nilai yang dikirim ke shader.

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

Kode di atas berfungsi untuk atlas tekstur di mana angka mulai dari 0 pada kolom ke-7 dari baris ke-2 font atlas (tekstur).

Lihat https://www.shadertoy.com/view/Xl23Dw untuk demonstrasi (dengan tekstur yang salah)

Pete
sumber
0

Di OpenGL ES 2.0 / 3.0 Anda juga dapat menggabungkan OGL View dan elemen-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);
        ...
    }
}

Buat tata letak 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>

Untuk memperbarui elemen dari utas render, dapat menggunakan Handler / Looper.

alexrnov
sumber