Vẽ văn bản trong OpenGL ES


131

Tôi hiện đang phát triển một trò chơi OpenGL nhỏ cho nền tảng Android và tôi tự hỏi liệu có cách nào dễ dàng để hiển thị văn bản trên đầu khung được kết xuất không (như HUD với điểm số của người chơi, v.v.). Các văn bản sẽ cần phải sử dụng một phông chữ tùy chỉnh cũng.

Tôi đã thấy một ví dụ sử dụng Chế độ xem dưới dạng lớp phủ, nhưng tôi không biết liệu tôi có muốn làm điều đó không vì tôi có thể muốn chuyển trò chơi sang các nền tảng khác sau này.

Có ý kiến ​​gì không?


hãy xem dự án này: code.google.com/p/rokon
whunmr

Nhìn vào cách libgdx thực hiện điều này thông qua Phông chữ Bitmap.
Robert Massaioli

Câu trả lời:


103

SDK Android không đi kèm với bất kỳ cách dễ dàng nào để vẽ văn bản trên chế độ xem OpenGL. Để lại cho bạn các tùy chọn sau.

  1. Đặt TextView trên SurfaceView của bạn.Đây là chậm và xấu, nhưng cách tiếp cận trực tiếp nhất.
  2. Kết xuất các chuỗi phổ biến cho kết cấu, và chỉ cần vẽ các kết cấu đó.Đây là cách đơn giản nhất và nhanh nhất, nhưng kém linh hoạt nhất.
  3. Cuộn mã kết xuất văn bản của riêng bạn dựa trên một sprite. Có lẽ là sự lựa chọn tốt thứ hai nếu 2 không phải là một lựa chọn. Một cách tốt để làm ướt chân nhưng lưu ý rằng mặc dù có vẻ đơn giản (và các tính năng cơ bản), nhưng sẽ khó khăn và khó khăn hơn khi bạn thêm nhiều tính năng (căn chỉnh kết cấu, xử lý ngắt dòng, phông chữ có độ rộng thay đổi, v.v. ) - nếu bạn đi theo con đường này, hãy làm cho nó đơn giản như bạn có thể thoát khỏi!
  4. Sử dụng một thư viện nguồn mở / sẵn có. Có một vài thứ xung quanh nếu bạn săn trên Google, một chút khó khăn là khiến chúng được tích hợp và chạy. Nhưng ít nhất, một khi bạn làm điều đó, bạn sẽ có tất cả sự linh hoạt và trưởng thành mà họ cung cấp.

3
Tôi đã quyết định thêm chế độ xem qua GLView của mình, đây có thể không phải là cách hiệu quả nhất để thực hiện nhưng chế độ xem không được cập nhật thường xuyên, cộng với nó giúp tôi linh hoạt thêm bất kỳ phông chữ nào tôi muốn. Cảm ơn vì tất cả những hồi đáp!
run rẩy

1
Làm cách nào tôi có thể kết xuất các chuỗi phổ biến thành kết cấu và chỉ cần vẽ các kết cấu đó? Cảm ơn.
VansFannel

1
VansFannel: chỉ cần sử dụng chương trình vẽ để đặt tất cả các chuỗi của bạn vào một hình ảnh, sau đó trong ứng dụng của bạn sử dụng offset để chỉ hiển thị phần hình ảnh có chứa chuỗi bạn muốn.displayed.
Dave

2
Hoặc xem câu trả lời của JVitela dưới đây để biết cách lập trình hơn để đạt được điều này.
Dave

4
Câu trả lời của JVitela là tốt hơn. Đó là những gì tôi đang sử dụng. Lý do tại sao bạn chuyển từ tiêu chuẩn android vier + canvas sang opengl là (trong số những người khác) cho tốc độ. Thêm một hộp văn bản trên loại opengl của bạn phủ nhận điều đó.
Rồng Shivan

166

Kết xuất văn bản thành một kết cấu đơn giản hơn so với bản demo Sprite Text trông giống như vậy, ý tưởng cơ bản là sử dụng lớp Canvas để kết xuất thành Bitmap và sau đó chuyển Bitmap sang kết cấu 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
Điều này giúp tôi hiển thị tại quầy fps nhỏ trên ứng dụng của mình để gỡ lỗi, cảm ơn!
tàng hình

2
Bạn có thể sử dụng điều này để tạo ra tất cả các chữ cái và số dưới dạng kết cấu khi bạn tải các kết cấu khác của bạn và sau đó đặt chúng lại với nhau để tạo thành từ hoặc số. Sau đó, nó sẽ không kém hiệu quả hơn bất kỳ kết cấu gl khác.
twDuke

9
Điều này rất chậm, nó sẽ giết chết khung hình / giây trên một trò chơi cho văn bản luôn thay đổi (điểm số, v.v.), tuy nhiên nó hoạt động tốt cho các công cụ bán tĩnh (tên người chơi, tên cấp độ, v.v.).
dẫn42

3
Tôi muốn lưu ý rằng mã trong câu trả lời này có lẽ chỉ là bản demo và không được tối ưu hóa cho đến nay! Vui lòng tối ưu hóa / bộ nhớ cache theo cách riêng của bạn.
Sherif elKhatib

1
Bạn có thể cung cấp cái này cho OpenGL ES 2.0 không?
không giới hạn

36

Tôi đã viết một hướng dẫn mở rộng về câu trả lời được đăng bởi JVitela . Về cơ bản, nó sử dụng cùng một ý tưởng, nhưng thay vì hiển thị từng chuỗi thành một kết cấu, nó kết xuất tất cả các ký tự từ tệp phông thành kết cấu và sử dụng điều đó để cho phép hiển thị văn bản động hoàn toàn mà không bị chậm lại (khi quá trình khởi tạo hoàn tất) .

Ưu điểm chính của phương pháp của tôi, so với các trình tạo tập bản đồ phông khác nhau, là bạn có thể gửi các tệp phông chữ nhỏ (.ttf .otf) với dự án của bạn thay vì phải gửi bitmap lớn cho mỗi biến thể và kích thước phông chữ. Nó có thể tạo phông chữ chất lượng hoàn hảo ở mọi độ phân giải chỉ bằng một tệp phông chữ :)

Các hướng dẫn bao gồm mã đầy đủ mà có thể được sử dụng trong bất kỳ dự án :)


Tôi hiện đang xem xét giải pháp này và tôi chắc chắn rằng tôi sẽ tìm thấy câu trả lời kịp thời, nhưng việc triển khai của bạn có sử dụng bất kỳ phân bổ thời gian chạy nào không?
Nick Hartung

@Nick - Tất cả các phân bổ (kết cấu, bộ đệm đỉnh, v.v.) được thực hiện khi tạo một thể hiện phông chữ, kết xuất chuỗi bằng cách sử dụng phiên bản phông chữ không yêu cầu phân bổ thêm. Vì vậy, bạn có thể tạo phông chữ tại "thời gian tải" mà không cần phân bổ thêm vào thời gian chạy.
free3dom

Wow, công việc tuyệt vời! Điều này thực sự hữu ích đặc biệt trong trường hợp văn bản của bạn thay đổi thường xuyên.
mdiener

Đây là câu trả lời tốt nhất, hiệu năng, cho các ứng dụng phải thay đổi văn bản thường xuyên khi chạy. Không thấy bất cứ điều gì tốt đẹp như thế này cho Android. Nó có thể sử dụng một cổng để OpenGL ES, mặc dù.
greeble31

1
Nếu bạn không muốn xử lý căn chỉnh văn bản, ngắt dòng, v.v. - bạn có thể sử dụng TextView. Một TextView có thể dễ dàng được kết xuất thành một khung vẽ. Hiệu suất không nên nặng hơn cách tiếp cận đã cho, bạn chỉ cần một phiên bản TextView để hiển thị tất cả các văn bản bạn cần. Bằng cách này, bạn cũng có được định dạng HTML đơn giản miễn phí.
Gena Batsyan

8

Theo liên kết này:

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

Bạn có thể kết xuất bất kỳ Chế độ xem nào thành bitmap. Có lẽ bạn nên giả sử rằng bạn có thể bố trí chế độ xem theo yêu cầu (bao gồm văn bản, hình ảnh, v.v.) và sau đó hiển thị nó thành Bitmap.

Sử dụng mã của JVitela ở trên, bạn sẽ có thể sử dụng Bitmap đó làm kết cấu OpenGL.


Vâng, tôi đã làm điều đó với MapView trong một trò chơi và ràng buộc nó với kết cấu gl để kết hợp các bản đồ và opengl. Vì vậy, yeah, tất cả các chế độ xem đều có onDraw (Canvas c) và bạn có thể chuyển bất kỳ canvas nào vào và liên kết bất kỳ canvas nào với bất kỳ bitmap nào.
HaMMeReD


6

Tôi đã xem xét ví dụ văn bản sprite và nó có vẻ cực kỳ phức tạp đối với một tác vụ như vậy, tôi cũng đã xem xét kết xuất thành một kết cấu, nhưng tôi lo lắng về hiệu năng có thể gây ra. Thay vào đó, tôi có thể phải đi với một góc nhìn và lo lắng về việc chuyển khi đến lúc đi qua cây cầu đó :)



4

IMHO có ba lý do để sử dụng OpenGL ES trong trò chơi:

  1. Tránh sự khác biệt giữa các nền tảng di động bằng cách sử dụng một tiêu chuẩn mở;
  2. Để có nhiều quyền kiểm soát quá trình kết xuất;
  3. Để hưởng lợi từ việc xử lý song song GPU;

Vẽ văn bản luôn là một vấn đề trong thiết kế trò chơi, bởi vì bạn đang vẽ mọi thứ, vì vậy bạn không thể có giao diện của một hoạt động chung, với các vật dụng, v.v.

Bạn có thể sử dụng khung để tạo phông chữ Bitmap từ phông chữ TrueType và hiển thị chúng. Tất cả các khung tôi đã thấy hoạt động theo cùng một cách: tạo tọa độ đỉnh và kết cấu cho văn bản trong thời gian vẽ. Đây không phải là cách sử dụng OpenGL hiệu quả nhất.

Cách tốt nhất là phân bổ bộ đệm từ xa (đối tượng bộ đệm đỉnh - VBO) cho các đỉnh và kết cấu sớm trong mã, tránh các hoạt động chuyển bộ nhớ lười biếng trong thời gian vẽ.

Hãy nhớ rằng người chơi trò chơi không thích đọc văn bản, vì vậy bạn sẽ không viết một văn bản được tạo động dài. Đối với nhãn, bạn có thể sử dụng họa tiết tĩnh, để lại văn bản động theo thời gian và điểm số và cả hai đều là số có một vài ký tự.

Vì vậy, giải pháp của tôi rất đơn giản:

  1. Tạo kết cấu cho các nhãn và cảnh báo phổ biến;
  2. Tạo kết cấu cho các số 0-9, ":", "+" và "-". Một kết cấu cho mỗi nhân vật;
  3. Tạo VBO từ xa cho tất cả các vị trí trong màn hình. Tôi có thể hiển thị văn bản tĩnh hoặc động ở các vị trí đó, nhưng VBO là tĩnh;
  4. Tạo chỉ một Texture VBO, vì văn bản luôn được hiển thị một chiều;
  5. Trong thời gian vẽ, tôi kết xuất văn bản tĩnh;
  6. Đối với văn bản động, tôi có thể nhìn trộm vị trí VBO, lấy kết cấu ký tự và vẽ nó, một ký tự tại một thời điểm.

Thao tác vẽ rất nhanh, nếu bạn sử dụng bộ đệm tĩnh từ xa.

Tôi tạo một tệp XML với các vị trí màn hình (dựa trên tỷ lệ phần trăm đường chéo của màn hình) và kết cấu (tĩnh và ký tự), sau đó tôi tải XML này trước khi kết xuất.

Để có được tốc độ FPS cao, bạn nên tránh tạo VBO vào thời gian bốc thăm.


Theo "VOB", bạn có nghĩa là "VBO" (đối tượng bộ đệm đỉnh)?
Dan Hulme

3

Nếu bạn khăng khăng sử dụng GL, bạn có thể kết xuất văn bản thành họa tiết. Giả sử rằng hầu hết HUD tương đối tĩnh, bạn không nên tải họa tiết vào bộ nhớ kết cấu quá thường xuyên.


3

Hãy xem CBFGvà cổng Android của mã tải / kết xuất. Bạn sẽ có thể thả mã vào dự án của bạn và sử dụng nó ngay lập tức.

  1. CBFG

  2. Trình tải Android

Tôi có vấn đề với việc thực hiện này. Nó chỉ hiển thị một ký tự, khi tôi thử thay đổi kích thước bitmap của phông chữ (tôi cần các chữ cái đặc biệt), toàn bộ bản vẽ không thành công :(


2

Tôi đã tìm kiếm điều này trong một vài giờ, đây là bài viết đầu tiên tôi tìm thấy và mặc dù nó có câu trả lời tốt nhất, những câu trả lời phổ biến nhất tôi nghĩ là không đúng. Chắc chắn cho những gì tôi cần. Câu trả lời của weichsel và shakazed nằm ngay trên nút nhưng hơi bị che khuất trong các bài viết. Để đặt bạn ngay vào dự án. Tại đây: Chỉ cần tạo một dự án Android mới dựa trên mẫu hiện có. Chọn ApiDemos:

Xem trong thư mục nguồn

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

Và bạn sẽ tìm thấy mọi thứ bạn cần.


1

Đối với văn bản tĩnh :

  • Tạo một hình ảnh với tất cả các từ được sử dụng trên PC của bạn (Ví dụ với GIMP).
  • Tải cái này như một kết cấu và sử dụng nó làm vật liệu cho một mặt phẳng.

Đối với văn bản dài cần được cập nhật một lần trong một thời gian:

  • Hãy để android vẽ trên khung hình bitmap (giải pháp của JVitela).
  • Tải này làm vật liệu cho một mặt phẳng.
  • Sử dụng các tọa độ kết cấu khác nhau cho mỗi từ.

Đối với một số (được định dạng 00.0):

  • Tạo một hình ảnh với tất cả các số và một dấu chấm.
  • Tải này làm vật liệu cho một mặt phẳng.
  • Sử dụng shader dưới đây.
  • Trong sự kiện onDraw của bạn chỉ cập nhật biến giá trị được gửi tới trình đổ bóng.

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

Mã ở trên hoạt động cho một tập bản đồ kết cấu trong đó các số bắt đầu từ 0 ở cột thứ 7 của hàng thứ 2 của tập bản đồ phông chữ (kết cấu).

Tham khảo https://www.shadertoy.com/view/Xl23Dw để trình diễn (mặc dù với kết cấu sai)


0

Trong OpenGL ES 2.0 / 3.0, bạn cũng có thể kết hợp các yếu tố UI của OGL và 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);
        ...
    }
}

Tạo bố cục 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>

Để cập nhật các phần tử từ luồng kết xuất, có thể sử dụng Handler / Looper.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.