วาดข้อความใน OpenGL ES


131

ฉันกำลังพัฒนาเกม OpenGL ขนาดเล็กสำหรับแพลตฟอร์ม Android และฉันสงสัยว่ามีวิธีง่ายๆในการแสดงข้อความที่ด้านบนของเฟรมที่แสดงผลหรือไม่ (เช่น HUD ที่มีคะแนนของผู้เล่นเป็นต้น) ข้อความจะต้องใช้แบบอักษรที่กำหนดเองด้วย

ฉันเคยเห็นตัวอย่างที่ใช้มุมมองเป็นภาพซ้อนทับ แต่ฉันไม่รู้ว่าฉันต้องการทำเช่นนั้นหรือไม่เพราะฉันอาจต้องการพอร์ตเกมไปยังแพลตฟอร์มอื่นในภายหลัง

ความคิดใด ๆ ?


ดูโครงการนี้: code.google.com/p/rokon
whunmr

ดูวิธีที่ libgdx ทำผ่าน Bitmap Fonts
Robert Massaioli

คำตอบ:


103

Android SDK ไม่ได้มาพร้อมกับวิธีง่ายๆในการวาดข้อความบนมุมมอง OpenGL ปล่อยให้คุณมีตัวเลือกต่อไปนี้

  1. วาง TextView บน SurfaceView ของคุณ นี่เป็นวิธีที่ช้าและไม่ดี แต่เป็นแนวทางที่ตรงที่สุด
  2. สร้างสตริงทั่วไปให้กับพื้นผิวและเพียงแค่วาดพื้นผิวเหล่านั้น นี่เป็นวิธีที่ง่ายและเร็วที่สุด แต่ยืดหยุ่นน้อยที่สุด
  3. ม้วนโค้ดการแสดงผลข้อความของคุณเองตามสไปรต์ อาจเป็นทางเลือกที่ดีที่สุดอันดับสองถ้า 2 ไม่ใช่ตัวเลือก วิธีที่ดีในการทำให้เท้าเปียก แต่โปรดทราบว่าแม้ว่าจะดูเหมือนเรียบง่าย (และมีคุณสมบัติพื้นฐาน) แต่ก็ยากและท้าทายมากขึ้นเมื่อคุณเพิ่มคุณสมบัติอื่น ๆ (การจัดแนวพื้นผิวการจัดการกับการแบ่งบรรทัดแบบอักษรความกว้างตัวแปรเป็นต้น ) - หากคุณใช้เส้นทางนี้ทำให้ง่ายที่สุดเท่าที่จะทำได้!
  4. ใช้ไลบรารีนอกชั้นวาง / โอเพนซอร์ส มีอยู่สองสามอย่างหากคุณค้นหาบน Google สิ่งที่ยุ่งยากคือการทำให้พวกมันรวมเข้าด้วยกันและทำงานได้ แต่อย่างน้อยเมื่อคุณทำเช่นนั้นคุณจะมีความยืดหยุ่นและความเป็นผู้ใหญ่ที่มีให้

3
ฉันตัดสินใจที่จะเพิ่มมุมมองบน GLView ของฉันมันอาจไม่ใช่วิธีที่มีประสิทธิภาพมากที่สุด แต่มุมมองนั้นไม่ได้รับการอัปเดตบ่อยนักนอกจากนี้ยังให้ความยืดหยุ่นในการเพิ่มแบบอักษรที่ฉันต้องการ ขอบคุณสำหรับทุกคำตอบ!
สั่น

1
ฉันจะสร้างสตริงทั่วไปเป็นพื้นผิวและวาดพื้นผิวเหล่านั้นได้อย่างไร ขอบคุณ.
VansFannel

1
VansFannel: เพียงใช้โปรแกรมระบายสีเพื่อใส่สตริงทั้งหมดของคุณในภาพเดียวจากนั้นในแอพของคุณใช้ออฟเซ็ตเพื่อแสดงเฉพาะส่วนของรูปภาพที่มีสตริงที่คุณต้องการเท่านั้นที่แสดง
Dave

2
หรือดูคำตอบของ JVitela ด้านล่างสำหรับวิธีที่เป็นโปรแกรมมากขึ้นในการบรรลุเป้าหมายนี้
Dave

4
คำตอบของ JVitela ดีกว่า นั่นคือสิ่งที่ฉันกำลังใช้อยู่ เหตุผลที่คุณเปลี่ยนจาก Android vier + canvas มาตรฐานเป็น opengl คือ (ท่ามกลางคนอื่น ๆ ) เพื่อความรวดเร็ว การเพิ่มกล่องข้อความบนประเภท opengl ของคุณจะเป็นการลบล้างสิ่งนั้น
Shivan Dragon

168

การแสดงผลข้อความเป็นพื้นผิวนั้นง่ายกว่าที่การสาธิต 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
สิ่งนี้ช่วยให้ฉันแสดงที่ตัวนับ fps เล็ก ๆ ในแอปของฉันสำหรับการดีบักขอบคุณ!
stealthcopter

2
คุณสามารถใช้สิ่งนี้เพื่อสร้างตัวอักษรและตัวเลขทั้งหมดเป็นพื้นผิวเมื่อคุณโหลดพื้นผิวอื่น ๆ ของคุณแล้วนำมารวมกันเพื่อสร้างคำหรือตัวเลข จากนั้นจะมีประสิทธิภาพไม่น้อยไปกว่าเนื้อ gl อื่น ๆ
twDuke

9
สิ่งนี้ช้ามากมันจะฆ่า fps ในเกมสำหรับข้อความที่เปลี่ยนแปลงตลอดเวลา (คะแนน ฯลฯ ) อย่างไรก็ตามมันทำงานได้ดีสำหรับสิ่งกึ่งคงที่ (ชื่อผู้เล่นชื่อระดับ ฯลฯ )
led42

3
ฉันต้องการทราบว่ารหัสในคำตอบนี้อาจเป็นเพียงการสาธิตและไม่ได้รับการปรับให้เหมาะสมเท่าที่เคยมีมา! โปรดทำการเพิ่มประสิทธิภาพ / แคชในแบบของคุณเอง
Sherif elKhatib

1
คุณช่วยให้ OpenGL ES 2.0 ได้หรือไม่
unlimited101

36

ฉันได้เขียนบทช่วยสอนที่ขยายความเกี่ยวกับคำตอบที่โพสต์โดยJVitela JVitelaโดยทั่วไปจะใช้แนวคิดเดียวกัน แต่แทนที่จะแสดงผลแต่ละสตริงเป็นพื้นผิวจะแสดงผลอักขระทั้งหมดจากไฟล์แบบอักษรไปยังพื้นผิวและใช้สิ่งนั้นเพื่อให้สามารถแสดงผลข้อความแบบไดนามิกเต็มรูปแบบโดยไม่มีการชะลอตัวอีกต่อไป (เมื่อการเริ่มต้นเสร็จสมบูรณ์) .

ข้อได้เปรียบหลักของวิธีการของฉันเมื่อเทียบกับโปรแกรมสร้างฟอนต์ atlas ต่างๆคือคุณสามารถจัดส่งไฟล์ฟอนต์ขนาดเล็ก (.ttf .otf) ไปกับโปรเจ็กต์ของคุณแทนที่จะต้องจัดส่งบิตแมปขนาดใหญ่สำหรับรูปแบบและขนาดฟอนต์ทุกรูปแบบ มันสามารถสร้างฟอนต์คุณภาพเยี่ยมที่ความละเอียดใดก็ได้โดยใช้ไฟล์ฟอนต์เท่านั้น :)

กวดวิชามีรหัสเต็มรูปแบบที่สามารถนำมาใช้ในโครงการใด ๆ :)


ฉันกำลังพิจารณาวิธีแก้ปัญหานี้และฉันแน่ใจว่าจะพบคำตอบได้ทันเวลา แต่การใช้งานของคุณใช้การจัดสรรเวลาทำงานหรือไม่
Nick Hartung

@Nick - การจัดสรรทั้งหมด (พื้นผิวบัฟเฟอร์จุดยอด ฯลฯ ) เสร็จสิ้นเมื่อสร้างอินสแตนซ์แบบอักษรการแสดงผลสตริงโดยใช้อินสแตนซ์แบบอักษรไม่จำเป็นต้องมีการจัดสรรเพิ่มเติม ดังนั้นคุณสามารถสร้างแบบอักษรที่ "เวลาโหลด" โดยไม่มีการจัดสรรเพิ่มเติมในขณะทำงาน
free3dom

ว้าวงานดีมาก! สิ่งนี้มีประโยชน์มากโดยเฉพาะในกรณีที่ข้อความของคุณมีการเปลี่ยนแปลงบ่อยๆ
mdiener

นี่คือคำตอบที่ดีที่สุดสำหรับแอพพลิเคชั่นที่ต้องเปลี่ยนข้อความบ่อยๆขณะรันไทม์ ยังไม่เห็นอะไรที่ดีเท่านี้สำหรับ Android สามารถใช้พอร์ตไปยัง OpenGL ES ได้
greeble31

1
หากคุณไม่ต้องการจัดการกับการจัดแนวข้อความการแบ่งบรรทัด ฯลฯ คุณสามารถใช้ TextView TextView สามารถแสดงผลเป็นผืนผ้าใบได้อย่างง่ายดาย ประสิทธิภาพที่ชาญฉลาดไม่ควรหนักกว่าแนวทางที่กำหนดคุณต้องใช้อินสแตนซ์ TextView เพียงชุดเดียวเพื่อแสดงข้อความทั้งหมดที่คุณต้องการ ด้วยวิธีนี้คุณจะได้รับการจัดรูปแบบ HTML อย่างง่ายฟรี
Gena Batsyan

8

ตามลิงค์นี้:

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

คุณสามารถแสดงมุมมองเป็นบิตแมปได้ อาจคุ้มค่าที่จะสมมติว่าคุณสามารถจัดวางมุมมองได้ตามที่คุณต้องการ (รวมถึงข้อความรูปภาพ ฯลฯ ) จากนั้นแสดงเป็นบิตแมป

การใช้โค้ดของ JVitela ด้านบนคุณจะสามารถใช้ Bitmap นั้นเป็นพื้นผิว 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. สร้าง Texture 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)
  • ใส่สิ่งนี้เป็นพื้นผิวและใช้เป็นวัสดุสำหรับเครื่องบิน

สำหรับข้อความยาว ๆที่ต้องอัปเดตนาน ๆ ครั้ง:

  • ให้ Android วาดบนผ้าใบบิตแมป (โซลูชันของ JVitela)
  • โหลดสิ่งนี้เป็นวัสดุสำหรับเครื่องบิน
  • ใช้พิกัดพื้นผิวที่แตกต่างกันสำหรับแต่ละคำ

สำหรับตัวเลข (รูปแบบ 00.0):

  • สร้างภาพด้วยตัวเลขและจุดทั้งหมด
  • โหลดสิ่งนี้เป็นวัสดุสำหรับเครื่องบิน
  • ใช้ด้านล่าง shader
  • ในเหตุการณ์ onDraw ของคุณอัปเดตเฉพาะตัวแปรค่าที่ส่งไปยัง 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;
    
    }
    

โค้ดด้านบนใช้สำหรับแผนที่พื้นผิวโดยที่ตัวเลขเริ่มต้นจาก 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);
        ...
    }
}

สร้างเค้าโครง 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>

ในการอัปเดตองค์ประกอบจากเธรดการแสดงผลสามารถใช้ Handler / Looper

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.