Bloom đang ở đâu: lựa chọn thiết kế, tiến độ và những gì sắp tới
Tuần này Bloom tròn năm tuần tuổi. Sau ba trăm mười một commit, chúng tôi muốn ghi lại những gì mình đang có, vì sao chúng tôi xây dựng theo cách đó, và những phần nào vẫn còn thô.
Lời chào hàng, gói trong một câu
Viết game của bạn bằng TypeScript, biên dịch trước thời gian chạy và xuất bản một binary native thực sự trên macOS, Windows, Linux, iOS và tvOS — hoặc một bundle WASM cho web. Không Electron, không WebView, không runtime JavaScript nhúng trong game đã xuất bản của bạn.
Câu nói đó đã làm rất nhiều việc. Nó cũng là lý do hầu hết các quyết định thiết kế ban đầu của chúng tôi trông như vậy.
Vì sao chọn TypeScript
Chúng tôi không chọn TypeScript vì yêu thích JavaScript. Chúng tôi chọn nó vì đây là ngôn ngữ kiểu tĩnh được dùng rộng rãi nhất với hệ thống kiểu cấu trúc, một câu chuyện công cụ khổng lồ và một cú pháp không làm ai phải sợ hãi. Phần lớn những người chúng tôi muốn cùng làm game đã từng xuất bản TypeScript. Rất ít người đã từng xuất bản C++.
Chúng tôi cũng muốn một ngôn ngữ có thể được biên dịch trước thời gian chạy một cách gọn gàng, mà không phải kéo theo một garbage collector và một trình thông dịch bytecode vào mọi binary đã xuất bản. Điều đó loại bỏ bất cứ thứ gì đòi hỏi runtime nặng (Python, C#, JS-the-language). TypeScript — bỏ đi những phần dynamic-by-default — hóa ra là một điểm ngọt.
Vì sao chọn Perry
Perry là trình biên dịch trước thời gian chạy biến TypeScript của bạn thành mã native. Đường đi từ TS sang binary là phần cho phép chúng tôi hứa “không có chi phí runtime.” Game của bạn trở thành một binary duy nhất gọi vào lõi Rust thông qua một C ABI ổn định. Không có V8, không có Bun, không có JIT.
Việc chọn Perry cho phép chúng tôi cứng rắn về bề mặt ngôn ngữ mà chúng tôi phơi bày. API của Bloom là các hàm và interface đơn giản — không class, không decorator, không proxy, không eval. Nếu một tính năng TypeScript không biên dịch gọn gàng thành một lời gọi native, chúng tôi không dùng nó trong API. Kết quả là một API gói gọn trong một tờ cheatsheet và một bản build gói gọn trong đầu bạn.
Vì sao chọn wgpu thay vì bốn bộ kết xuất riêng biệt
Bản năng đầu tiên của chúng tôi là viết một bộ kết xuất Metal, rồi một bộ DirectX 12, rồi một bộ Vulkan. Chúng tôi đã tự thuyết phục bản thân từ bỏ ý định đó trong vòng một cuối tuần. Bốn backend đồng nghĩa với bốn ngôn ngữ shader, bốn mô hình tài nguyên, bốn bề mặt lỗi, và bốn nơi để quên về HiDPI.
Thay vào đó, toàn bộ bộ kết xuất được viết một lần trên nền wgpu. Shader là WGSL. Chúng tôi có Metal trên các nền tảng Apple, DirectX 12 trên Windows, Vulkan trên Linux và Android, và WebGPU (với WebGL dự phòng) trong trình duyệt — từ một codebase. Cái giá phải trả là chúng tôi bị ràng buộc bởi bộ tính năng của wgpu, và một vài thứ kỳ lạ (mesh shader, ray tracing) tạm thời không khả dụng. Chúng tôi nghĩ đó là một cuộc đánh đổi công bằng cho một đội nhỏ.
Hôm nay thực sự có gì trong hộp
Chúng tôi cố gắng không để bất cứ thứ gì lên trang marketing mà không hoạt động. Đây là những gì có thật, ngay hôm nay:
- Chín module có thể import —
bloom/core,bloom/shapes,bloom/textures,bloom/text,bloom/audio,bloom/models,bloom/math,bloom/physicsvàbloom/scene. - Một bộ kết xuất PBR thực thụ — vật liệu phân tầng kiểu substrate, bản đồ bóng phân tầng có cache cho hình học tĩnh, tone mapping ACES/AgX, auto-exposure, bloom, độ sâu trường ảnh, motion blur, SSGI, SSAO, TAA, và một lượt CAS sharpen cho các tỉ lệ kết xuất phân số.
- Hoạt hình xương trên GPU — import glTF 2.0, linear blend skinning bốn xương trên GPU, lên đến 128 khớp mỗi bộ xương.
- Vật lý Jolt — vật thể cứng và mềm, bộ điều khiển nhân vật, phương tiện, raycast, ràng buộc, callback va chạm. Mục tiêu web sử dụng JoltPhysics.js làm dự phòng để cùng một mã nguồn chạy được trong trình duyệt.
- Sáu nền tảng đích — macOS, Windows, Linux, iOS, tvOS, và Web. Android đã được kết nối một phần nhưng chưa thể xuất bản.
- Hot reload — lưu một shader WGSL hoặc một file JSON vật liệu và thay đổi xuất hiện trên màn hình trong chưa đầy một giây trong lúc phát triển. Mã file-watching được loại bỏ trong các bản build phát hành.
- Mười bảy dự án ví dụ — từ một Pong 170 dòng đến việc tải các scene Intel Sponza và Bistro.
Vài thứ gần đây chúng tôi tự hào
Bộ kết xuất đã có một tháng tốt. Vài điểm nổi bật:
- Auto-DRS. Bộ kết xuất tự điều chỉnh tỉ lệ kết xuất để đạt được framerate đích, sau đó chạy một lượt upscale cộng RCAS sharpen để hình ảnh giữ được độ sắc nét. Bạn đặt FPS đích; engine làm phần còn lại.
- Phản chiếu phẳng. Chụp mặt phẳng gương thực thụ với oblique-clip và IBL dự phòng khi ngân sách phản chiếu đã cạn. Hữu ích cho mặt nước, sàn đánh bóng và mặt tiền cửa hàng.
- Splat mapping bằng texture-array. Các lớp địa hình và chi tiết giờ có thể bind một texture array với mip phù hợp, để bạn có thể tô vẽ nhiều vật liệu mỗi tile mà không phải trả cho một draw call mỗi lớp.
- Bộ bake imposter. Một CLI nhỏ bake các atlas imposter octahedral cho các LOD ở xa. Chúng tôi cần đến nó ngay khoảnh khắc bắt đầu thả rừng cây vào scene.
- HiDPI đa nền tảng. Windows, Linux và Web cuối cùng cũng chia sẻ cùng cách xử lý HiDPI mà macOS và iOS đã có. UI giữ được độ sắc nét trên mọi màn hình.
Những thứ chúng tôi không giả vờ là đã xong
Chúng tôi cũng nợ bạn danh sách kém đẹp đẽ:
- Android. Crate nền tảng đã tồn tại và phần lớn bề mặt FFI đã được kết nối, nhưng phần keo native-activity chưa hoàn tất. Chúng tôi chưa khuyên ai xuất bản Android với Bloom.
- watchOS. Shader biên dịch được, stub nền tảng đã có, nhưng chúng tôi đang bị chặn bởi hỗ trợ watchOS của Perry trước khi điều này thành hiện thực.
- Hình học ảo hóa. Không có thứ tương đương Nanite, không mesh shader, không ray tracing phần cứng. Đây nằm trên lộ trình, không nằm trong binary.
- Giới hạn hoạt hình xương. 128 khớp mỗi bộ xương, giới hạn cứng, do kích thước UBO. Các rig lớn hôm nay cần các giải pháp tránh né.
- Đồ thị scene còn non. Biến đổi, hiển thị, đổ bóng và gắn vật liệu đều hoạt động. Hệ thống truy vấn và các lượt tối ưu hóa rộng hơn vẫn chưa tồn tại.
- Shader của người dùng. Bạn có thể tự tay viết WGSL và tải nó qua pipeline vật liệu, nhưng không có đồ thị shader trong engine và không có biên dịch shader runtime từ TypeScript.
Hình hài của API
Chúng tôi mượn rất nhiều từ Raylib cho các phần immediate-mode của API. Nếu bạn từng viết một game Raylib, trí nhớ cơ bắp sẽ chuyển giao:
import { initWindow, windowShouldClose, beginDrawing,
endDrawing, clearBackground, drawText, Colors } from "bloom";
initWindow(800, 450, "Hello Bloom");
while (!windowShouldClose()) {
beginDrawing();
clearBackground(Colors.RAYWHITE);
drawText("Hello, Bloom!", 190, 200, 20, Colors.DARKGRAY);
endDrawing();
} Đối với công việc 3D và vật lý, cùng triết lý đó được áp dụng: interface đơn giản, hàm thuần, không có máy trạng thái ẩn. Một camera là một object literal. Một rigid body là một handle. Pipeline kết xuất được cấu hình bằng cách gọi các hàm theo một thứ tự cụ thể, không phải bằng cách đăng ký listener trên một bộ điều phối vô hình.
Sắp tới là gì
Danh sách ngắn của chúng tôi cho vài tuần tới:
- Hoàn tất phần keo native-activity của Android và tuyên bố Android có thể xuất bản.
- Đẩy công việc đồ thị scene vượt qua mức “cơ bản” để thực sự hữu ích: truy vấn, tinh chỉnh frustum culling, các trợ giúp instancing.
- Ra mắt phiên bản đầu tiên của tích hợp editor để bạn có thể lặp lại trên một scene mà không cần khởi động lại game.
- Mở mã nguồn các game ví dụ, bao gồm một demo lớn hơn Pong một chút.
- Viết chi tiết về pipeline biên dịch của Perry. Vài người đã hỏi.
Thử ngay
Bloom là mã nguồn mở theo giấy phép MIT. Toàn bộ kho lưu trữ nằm trên GitHub. Nếu bạn muốn theo dõi mà không cần fork, tài liệu có một quick start 12 dòng, và trang trưng bày liệt kê những gì chính chúng tôi đang xây dựng với nó.
Dù sao đi nữa: cảm ơn vì đã ghé xem. Chúng tôi sẽ tiếp tục đăng ở đây khi các mảnh lớn hơn được hoàn thành.