- พัฒนา เทคนิคการเรนเดอร์ ASCII ที่คงเส้นขอบและรูปทรงของภาพ เพื่อแก้ปัญหาขอบเบลอของวิธีแบบเดิม
- แทนที่จะแมปความสว่างแบบง่าย ๆ ในระดับพิกเซล ใช้ แนวทางแบบเวกเตอร์มิติสูง ที่วัดและจับคู่ รูปร่างเชิงภาพ (shape) ของแต่ละอักขระ
- วัดความหนาแน่นของแต่ละอักขระในบริเวณบน·ล่าง·ซ้าย·ขวา เพื่อสร้าง shape vector ที่ขยายจาก 2 มิติเป็น 6 มิติ ทำให้เลือกอักขระได้แม่นยำยิ่งขึ้น
- ใช้อัลกอริทึม เพิ่มคอนทราสต์แบบทั่วทั้งภาพและแบบมีทิศทาง (contrast enhancement) เพื่อให้เส้นขอบคมชัดขึ้น
- ด้วยการเร่งความเร็วด้วย GPU การแคช และการค้นหาด้วย k-d tree ทำให้ได้ ประสิทธิภาพการเรนเดอร์ ASCII แบบเรียลไทม์ และเอฟเฟกต์ภาพคุณภาพสูง
การแปลงภาพเป็น ASCII
- ASCII มีอักขระที่พิมพ์ได้ 95 ตัว และใช้ ฟอนต์โมโนสเปซ เพื่อแบ่งภาพเป็นกริด
- คำนวณความสว่างของแต่ละช่อง แล้วแมปตามความหนาแน่นของอักขระ
- การอินเตอร์โพเลตแบบ nearest-neighbor อย่างง่ายจะทำให้เกิด jaggies หรือขอบหยักไม่เรียบ
- หากใช้ supersampling เพื่อเก็บหลายตัวอย่างภายในแต่ละช่องแล้วคำนวณค่าเฉลี่ยความสว่าง ภาพจะนุ่มนวลขึ้น แต่ขอบก็ยังเบลออยู่
- แก่นของปัญหาคือการ ปฏิบัติต่ออักขระเหมือนพิกเซล โดยไม่ได้คำนึงถึงรูปร่างเฉพาะของอักขระ
การใช้รูปร่างของอักขระ (Shape)
- อักขระแต่ละตัวมี การกระจายความหนาแน่นเชิงภาพ ภายในช่องต่างกัน
- ตัวอย่าง:
T มีน้ำหนักด้านบนมาก ส่วน L มีน้ำหนักด้านล่างมาก
- เพื่อวัดสิ่งนี้ จึงวาง วงกลมสำหรับการสุ่มตัวอย่าง ไว้ภายในช่อง และคำนวณสัดส่วนพื้นที่ที่อักขระครอบครองในแต่ละส่วน
- แทนสัดส่วนการครอบครองของบริเวณบน·ล่างเป็นเวกเตอร์ เพื่อสร้าง shape vector แบบ 2 มิติ
- คำนวณ shape vector ของแต่ละอักขระไว้ล่วงหน้า แล้วเลือกอักขระที่ใกล้กับเวกเตอร์ตัวอย่างของภาพมากที่สุดด้วย Euclidean distance
การขยายเป็นเวกเตอร์รูปร่าง 6 มิติ
- ข้อมูล 2 มิติเฉพาะบน·ล่างยังไม่เพียงพอสำหรับอักขระอย่าง
-, p, q ที่มีจุดศูนย์กลางอยู่ตรงกลางหรือเอนซ้าย·ขวา
- ขยายช่องเป็นวงกลมสำหรับการสุ่มตัวอย่าง 6 จุด เพื่อจับทั้งความต่างของ บน·กลาง·ล่าง และซ้าย·ขวา
- shape vector 6 มิติ สะท้อนรูปร่างของอักขระได้ละเอียดขึ้นมาก และถ่ายทอดอักขระทรงกลมหรือเส้นทแยงได้ดี
- เมื่อใช้เรนเดอร์ฉาก 3D แม้เส้นขอบภายนอกจะคมชัด แต่ยังเกิดปัญหาขอบระหว่างพื้นผิวดูเบลอ
การเพิ่มคอนทราสต์ (Contrast Enhancement)
- ปรับองค์ประกอบแต่ละตัวของเวกเตอร์ตัวอย่างด้วย เลขชี้กำลัง (exponent) เพื่อให้ค่ามืดมืดยิ่งขึ้น ขณะที่ค่าที่สว่างยังคงเดิม
- นอร์มัลไลซ์เวกเตอร์ก่อน ใช้เลขชี้กำลัง แล้วจึงคืนกลับสู่ช่วงเดิม
- กระบวนการนี้ช่วย เพิ่มความต่างของเส้นขอบในเชิงภาพ ทำให้การเลือกอักขระชัดเจนขึ้น
- ในบริเวณที่มีความสว่างสม่ำเสมอแทบไม่เกิดการเปลี่ยนแปลง จึงยัง รักษาไล่ระดับที่นุ่มนวล ไว้ได้
- อย่างไรก็ตาม ขอบบางส่วนอาจเกิดอาการ staircasing เป็นขั้นบันได
การเพิ่มคอนทราสต์แบบมีทิศทาง (Directional Contrast Enhancement)
- วาง วงกลมสำหรับการสุ่มตัวอย่างภายนอก ไว้นอกแต่ละช่องด้วย เพื่อเก็บข้อมูลความสว่างของบริเวณรอบข้าง
- ค่าสว่างในเวกเตอร์ตัวอย่างภายนอกจะไปปรับให้องค์ประกอบที่สอดคล้องกันของเวกเตอร์ภายในมืดลง เป็นการ เพิ่มคอนทราสต์ตามทิศทางของขอบ
- เมื่อขยายการสุ่มตัวอย่างภายนอกให้ส่งผลระหว่างส่วนบน·กลาง·ล่างได้กว้างขึ้น ก็สามารถ ถ่ายทอดขอบที่นุ่มนวลและคมชัด ได้
- เมื่อนำไปรวมกับการเพิ่มคอนทราสต์แบบทั่วทั้งภาพ จะได้ การเรนเดอร์ ASCII สำหรับฉาก 3D ที่ขอบเด่นชัดและอ่านง่าย
การเพิ่มประสิทธิภาพ
- หากเลือกอักขระด้วย การค้นหาเพื่อนบ้านใกล้สุด แบบวนซ้ำตรง ๆ จะช้า จึงใช้ k-d tree เพื่อค้นหาอย่างรวดเร็วในปริภูมิหลายมิติ
- ใช้แคชเพื่อนำผลลัพธ์ของเวกเตอร์ตัวอย่างเดิมกลับมาใช้ซ้ำ
- ควอนไทซ์แต่ละเวกเตอร์เป็นหน่วย 5 บิต เพื่อสร้าง คีย์แคชที่ประหยัดหน่วยความจำ
- ตั้งช่วงค่าไว้ที่ 8 เพื่อรักษาสมดุลระหว่างคุณภาพกับการใช้หน่วยความจำ
- การค้นหาจากแคชทำได้รวดเร็วมาก และ สามารถประมวลผลอักขระหลายพันตัวแบบเรียลไทม์ได้
- ย้ายการคำนวณเวกเตอร์ตัวอย่างไปทำบน GPU เพื่อให้ การสุ่มตัวอย่างภายใน·ภายนอก และการเพิ่มคอนทราสต์ถูกประมวลผลใน shader pipeline
- ประสิทธิภาพสูงกว่า CPU หลายเท่า
บทสรุป
- แนวทางที่แปลงรูปร่างของอักขระเป็นเวกเตอร์เชิงตัวเลขแล้วนำมาใช้ ช่วยยกระดับความละเอียดและความคมชัดของการเรนเดอร์ ASCII อย่างมาก
- วิธีนี้มีแนวคิดคล้ายกับ word embedding และอาจประยุกต์ใช้กับปัญหาด้านภาพอื่น ๆ ได้
- แม้การทำงานช่วงแรกจะช้า แต่ด้วย GPU acceleration, caching และการค้นหาด้วย k-d tree ก็ทำให้ได้ FPS ที่ลื่นไหลแม้บนมือถือ
- บทความนี้ยังไม่ได้กล่าวถึงการแสดงผล ASCII แบบอิงสี และชี้ว่าต่อไปยังสามารถทดลองการผสมผสานรูปทรง·คอนทราสต์ได้หลากหลายขึ้น
- การเรนเดอร์ ASCII ไม่ได้เป็นเพียงเอฟเฟกต์ภาพธรรมดา แต่เป็นตัวอย่างที่แสดงให้เห็นถึง ศักยภาพในการขยายการรู้จำรูปทรงและการแทนค่าแบบเวกเตอร์
1 ความคิดเห็น
ความเห็นจาก Hacker News
ถ้าทำการ normalize เวกเตอร์แล้วคำนวณ ระยะห่างแบบยุคลิด ก็สามารถได้ผลลัพธ์เดียวกันด้วยการทำ matrix multiplication (matmul) ธรรมดา
เพราะสำหรับเวกเตอร์ที่ถูก normalize แล้ว ระยะห่างแบบยุคลิดเป็น linear transform ของ cosine distance
ถ้าสิ่งที่สำคัญคือแค่ ลำดับอันดับ (ranking) ไม่ใช่ค่าระยะจริง ก็ข้ามการคำนวณ sqrt ได้และจะได้ผลลัพธ์เหมือนเดิม พร้อมคำนวณได้เร็วขึ้นเล็กน้อย
ชอบบทความแนวนี้มาก ภายนอกดูเหมือนง่าย แต่ถ้าจะทำให้ออกมาดีจริง ๆ ต้องมี การเจาะลึกอย่างจริงจัง
ข้อเขียนของ Lucas Pope ตอนพัฒนาระบบ dithering ของ Return of The Obra Dinn ก็แนะนำเช่นกัน
บันทึกการพัฒนาของ Lucas Pope
สะดุดใจกับประโยคที่ว่า “สร้างภาพดาวเสาร์ด้วย ChatGPT”
ทั้งที่ภาพถ่ายดาวเสาร์จริงมีอยู่เต็มไปหมดใน public domain เลยสงสัยว่าทำไมต้องสร้างภาพปลอมมาทำให้อินเทอร์เน็ตปนเปื้อนด้วย
สักวันหนึ่งเราอาจไม่ต้องเขียนวิกิหรือเว็บไซต์หรือแม้แต่ฟอรัมด้วยตัวเองแล้วก็ได้
ถ้าสามารถสร้าง “ภาพดาวเสาร์คอนทราสต์สูงขนาด X×Y” ได้ทันที นั่นคงเป็น การเปลี่ยนแปลงระดับเวทมนตร์
เช่นเดียวกับที่เครื่องคิดเลขหรืออินเทอร์เน็ตไม่ได้ฆ่าความคิดสร้างสรรค์ มนุษย์จะเลือกใช้ เครื่องมือที่มีแรงเสียดทานน้อยที่สุด และยังคงมุ่งหน้าไปหาดวงดาวต่อไปเสมอ
ทุกครั้งที่ดูตัวอย่างจะคิดว่า “ก็ดีนะ แต่ยังปรับปรุงได้อีกนี่?” แล้วก็ทึ่งที่ผู้เขียนแก้ปัญหานั้นให้จริง ๆ
เป็นบทความที่สวยงามมาก และทั้งบล็อกก็มี ความลึกในระดับนี้ จนคุ้มค่าที่จะติดตาม
alexharri.com/blog
ตอนทำโปรเจกต์ ascii-side-of-the-moon เคยลังเลว่าจะลองเขียน ASCII renderer เองดีไหม
สุดท้ายใช้ chafa แต่ก็คิดว่าไว้วันหนึ่งจะลองใหม่อีกครั้ง
เลยสงสัยว่ามีแผนจะปล่อยสิ่งนี้เป็นไลบรารีไหม หรือสามารถอ้างอิงจาก โค้ดของเว็บไซต์ ได้หรือเปล่า
ตอนนี้ยังไม่มีแผนทำเป็นไลบรารี แต่ถ้าต้องการก็หยิบโค้ดจากเว็บไซต์ไปใช้ได้อย่างอิสระ
ถ้าจะทำจริง ๆ ก็น่าจะต้องมี การแปลง WebGL 2 → WebGL 1 เพื่อให้รองรับได้กว้างขึ้น และต้องมีเครื่องมือสำหรับคำนวณ shape vector ของแต่ละฟอนต์ล่วงหน้าด้วย
สำหรับคำพูดที่ว่า “ไม่เคยเห็นตัวอย่างใน ASCII art ที่ใช้ shape มาก่อน” จริง ๆ แล้วมีตัวสร้างที่ใช้ shape อยู่
เป็นโปรเจกต์ชื่อ ascii-silhouettify ซึ่งใช้อัลกอริทึมที่เลือกอักขระที่ใหญ่ที่สุดให้สอดคล้องกับเส้นขอบของพื้นที่สี
Acerola เคยลองทำ ASCII rendering แบบอิง edge detection ในปี 2024
วิธีคือซ้อนสัญลักษณ์ที่มีทิศทาง (| / - \) ทับบนพาสที่อิงความสว่าง
ดูได้ในวิดีโอที่เกี่ยวข้อง
เช่น อาจลองใช้เส้นขอบหนาแบบงาน 2D ดั้งเดิม หรือแสดงคอนทราสต์แสงเงานุ่ม ๆ แบบ Chiaroscuro ก็ยังได้
ฟิลเตอร์ ASCII ส่วนใหญ่ไม่ได้คำนึงถึง รูปร่าง (shape) ของ glyph
chafa จัดการ glyph แต่ละตัวเป็นบิตแมป 8×8 ซึ่งเป็นแนวทางที่น่าประทับใจ
พอดู ซอร์สของ chafa กับ แกลเลอรี ก็จะรู้สึกได้ถึงความประณีตนั้น
เลยสงสัยว่าแนวทางที่เน้นทิศทางจะถ่ายทอดรูปทรงที่ใหญ่กว่าได้ดีกว่าหรือไม่
ดู oldschool PC fonts แล้วเหมือนหลุดเข้าไปในโพรงกระต่ายที่ไม่มีที่สิ้นสุดจริง ๆ
ตอนว่างกำลังทดลองทำ กราฟิกสีแบบอิงอักษรเบรลล์ อยู่
ความละเอียดมีพอ แต่ความแม่นยำของการแสดงสีไม่พอ จึงต้องมี การปรับคอนทราสต์ (contrast fixup) หลังจาก sampling
คิดว่าน่าจะลองเอาเทคนิค sampling ของผู้เขียนมาปรับใช้เพื่อเสริมคอนทราสต์ของสีได้
ก่อนหน้านี้เคยพยายามเพิ่มคอนทราสต์ด้วย Sobel filter แต่ไม่สำเร็จเพราะมันไม่ตรงกับการจัดแนวของกริดตัวอักษร
เป็นแนวทางเชิงเทคนิคที่ยอดเยี่ยมและวิเคราะห์ได้ลึกมาก
ตอนท้ายแอบหวังว่าจะได้เห็น Cognition cube array เวอร์ชันปรับปรุง แต่ก็ไม่มีเลยน่าเสียดาย
ทำให้นึกถึงนักออกแบบคนหนึ่งบน YouTube ที่เคยทำ favicon ให้ดีขึ้นด้วย subpixel color contrast
บทความที่เกี่ยวข้อง (Web Archive)
ถึงอย่างนั้นตัวบทความเองก็ยอดเยี่ยม และตัวอย่างแบบโต้ตอบก็ประทับใจมาก