- สรุปประสบการณ์การลงมือสร้าง เอนจินเกมขนาดเล็กพร้อมเดโมเกม 2 เกม ด้วยตัวเอง ระหว่างเรียนรู้ Vulkan เป็นเวลา 3 เดือน
- ค่อย ๆ เอาชนะความซับซ้อนของ Vulkan แบบเป็นขั้นเป็นตอน ต่อยอดจากประสบการณ์เดิมกับ OpenGL พร้อมทำฟีเจอร์หลักอย่างการโหลด glTF, skinning และ shadow mapping
- เอนจินชื่อ EDBR (Elias Daler’s Bikeshed Engine) มีโค้ดราว 19,000 บรรทัด และใช้เทคนิคกราฟิกสมัยใหม่ เช่น bindless descriptor, PVP, BDA
- ในบทความยังแชร์รายละเอียดเชิงปฏิบัติของการพัฒนา เช่นไลบรารีจำเป็นอย่าง vk-bootstrap, VMA, volk รวมถึง pipeline pattern, การทำ shader build อัตโนมัติ, การจัดการ synchronization
- การย้ายมาใช้ Vulkan ทำให้ได้ การตัด global state ออก, การควบคุมแบบ explicit, สภาพแวดล้อมสำหรับดีบักที่ดีขึ้น, ความสม่ำเสมอข้าม GPU และมีแผนเพิ่ม render graph, ฟอนต์ SDF, volumetric effects ต่อไป
ภาพรวมการเรียนรู้ Vulkan และการพัฒนาเอนจิน
- ผู้เขียนเริ่มเรียนกราฟิกส์โปรแกรมมิงด้วยตัวเอง และเคยเขียนเอนจิน 3D ด้วย OpenGL มาก่อนเมื่อราว 1 ปีครึ่งก่อนหน้านี้
- เอนจินที่ใช้ Vulkan ตัวนี้เหมาะกับ เกมขนาดเล็กแบบเป็นด่าน โดยเน้นเป้าหมายด้านการเรียนรู้และการทดลอง มากกว่าประสิทธิภาพ
- ในช่วงแรกใช้แนวทางทำเกม 3D ง่าย ๆ ก่อน แล้วค่อยแยกส่วนที่นำกลับมาใช้ซ้ำได้ออกมาให้กลายเป็นเอนจิน
- เหตุผลที่ทำเสร็จได้ในเวลา 3 เดือน เป็นเพราะจำกัดขอบเขตให้เป็น เอนจินเฉพาะจุดประสงค์ ไม่ใช่เอนจินสารพัดประโยชน์
เส้นทางการเรียนรู้กราฟิกส์โปรแกรมมิง
- สำหรับผู้เริ่มต้น แนะนำให้เริ่มจาก OpenGL เพื่อเรียนรู้การแสดงโมเดลพร้อมเท็กซ์เจอร์, แสงแบบ Blinn-Phong, shadow mapping เป็นต้น
- แหล่งเรียนรู้ที่แนะนำ ได้แก่ learnopengl.com, Anton’s OpenGL 4 Tutorials, Thorsten Thormählen lectures
- ผู้เขียนยังเน้นความสำคัญของการเข้าใจ พีชคณิตเชิงเส้น (เวกเตอร์, เมทริกซ์, ควอเทอร์เนียน) ควบคู่ไปกับสื่อฝึกปฏิบัติ OpenGL 4.6 สมัยใหม่
คำแนะนำเพื่อหลีกเลี่ยง bike-shedding
- หลีกเลี่ยง การออกแบบและ abstraction ที่มากเกินจำเป็น และยึดหลัก “ทำเฉพาะสิ่งที่จำเป็นตอนนี้”
- แนะนำแนวทาง “ทำให้มันใช้งานได้ก่อน แล้วค่อยปรับปรุงทีหลัง”
- การทำ เกมเล็ก ๆ ให้เสร็จก่อน มีประสิทธิภาพกว่าการมุ่งไปที่ เอนจินอเนกประสงค์
- อย่าลอกเลียนโค้ดหรือโครงสร้างซับซ้อนของคนอื่นตรง ๆ แต่ควรเริ่มจากโครงสร้างเรียบง่าย
เหตุผลที่เลือก Vulkan
- เกม AAA มักใช้ DirectX, บน macOS/iOS ใช้ Metal และบนเว็บใช้ WebGPU/WebGL เป็นหลัก
- ผู้เขียนเลือก Vulkan เพราะ ชอบโอเพนซอร์สและเทคโนโลยีมาตรฐาน และเหมาะกับเป้าหมายการทำเกม 3D เดสก์ท็อปขนาดเล็ก
- OpenGL ไม่ได้พัฒนาต่อแล้ว และถูกยกเลิกการรองรับบน macOS
- WebGPU แม้จะกระชับกว่า แต่ยังมีข้อจำกัด เช่น ยังไม่เสถียรพอ, ฟีเจอร์จำกัด, ไม่รองรับ bindless และ push constants
กระบวนการเรียนรู้ Vulkan
- ในช่วงแรกมันยากจนให้ความรู้สึกเหมือน “กำลังเขียนกราฟิกไดรเวอร์เอง”
แต่การมาของ dynamic rendering, vk-bootstrap, vkguide ทำให้เข้าถึงได้ง่ายขึ้น
- แหล่งเรียนรู้หลัก:
- vkguide.dev (ปูพื้นฐานและเน้นลงมือทำ)
- TU Wien Vulkan Lecture Series
- 3D Graphics Rendering Cookbook, Mastering Graphics Programming with Vulkan
- ภายในเดือนแรกก็ทำการโหลด glTF, compute skinning, frustum culling และ shadow mapping ได้สำเร็จ
โครงสร้างเอนจิน EDBR และการประมวลผลเฟรม
- โค้ดเอนจินมีประมาณ 19,000 บรรทัด, เกม 3D มี 4,600 บรรทัด, เกมแพลตฟอร์ม 2D มี 1,200 บรรทัด
- ลำดับขั้นตอนเรนเดอร์หลัก:
- Compute skinning → Cascaded Shadow Mapping (4096×4096) → geometry shading แบบ PBR
- Depth Resolve → Post FX (หมอกตามความลึก) → UI rendering
- ระบบกราฟิกทั้งหมดถูก เขียนใหม่สำหรับ Vulkan โดยเฉพาะ โดยไม่ผสมกับโค้ด OpenGL เดิม
เคล็ดลับเชิงปฏิบัติในการพัฒนา Vulkan
ไลบรารีที่แนะนำ
- vk-bootstrap: ทำให้การตั้งค่าเริ่มต้นและ swapchain ง่ายขึ้น
- Vulkan Memory Allocator (VMA): ช่วยจัดการหน่วยความจำแบบอัตโนมัติ
- volk: ทำให้การโหลดฟังก์ชันส่วนขยายง่ายขึ้น
abstraction ของ GfxDevice
- จัดการ
VkDevice, VkQueue, VmaAllocator และองค์ประกอบอื่น ๆ ผ่านออบเจ็กต์เดียว
- รับผิดชอบการเริ่ม/จบเฟรม, การสร้าง image และ buffer, และการจัดการ bindless descriptor
การจัดการเชดเดอร์
- ใช้ GLSL และ precompile เป็น SPIR-V ด้วย
glslc ตอน build
- ใช้ CMake
DEPFILE เพื่อ rebuild อัตโนมัติเมื่อ shader มีการเปลี่ยนแปลง
pipeline pattern
- แยกแต่ละขั้นของการเรนเดอร์เป็น pipeline ระดับคลาส (
init, cleanup, draw)
- ใช้
VK_KHR_dynamic_rendering เพื่อ ไม่ต้องมี render pass และ subpass ทำให้โครงสร้างเรียบง่าย
Programmable Vertex Pulling + Buffer Device Address
- ใช้ โครงสร้าง Vertex แบบเดียว กับทุกเมช
- ให้ shader เข้าถึงโดยตรงผ่าน buffer_reference จึงไม่ต้องใช้ VAO
Bindless Descriptor
- ใช้อาร์เรย์เท็กซ์เจอร์แบบ global (
textures[], samplers[]) เพื่อ sample จาก texture ID
- เก็บ texture ID ไว้ในโครงสร้าง material แล้วส่งผ่าน push constants
การอัปโหลดข้อมูลแบบไดนามิก
- ส่งข้อมูลด้วยการสลับ GPU buffer รายเฟรม หรือใช้ CPU staging buffer
- ใช้คลาส
NBuffer เพื่อจัดการโครงสร้าง frame-in-flight
การเคลียร์รีซอร์สและ synchronization
- ใช้ ฟังก์ชัน cleanup แบบ explicit และจัดการด้วยมือ แทนการพึ่งการเคลียร์อัตโนมัติใน destructor
- ใช้
vkCmdPipelineBarrier2 เพื่อทำ memory synchronization ระหว่างแต่ละ pass
- มีแผนจะทำ Render Graph ในอนาคต
ตัวอย่างรายละเอียดการทำจริง
การเรนเดอร์สไปรต์
- ใช้ bindless texture และ instancing เพื่อเรนเดอร์สไปรต์หลายพันตัวในครั้งเดียว
- อัปโหลดโครงสร้าง
SpriteDrawCommand ไปยัง GPU buffer แล้วเรียก vkCmdDraw(6, N)
- เรนเดอร์สไปรต์ 10,000 ตัวได้ในเวลา 315μs
Compute skinning
- ใช้ compute shader ทำ vertex deformation ตาม bone matrix และน้ำหนัก
- สร้าง output buffer แยกสำหรับแต่ละอินสแตนซ์ แล้วนำไปใช้ต่อในขั้นเรนเดอร์แบบเดียวกัน
การแยกเกมกับเรนเดอเรอร์
- ลอจิกเกมใช้ entt ECS ส่วนเรนเดอเรอร์ประมวลผลเพียง เวกเตอร์ของ DrawCommand
- สร้างคำสั่งเรนเดอร์ผ่านการเรียก
drawMesh, drawSkinnedMesh
การโหลดซีนและ prefab
- จัดเลย์เอาต์ด่านใน Blender แล้วส่งออกเป็น glTF และใช้กติกาการตั้งชื่อโหนดเพื่อ spawn prefab อัตโนมัติ
- prefab นิยามด้วย JSON และอ้างอิง glTF ภายนอก
MSAA, UI, ImGui
- ใช้ MSAA x8 บนพื้นฐาน forward rendering
- สร้างระบบ auto layout ที่ได้แรงบันดาลใจจาก Roblox UI API
- เขียน Vulkan backend เองเพื่อแก้ ปัญหา sRGB ของ Dear ImGui
องค์ประกอบอื่น ๆ
- ใช้ Jolt Physics สำหรับฟิสิกส์, entt สำหรับ ECS, OpenAL-soft สำหรับเสียง และ Tracy สำหรับโปรไฟล์
ข้อดีของการย้ายมาใช้ Vulkan
- การ ตัด global state ออก ทำให้ได้โครงสร้างโค้ดที่ explicit และเป็นโมดูลมากขึ้น
- validation layers และการดีบักด้วย RenderDoc ช่วยให้ตามหาปัญหาได้ง่าย
- ได้ ความสม่ำเสมอระหว่าง GPU และ OS ที่ดีขึ้น และพฤติกรรมคาดเดาได้มากกว่า OpenGL
- เปิดทางให้ขยายต่อด้วย ภาษา shading ใหม่ ๆ (slang, shady)
- มี abstraction น้อยลงและควบคุม pipeline ได้ชัดเจน จึงดูแลง่ายขึ้น
แผนในอนาคต
- มีแผนเพิ่ม ฟอนต์ SDF, การโหลดภาพแบบขนานและการสร้าง mipmap, Bloom, volumetric fog, animation blending, render graph, AO
- แม้การเรียน Vulkan จะยาก แต่ก็ช่วยอย่างมากในการ เข้าใจ modern graphics API และพัฒนาทักษะการออกแบบเอนจิน
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
แม้จะผ่านไปแล้วตั้งแต่ โพสต์ของฉัน เมื่อ 1 ปีก่อน ความคิดของฉันเกี่ยวกับ Vulkan ก็แทบไม่เปลี่ยน
มันอาจน่าสนใจสำหรับคนที่ต้องการควบคุมกราฟิกระดับล่าง แต่สำหรับฉันมันเป็น API ที่ใช้งานทรมานมาก
ฉันยังอยากลองสร้างเกมเอนจินด้วยตัวเองอยู่เหมือนเดิม แต่แม้แต่การตั้งค่าเริ่มต้นของ Vulkan ก็ยังน่ากลัวสำหรับฉัน
สิ่งที่ฉันต้องการคืออะไรสักอย่างที่เหมือน SDL เวอร์ชัน 3D ในแบบที่ SDL จัดการกราฟิก 2D
ถ้าจะทำ 3D บน SDL ก็สุดท้ายต้องลงไปใช้ OpenGL อยู่ดี ซึ่งไม่ใช่ระดับที่ฉันต้องการ
บางที WebGPU อาจเป็นทางเลือกที่ฉันสนุกกับมันได้
ฉันเองก็เคยสร้างเอนจินตัวหนึ่งด้วยมัน แต่สุดท้ายก็กลับไปใช้เอนจินที่อิง Vulkan เพราะต้องการการควบคุมและประสิทธิภาพมากกว่า
ถึงอย่างนั้น ฉันก็ได้เรียนรู้ แพตเทิร์นการซิงโครไนซ์ จากโค้ด SDL GPU และมันช่วยกับเอนจิน Vulkan ของฉันมาก
wgpuของ Rust เป็น จุดกึ่งกลาง ที่ให้ระดับนามธรรมแบบ WebGPUมันทรงพลังกว่า OpenGL แต่ไม่ต้องลงมือจัดการรายละเอียดอย่าง resource barrier หรือ layout transition เอง
มันช่วยทำ bookkeeping บางส่วนให้ตอนรันไทม์ และมีข้อจำกัดอย่างรองรับคิวเดียวเท่านั้น
Vulkan นั้นยาก แต่ถ้าใช้ส่วนขยายที่ผู้ผลิตหลักรองรับก็จะดีขึ้นพอสมควร
อย่างไรก็ดี มันก็ยังมี ความซับซ้อนที่ไม่จำเป็น อยู่ เช่น บังคับให้ต้องตั้งค่ารายละเอียดที่ไดรเวอร์มักเมินอยู่ดี
ตอนนี้ API ระดับกลาง ระหว่างเกมเอนจินระดับสูงกับ Vulkan/Metal ระดับล่างแทบหายไปแล้ว
ถ้ามือใหม่จะเริ่มเรียนกราฟิก 3D ก็ควรมี API ระดับง่ายแบบ “วาดสามเหลี่ยม” ที่ไม่ต้องรู้เรื่อง shader หรือ buffer ก่อน
การควบคุมจุกจิกแบบ Vulkan มีความจำเป็นกับนักพัฒนาเอนจินเพียงส่วนน้อยมาก และสำหรับคนส่วนใหญ่ระดับ OpenGL ก็เพียงพอแล้ว
3D มีองค์ประกอบที่นำมาประกอบกันได้มากกว่า 2D มาก จนกราฟิก API แบบเรียบง่ายรับมือได้ยาก
เดิมที OpenGL ก็มีเป้าหมายประมาณนั้น แต่สุดท้ายก็ซับซ้อนขึ้นอยู่ดี
‘bike shedding’ หมายถึงการหมกมุ่นกับ ปัญหาเล็กน้อยจนพลาดเรื่องสำคัญ
สิ่งที่อธิบายในต้นฉบับจริง ๆ แล้วใกล้กับ feature creep หรือ over-engineering มากกว่า
หมายถึงการจดจ่อกับความเพลิดเพลินส่วนตัวจนขัดขวางความก้าวหน้าของโปรเจ็กต์
bike shedding มักอธิบายว่าเป็นการ “มัวแต่เลือกสีโรงเก็บจักรยานก่อนที่บ้านจะสร้างเสร็จ”
มีคนบอกว่า “การเริ่มพัฒนาเอนจินด้วย Minecraft multiplayer clone ไม่ใช่ความคิดที่ดี” แต่
จริง ๆ แล้วหลายคนก็สร้าง เกมสไตล์ Minecraft เป็นโปรเจ็กต์เอนจินชิ้นแรก
สำหรับเอนจิน voxel นั่นแทบจะเป็น “Hello, world” เลย
โพสต์ในตอนนั้น (2024) ได้ 625 คะแนนและ 260 คอมเมนต์ — ลิงก์ต้นฉบับ
Vulkan เป็น เทคโนโลยีที่ยากที่สุด ที่ฉันเคยเรียน
มันทั้งไม่เป็นธรรมชาติและมีงานซ้ำซากมากจนดูดความสนุกของการเขียนโปรแกรมออกไป
ถ้าอยากเริ่มแบบง่ายกว่านี้ แนะนำ OpenGL โดยเฉพาะเวอร์ชันก่อนมี shader
แต่ตอนนี้อุตสาหกรรมก็กำลังค่อย ๆ ผลัก OpenGL ออกไป
ฉันทำได้แค่ตาม tutorial แล้วคัดลอกโค้ด โดยไม่ได้เข้าใจแนวคิดจริง ๆ
เลยเปลี่ยนไปใช้ WebGPU (Google Dawn) ซึ่งง่ายกว่า Vulkan มาก
หลังจากเรียนรู้แนวคิดผ่านข้อจำกัดของ WebGPU แล้ว ค่อยกลับมาเรียน Vulkan อีกครั้งก็ง่ายขึ้นมาก
WebGPU ไม่มี push constant และมีปัญหา pipeline explosion แต่ Vulkan ยากกว่าในด้าน synchronization และ memory management
SDL_GPU ก็เป็น API ระดับใกล้เคียงกัน จึงเหมาะสำหรับเริ่มต้น
Vulkan เป็น API ที่ออกแบบเกินจำเป็น
การจองหน่วยความจำ GPU ที่ใน CUDA ทำได้ในบรรทัดเดียว กลับต้องใช้ boilerplate จำนวนมากใน Vulkan
แม้ Vulkan รุ่นใหม่จะดีขึ้นมากแล้ว แต่ก็ยังไปไม่สุด
ฉันหวังว่า SDL3 หรือ wgpu จะกลายเป็นชั้นนามธรรมที่ช่วยลดความซับซ้อนนี้ลง
Valve สนับสนุน SDL3 อยู่ จึงคิดว่าทางนั้นมีแววมากกว่า
คำถามแรกที่ต้องถามคือ “จำเป็นต้องประมวลผลกราฟิกแบบมัลติเธรดไหม?”
ถ้าไม่ ก็ไม่มีเหตุผลจะใช้ Vulkan/DX12
จนกว่าจะเจอปัญหาด้านประสิทธิภาพ การใช้ OpenGL, DX11 หรือเกมเอนจินจะดีกว่ามาก
ฉันหลงใหลในงานเขียนโปรแกรม 3D/เกม และมักดูยูทูบเบอร์บางคนทำเกมอยู่บ่อย ๆ
แต่เมื่อเทียบกับเว็บแอปหรือ DevOps แล้ว มันเป็น โลกที่ซับซ้อนกว่ามาก
มีทั้ง pixel shader, compute shader, geometry, linear algebra ไปจนถึง PDE
ช่อง YouTube ของ TokyoSpliff
ฉันชอบที่ทุกวันนี้การ สร้างเกมเอนจินเป็นงานอดิเรกถูกมองว่าเท่มาก
ฉันเองก็พัฒนาเอนจินส่วนตัวมา 10 ปีแล้ว และมันเป็นประสบการณ์ที่คุ้มค่ามาก
ถ้าจะเริ่มทำกราฟิกโปรแกรมมิงครั้งแรก ควร เริ่มจาก OpenGL
ฉันอ่านบทเรียน OpenGL ของ NeHe เมื่อ 23 ปีก่อน และจนถึงตอนนี้ก็ยังคิดว่ามันเป็นหนึ่งในสื่อการเรียนรู้ที่วางโครงสร้างไว้ดีที่สุด
อ้อ ฉันไม่ใช่ผู้เขียนบทความต้นฉบับ แค่คงชื่อเรื่องไว้เท่านั้น