2 คะแนน โดย GN⁺ 2024-05-03 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

ทำ 3D โมเดลเลอร์ด้วย C ในหนึ่งสัปดาห์

  • เข้าร่วมกิจกรรมโปรแกรมมิงระยะเวลา 1 สัปดาห์ชื่อ “Wheel Reinvention Jam” ในฤดูใบไม้ร่วงปีที่แล้ว
  • จุดมุ่งหมายคือการกลับมามองระบบซอฟต์แวร์เดิมด้วยมุมมองใหม่
  • สร้าง 3D โมเดลเลอร์ชื่อ “ShapeUp” และก่อนอ่านบล็อกนี้ ควรดูวิดีโอดีโมของ ShapeUp ก่อนเพื่อช่วยให้เข้าใจมากขึ้น
  • ShapeUp ใช้งานได้ในเบราว์เซอร์โดยตรง

การเลือกภาษา: C

  • เข้าร่วม Jam เพราะไม่พอใจกับความช้าของการคอมไพล์ TypeScript
  • หากเริ่มจากตัวแยกวิเคราะห์ (parser) ของ TypeScript ใน esbuild หรือ Bun ดูเหมือนว่าจะสามารถทำโปรเจกต์ที่สร้าง subset ที่เร็วของ TypeScript ได้
  • แต่เมื่อเปรียบเทียบความเร็วในการรันคำสั่งเทอร์มินัลแล้วไม่ก็น่าจะเป็นเดโมที่น่าสนใจ จึงหันไปทำโปรเจกต์ 3D
  • ด้วยเทคโนโลยี Ray marched signed distance fields (SDFs) จึงรู้สึกว่าการสร้างโปรเจกต์ 3D ตั้งแต่ศูนย์ในเวลาเพียงหนึ่งสัปดาห์เป็นไปได้
  • ฉากที่ใช้ SDF ทำได้เร็วกว่าระบบเรนเดอร์แบบอาศัยสามเหลี่ยมที่มีความสามารถเทียบเท่า
  • เคยเขียน shader ของ SDF มาก่อน แต่มีความเข้าใจเพียงพื้นฐาน และการโมเดลด้วยการแก้โค้ดรู้สึกไม่เป็นธรรมชาติ
  • อยากแก้ไขรูปร่างด้วยเมาส์ และคิดว่า Jam นี้เป็นโอกาสที่ดีในการทำให้เป็นจริง
  • ตั้งชื่อโปรเจกต์ว่า ShapeUp

ข้อดีของการใช้ภาษา C

  • C เป็นภาษาที่เรียบง่ายและดิบ จึงอาจคิดว่าใช้เวลามากกับการแก้ปัญหาความขาดแคลนโครงสร้างข้อมูลในตัวและบั๊กที่เกี่ยวกับ pointer
  • แต่ความเรียบง่ายของ C กลับเป็นข้อดี
    • คอมไพล์ได้เร็ว
    • ไวยากรณ์ไม่ซ่อนการคำนวณที่ซับซ้อน
    • เรียบง่าย จึงไม่ต้องค้นหาไวยากรณ์อยู่ตลอดเวลา
    • คอมไพล์เป็น native และ WebAssembly ได้ง่าย
  • ข้อเสียของ C สามารถหลีกเลี่ยงได้ด้วยนิสัยที่พัฒนาจากการใช้งาน 22 ปี
  • ShapeUp มีความเรียบง่ายมาก อยู่ในไฟล์ C เดียวขนาดเล็ก

โครงสร้างข้อมูลของ ShapeUp

  • โมเดลเป็นอาร์เรย์ของโครงสร้างที่ชื่อว่า Shapes
  • Shapes ถูกเก็บในอาร์เรย์ที่จัดสรรแบบคงที่
    • ไม่มีความเสี่ยงเรื่องการจัดสรรที่ล้มเหลวหรือการรั่วของหน่วยความจำ
    • ขีดจำกัด 100 Shape ในทางปฏิบัติไม่เป็นข้อจำกัด
    • เนื่องจากเวลาเพิ่มประสิทธิภาพเรนเดอร์ไม่พอ คาดว่าก่อนถึง 100 กรอบเรตจะเริ่มตก
    • ถ้ามีเวลาเพิ่มขึ้น คงจะแบ่งโมเดลเป็นบล็อกเล็ก ๆ แล้วทำ raymarching ภายในแต่ละบล็อก
  • มีการเรียก malloc ในหน่วยความจำแบบ dynamic เพียง 3 จุด
    • บันทึก (จองบัฟเฟอร์ขนาดใหญ่พอที่เก็บเอกสารทั้งหมด)
    • ส่งออก OBJ (จองบัฟเฟอร์ขนาดใหญ่พอที่เก็บจุดยอดทั้งหมด)
    • สร้าง shader GLSL (บัฟเฟอร์สำหรับซอร์สโค้ด shader)
  • ในทุกกรณีมี free ตัวเดียวอยู่ท้ายฟังก์ชัน
  • เป็นตัวอย่างที่แสดงให้เห็นว่าใน C การจัดการหน่วยความจำสามารถทำได้ง่าย
  • ภาษาอย่าง C#, JavaScript, Python จะบังคับรูปแบบการจัดสรรที่ต้องเรียก malloc แยกต่อแต่ละ Shape และเก็บ pointer เหล่านั้นในอาร์เรย์แบบ dynamic
  • C มีข้อดีตรงควบคุม memory layout ได้

อินเทอร์เฟซผู้ใช้

  • ใช้ immediate mode user interface (IMGUI)
  • ชอบ UI แบบ IMGUI
    • ดีบักได้ง่ายมาก
    • ใช้ภาษาการเขียนโปรแกรมจริงในการจัดวางองค์ประกอบ (ไม่เหมือน CSS, constraints, SwiftUI)
  • โดยทั่วไป IMGUI ก็ใช้ enum เพื่อติดตามว่าองค์ประกอบใดมีโฟกัสหรือเมาส์กำลังทำปฏิสัมพันธ์อะไร
  • โปรเจกต์นี้ไม่ต้องใช้ dynamic array หรือ hash map แต่ถ้าต้องใช้ก็คงใช้ stb_ds.h เป็นต้น

ปัญหาของไลบรารี raylib

  • การตัดสินใจใช้ C เป็นอย่างดี แต่ raylib กลับเป็นจุดเดือด
  • มีทางออกแบบที่ไม่ค่อยเป็นมิตรกับประสบการณ์นักพัฒนา
    • ใช้ int ในที่ที่ควรเป็น enum ทำให้การตรวจชนิดของคอมไพเลอร์ไม่ได้ผล และฟังก์ชันไม่ self-document
    • ไม่ตรวจสอบความถูกต้องของพารามิเตอร์ค่าเริ่มต้น (ตามการออกแบบ)
    • ไม่รับผิดชอบต่อ dependency (ไม่แก้ปัญหาหรือส่ง patch ให้ GLFW)
  • ไลบรารี UI raygui เป็นเหมือนของเล่น
    • แสดงตัวเลขทศนิยมไม่ได้
    • ไม่จัดการเส้นทางเหตุการณ์เมาส์สำหรับองค์ประกอบที่ซ้อนทับหรือถูกคลิป
    • ทำมุมโค้งไม่ได้
    • ไม่สามารถสไตล์ให้ดูดี
  • มีบั๊กอีก
    • บั๊กป้องกันการเปลี่ยนฟอนต์
    • ฟังก์ชันวาดไม่แบ่งปันจุดยอดระหว่างสามเหลี่ยม จึงเกิดช่องว่างพิกเซล
  • เมื่อพบปัญหาทุกครั้งก็รายงาน แต่ส่วนใหญ่ปิดเป็น “won’t fix” และรายงานบั๊กใช้เวลามากจึงยกเลิกไป
  • OpenGL window ที่ให้มาเป็นข้อดี แต่จ่ายต้นทุนความสะดวกที่ค่อนข้างสูง
  • โชคดีที่สามารถหาทางออกได้ด้วยการใช้ฟังก์ชัน OpenGL โดยตรงหรือสร้างคุณสมบัติใหม่ตั้งแต่ต้น
  • ในอนาคตจะใช้ sokol

กระบวนการพัฒนาในหนึ่งสัปดาห์

  • ShapeUp เป็นส่วนสำคัญ 4 ส่วนที่ต้องเสร็จภายใน 6 วัน
    1. อินเทอร์เฟซผู้ใช้ (เครื่องมือ 3D, ทางลัดคีย์บอร์ด, แถบด้านข้าง, game controller)
    2. ตัวสร้าง shader GLSL + เรนเดอร์ Ray marching
    3. การเลือกเมาส์ด้วย GPU
    4. Marching cubes for export
  • แต่ละส่วนไม่ยากมาก แต่ยากตรงการจัดลำดับความสำคัญให้ถูกต้องและไม่หลุดไปจากเป้าหมาย
  • เรื่องยากหรือต้องใช้เวลามากช่วยแก้ได้ด้วยการออกแบบ หรือใช้วิธีแก้ที่ดูหยาบแต่ทำงานได้ประมาณ 90%
  • บางครั้งแค่เลื่อนการพัฒนา feature ไปสักวันหนึ่ง ก็ช่วยให้ค้นพบคำตอบแบบไม่รู้ตัว
  • รักษาให้มี 3D โมเดลเลอร์ที่ทำงานได้ตลอดเวลา และค่อยๆ ปรับปรุงเพิ่มทีละขั้นตามเวลาที่มี
  • คิดแบบการทำพีระมิด ทำเป็นชั้น ๆ แม้หยุดได้กลางทาง แต่ทุกขั้นสามารถเป็นพีระมิดที่สมบูรณ์ได้

ผลลัพธ์ของโปรเจกต์

  • หลังหนึ่งสัปดาห์ ผมมีโปรแกรม 3D ที่สามารถสร้างโมเดล 3D ที่มีความหมายและส่งออกไฟล์ .obj ได้
  • รันได้หลายแพลตฟอร์มและมีฟังก์ชันเปิด/บันทึกไฟล์
  • โปรเจกต์นี้ประกอบด้วยโค้ด C 2,024 บรรทัดและ GLSL 250 บรรทัด
  • ประมาณ 2,300 บรรทัดสามารถอธิบายแนวคิด 3D โมเดลเลอร์ที่ใช้งานได้ได้อย่างน่าประทับใจนิดหน่อย
  • ได้รับคำเชิญให้สรุปงาน Jam และสาธิตเดโม ShapeUp ที่งาน Handmade Seattle conference
  • ผู้คนดูเหมือนประทับใจ ShapeUp แต่ไม่รู้สึกว่ามันเป็นความสำเร็จที่ยิ่งใหญ่ เป็นโปรเจกต์ที่ค่อนข้างเรียบง่าย
  • ถ้าต้องหาสิ่งที่พิเศษในสิ่งที่ผมทำ คือ สัญชาตญาณในการเลือกว่าจะทำอะไร ความรู้ที่ต้องใช้ในการทำ และวินัยในการเสร็จภายในหนึ่งสัปดาห์

ความเห็นของ GN⁺

  • เป็นโปรเจกต์ที่น่าสนใจซึ่งชี้ให้เห็นจุดเด่นของความเรียบง่ายและความเร็วของ C ได้ดี แต่ด้วยระดับการนามธรรมที่ต่ำของ C จึงดูเหมือนว่าใช้งานในโปรเจกต์เชิงพาณิชย์ได้ยาก หากจะนำฟีเจอร์ของเครื่องมือโมเดลลิ่ง 3D สมัยใหม่ทั้งหมดไปพัฒนาใน C คาดว่าจะต้องใช้ความพยายามมาก
  • การมีโปรแกรมที่ทำงานได้ภายในหนึ่งสัปดาห์เป็นเรื่องน่าชื่นชม แต่ในภาพรวมระยะยาวเรื่องการดูแลรักษาและเพิ่มฟีเจอร์ C++ หรือ Rust อาจเป็นตัวเลือกที่ดี
  • เทคนิคเรนเดอร์ด้วย SDF เร็วและง่าย แต่มีข้อจำกัดด้านอิสระในการโมเดลและคุณภาพ ด้านหนึ่งเชิงพาณิชย์มักใช้ SubD หรือ NURBS ซึ่งเป็นการโมเดลแบบพื้นผิวเป็นหลัก แต่สำหรับเกมหรือตัวอย่างที่ต้องการเรียลไทม์ SDF rendering ยังมีคุณค่าใช้งานสูง
  • เป็นกรณีตัวอย่างชัดเจนถึงความท้าทายของการเลือกไลบรารีโอเพ่นซอร์ส ต้องพิจารณาเอกสาร คุณภาพโค้ด และการสนับสนุนอย่างรอบคอบก่อนเลือก และการพัฒนาขึ้นเองก็เป็นทางเลือกที่ดี
  • การทำโปรแกรมให้ใช้งานได้ก่อน แล้วค่อยๆ พัฒนาเพิ่มทีละนิดเป็นแนวทางที่มีประโยชน์มากในงานจริง การทำให้ฟังก์ชันหลักเสร็จก่อนและค่อยปรับรายละเอียดให้ดีขึ้นเป็นเรื่องสำคัญมาก

1 ความคิดเห็น

 
GN⁺ 2024-05-03
ความคิดเห็นจาก Hacker News
  • เห็นด้วยกับผู้เขียนอย่างเต็มที่ในเรื่องข้อจำกัดของ Raylib
    • ขณะนี้กำลังพัฒนาเกมแนวป้องกันป้อมสไตล์ tower defense โดยเริ่มจาก Raylib อยู่ และกำลังเจอข้อจำกัดแบบเดียวกัน
    • มีปัญหาหลายอย่าง เช่น การสลับโหมดเต็มหน้าจอระหว่างแพลตฟอร์มไม่ตรงกัน การไม่สามารถแสดงรายชื่อโหมดหน้าจอได้ การสลับฟังก์ชันการเรนเดอร์ในช่วง runtime ไม่ได้ และการบันทึกเชเดเดอร์ที่คอมไพล์แล้วได้
    • Raylib เหมาะสำหรับการทำโปรโตไทป์ แต่การก้าวข้ามข้อจำกัดที่รุนแรงแบบนี้ต่อไปจึงยากมาก
    • งานพัฒนาได้คืบหน้าไปมากแล้ว ตอนนี้แทบช้าเกินไปที่จะเปลี่ยนจาก Raylib ไปใช้ SDL หรืออย่างอื่นได้
  • การเก็บรูปทรงไว้ในอาร์เรย์ที่จัดสรรแบบคงที่เป็นวิธีที่เรียบง่ายและสวยงาม เพราะไม่เสี่ยงต่อความล้มเหลวจากการจัดสรรหน่วยความจำหรือการรั่วไหลของหน่วยความจำ โดยตัวเลขจำกัด 100 รูปร่างก็ไม่ใช่อุปสรรคในทางปฏิบัติ
  • หวังว่าโปรเจกต์นี้จะพัฒนาไปอีกต่อไปอีกหน่อย สักไม่กี่เดือนอาจกลายเป็นทางเลือกที่จริงจังสำหรับกรณีใช้งานเฉพาะบางอย่างที่มี learning curve ที่อ่อนโยนกว่าได้เมื่อเทียบกับ Blender/FreeCAD
  • ประทับใจกับไลฟ์เดโมในวิดีโอมาก สร้างแอปไม่ได้เลย แค่การทำวิดีโอแบบนั้นในหนึ่งสัปดาห์ย่อมไม่ง่าย
  • บทความนี้น่าสนใจมากที่พูดถึงการตัดสินใจหลากหลาย ทั้งเรื่องการจัดการหน่วยความจำ ขณะนี้ฉันกำลังกลับมาศึกษา C อีกครั้งจากการก้าวเข้าสู่ภาค 2 ของ Crafting Interpreters และช่วยเตือนใจถึงสิ่งที่ C ทำได้ดี
  • ขอบคุณสำหรับความพยายามที่ทำให้สิ่งนี้เป็นจริงด้วยโค้ด C เพียง 2,024 บรรทัด :)
  • การสร้างสิ่งที่เจ๋ง ๆ ขึ้นมาโดยใช้เครื่องมือที่เราคุ้นเคยมาก่อน มีพลังบางอย่างที่ยอดเยี่ยมมาก อ่านคอมเมนต์ดีมากครับ
  • เห็นด้วยกับข้อถกเถียงเรื่อง C อย่างมาก โดยเฉพาะอย่างยิ่งกับคำพูดว่า "ไวยากรณ์ไม่ปิดบังการทำงานที่ซับซ้อน ทำให้ไม่ต้องคอยค้นหาต่อเนื่อง" ฉันยิ่งอินมาก อีกอย่าง ถ้าจำเป็นต้องหาข้อมูล C อะไรสักอย่าง มักหาง่ายและมีประโยชน์มาก เป็นข้อดีของภาษาที่เรียบง่ายและดั้งเดิม
  • บางครั้งคิดว่า C คือสิ่งที่เราจำเป็นทั้งหมด
  • ความเร็วในการพัฒนานี้ยิ่งใหญ่มาก และวิดีโออธิบายงานก็ดูน่าดูมาก