Bloom อยู่ตรงไหน: การตัดสินใจด้านการออกแบบ ความคืบหน้า และอะไรกำลังจะมา
Bloom มีอายุครบห้าสัปดาห์ในสัปดาห์นี้ หลังจาก 311 commit เราอยากบันทึกสิ่งที่เรามี เหตุผลที่เราสร้างมันแบบนี้ และส่วนใดที่ยังขรุขระ
ข้อเสนอในประโยคเดียว
เขียนเกมของคุณด้วย TypeScript คอมไพล์แบบ ahead-of-time และส่งมอบเป็นไบนารีเนทีฟจริงบน macOS, Windows, Linux, iOS และ tvOS — หรือเป็นบันเดิล WASM สำหรับเว็บ ไม่มี Electron ไม่มี WebView ไม่มีรันไทม์ JavaScript ฝังในเกมที่ส่งมอบ
ประโยคนั้นทำงานหนักมาก และเป็นเหตุผลที่การตัดสินใจด้านการออกแบบช่วงต้นของเราส่วนใหญ่เป็นแบบที่เห็น
ทำไมต้อง TypeScript
เราไม่ได้เลือก TypeScript เพราะเรารัก JavaScript เราเลือกมันเพราะเป็นภาษาที่ใช้กันแพร่หลายที่สุดที่มีระบบไทป์แบบสแตติก พร้อมระบบไทป์เชิงโครงสร้าง มีเครื่องมือมหาศาล และมีไวยากรณ์ที่ไม่ทำให้ใครหวาดกลัว คนส่วนใหญ่ที่เราอยากร่วมสร้างเกมด้วยเคยส่งมอบ TypeScript มาก่อน น้อยคนที่เคยส่งมอบ C++
เรายังต้องการภาษาที่ สามารถ คอมไพล์แบบ ahead-of-time ได้อย่างหมดจด โดยไม่ลาก garbage collector และตัวแปลไบต์โค้ดเข้าไปในไบนารีที่ส่งมอบทุกตัว นั่นตัดทุกอย่างที่ต้องการรันไทม์หนัก ๆ ออก (Python, C#, JS-ภาษา) TypeScript — เมื่อเอาส่วนที่เป็น dynamic-by-default ออก — กลายเป็นจุดที่ลงตัวพอดี
ทำไมต้อง Perry
Perry คือคอมไพเลอร์แบบ ahead-of-time ที่แปลง TypeScript ของคุณให้เป็นโค้ดเนทีฟ เส้นทาง TS-สู่-ไบนารีคือส่วนที่ทำให้เราสัญญาได้ว่า “ไม่มีโอเวอร์เฮดของรันไทม์” เกมของคุณกลายเป็นไบนารีเดียวที่เรียกเข้าสู่คอร์ Rust ผ่าน C ABI ที่เสถียร ไม่มี V8 ไม่มี Bun ไม่มี JIT
การเลือก Perry ทำให้เราเข้มงวดอย่างไร้ปรานีกับพื้นผิวของภาษาที่เราเปิดเผย API ของ Bloom เป็นฟังก์ชันและอินเทอร์เฟซธรรมดา — ไม่มีคลาส, ไม่มี decorator, ไม่มี proxy, ไม่มี eval ถ้าฟีเจอร์ใดของ TypeScript คอมไพล์เป็นการเรียกเนทีฟไม่ได้อย่างหมดจด เราจะไม่ใช้มันใน API ผลลัพธ์คือ API ที่อยู่ในชีตเดียว และ build ที่อยู่ในหัวคุณได้
ทำไมต้อง wgpu แทนที่จะเขียนเรนเดอเรอร์เฉพาะทางสี่ตัว
สัญชาตญาณแรกของเราคือการเขียนเรนเดอเรอร์ Metal แล้วเรนเดอเรอร์ DirectX 12 แล้วเรนเดอเรอร์ Vulkan เราโน้มน้าวตัวเองให้เลิกคิดได้ภายในสุดสัปดาห์เดียว สี่แบ็กเอนด์หมายถึงสี่ภาษา shader, สี่โมเดลทรัพยากร, สี่พื้นผิวของบั๊ก และสี่จุดที่จะลืมเรื่อง HiDPI
แทนที่จะเป็นเช่นนั้น เรนเดอเรอร์ทั้งหมดเขียนเพียงครั้งเดียวบน wgpu Shader คือ WGSL เราได้ Metal บนแพลตฟอร์ม Apple, DirectX 12 บน Windows, Vulkan บน Linux และ Android และ WebGPU (พร้อม WebGL เป็นทางสำรอง) ในเบราว์เซอร์ — จากโค้ดเบสเดียว ค่าใช้จ่ายคือเราถูกผูกกับชุดฟีเจอร์ของ wgpu และสิ่งแปลกใหม่บางอย่าง (mesh shader, ray tracing) อยู่นอกเหนือความเป็นไปได้ในตอนนี้ เราคิดว่านั่นเป็นการแลกเปลี่ยนที่ยุติธรรมสำหรับทีมเล็ก ๆ
อะไรอยู่ในกล่องจริง ๆ ในวันนี้
เราพยายามไม่ใส่อะไรบนเว็บไซต์การตลาดที่ไม่ทำงาน นี่คือสิ่งที่เป็นจริง วันนี้:
- เก้าโมดูลที่นำเข้าได้ —
bloom/core,bloom/shapes,bloom/textures,bloom/text,bloom/audio,bloom/models,bloom/math,bloom/physicsและbloom/scene - เรนเดอเรอร์ PBR ของจริง — วัสดุแบบเลเยอร์สไตล์ substrate, แผนที่เงาแบบ cascaded พร้อมแคชสำหรับเรขาคณิตคงที่, ACES/AgX tone mapping, auto-exposure, bloom, depth of field, motion blur, SSGI, SSAO, TAA และ CAS sharpen pass สำหรับ render scale แบบเศษส่วน
- แอนิเมชันโครงกระดูกบน GPU — การนำเข้า glTF 2.0, four-bone linear blend skinning บน GPU, สูงสุด 128 joint ต่อโครงกระดูก
- ฟิสิกส์ Jolt — วัตถุแข็งและอ่อน, ตัวควบคุมตัวละคร, ยานพาหนะ, raycast, ข้อจำกัด, callback การสัมผัส เป้าหมายเว็บใช้ JoltPhysics.js เป็นทางสำรองเพื่อให้โค้ดเดียวกันทำงานในเบราว์เซอร์
- หกแพลตฟอร์มเป้าหมาย — macOS, Windows, Linux, iOS, tvOS และเว็บ Android เชื่อมต่อบางส่วนแล้วแต่ยังไม่พร้อมส่งมอบ
- Hot reload — บันทึก WGSL shader หรือ JSON ของวัสดุแล้วการเปลี่ยนแปลงปรากฏบนหน้าจอในเวลาน้อยกว่าหนึ่งวินาทีระหว่างการพัฒนา โค้ดติดตามไฟล์ถูกตัดออกจาก build รุ่นเผยแพร่
- โปรเจกต์ตัวอย่างสิบเจ็ดโปรเจกต์ — ตั้งแต่ Pong 170 บรรทัดไปจนถึงการโหลดฉาก Intel Sponza และ Bistro
สิ่งล่าสุดบางอย่างที่เราภูมิใจ
เรนเดอเรอร์มีเดือนที่ดี ไฮไลต์บางส่วน:
- Auto-DRS เรนเดอเรอร์ปรับ render scale ของตัวเองเพื่อให้ถึง framerate เป้าหมาย จากนั้นรัน upscale บวก RCAS sharpen pass เพื่อให้ภาพคมชัด คุณตั้ง FPS เป้าหมาย เอนจินทำที่เหลือ
- Planar reflection การจับภาพระนาบกระจกของจริงด้วย oblique-clip และ IBL เป็นทางสำรองเมื่องบประมาณการสะท้อนหมด มีประโยชน์สำหรับน้ำ, พื้นขัดเงา และหน้าร้าน
- Splat mapping ด้วย texture array เลเยอร์ภูมิประเทศและรายละเอียดสามารถผูก texture array พร้อม mips ที่ถูกต้องได้ ดังนั้นคุณสามารถระบายวัสดุหลายชนิดต่อ tile ได้โดยไม่ต้องจ่าย draw call หนึ่งครั้งต่อเลเยอร์
- Imposter baker CLI เล็ก ๆ ที่อบ atlas อิมโพสเตอร์แบบ octahedral สำหรับ LOD ระยะไกล เราต้องการสิ่งนี้ตั้งแต่วินาทีที่เราเริ่มวางป่าลงในฉาก
- HiDPI ข้ามแพลตฟอร์ม ในที่สุด Windows, Linux และเว็บก็ใช้การจัดการ HiDPI แบบเดียวกับที่ macOS และ iOS มีอยู่แล้ว UI ยังคมชัดบนทุกจอแสดงผล
สิ่งที่เราไม่ได้แสร้งว่าเสร็จแล้ว
เราติดค้างรายการที่ไม่น่าฟังกับคุณด้วย:
- Android Crate ของแพลตฟอร์มมีอยู่และพื้นผิว FFI ส่วนใหญ่เชื่อมต่อแล้ว แต่ส่วนเชื่อม native-activity ยังไม่เสร็จ เรายังไม่บอกใครให้ส่งมอบ Android ด้วย Bloom
- watchOS Shader คอมไพล์ได้, stub ของแพลตฟอร์มอยู่ที่นั่น แต่เราถูกบล็อกที่การรองรับ watchOS ของ Perry ก่อนที่สิ่งนี้จะเป็นจริง
- เรขาคณิตเสมือน ไม่มีสิ่งเทียบเท่า Nanite, ไม่มี mesh shader, ไม่มี ray tracing บนฮาร์ดแวร์ สิ่งนี้อยู่ในแผนงาน ไม่ใช่ในไบนารี
- ขีดจำกัดของแอนิเมชันโครงกระดูก 128 joint ต่อโครงกระดูก ขีดจำกัดที่แน่นอน เนื่องจากขนาดของ UBO โครงกระดูกขนาดใหญ่ต้องใช้วิธีหลีกเลี่ยงในวันนี้
- ซีนกราฟยังเด็กอยู่ การแปลง, การมองเห็น, เงา และการผูกวัสดุทำงานได้ทั้งหมด ระบบ query และ pass การปรับให้เหมาะสมที่กว้างกว่ายังไม่มี
- Shader ของผู้ใช้ คุณสามารถเขียน WGSL ด้วยมือและโหลดผ่านไปป์ไลน์วัสดุได้ แต่ไม่มี shader graph ในเอนจินและไม่มีการคอมไพล์ shader ขณะรันจาก TypeScript
รูปร่างของ API
เรายืมจาก Raylib อย่างมากสำหรับส่วน immediate-mode ของ API ถ้าคุณเคยเขียนเกม Raylib ความจำของกล้ามเนื้อจะถ่ายโอนได้:
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();
} สำหรับงาน 3D และฟิสิกส์ ใช้ปรัชญาเดียวกัน: อินเทอร์เฟซธรรมดา, ฟังก์ชันบริสุทธิ์, ไม่มี state machine ที่ซ่อนอยู่ กล้องคือ object literal วัตถุแข็งคือ handle ไปป์ไลน์การเรนเดอร์ถูกกำหนดค่าโดยการเรียกฟังก์ชันตามลำดับเฉพาะ ไม่ใช่โดยการลงทะเบียน listener บนตัวจัดเรียงที่มองไม่เห็น
อะไรต่อไป
รายการสั้นของเราสำหรับไม่กี่สัปดาห์ข้างหน้า:
- ทำส่วนเชื่อม native-activity ของ Android ให้เสร็จและประกาศว่า Android พร้อมส่งมอบ
- ผลักดันงานซีนกราฟให้เลย “พื้นฐาน” ไปสู่สิ่งที่มีประโยชน์จริง: query, การปรับ frustum culling, ตัวช่วย instancing
- ลงจอดส่วนแรกของการรวมกับ editor เพื่อให้คุณสามารถปรับฉากซ้ำ ๆ ได้โดยไม่ต้องรีสตาร์ตเกม
- เปิดซอร์สเกมตัวอย่าง รวมถึงเดโมที่ใหญ่กว่า Pong เล็กน้อย
- เขียนรายละเอียดของไปป์ไลน์การคอมไพล์ Perry หลายคนได้ถามถึง
ลองดู
Bloom เป็นโอเพนซอร์สภายใต้ MIT รีพอซิทอรีทั้งหมดอยู่บน GitHub ถ้าคุณอยากติดตามโดยไม่ fork เอกสาร มี quick start 12 บรรทัด และหน้า ผลงาน แสดงรายการสิ่งที่เรากำลังสร้างด้วยมันเอง
ไม่ว่าทางใด: ขอบคุณที่แวะมาดู เราจะโพสต์ที่นี่ต่อไปเมื่อชิ้นส่วนใหญ่ ๆ ลงจอด