ทำ 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 วัน
- อินเทอร์เฟซผู้ใช้ (เครื่องมือ 3D, ทางลัดคีย์บอร์ด, แถบด้านข้าง, game controller)
- ตัวสร้าง shader GLSL + เรนเดอร์ Ray marching
- การเลือกเมาส์ด้วย GPU
- 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 ความคิดเห็น
ความคิดเห็นจาก Hacker News