OpenGL ES'de metin çizin


131

Şu anda Android platformu için küçük bir OpenGL oyunu geliştiriyorum ve işlenen çerçevenin üstünde metin oluşturmanın kolay bir yolu olup olmadığını merak ediyorum (oyuncunun puanına sahip bir HUD gibi). Metnin de özel bir yazı tipi kullanması gerekir.

Görünümü kaplama olarak kullanan bir örnek gördüm, ancak oyunu daha sonra başka platformlara taşımak isteyebileceğim için bunu yapmak isteyip istemediğimi bilmiyorum.

Herhangi bir fikir?


bu projeye bir göz atın: code.google.com/p/rokon
whunmr

Libgdx'in bunu Bitmap Yazı Tipleri aracılığıyla nasıl yaptığına bakın.
Robert Massaioli

Yanıtlar:


103

Android SDK, OpenGL görünümlerinde metin çizmenin kolay bir yolunu sunmaz. Sizi aşağıdaki seçeneklerle bırakıyorum.

  1. SurfaceView'unuzun üzerine bir TextView yerleştirin. Bu yavaş ve kötü, ancak en doğrudan yaklaşım.
  2. Ortak dizeleri dokulara işleyin ve bu dokuları çizin. Bu, açık farkla en basit ve en hızlı, ancak en az esnek olanıdır.
  3. Bir hareketli grafiğe dayalı olarak kendi metin oluşturma kodunu toplayın. 2 bir seçenek değilse, muhtemelen ikinci en iyi seçenek. Ayaklarınızı ıslatmanın iyi bir yolu, ancak basit görünse de (ve temel özellikler olsa da), daha fazla özellik ekledikçe (doku hizalama, satır sonlarıyla başa çıkma, değişken genişlikli yazı tipleri vb.) Gittikçe zorlaştığını unutmayın. ) - bu rotayı kullanırsanız, kurtulabileceğiniz kadar basit hale getirin!
  4. Hazır / açık kaynaklı bir kitaplık kullanın. Google'da avlanırsanız, etrafta birkaç tane var, zor olan şey onları entegre etmek ve çalıştırmaktır. Ama en azından bunu bir kez yaptığınızda, onların sağladığı tüm esnekliğe ve olgunluğa sahip olacaksınız.

3
GLView'ime bir görünüm eklemeye karar verdim, bunu yapmanın en verimli yolu olmayabilir, ancak görünüm çok sık güncellenmiyor, ayrıca bana istediğim yazı tipini ekleme esnekliği sağlıyor. Tüm cevaplar için teşekkürler!
sallandı

1
Ortak dizeleri dokulara nasıl işleyebilirim ve bu dokuları nasıl basitçe çizebilirim? Teşekkürler.
VansFannel

1
VansFannel: tüm dizelerinizi tek bir görüntüye yerleştirmek için bir boyama programı kullanın, ardından uygulamanızda görüntünün yalnızca istediğiniz dizeyi içeren bölümünü oluşturmak için ofsetleri kullanın. Görüntülenen.
Dave

2
Veya bunu başarmanın daha programlı bir yolu için aşağıdaki JVitela yanıtına bakın.
Dave

4
JVitela'nın cevabı daha iyi. Şu anda kullandığım şey bu. Standart android vier + canvas'dan opengl'e geçmenizin nedeni (diğerlerinin yanı sıra) hız içindir. Opengl'inizin üzerine bir metin kutusu eklemek, bunu reddeder.
Shivan Dragon

166

Metni bir dokuya dönüştürmek, Sprite Text demosunun göründüğünden daha basittir; temel fikir, Canvas sınıfını bir Bitmap'e dönüştürmek ve ardından Bitmap'i bir OpenGL dokusuna aktarmak için kullanmaktır:

// 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
Bu, hata ayıklama için uygulamamdaki küçük fps sayacında göstermeme yardımcı oldu, teşekkürler!
stealthcopter

2
Bunu, diğer dokularınızı yüklediğinizde tüm harfleri ve sayıları doku olarak oluşturmak ve ardından sözcükler veya sayılar oluşturmak için bir araya getirmek için kullanabilirsiniz. O zaman diğer gl dokulardan daha az verimli olmayacaktır.
twDuke

9
Bu çok yavaştır, bir oyunda sürekli değişen metinler (skor vb.) İçin fps'yi öldürecektir, ancak yarı statik şeyler için (oyuncu adı, seviye adı vb.) İyi çalışır.
led42

3
Bu yanıttaki kodun muhtemelen sadece bir demo olduğunu ve ne olursa olsun optimize edilmediğini belirtmek isterim! Lütfen kendi yönteminizle optimize edin / önbelleğe alın.
Sherif elKhatib

1
Bunu OpenGL ES 2.0 için sağlayabilir misiniz?
sınırsız101

36

JVitela tarafından yayınlanan cevabı genişleten bir eğitim yazdım . Temel olarak, aynı fikri kullanır, ancak her dizeyi bir dokuya dönüştürmek yerine, tüm karakterleri bir yazı tipi dosyasından bir dokuya dönüştürür ve bunu, daha fazla yavaşlama olmadan tam dinamik metin oluşturmaya izin vermek için kullanır (başlatma tamamlandıktan sonra) .

Metodumun çeşitli yazı tipi atlas oluşturuculara kıyasla ana avantajı, her yazı tipi varyasyonu ve boyutu için büyük bit eşlemler göndermek zorunda kalmadan projenizle birlikte küçük yazı tipi dosyalarını (.ttf .otf) gönderebilmenizdir. Sadece bir yazı tipi dosyası kullanarak her çözünürlükte mükemmel kalitede yazı tipleri oluşturabilir :)

Öğretici herhangi bir proje kullanılabilecek tam kodu :)


Şu anda bu çözümü araştırıyorum ve eminim cevabı zamanında bulacağım, ancak uygulamanız herhangi bir çalışma süresi tahsisatı kullanıyor mu?
Nick Hartung

@Nick - Tüm ayırmalar (doku, köşe tamponları, vb.) Bir font örneği oluştururken yapılır, font örneğini kullanarak dizeleri oluşturmak daha fazla ayırma gerektirmez. Böylece, yazı tipini çalışma zamanında başka ayırma yapmadan "yükleme zamanında" oluşturabilirsiniz.
free3dom

Harika iş çıkardın! Bu, özellikle metninizin sık sık değiştiği durumlarda gerçekten yararlıdır.
mdiener

Bu, çalışma zamanında metni sık sık değiştirmek zorunda olan uygulamalar için performans açısından en iyi yanıttır. Android için bunun kadar güzel bir şey görmedim. Yine de OpenGL ES'ye bir bağlantı noktası kullanabilir.
greeble31

1
Metin hizalama, satır kesmeleri vb. İle uğraşmak istemiyorsanız, bir TextView kullanabilirsiniz. Bir TextView kolaylıkla bir tuvale dönüştürülebilir. Performans açısından verilen yaklaşımdan daha ağır olmamalıdır, ihtiyacınız olan tüm metinleri oluşturmak için yalnızca bir TextView örneğine ihtiyacınız vardır. Bu şekilde, ücretsiz olarak basit HTML biçimlendirmesi de elde edersiniz.
Gena Batsyan

8

Bu bağlantıya göre:

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

Herhangi bir Görünümü işleyebilirsiniz bitmap'e. Muhtemelen bir görünümü istediğiniz gibi düzenleyebileceğinizi (metin, resimler vb. Dahil) ve ardından onu bir Bitmap'e dönüştürebileceğinizi varsaymaya değer.

Yukarıdaki JVitela kodunu kullanarak Bitmap'i bir OpenGL dokusu olarak kullanabilmelisiniz.


Evet, bunu bir oyunda MapView ile yaptım ve onu bir gl dokuya bağladım, böylece haritaları ve opengl'i birleştirdim. Yani evet, tüm görünümlerde onDraw (Canvas c) bulunur ve herhangi bir tuvali içeri aktarabilir ve herhangi bir tuvali herhangi bir bitmap'e bağlayabilirsiniz.
HaMMeReD


6

Sprite metni örneğine baktım ve böyle bir görev için çok karmaşık görünüyor, bir dokuya dönüştürmeyi de düşündüm, ancak bunun neden olabileceği performans artışı konusunda endişeliyim. Bunun yerine bir manzarayla gitmem ve o köprüyü geçme zamanı geldiğinde taşıma konusunda endişelenmem gerekebilir :)



4

IMHO Bir oyunda OpenGL ES kullanmanın üç nedeni vardır:

  1. Açık bir standart kullanarak mobil platformlar arasındaki farklılıklardan kaçının;
  2. Render işlemi üzerinde daha fazla kontrole sahip olmak için;
  3. GPU paralel işlemeden yararlanmak için;

Metin çizmek, oyun tasarımında her zaman bir sorundur, çünkü bir şeyler çiziyorsunuz, bu nedenle ortak bir etkinliğin görünümünü ve hissini, widget'lar vb. İle elde edemezsiniz.

TrueType yazı tiplerinden Bitmap yazı tipleri oluşturmak ve bunları işlemek için bir çerçeve kullanabilirsiniz. Gördüğüm tüm çerçeveler aynı şekilde çalışıyor: çizim zamanında metin için köşe ve doku koordinatlarını oluşturun. Bu, OpenGL'nin en verimli kullanımı değildir.

En iyi yol, kodun başlarında köşeler ve dokular için uzak arabellekleri (köşe arabellek nesneleri - VBO'lar) ayırmak ve çizim zamanında yavaş bellek aktarım işlemlerinden kaçınmaktır.

Oyun oyuncularının metin okumayı sevmediğini ve bu nedenle dinamik olarak oluşturulmuş uzun bir metin yazmayacağınızı unutmayın. Etiketler için, zaman ve puan için dinamik metin bırakarak statik dokular kullanabilirsiniz ve her ikisi de birkaç karakter uzunluğunda sayısaldır.

Yani benim çözümüm basit:

  1. Yaygın etiketler ve uyarılar için doku oluşturun;
  2. 0-9, ":", "+" ve "-" sayıları için doku oluşturun. Her karakter için bir doku;
  3. Ekrandaki tüm pozisyonlar için uzak VBO'lar oluşturun. Bu konumlarda statik veya dinamik metin oluşturabilirim, ancak VBO'lar statiktir;
  4. Metin her zaman tek bir şekilde oluşturulduğundan yalnızca bir Doku VBO oluşturun;
  5. Çizim zamanında statik metni oluşturuyorum;
  6. Dinamik metin için, VBO konumuna göz atabilir, karakter dokusunu alabilir ve her seferinde bir karakter çizebilirim.

Uzak statik tamponlar kullanırsanız, çizim işlemleri hızlıdır.

Ekran konumları (ekranın çapraz yüzdesine bağlı olarak) ve dokuları (statik ve karakterler) içeren bir XML dosyası oluşturuyor ve ardından bu XML'i oluşturmadan önce yüklüyorum.

Yüksek bir FPS oranı elde etmek için, çekim anında VBO'lar oluşturmaktan kaçınmalısınız.


"VOB" ile "VBO" (köşe arabelleği nesnesi) mi demek istiyorsunuz?
Dan Hulme

3

GL kullanmakta ısrar ediyorsanız, metni dokulara dönüştürebilirsiniz. Gösterge Paneli'nin çoğunun nispeten statik olduğunu varsayarsak, doku hafızasını çok sık yüklemeniz gerekmez.


3

CBFGYükleme / işleme kodunun Android bağlantı noktasına bir göz atın . Kodu projenize bırakıp hemen kullanabilmelisiniz.

  1. CBFG

  2. Android yükleyici

Bu uygulamayla ilgili sorunlarım var. Yazı tipinin bit eşleminin boyutunu değiştirmeye çalıştığımda (özel harflere ihtiyacım var) tüm çizim başarısız :(


2

Birkaç saattir bunu arıyordum, karşıma çıkan ilk makale buydu ve en iyi cevaba sahip olmasına rağmen, bence en popüler cevaplar uygun değil. Kesinlikle ihtiyacım olan şey için. Weichsel'in ve shakazed'in yanıtları hemen düğmenin üstündeydi, ancak makalelerde biraz gizlenmişti. Sizi doğrudan projeye sokmak için. Burada: Mevcut örneğe dayalı olarak yeni bir Android projesi oluşturun. ApiDemos'u seçin:

Kaynak klasörün altına bakın

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

Ve ihtiyacınız olan her şeyi bulacaksınız.


1

İçin statik metin :

  • Bilgisayarınızda kullanılan tüm sözcükleri içeren bir görüntü oluşturun (Örneğin GIMP ile).
  • Bunu bir doku olarak yükleyin ve bir düzlem için malzeme olarak kullanın.

İçin uzun metin ihtiyaçları arada bir güncellenmesi gerektiğini:

  • Android'in bir bit eşlem tuvali üzerinde çizmesine izin verin (JVitela'nın çözümü).
  • Bunu bir uçak için malzeme olarak yükleyin.
  • Her kelime için farklı doku koordinatları kullanın.

İçin bir dizi (00.0 biçimlendirilmiş):

  • Tüm sayılar ve bir noktadan oluşan bir görüntü oluşturun.
  • Bunu bir uçak için malzeme olarak yükleyin.
  • Gölgelendiricinin altında kullanın.
  • OnDraw etkinliğinizde yalnızca gölgelendiriciye gönderilen değer değişkenini güncelleyin.

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

Yukarıdaki kod, yazı tipi atlasının (doku) 2. satırının 7. sütununda sayıların 0'dan başladığı bir doku atlası için çalışır.

Gösteri için https://www.shadertoy.com/view/Xl23Dw adresine bakın (yine de yanlış doku ile)


0

OpenGL ES 2.0 / 3.0'da ayrıca OGL Görünümü ve Android'in kullanıcı arayüzü öğelerini birleştirebilirsiniz:

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

Düzen activity_gl.xml oluşturun:

<?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>

Oluşturma iş parçacığından öğeleri güncellemek için İşleyici / Döngüleyici kullanabilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.