Tag Archives: Text Rendering

เรื่องวุ่น ๆ กับตัวหนังสือ – ตอนที่ 5 – วาดตัวหนังสือแบบ Tile-Base

สมัยนี้หลาย ๆ คนอาจจะคิดว่าเราคงไม่ได้เขียนเกมแบบ Tile Base แท้ ๆ กันแล้ว ซึ่งก็คงจะจริงเพราะว่าส่วนใหญ่เราจะใช้การวาดกราฟิคแบบ 3D กันหมดแล้ว (ผ่าน OpenGL หรือ API อะไรก็แล้วแต่) แต่ผมว่าเรื่อง Tile Base เนี่ยมันน่าสนใจนะครับ แล้วมันก็คลาสสิคด้วย

image

ภาพข้างบนผมจับมาจากเกมที่ชื่อว่า Lufia II : Sin of Sinistral ซึ่งเป็นภาคแปลจากเกมที่ชื่อว่า Espolis Biography II … เกมนี้เป็นเกมที่ผมชอบมากเมื่อสมัยเด็ก ๆ เพราะเป็นเกมที่ต้องใช้ความคิดในการแก้ปริศนาเยอะมาก (ยากด้วย) ปัจจุบันมีการ Remake เกมนี้ขึ้นมาใหม่ กลายเป็น Lufia ภาคแรกไป (เพราะเนื้อเรื่องภาคนี้จะอยู่ก่อนหน้าอีกภาคนึง) ซึ่งไม่สนุกเหมือนตัวเก่า แต่ก็ออกมาแล้วหายคิดถึงไปเยอะเลย

นอกเรื่องมาไกล อยากให้สังเกตความเป็น Tile ของเกมน่ะครับ สำหรับเครื่องเกมในอดีต ถ้าเป็นของ Nintendo ก็นับตั้งแต่ Famicom ขึ้นมา ยันเครื่อง DS จะเป็นเครื่องที่รองรับการทำ Tile ในระดับฮาร์ดแวร์ ซึ่งทางนินเทนโดเองจะเรียกว่า Character ที่เรียกแบบนี้ก็เพราะจริง ๆ แล้วเรากำลังเขียนเกมอยู่บน Text Mode ที่สวยขึ้นมาอีกหน่อยเท่านั้นเอง

ที่ว่าเป็น Tile Base System ตั้งแต่ระดับ Hardware ก็คือตัว Hardware จะมีหน่วยความจำพิเศษสำหรับเก็บข้อมูล Tile และหน่วยความจำพิเศษอีกส่วนนึงที่เก็บข้อมูลกราฟิคบนจอ โดยจะเก็บเป็น aray ของ index ของ Tile ดังนั้นเราจะไม่มีทางที่จะวาดภาพบนจอภาพแบบระบุพิกัดเป็น Pixel ได้ นั่นคือที่มาว่าทำไมเกมสมัยก่อนจึงเป็นตารางครับ

กลับมาเข้าเรื่องของเรากัน จากภาพข้างบนเราจะเห็นว่ามีการแสดงผลข้อความที่เขียนว่า “Ta-daaa! It’s fish pot pie made of ‘navaroa’, the phantom fish!” นะครับ ผมคิดว่าคุณอาจจะคิดว่าข้อความนี้เก็บข้อมูลเป็นระหัส ASCII ซึ่งผมยืนยันได้เลยว่า “ไม่ใช่”

ในระบบแบบ Tile-base เนี่ย เราจะมองว่า ช่องไหนใช้ tile index ที่เท่าไหร่ การที่จะต้องเสียพื้นที่เก็บข้อมูลสำหรับ Tile โดยใช้ ASCII Code เนี่ย ไม่ Efficient เพราะว่า ASCII Code มีโค๊ดบางส่วนที่ไม่มีการแสดงผล การที่จะต้องเก็บข้อมูลว่าง ๆ ลงไปใน Tileset นั้นทำให้เสียพื้นที่ที่น้อยนิดไปอย่างฟรี ๆ ดังนั้น Encoding ที่ใช้นั้นจะเป็น Encoding ที่สร้างขึ้นมาใหม่ครับ (ผมเข้าใจว่ามี Tool ที่มาช่วยตรงนี้อยู่แล้ว)

อีกอย่างนึง อยากให้ดูโครงสร้างการเก็บข้อมูลหน้าจอของอุปกรณ์ประเภทนี้ สมมติว่า บัฟเฟอร์การแสดงผลของเรานั้นสามารถแสดงผลได้ 64*64 tile บนหน้าจอ มันจะเก็บข้อมูลแบบนี้ครับ

BYTE dispBuffer[64*64]; // 4096 tile

image

นั่นก็คือ ถ้าเราต้องการจะวาดตัวหนังสือให้อยู่ตรงกลาง เราจำเป็นจะต้องใส่รหัสพิเศษระหว่างตัวอักษรเพื่อจัดตำแหน่งให้ตัวหนังสือมันมาอยู่ตรงกลาง ในตำแหน่งที่เราต้องการด้วย

และสุดท้ายสังเกตตัวอักษรให้ดี ๆ นะครับ

image

สังเกตมั้ยว่าด้านสูงกับด้านกว้างของตัวอักษรไม่เท่ากัน จริง ๆ แล้วในแต่ละตัวอักษรมันเป็นสอง Tile ประกบกันบนล่างครับ

image

วิธีการใช้การประกบบนล่างแบบนี้จริง ๆ ก็มีข้อดีอย่างนึงคือ บาง Tile ก็ประหยัดได้ อย่าง ตัว i กับตัว j เนี่ย มันจะต่างกันแค่ด้านล่างใช่มั้ยครับ ถ้าด้านบนใช้ tile เดียวกัน ก็ประหยัดไปได้ 1 tile (ตัว h กับ n ก็ได้อีกคู่นะ)

และถ้าจะทำจริง ๆ เราสามารถทำให้บางตัวอักษรกว้างสองช่องก็ทำได้ครับ แต่พอดีผมหาตัวอย่างไม่เจอน่ะ

วันนี้พูดแค่ผ่าน ๆ ก็คงพอ เพราะว่าเราคงไม่ได้ใช้ tile-base แท้ ๆ กันสักเท่าไหร่แล้ว คราวต่อไปจะพูดถึงการวาดตัวอักษรแบบที่เป็น raster จริง ๆ แล้วครับ

เรื่องวุ่น ๆ กับตัวหนังสือ – ตอนที่ 4 – ประเภทของ Font

อย่างที่เคยบอกว่า Font คือ ชุดของตัวอักษร ถ้าจะให้อธิบายให้ละเอียดยิ่งขึ้น Font ก็คือ ชุดของภาพตัวอักษร (Glyph) ครับ

เราสามารถแบ่งประเภทของ Font ได้จากลักษณะของ Glyph ที่มันเก็บเอาไว้ได้สองลักษณะ ดังนี้

1. Raster Font

Raster Font ก็คือ Font ที่เก็บ Glyph ในลักษณะของ Raster … ก็คือ … Bitmap น่ะล่ะครับ

image

Font ในยุคต้น ๆ จะเป็น Raster ทั้งหมด เพราะว่าเครื่อง PC ในสมัยนั้นมีความสามารถต่ำ และ คนที่สร้างฟอนท์เองก็ไม่ได้เก่งอะไร (อย่าลืมว่าคอมพิวเตอร์ในยุคแรก ๆ นั้นผู้ใช้เป็นคนที่อยู่ในวงการคอมพิวเตอร์ และคณิตศาสตร์ เสียส่วนใหญ่นะครับ) การวาดฟอนท์ด้วยภาพ Bitmap นั้นไม่ได้ยากมาก แต่อาจจะดูไม่สวยอยู่สักหน่อย

2. Vector Font

Vector Font ก็คือ Font ที่เก็บข้อมูล Glyph ในแบบ Vector (กำปั้นทุบดินจริง ๆ ) เหมือนภาพใน Illustrator หรือ Corel Draw นั่นล่ะครับ

image

ในยุคหลัง ๆ มีความพยายามจากทางวงการสิ่งพิมพ์ว่า เขาอยากได้ฟอนท์ที่สามารถย่อ-ขยายได้ โดยที่ตัวหนังสือยังคงดูคมชัดเหมือนเดิม ก็เลยมีคนสร้างฟอนท์ที่ใช้ภาพ Vector ขึ้นมา เพราะสามารถยืดขยายได้ตามใจชอบ เรียกได้ว่าเป็นฟอนท์ที่มีความยืดหยุ่นสูงนั่นเอง

Raster vs Vector

มาดูข้อดีข้อเสียของฟอนท์แต่ละประเภทกันดีกว่า

1. ความยืดหยุ่น

  • Vector Font จะได้เปรียบในข้อที่ว่า ตัว Glyph สามารถยืดขยายได้โดยที่ไม่เสียคุณภาพภาพมากนัก
  • ในขณะที่ถ้าเป็น Raster Font พอยืดขยายแล้วในบางครั้ง Glyph อาจจะเละจนอ่านไม่ออกไปเลยก็มี

2. ความยากง่ายในการใช้งาน

  • Raster Font นั้น เก็บข้อมูลในรูปของภาพ Bitmap ซึ่ง การแสดงผลบนหน้าจอของคอมพิวเตอร์ในปัจจุบันก็เป็นภาพ Bitmap ทั้งหมด ดังนั้นเราจึงสามารถวาด Glyph ลงไปได้ตรง ๆ โดยที่ไม่ต้องมีการแปลงข้อมูล
  • Vector Font นั้น เก็บช้อมูลในรูปของชุดคำสั่งการวาดภาพ ดังนั้นเราต้องนำชุดคำสั่งนี้ไปประมวลผลก่อน (เรียกว่าการทำ Rasterization) เพื่อสร้างภาพ Bitmap ก่อนที่จะนำภาพนั้นขึ้นไปวาดบนจอ

แล้วเราจะใช้ Font แบบไหนดี ในเกมของเรา

ในเกมคอนโซลยุคเก่า ๆ Font จะเป็น Raster Font ทั้งหมด เพราะว่าเกมคอนโซลนั้นจะทำงานอยู่บนอุปกรณ์ที่มีความละเอียดคงที่ เช่น 640×480, 256×198 เป็นต้น ดังนั้นจึงไม่จำเป็นที่จะต้องใช้ฟอนท์ที่ย่อ/ขยายขนาดได้ รวมทั้งการทำ Rasterization ก็เป็นกระบวนการที่มีความซับซ้อนสูง และกินเวลาในการคำนวนค่อนข้างนานอีกด้วย

สำหรับเกมคอนโซลในยุคใหม่ ๆ อย่าง XBox 360 หรือ Playstatin 3 และเกม PC มักจะใช้ Vector Font เพราะว่าตัวระบบปฎิบัติการณ์ของแต่ละเครื่องรองรับการวาดภาพตัวอักษรด้วยฟอนท์ประเภทนี้อยู่แล้ว

แต่โดยส่วนตัวผมยังคิดว่า การวาดตัวอักษรด้วยฟอนท์แบบ Raster นั้นเหมาะสมกับเกมมากกว่า เพราะผู้ใช้แทบไม่มีความจำเป็นจะต้องควบคุมขนาดของตัวอักษรเลย และในความเป็นจริงเกมของเราก็ยังคงทำงานอยู่บนหน้าจอที่มีความละเอียดคงที่ (อาจจะรองรับแค่ 480i + 720p ก็พอ) หรือแม้ว่าเราจะอนุญาตให้ผู้ใช้ปรับแต่งความละเอียดเองได้ เราก็ยังคงสามารถจะ scale ภาพ Glyph ให้เล็กหรือใหญ่ขึ้นตามความละเอียดของหน้าจอได้อยู่ดี (แน่นอนว่าต้องแลกกับคุณภาพของภาพที่ได้ครับ)

เรื่องวุ่น ๆ กับตัวหนังสือ – ตอนที่ 3 – Character Encoding

บทที่แล้วมีเกริ่นถึง Character Encoding ไปนิดหน่อย ก็อย่างที่บอกว่า Encoding ก็คือ วิธีเข้ารหัสตัวอักษรให้อยู่ในรูปของรหัสที่คอมพิวเตอร์แยกแยะและนำไปใช้ได้ บทนี้เราจะพูดถึงรายละเอียดลึกลงไปหน่อยว่า เทคนิคการ Encoding ทั่ว ๆ ไป ที่ใช้ ๆ กันในมาตรฐานต่าง ๆ นั้นมันเป็นยังไงบ้าง

บทนี้จะพูดถึงเรื่อง Technical ล้วน ๆ ครับ ใครอยู่สายอื่นอาจจะไม่ต้องทำความเข่้าใจมันมากก็ได้ อันที่จริงมันไม่ค่อยมีประโยชน์ต่อคนในสาย Artist/Designer เท่าไหร่หรอก

String

ก่อนจะเข้าเรื่องการเข้ารหัส จะขอพูดถึง String ก่อน String ก็คือ สายอักขระ … สายของตัวอักษรนั่นล่ะ จริง ๆ ก็คือ ชุดของตัวอักษรที่เรียงต่อกันเป็นแนวยาว

ผมเองก็ไม่รู้ว่าทำไมต้องคิดมากขนาดนั้น เพราะจริง ๆ เราก็เขียนหนังสือเป็นแนวเดียว (ไม่ขวางก็ตั้ง) และในทิศทางเดียว แต่ก็นั่นล่ะคำคำนี้เป็นสิ่งที่วิศวกร/นักวิทยาศาสตร์ในอดีตเป็นคนบัญญัติขึ้น จะไม่เอามาใช้ก็เห็นจะแปลกไปนิด

ลักษณะพิเศษของ String ก็คือ มันเป็นข้อมูลแบบ Stream ที่ไหลไปทางเดียวกันตลอด จะสังเกตได้ว่าเวลาเราอ่านตัวหนังสือเราจะไม่ค่อยมีการอ่านย้อนกลับมากนัก (การอ่านข้อมูลในทิศทางเดียวเป็นลักษณะเฉพาะของ Stream) ดังนั้นเราจะมองว่า String คือ Stream ของ Character ก็ได้เหมือนกัน

Fixed-Length Character Encoding (การเข้ารหัสโดยใช้รหัสที่มีจำนวนหลักตายตัว)

การเข้ารหัสในลักษณะนี้จะเป็นการแทนที่ตัวอักษรใด ๆ ด้วยตัวเลขที่มีจำนวนหลัก (digit) แบบคงที่ เช่น การเข้่ารหัสด้วยตัวเลข 8หลัก (8บิท) 16หลัก หรือ 32 หลัก

ที่ผมพูดถึง ตัวเลขในที่นี้ หมายถึงเลขฐานสองนะครับ เพราะคอมพิวเตอร์คำนวนโดยใช้เลขฐานสอง และจำนวนหลักที่ว่านี้ก็คือจำนวนหลักของเลขฐานสองนั่นเอง

ASCII และ Extended ASCII

ASCII เป็น การเข้ารหัสโดยใช้รหัสแบบ 8 บิท (หรือ 1ไบท์) โดยที่หลักสุดท้ายนั้นจะเป็น 0 เสมอ
โดยเราจะนับหลักหน่วย (หลักขวาสุด) เป็นหลักแรกนะครับ ดังนั้นจะมีจำนวนรหัสที่เป็นไปได้ทั้งหมดที่ 128 รหัส (ก็คือ 0-127 นั่นเอง) ASCII เป็นมาตรฐานที่รวบรวมเอาตัวอักษรและสัญลักษณ์ในภาษาอังกฤษ ซึ่งรวมถึงรหัสอักขระพิเศษที่ไม่ได้ใช้แสดงผลเอาไว้ด้วย (รหัสอักขระพิเศษ คือรหัสอักขระที่มีหน้าที่พิเศษ เช่น ตัดบรรทัด ขึ้นย่อหน้าใหม่ เป็นต้น เป็นอักขระที่ไม่ได้ใช้ในการแสดงผลครับ) ASCII เป็นมาตรฐานแรก ๆ ในโลกที่ยังคงใช้จนถึงปัจจุบันนี้ครับ

Extended ASCII เป็นการเพิ่มเติมส่วนขยายให้แก่ ASCII โดยจะมีการใช้ 1 บิทสุดท้าย (ที่ไม่ได้ใช้ใน ASCII) เพื่อเพิ่มจำนวนรหัสให้มากขึ้นเป็น 256 รหัส เพื่อที่จะเพิ่มรหัสสำหรับตัวอักษรที่ไม่ได้ใช้ในภาษาอังกฤษนั่นเองครับ

ตัวอย่างสำหรับมาตรฐาน Extended ASCII ก็เช่น ISO8859-1 (ชุดตัวอักษรลาติน) TIS620 (ชุดตัวอักษรภาษาไทย) เป็นต้น

UTF-16

UTF-16 เป็นมาตรฐานการเข้ารหัสที่กำหนดโดย Unicode เป็นการเข้ารหัสโดยการใช้รหัสตัวเลขที่ยาวถึง 16 หลัก (16บิท หรือ 2 ไบท์) ทำให้สามารถรองรับจำนวนรหัสได้ถึง 65,532 รหัสครับ ที่ต้องมากถึงขนาดนี้เพราะว่า Unicode เป็นมาตรฐานที่รวมเอารหัสของตัวอักษรในภาษาหลัก ๆ ที่ใช้กันในโลกนี้เอาไว้นั่นเอง

UTF-16 สามารถแบ่งย่อย ๆ ได้ตาม Endian ของมัน ก็คือ Big Endian และ Little Endian UTF16 แบบ Little Endian จะเรียกว่า UTF-16le ครับ ท่านที่่ไม่รู้ว่า endian คืออะไร ก็ขอความกรุณาศึกษาเพิ่มเติมเอาเองนะครับ

Variable-Length Character Encodeing (การเข้ารหัสโดยใช้รหัสที่มีจำนวนหลักไม่คงที่)

อันนี้ก็จะตรงกันข้าม ก็คือ รหัสแต่ละตัวอาจจะสั้นยาวไม่เท่ากัน ตามแต่ผู้ออกแบบกำหนด โดยส่วนใหญ่จะใช้วิธีกำหนดช่วงเอาไว้ว่า รหัสในช่วงใดช่วงนึงจะมีความยาวที่ระดับนึง ในขณะที่ในอีกช่วงนึงก็จะมีความยาวที่อีกระดับนึง เช่น ตัวอักษร 128 ตัวแรก ใช้รหัสที่ยาว 8 หลัก (8บิท หรือ 1 ไบท์) ในขณะที่ตัวที่เหลือจะใช้ความยาวที่ 16 หลัก (16 บิท หรือ 2 ไบท์) เป็นต้น

การเข้ารหัสแบบนี้เกิดขึ้นในสมัยที่หน่วยความจำยังมีราคาแพง แต่ จำนวนตัวอักษรที่ Extended ASCII รองรับนั้นไม่เพียงพอต่อการใช้งานในบางภาษ เช่น ในภาษาจีนมีอักขระที่แตกต่างกันเป็นหมื่นตัว ภาษาญี่ปุ่นที่มีชุดอักขระมากถึงสามชุด เป็นต้น แต่การที่จะไปใช้การเข้ารหัสแบบ 16 บิทนั้นใช้หน่วยความจำมาถึง 2 เท่าของปรกติ และ ซอฟท์แวร์ที่ใช้จะเข้ากับซอฟท์ดั้งเดิมที่ใช้การเข้ารหัสแบบ 8 บิทไม่ได้ จึงมีการพัฒนาการเข้ารหัสแบบนี้ขึ้นมาครับ

สามภาษาที่เป็นต้นเหตุของการเข้ารหัสแบบนี้นั้น เป็นประเทศในเอเซียตะวันออกทั้งหมดเลย นั่นคือ จีน ญี่ปุ่น และเกาหลี เราจะเรียกกลุ่มเประเทศเรื่องมากพวกนี้ว่า กลุ่ม CJK ครับ

ตัวอย่างของการเข้ารหัสแบบนี้ก็คือ BIG5 (Chinese), Shift-JIS(Japanese), EUC-KR (Korean) เป็นต้น ผมจะไม่พูดถึงรายละเอียดนะครับ แต่ลองค้นคว้ากันดูก็ได้นะ

UTF-8

UTF-8 เป็นการเข้ารหัสที่กำหนดโดย UNICODE เช่นเดียวกับ UTF-16 โดยการเข้ารหัสแบบนี้จะใช้รหัสที่สั้นที่สุดที่ 8 บิท (1ไบท์) จนถึงยาวที่สุดที่ 4ไบท์ โดยตัวที่จะบอกว่ารหัสนั้นยาวแค่ไหนก็คือบิทสุดท้ายของไบท์แรกที่อ่านครับ

  • ถ้าหลักสุดท้ายของไบท์แรก คือ 0 รหัสนี้จะยาว 8บิท
  • ถ้า 3 หลักสุดท้ายของไบท์แรก คือ 110 รหัสนี้จะยาว 16บิท
  • ถ้า 4 หลักสุดท้ายของไบท์แรก คือ 1110 รหัสนี้จะยาว 32บิท

โดยเราจะนับหลักหน่วย (หลักขวาสุด) เป็นหลักแรกนะครับ

แล้ว Encoding ประเภทไหนที่เราจะใช้ในเกมล่ะ ???

ก่อนจะพูดถึงว่าเราจะใช้ประเภทไหน เราก็ต้องรู้ก่อนว่าข้อดีข้อเสียของแต่ละประเภทนั้นมันเป็นอะไรบ้างก่อนนะครับ

เมื่อนำทั้งสองตัวมาเปรียบเทียบกัน เราจะเห็นว่า

  • Fixed-Length เนี่ย ข้อดีคือ มันง่ายที่จะ Implement ครับ เพราะรหัสทุกตัวจะยาวเท่ากันหมด ส่วนข้อเสียคือมันใช้พื้นที่เยอะกว่าอีกแบบ
  • Variable-Length นั้น จะใช้พื้นที่น้อยกว่าครับ โดยเฉพาะถ้าเราเอาตัวอักษรที่ใช้บ่อยมาอยู่ในส่วนที่ใช้รหัสสั้น ๆ ได้ แต่ข้อเสียคือมันค่อนข้างลำบากกว่าที่จะ Implement

ดังนั้น ในเกม ซึ่งต้องการ Efficiency มาก ๆ เรามักจะใช้ Fixed-Length สำหรับการแสดงผล เพราะในระบบส่วนใหญ่จะมีหน่วยความจำมากเกินพอสำหรับการใช้งาน Encoding ประเภทนี้ครับ ในกรณีนี้การคำนวนที่เรียบง่ายนั้นเป็นสิ่งที่สำคัญกว่า และ Variable-Length จะใช้ในการส่งข้อมูลระหว่างเครื่องในเน็ทเวิร์ค เพราะขนาดของข้อมูลที่่รับส่งกันภายในเน็ทเวิร์คจะเล้กกว่า Fixed-length ทำให้รับส่งได้เร็วกว่าครับ