11 điểm bởi GN⁺ 2025-11-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • Tổng kết trải nghiệm tự tay hiện thực một game engine nhỏ kèm hai game demo trong 3 tháng học Vulkan
  • Từng bước vượt qua độ phức tạp của Vulkan dựa trên kinh nghiệm OpenGL sẵn có, hiện thực các tính năng cốt lõi như tải glTF, skinning và shadow mapping
  • Engine có tên EDBR (Elias Daler’s Bikeshed Engine), gồm khoảng 19 nghìn dòng mã, sử dụng các kỹ thuật đồ họa hiện đại như bindless descriptor, PVP, BDA
  • Bài viết chia sẻ các chi tiết triển khai thực tế như những thư viện thiết yếu vk-bootstrap, VMA, volk cùng mẫu pipeline, tự động hóa build shader, quản lý đồng bộ hóa
  • Việc chuyển sang Vulkan mang lại loại bỏ trạng thái toàn cục, kiểm soát tường minh, môi trường debug tốt hơn, tính nhất quán giữa các GPU, và có kế hoạch bổ sung render graph, font SDF, hiệu ứng thể tích trong tương lai

Tổng quan về việc học Vulkan và phát triển engine

  • Tác giả bắt đầu học lập trình đồ họa theo kiểu tự học và đã từng viết một engine 3D bằng OpenGL cách đây khoảng một năm rưỡi
  • Engine dựa trên Vulkan phù hợp với game nhỏ theo level, tập trung vào mục tiêu học hỏi và thử nghiệm hơn là tối ưu hiệu năng
  • Ban đầu tác giả làm một game 3D đơn giản, rồi tách các phần có thể tái sử dụng để dần engine hóa
  • Lý do có thể hoàn thành trong 3 tháng là vì giới hạn nó thành một engine cho mục đích cụ thể, chứ không phải engine đa dụng

Lộ trình học lập trình đồ họa

  • Với người mới bắt đầu, nên học từ OpenGL để làm quen với hiển thị model có texture, ánh sáng Blinn-Phong, shadow mapping, v.v.
  • Các tài liệu được khuyến nghị gồm learnopengl.com, Anton’s OpenGL 4 Tutorials, bài giảng của Thorsten Thormählen
  • Bài viết cũng nhấn mạnh tầm quan trọng của việc hiểu đại số tuyến tính (vector, ma trận, quaternion) cùng với tài liệu thực hành OpenGL 4.6 hiện đại

Lời khuyên để tránh bike-shedding

  • Tránh thiết kế quá mức và trừu tượng hóa không cần thiết, giữ nguyên tắc “chỉ hiện thực thứ cần ngay lúc này”
  • Khuyến khích cách tiếp cận “cứ làm cho chạy trước rồi cải thiện sau”
  • Hoàn thành một game nhỏ trước sẽ hiệu quả hơn là nhắm ngay đến engine đa dụng
  • Đừng sao chép nguyên xi cấu trúc hay mã nguồn phức tạp của người khác, hãy bắt đầu từ cấu trúc đơn giản

Lý do chọn Vulkan

  • Với game AAA thường dùng DirectX, trên macOS/iOS là Metal, còn web chủ yếu dùng WebGPU/WebGL
  • Tác giả ưu tiên mã nguồn mở và công nghệ tiêu chuẩn, đồng thời chọn Vulkan vì phù hợp với mục tiêu phát triển game 3D nhỏ trên desktop
  • OpenGL không còn được phát triển thêm và đã bị loại bỏ trên macOS
  • WebGPU tuy gọn hơn nhưng vẫn có các hạn chế như độ ổn định chưa cao, giới hạn tính năng, chưa hỗ trợ bindless và push constant

Quá trình học Vulkan

  • Ban đầu nó khó đến mức có cảm giác như đang “tự viết driver đồ họa”,
    nhưng nhờ sự xuất hiện của dynamic rendering, vk-bootstrap, vkguide mà việc tiếp cận đã dễ hơn
  • Các tài liệu học chính:
    • vkguide.dev (thực hành từ cơ bản)
    • TU Wien Vulkan Lecture Series
    • 3D Graphics Rendering Cookbook, Mastering Graphics Programming with Vulkan
  • Trong tháng đầu tiên, tác giả đã hoàn thành việc tải glTF, compute skinning, frustum culling và shadow mapping

Cấu trúc engine EDBR và xử lý khung hình

  • Mã nguồn engine khoảng 19.000 dòng, game 3D khoảng 4.600 dòng, game platform 2D khoảng 1.200 dòng
  • Các bước render chính:
    • Compute skinningCascaded Shadow Mapping (4096×4096)geometry shading dựa trên PBR
    • Depth ResolvePost FX (sương mù dựa trên độ sâu)render UI
  • Toàn bộ hệ thống đồ họa được viết lại hoàn toàn dành riêng cho Vulkan, không trộn với mã OpenGL cũ

Mẹo thực chiến khi phát triển với Vulkan

Thư viện khuyến nghị

  • vk-bootstrap: đơn giản hóa khởi tạo và cấu hình swapchain
  • Vulkan Memory Allocator (VMA): tự động hóa quản lý bộ nhớ
  • volk: đơn giản hóa việc nạp các hàm mở rộng

Trừu tượng hóa GfxDevice

  • Quản lý VkDevice, VkQueue, VmaAllocator trong một đối tượng duy nhất
  • Phụ trách bắt đầu/kết thúc frame, tạo image·buffer, quản lý bindless descriptor

Quản lý shader

  • Sử dụng GLSL, tiền biên dịch sang SPIR-V bằng glslc tại thời điểm build
  • Dùng CMake DEPFILE để tự động build lại khi shader thay đổi

Mẫu pipeline

  • Tách từng giai đoạn render thành pipeline cấp lớp (init, cleanup, draw)
  • Dùng VK_KHR_dynamic_rendering để bỏ qua render pass·subpass, giữ cấu trúc đơn giản hơn

Programmable Vertex Pulling + Buffer Device Address

  • Xử lý mọi mesh bằng một struct Vertex duy nhất
  • Truy cập trực tiếp từ shader bằng buffer_reference, không cần VAO

Bindless Descriptor

  • Dùng mảng texture toàn cục (textures[], samplers[]) để sample theo texture ID
  • Lưu texture ID trong struct material và truyền qua push constant

Tải dữ liệu động lên GPU

  • Truyền dữ liệu bằng cách thay buffer GPU theo từng frame hoặc dùng buffer staging phía CPU
  • Quản lý cấu trúc frame-in-flight bằng lớp NBuffer

Dọn dẹp tài nguyên và đồng bộ hóa

  • Dùng hàm cleanup tường minh, quản lý thủ công thay vì trông chờ destructor tự dọn dẹp
  • Thực hiện đồng bộ bộ nhớ giữa các pass bằng vkCmdPipelineBarrier2
  • Render Graph dự kiến sẽ được hiện thực trong tương lai

Các ví dụ chi tiết trong hiện thực

Render sprite

  • Dùng bindless textureinstancing để render hàng nghìn sprite trong một lần
  • Tải struct SpriteDrawCommand lên buffer GPU rồi gọi vkCmdDraw(6, N)
  • Render 10.000 sprite trong 315μs

Compute skinning

  • Thực hiện biến đổi vertex dựa trên ma trận xương và trọng số trong compute shader
  • Tạo buffer đầu ra riêng cho từng instance, sau đó xử lý như nhau ở bước render

Tách biệt game và renderer

  • Logic game dùng entt ECS, còn renderer chỉ xử lý vector DrawCommand
  • Sinh lệnh render thông qua các lời gọi drawMesh, drawSkinnedMesh

Tải scene và prefab

  • Dựng level trong Blender rồi xuất sang glTF, tự động spawn prefab theo quy ước tên node
  • Prefab được định nghĩa bằng JSON và tham chiếu glTF bên ngoài

MSAA, UI, ImGui

  • Áp dụng MSAA x8 dựa trên forward rendering
  • Hiện thực hệ thống layout tự động lấy cảm hứng từ Roblox UI API
  • Tự viết backend Vulkan riêng để xử lý vấn đề sRGB của Dear ImGui

Các thành phần khác

  • Dùng Jolt Physics cho vật lý, entt cho ECS, OpenAL-soft cho âm thanh, Tracy cho profiler

Lợi ích của việc chuyển sang Vulkan

  • Loại bỏ trạng thái toàn cục để có cấu trúc mã tường minh và mô-đun hơn
  • Validation layer và debug bằng RenderDoc giúp truy vết lỗi dễ dàng hơn
  • Tính nhất quán cao hơn giữa GPU·OS, hành vi dễ dự đoán hơn so với OpenGL
  • Mở ra khả năng mở rộng như ngôn ngữ shading mới (slang, shady)
  • Ít trừu tượng hơn, kiểm soát pipeline rõ ràng hơn nên dễ bảo trì hơn

Kế hoạch sắp tới

  • Dự kiến bổ sung font SDF, tải ảnh song song và tạo mipmap, Bloom, volumetric fog, animation blending, render graph, AO
  • Học Vulkan tuy khó nhưng mang lại nhiều giá trị trong việc hiểu API đồ họa hiện đại và nâng cao năng lực thiết kế engine

1 bình luận

 
GN⁺ 2025-11-24
Ý kiến trên Hacker News
  • Kể từ bài viết của tôi đăng cách đây 1 năm, suy nghĩ của tôi về Vulkan vẫn không thay đổi nhiều
    Nó có thể thú vị với những ai muốn kiểm soát đồ họa ở mức thấp, nhưng với tôi thì đó thực sự là một API rất khổ sở để sử dụng
    Tôi vẫn muốn tự làm một game engine, nhưng ngay cả phần thiết lập ban đầu của Vulkan đến giờ vẫn khiến tôi e ngại
    Điều tôi muốn là một phiên bản 3D của cách SDL xử lý đồ họa 2D
    Nếu muốn làm 3D với SDL thì cuối cùng vẫn phải hạ xuống OpenGL, mà đó không phải mức trừu tượng tôi mong muốn
    Có lẽ WebGPU sẽ là một lựa chọn thay thế mà tôi có thể dùng một cách thoải mái hơn

    • SDL 3.0 đã giới thiệu GPU API vào khoảng 1 năm trước. Đây là một lớp trừu tượng hóa bao bọc nhiều backend như Vulkan, nên đáng để xem thử
      Tôi cũng đã làm một engine với nó, nhưng cuối cùng lại quay về engine dựa trên Vulkan vì muốn có thêm quyền kiểm soát và hiệu năng
      Dù vậy, tôi đã học được các mẫu đồng bộ hóa từ mã SDL GPU, và điều đó giúp ích rất nhiều cho engine Vulkan của tôi
    • wgpu của Rust là một điểm trung gian cung cấp mức trừu tượng kiểu WebGPU
      Nó mạnh hơn OpenGL, nhưng không cần tự xử lý trực tiếp những chi tiết như resource barrier hay layout transition
      Nó đảm nhận một phần bookkeeping ở runtime, và có những giới hạn như chỉ hỗ trợ một hàng đợi duy nhất
      Vulkan đúng là khó, nhưng nếu dùng các extension được những nhà cung cấp lớn hỗ trợ thì cũng cải thiện khá nhiều
      Tuy nhiên, nó vẫn tồn tại độ phức tạp không cần thiết, như yêu cầu các thiết lập chi tiết mà driver lại bỏ qua
    • Với tư cách là một lập trình viên OpenGL lâu năm, tôi hoàn toàn đồng cảm
      Hiện giờ, API trung gian giữa game engine cấp cao và Vulkan/Metal cấp thấp gần như đã biến mất
      Nếu người mới muốn học đồ họa 3D, họ cần một API đơn giản ở mức “vẽ tam giác”, nơi không cần biết đến các khái niệm như shader hay buffer
      Sự kiểm soát tỉ mỉ của Vulkan chỉ cần thiết cho số rất ít nhà phát triển engine, còn với đa số thì mức OpenGL là đã đủ
    • Những nỗ lực tạo ra một “3D giống SDL” rất nhanh sẽ phình to thành engine full-stack
      3D có nhiều thành phần có thể kết hợp hơn 2D rất nhiều, nên một API đồ họa đơn giản khó mà gánh nổi
      OpenGL ban đầu cũng nhắm tới mục tiêu đó, nhưng cuối cùng vẫn trở nên phức tạp
  • ‘bike shedding’ nghĩa là ám ảnh vào chuyện nhỏ nhặt mà bỏ lỡ phần quan trọng
    Điều được mô tả trong bài gốc thực ra gần với feature creep hoặc over-engineering hơn

    • Nếu là tác giả, có lẽ có thể dùng cụm “hobby horsing
      Nó chỉ việc tập trung vào niềm vui cá nhân đến mức cản trở tiến độ dự án
      bike shedding thường được giải thích là “chọn màu nhà để xe đạp trước cả khi xây xong ngôi nhà”
    • Đang bike shedding chính từ “bike shedding” luôn rồi
  • Có người nói “không nên bắt đầu phát triển engine bằng một bản clone multiplayer của Minecraft”,
    nhưng thật ra rất nhiều người làm game kiểu Minecraft như dự án engine đầu tiên
    Với voxel engine, đó gần như là một kiểu “Hello, world”

  • Bài đăng vào thời điểm (2024) đã đạt 625 điểm và 260 bình luận — liên kết gốc

  • Vulkan là công nghệ khó nhất mà tôi từng học
    Nó quá thiếu trực quan và có quá nhiều việc lặp đi lặp lại, đến mức làm mất đi niềm vui lập trình

    • Không phải là đầu óc bạn kém đâu. Vulkan là một API trừu tượng hóa chip ở mức thấp, nên nó cũng ít thú vị chẳng khác gì USB API
      Nếu muốn bắt đầu đơn giản hơn thì nên dùng OpenGL, đặc biệt là các phiên bản trước khi shader được đưa vào
      Nhưng ngành này đang dần gạt OpenGL ra ngoài
    • Khi mới học Vulkan, tôi cũng có cảm giác y hệt
      Tôi chỉ làm theo tutorial và chép mã, chứ không thực sự hiểu khái niệm
      Vì thế tôi đã chuyển sang WebGPU (Google Dawn), và nó đơn giản hơn Vulkan rất nhiều
      Nhờ các ràng buộc của WebGPU mà sau khi nắm được khái niệm rồi quay lại học Vulkan, mọi thứ trở nên dễ hơn hẳn
      WebGPU không có push constant và có vấn đề pipeline explosion, nhưng Vulkan thì khó hơn ở phần đồng bộ hóa và quản lý bộ nhớ
      SDL_GPU cũng là API ở mức tương tự nên rất phù hợp để nhập môn
    • Đó cũng là lý do tôi vẫn chưa chuyển từ OpenGL sang Vulkan
      Vulkan là một API bị thiết kế quá đà
      Trong CUDA, việc cấp phát bộ nhớ GPU có thể làm chỉ với một dòng, còn trong Vulkan lại cần vô số boilerplate
      Vulkan hiện đại đã cải thiện khá nhiều, nhưng vẫn còn một chặng đường dài
    • Chỉ cần nhìn ví dụ mã Vulkan thông thường là có thể thấy ngay rằng đây không phải thứ dành cho nhà phát triển game
      Tôi hy vọng SDL3 hay wgpu sẽ trở thành lớp trừu tượng giúp giảm bớt sự phức tạp này
      Vì Valve đang hỗ trợ SDL3 nên tôi nghĩ hướng đó có nhiều triển vọng
    • Lập trình Vulkan/DX12 thực sự rất đau đớn
      Trước tiên phải tự hỏi: “Có cần xử lý đồ họa theo kiểu đa luồng không?”
      Nếu không thì chẳng có lý do gì để dùng Vulkan/DX12 cả
      Trước khi gặp vấn đề hiệu năng, dùng OpenGL, DX11 hoặc một game engine sẽ tốt hơn nhiều
  • Tôi rất mê lập trình 3D/game và thường xem vài YouTuber làm game
    Nhưng so với web app hay DevOps thì đây là một thế giới phức tạp hơn nhiều
    Nào là pixel shader, compute shader, hình học, đại số tuyến tính, thậm chí cả PDE
    Kênh YouTube TokyoSpliff

  • Thật tốt khi dạo này việc tự làm game engine như một sở thích được xem là điều ngầu
    Tôi cũng đã phát triển engine cá nhân của mình suốt 10 năm, và đó là một trải nghiệm rất đáng giá

  • Nếu mới bắt đầu với lập trình đồ họa thì nên bắt đầu từ OpenGL
    Tôi đã đọc tutorial OpenGL của NeHe từ 23 năm trước, và đến giờ vẫn thấy đó là một trong những tài liệu học được tổ chức tốt nhất

  • Nhân tiện, tôi không phải tác giả bài gốc, tôi chỉ giữ lại tiêu đề mà thôi