ทำไมประสิทธิภาพของ 'Cities: Skylines 2' ถึงย่ำแย่
(blog.paavo.me)- แม้ Cities: Skylines 2 จะเป็นเกมแนวที่เน้นการจำลอง แต่ในสถานการณ์ส่วนใหญ่กลับเห็น คอขวดที่ GPU ชัดเจน และมีผลเบนช์มาร์กระบุว่าหากต้องการ 1080p 60FPS ที่การตั้งค่าสูงกว่าขั้นต่ำ จะต้องใช้การ์ดจอระดับประมาณ 1,000~2,000 ยูโร
- เฟรมตัวอย่างที่จับด้วย Renderdoc แสดงค่า 87.8ms, 6705 draw calls, API calls มากกว่า 50,000 ครั้ง และการใช้งานบัฟเฟอร์/เท็กซ์เจอร์ของ GPU ราว 6.7GB ซึ่งห่างจากเกณฑ์ 16.7ms สำหรับ 60FPS มาก
- แก่นของคอขวดคือ การขาด LOD ในเมชจำนวนมาก และ culling ที่เรียบง่าย ทำให้เรนเดอร์เรขาคณิตโพลิกอนสูงที่แทบไม่ช่วยต่อภาพบนหน้าจอซ้ำ ๆ โดยเฉพาะ shadow pass เพียงอย่างเดียวใช้ 40ms และคิดเป็น 72% ของ draw calls ทั้งหมด
- ฟันของประชาชนที่เป็นประเด็นถกเถียงนั้นถูกเรนเดอร์จริง แต่ไม่ใช่สาเหตุเดี่ยว ปัญหาที่ใหญ่กว่าคือ จำนวนเวอร์เท็กซ์ที่มากเกินไป ในโมเดลประชาชน พร็อพ และของตกแต่งโดยรวม รวมถึงการเรนเดอร์ที่ไม่มีประสิทธิภาพซึ่งสะสมกัน
- มีสัญญาณว่า Unity DOTS และ HDRP ยังผสานกันไม่สมบูรณ์ ทำให้ Colossal Order ต้อง implement การเชื่อม ECS กับ renderer, culling, virtual texturing ฯลฯ เอง แม้จะลดคอขวด CPU ได้ แต่กราฟิก pipeline ดูเหมือนยังไม่ได้รับการขัดเกลาเพียงพอ
ปัญหาประสิทธิภาพที่ปรากฏก่อนและหลังเปิดตัว
- Cities: Skylines 2 มีสัญญาณมาตั้งแต่ก่อนวางจำหน่าย ทั้ง การปรับสเปกแนะนำให้สูงขึ้น, การเลื่อนเวอร์ชันคอนโซลไปปี 2024 และข้อจำกัดในการพูดถึงประสิทธิภาพ
- หนึ่งสัปดาห์ก่อนวางจำหน่าย Colossal Order ออกประกาศที่ใกล้เคียงกับการชี้แจงปัญหาประสิทธิภาพล่วงหน้า และหลังเปิดตัว ประสิทธิภาพก็กลายเป็นเป้าหมายของคำวิจารณ์แทบทั่วไป
- เกมสร้างเมืองอาจทำเฟรมเรตสูงได้ยาก แต่เกมนี้โดดเด่นที่ คอขวด GPU มากกว่าคอขวด CPU ซึ่งพบได้บ่อยในเกมแนวนี้
- ตามเบนช์มาร์กของ PC Games Hardware และ Gamers Nexus มีผลว่าหากต้องการ 1080p 60FPS ที่ตั้งค่าเหนือ “very low” จำเป็นต้องใช้ GPU ระดับประมาณ 1,000~2,000 ยูโร
- ในสภาพแวดล้อม RTX 3080, Ryzen 7 5800X และจอ 5120×1440 เมื่อเปิดครั้งแรก เมนูหลักต่ำกว่า 10FPS และเมื่อปิด depth of field, motion blur, volumetric effects ตามคำแนะนำของผู้พัฒนา ก็เพิ่มขึ้นเป็นราว 90FPS
- บนแผนที่ว่างได้ประมาณ 30~40FPS และหลังเล่นราวหนึ่งชั่วโมงก็ยังอยู่ระดับใกล้เคียง พร้อมอาการกระตุกเป็นครั้งคราว
เอนจินและโครงสร้างการเรนเดอร์
- Cities: Skylines 2 ใช้ฐาน Unity 2022.3.7 และใช้ DOTS, ECS, Burst compiler ของ Unity
- ด้วย DOTS ดูเหมือนเกมจะใช้ CPU หลายคอร์ได้มีประสิทธิภาพกว่าภาคก่อนมาก
- ตามโค้ด เกมลอจิกประกอบด้วยระบบประมาณ 1,200 ระบบ และดูเหมือนลอจิกเกมส่วนใหญ่จริง ๆ วางอยู่บนโครงสร้าง ECS
- UI ไม่ได้ใช้ Unity UI Toolkit แต่ใช้ Coherent Gameface ที่อิง HTML, CSS, JavaScript
- ใน JS bundle มีร่องรอยการใช้ React และ Webpack
- UI อาจมีข้อดีด้านการบำรุงรักษาและการแก้ไข แต่จากข้อมูลที่วิเคราะห์ ไม่ใช่คอขวดหลัก
- กราฟิกใช้ Direct3D 11 และ Unity HDRP
- การเชื่อมเกมที่สร้างด้วย DOTS/ECS เข้ากับระบบเรนเดอร์เดิมของ Unity จำเป็นต้องมีเลเยอร์แยก แต่ Cities: Skylines 2 ดูเหมือนไม่ได้ใช้ Unity Entities Graphics
- skinning และ occlusion culling ของ Entities Graphics ถูกระบุเป็น experimental
- ไม่รองรับ virtual texturing
- แต่ดูเหมือนใช้
BatchRendererGroupและโค้ดระดับต่ำเพื่อ implement เลเยอร์เชื่อมของตัวเองแทน
สภาพแวดล้อมและข้อจำกัดของการวิเคราะห์ด้วย Renderdoc
- การวิเคราะห์การเรนเดอร์ใช้ Renderdoc
- เวอร์ชัน Game Pass มีปัญหา sandboxing หรือสิทธิ์ไฟล์ ทำให้ Renderdoc และ NVidia Nsight Graphics เข้าถึงไฟล์ executable ไม่ได้
- เวอร์ชัน Steam ก็เชื่อม Renderdoc ด้วยวิธีปกติได้ยาก เพราะ Paradox Launcher และ flow การยืนยันตัวตนของ Steam สุดท้ายจึงจับภาพสำเร็จด้วย Global Process Hook ของ Renderdoc
- NVidia Nsight Graphics ทำงานได้ด้วยวิธีเปิด Steam จาก Nsight แล้วรันเกมจากในนั้น แต่บน D3D11 ฟีเจอร์ profiling หลายอย่างไม่รองรับ จึงไม่ได้ข้อมูลมากกว่า Renderdoc นัก
- เฟรมที่วิเคราะห์จับจากเมืองที่มีประชากรราว 1,000 คน
- เวอร์ชันเกมคือ
1.0.11f1 - แพตช์ล่าสุด
1.0.12f1มีการปรับปรุงบางส่วน แต่ยังแก้ปัญหาได้ไม่หมด - เวลาเฟรมที่ Renderdoc วัดได้คือ 87.8ms เทียบเท่าประมาณ 11.4FPS
- ระหว่างเล่นจริงค่าเฉลี่ยอยู่ที่ 30~40FPS จึงอาจเป็น overhead ของ Renderdoc หรือเป็นเฟรมที่ผิดปกติ
- เวอร์ชันเกมคือ
ตัวชี้วัดการเรนเดอร์ของเฟรมตัวอย่าง
- สถิติเฟรมตัวอย่างที่ Renderdoc รายงานมีดังนี้
- Draw calls: 6705
- Dispatch calls: 191
- API calls: 53361
- Index/vertex bind calls: 8724
- Constant bind calls: 25006
- Resource update calls: 1679
- Textures: 342 รายการ, ประมาณ 3926MB
- Render targets: 180 รายการ, ประมาณ 2328MB
- Buffers: 4144 รายการ, ประมาณ 447MB
- ผลรวม GPU buffer และ texture: ประมาณ 6.7GB
- การใช้ VRAM 6.7GB ถือว่าสูงสำหรับฉากที่ค่อนข้างเรียบง่าย และยังมี GPU ระดับกลางรุ่นปัจจุบันที่มี VRAM 8GB อยู่
- ตามการวิเคราะห์เพิ่มเติมใน FAQ การเรนเดอร์เฟรมนี้มี input vertices 121 ล้าน และ rasterized triangles ประมาณ 36 ล้าน
- นี่ไม่ใช่จำนวนโพลิกอนทั้งหมดที่มองเห็นจริงบนหน้าจอ แต่เป็นปริมาณเรขาคณิตที่ถูกประมวลผลรวมทุก render pass
- บน Reddit ยังมีรายงานว่าในเมืองใหญ่กว่า เวอร์เท็กซ์ขึ้นไปถึงหลายร้อยล้าน และบางสถานการณ์สูงสุดถึง 1 พันล้านเวอร์เท็กซ์ต่อเฟรม
DOTS instancing และการอัปเดตข้อมูล GPU
- draw call เกือบทั้งหมดใช้ instancing
- เกมใส่ข้อมูล instance ที่จำเป็นสำหรับการเรนเดอร์ออบเจ็กต์ทั้งหมดไว้ในบัฟเฟอร์ขนาดใหญ่หนึ่งก้อน
- ออบเจ็กต์เกมทั่วไปใช้ float ประมาณ 50 ตัวต่อ instance และถนนดูเหมือนใช้ข้อมูลมากกว่านั้น
- ข้อมูล instance ของออบเจ็กต์ที่มองเห็นทั้งหมดถูกอัปเดตในบัฟเฟอร์ทุกเฟรมและอัปโหลดไปยัง GPU
- บัฟเฟอร์เริ่มที่ประมาณ 60MB และจะจัดสรรใหม่เป็นขนาดใหญ่ขึ้นหากจำเป็น
- บัฟเฟอร์นี้ถูกใช้ใน draw call แทบทั้งหมด และตาม Renderdoc อยู่ในสถานะที่ vertex shader และ pixel shader เข้าถึงได้
- ทุกเวอร์เท็กซ์อาจมีต้นทุนการ lookup บัฟเฟอร์ จึงมีโอกาสไปซ้ำเติมปัญหาเมชโพลิกอนสูง
การจำลองและ virtual texturing
- GPU compute shader ถูกใช้กับ การจำลองด้านกราฟิก เช่น น้ำ หิมะ particle และ skeletal animation
- งานเหล่านี้รวมกันใช้เวลาประมาณ 1.5ms หรือต่ำกว่า 2% ของเวลาเฟรมทั้งหมด
- การคาดเดาแรก ๆ ว่าเกม offload การจำลองจริงของเกมจำนวนมากไปยัง GPU ไม่สอดคล้องกับโค้ดที่ decompile และ GPU calls
- Cities: Skylines 2 ดูเหมือน implement ระบบ virtual texturing/texture streaming ของตัวเอง
- Streaming Virtual Texturing ในตัวของ Unity ยังอยู่ในสถานะทดลองและไม่รองรับ
- เกมดูเหมือนใช้ virtual texturing กับออบเจ็กต์ 3D แบบ static ส่วนใหญ่ ยกเว้นภูมิประเทศ
- virtual texturing สามารถประหยัดหน่วยความจำโดยโหลดเฉพาะ tile ของ texture ที่จำเป็น แต่ implementation ปัจจุบันมีปัญหาที่แม้แต่พื้นผิวใกล้ ๆ ก็ไม่โหลด texture ความละเอียดสูง
- การไม่รองรับ anisotropic texture filtering ก็อาจเกี่ยวข้องกับการใช้ virtual texturing
- pass นี้ใช้เวลาประมาณ 0.5ms
ต้นทุนตาม render pass หลัก
-
Skybox generation
- ใช้ระบบท้องฟ้าแบบ physical-based ของ Unity HDRP เพื่อสร้าง cubemap ทุกเฟรม
- ใช้เวลาประมาณ 0.65ms ซึ่งเท่ากับเกือบ 4% ของงบเฟรมสำหรับ 60FPS
-
Pre-pass
- เป็นขั้นแรกของ deferred rendering โดยบันทึก depth, normal และข้อมูลประมาณค่า smoothness ลงใน texture แยก
- ใช้เวลาประมาณ 8.2ms ถือว่าหนักมาก
-
Motion vectors
- เรนเดอร์ per-pixel motion vector ที่ใช้กับ anti-aliasing และ motion blur เป็น pass แยก
- ใช้เวลาประมาณ 0.6ms
- motion vector บางส่วนดูเหมือนเสีย และด้วยเหตุนี้ ณ เวลาที่เขียนจึงยังไม่รองรับ DLSS หรือ FSR2
-
Roads and decals
- เรนเดอร์ถนน หญ้า และองค์ประกอบที่ตามพื้นผิวภูมิประเทศ
- ใช้เวลาประมาณ 1ms
-
Main pass
- เป็น pass หลักของ deferred rendering โดยใช้บัฟเฟอร์ก่อนหน้าและ virtual texture cache เพื่อสร้าง albedo, normal, คุณสมบัติ PBR, depth ฯลฯ
- ข้อมูล visibility ของ virtual texture ก็ถูกสร้างที่นี่
- ดูเหมือนเรนเดอร์ที่ความละเอียดแนวนอนครึ่งหนึ่ง และภูมิประเทศไม่ใช้ virtual texturing แต่เรนเดอร์เต็มความละเอียด
- ใช้เวลาประมาณ 16.7ms เท่ากับเวลาทั้งเฟรมที่อนุญาตสำหรับ 60FPS
-
Ambient occlusion
- ตาม shader debug name ดูเหมือนใช้ GTAO
- ใช้เวลาประมาณ 1.6ms
-
SSR + SSGI
- ใช้ screen space reflections และ screen space global illumination ของ Unity HDRP
- สองเอฟเฟกต์รวมกันใช้เวลาประมาณ 3ms
-
Deferred lighting
- รวมบัฟเฟอร์กลางที่สร้างไว้ก่อนหน้าให้เป็นผลลัพธ์ที่ใกล้ภาพสุดท้าย
- ใช้เวลาประมาณ 2.1ms
-
Water rendering
- ใช้ compute shader preprocessing และภาพ input ที่ถูกย่อ/เบลอเพื่อเรนเดอร์ผิวน้ำ
- ใช้เวลาประมาณ 1ms
-
Post-processing
- ใช้ temporal AA, bloom, tonemapping และเมื่อเปิดใช้งานก็มี DOF กับ motion blur
- รวมทั้งหมดใช้เวลาประมาณ 1~2ms
-
UI
- เรนเดอร์ UI ที่ใช้ Gameface และข้อความในโลกเกม
- ชื่อถนนเรนเดอร์ด้วย 2D signed distance fields และใช้ depth buffer เพื่อ blend กับฉากเมื่ออยู่หลังอาคาร
- เวลา pass UI สุดท้ายเล็กจนละเลยได้
ประเด็นฟันและโมเดลตัวละคร
- ตัวละครประชาชนมี โมเดลฟันแบบสมบูรณ์ จริง และโดยมุมมองปกติในเกมจะมองไม่เห็น
- จากการวิเคราะห์ด้วย NVidia Nsight Graphics ของผู้ใช้ Reddit ชื่อ Hexcoder0 ทำให้เป็นที่รู้กันว่าฟันถูกเรนเดอร์ด้วยคุณภาพสูงสุดตลอดเวลา
- ปัญหาที่สำคัญกว่าคือเมชที่เกี่ยวกับตัวละครโดยรวม ไม่มี LOD variant
- Colossal Order ยอมรับปัญหานี้ต่อสาธารณะ และกล่าวถึงปัญหาการจัดการ LOD ที่กว้างกว่านั้นด้วย
- โมเดลประชาชนสร้างจาก Didimo Popul8 และโมเดลฟัน/ปากในเกมมี 6108 vertices มากกว่าเมชพื้นฐานของ Didimo ที่มี 1060 vertices
- ตัวละครเดี่ยวโดยไม่รวมผม เสื้อผ้า และเครื่องประดับ มีมากถึงประมาณ 56,000 vertices
- อาคารที่พักอาศัยความหนาแน่นต่ำโดยเฉลี่ย หากไม่นับพร็อพในสวนและรายละเอียดปลีกย่อย จะมีต่ำกว่า 10,000 vertices
- ในเฟรมตัวอย่าง มีการเรนเดอร์ฟัน 13 ชุด แต่ไม่มีผลต่อภาพสุดท้ายแม้แต่พิกเซลเดียว
- ฟันไม่ใช่สาเหตุเดี่ยวของประสิทธิภาพตก แต่ใกล้เคียงกับหลักฐานว่ามีเรขาคณิตโพลิกอนสูงที่ไม่จำเป็นกระจายอยู่กว้างขวาง
พร็อพโพลิกอนสูงและปัญหา culling
- เกมเรนเดอร์ออบเจ็กต์ที่แทบไม่ช่วยหรือไม่ช่วยเลยต่อภาพสุดท้ายมากเกินไป และด้วยจำนวนโพลิกอนสูงเกินไป
- สาเหตุหลักมีสองอย่าง
- โมเดลบางส่วน ไม่มี LOD variant
- ระบบ culling ที่ทำเองนั้นเรียบง่าย มีเพียง frustum culling และไม่พบร่องรอยของ occlusion culling
- มี distance-based culling อยู่ แต่ไม่ aggressive ทำให้ pop-in ลดลง แลกกับผลเสียต่อประสิทธิภาพ
- ตัวอย่างพร็อพโพลิกอนสูงที่พบมีดังนี้
- พาเลตถังแก๊ส: มากกว่า 17,000 vertices
- พร็อพราวตากผ้า: 25,000 vertices ต่อชิ้น, variant ที่หนาแน่นกว่าเกิน 30,000 vertices
- ป้อมจอดรถ: มากกว่า 40,000 vertices, ไม่มี LOD, แม้แต่สายเชื่อมต่อจอภาพกับคีย์บอร์ดยังถูกโมเดลแยก
- กองท่อนไม้: มากกว่า 100,000 vertices
- การรวมอาคารและพร็อพภายในเป็นเมชเดียวช่วยลด draw call ได้ แต่ไม่สามารถ cull พร็อพภายในแยกกันได้
- ในเกมสร้างเมือง โมเดลที่ไม่มีประสิทธิภาพแบบเดียวกันอาจถูกเรนเดอร์หลายร้อยครั้งในเฟรมเดียว ทำให้ความสิ้นเปลืองเล็ก ๆ สะสมขึ้น
- โมเดลความละเอียดสูงโดยตัวมันเองไม่ใช่ปัญหา ปัญหาปัจจุบันคือเกมรับรายละเอียดระดับสูงไม่ไหว และการใช้โพลิกอนไม่มีประสิทธิภาพ/ไม่สม่ำเสมอ
Shadow pass เป็นคอขวดที่ใหญ่ที่สุด
- Cities: Skylines 2 ใช้ cascaded shadow mapping
- เงามี artifact และ flickering มาก โดยเฉพาะเมื่อดวงอาทิตย์หรือใบไม้เคลื่อนไหว
- เกมใช้ 4 cascade และความละเอียดต่อ cascade คือ 2048×2048
- ในการตั้งค่ากราฟิกขั้นสูงมีรายการ directional shadow map resolution แต่ ณ เวลาที่เขียน โค้ดยังไม่ได้เชื่อมกับรายการนี้
- การตั้งค่าแยกหรือการตั้งค่า shadow quality โดยรวมไม่ได้เปลี่ยน shadow map resolution
- preset เงา medium และ high แทบเหมือนกัน
- preset low ปิดเงาที่ภูมิประเทศทอดลงมา
- แม้คุณภาพต่ำ shadow mapping ก็ใช้เวลาประมาณ 40ms จึงเป็น render pass ที่ช้าที่สุด
- ใน draw call 6705 ครั้งของเฟรมตัวอย่าง มี 4828 ครั้ง หรือ 72% ที่ใช้กับ shadow mapping
- เกมดูเหมือนปฏิบัติกับออบเจ็กต์ 3D ทุกชิ้นเป็น shadow caster ที่เป็นไปได้ในทุกการตั้งค่าคุณภาพ ไม่ว่าจะขนาดหรือระยะทางใด
- ใน performance counter ของ Renderdoc มี draw call จำนวนมากที่ส่งผลต่อ shadow map เพียง 0~ต่ำกว่า 100 พิกเซล และฟันก็ปรากฏอีกครั้งใน shadow pass
- การปรับปรุง LOD และ culling อาจส่งผลอย่างมากต่อประสิทธิภาพ shadow mapping ด้วย
- รายละเอียดเชิงบวกคือ เกมคำนวณตำแหน่งดวงอาทิตย์และดวงจันทร์โดยใช้วันที่ เวลา และพิกัดปัจจุบันของเมือง
ปัญหาประสิทธิภาพของเมนูหลัก
- เมนูหลักดูเหมือนมีเพียงภาพพื้นหลังนิ่งกับปุ่ม แต่จริง ๆ แล้วมี ฉาก 3D อยู่ตลอดเวลา
- ด้านหลังเมนูมีฉากที่ประกอบด้วยภูมิประเทศ น้ำ และ skybox ถูกเรนเดอร์ จากนั้น UI จึงปิดทับทั้งหมด
- แม้เป็นฉากที่มองไม่เห็น ก็ยังใช้ rendering pipeline เต็มรูปแบบ ดังนั้นการตั้งค่ากราฟิกจึงส่งผลต่อประสิทธิภาพเมนูหลักทันที
- ตอนเปิดตัว การตั้งค่าส่วนใหญ่เป็นค่าเริ่มต้นใกล้ระดับสูงสุด และรวมเอฟเฟกต์หนัก ๆ ที่ผู้พัฒนาแนะนำให้ปิดไว้ด้วย
- อย่างไรก็ตาม สาเหตุที่ครั้งแรกที่เปิดตกลงไปถึงระดับ 7FPS ยังไม่ถูกระบุแน่ชัด
- ประสิทธิภาพตกในระดับเดียวกันไม่เกิดซ้ำภายหลัง
- ตอนเปิดครั้งแรกมีการประมวลผล virtual texture cache แต่ยังยืนยันไม่ได้ว่าใช้ GPU หรือไม่
- ฉากเมนูหลักทั้งหมดมีประมาณ 400 draw calls, 563,000 input vertices และ 745,000 rasterized triangles
การตีความเพิ่มเติมจาก FAQ
- ยังสรุปได้ยากว่าควรสร้างด้วย Unreal Engine 5 หรือไม่
- UE5 มี Nanite, Lumen, Virtual Shadow Maps ซึ่งช่วยรับมือกับปัญหาบางอย่างที่ C:S2 เจอได้
- ในทางกลับกัน UE5 ยังขาดฟีเจอร์ระดับ production สำหรับลอจิก/การจำลองเกมขนาดใหญ่ที่เทียบกับ Unity ECS และโครงสร้างที่เน้น C++ อาจเสียเปรียบด้านความยืดหยุ่นและการเข้าถึงของ modding
- ไม่ใช่ว่าเกมไม่มี LOD เลย
- อาคารจำนวนมากดูเหมือนมี LOD ที่เหมาะสม
- แต่มีหลายกรณีที่องค์ประกอบอย่างท่อ พร็อพในสวน และของตกแต่งไม่มี LOD หรือไม่ได้ถูกเลือกใช้
- InstaLOD ดูเหมือนถูกใช้ใน asset pipeline ของเกม โดยเฉพาะเมื่อ import asset ใหม่ใน mod tool และดูเหมือนไม่ได้ใช้กับ runtime rendering
- UI ที่อิง JavaScript ไม่ใช่คอขวดหลักตามข้อมูลที่วิเคราะห์
- Gameface ไม่ได้อิง browser engine เต็มรูปแบบแบบ Electron แต่เป็น custom framework ที่ออกแบบสำหรับ UI เกม
- มีคำอธิบายว่าควรได้เปรียบด้านการใช้หน่วยความจำและประสิทธิภาพเมื่อเทียบกับ Chromium/Blink หรือ WebKit
- Renderdoc มีข้อจำกัดในฐานะเครื่องมือเบนช์มาร์กที่แม่นยำ แต่ให้หลักฐานเพียงพอในการเข้าใจว่า “เกมทำอะไรจึงช้า”
สรุป: ลดคอขวด CPU ได้ แต่ GPU pipeline ใกล้เคียงกับยังไม่เสร็จสมบูรณ์
- เหตุผลโดยตรงที่ Cities: Skylines 2 หนักกับ GPU เกินไป คือส่ง เรขาคณิตที่ไม่จำเป็น ไปยังการ์ดจอมากเกินไป
- ความสิ้นเปลืองของเรขาคณิตนี้มาจากการขาด LOD ในเมชจำนวนมาก และ implementation ของ culling ที่เรียบง่ายและยังปรับแต่งไม่พอ
- เหตุผลที่ต้อง implement culling และเลเยอร์เชื่อมการเรนเดอร์เอง ดูเหมือนเพราะการผสาน Unity DOTS กับ HDRP ยังอยู่ระหว่างดำเนินการ และมีข้อจำกัดมากสำหรับใช้ในเกมจริง
- virtual texturing ของ Unity ก็ยังอยู่ในสถานะทดลอง ทำให้ Colossal Order implement โซลูชันของตัวเอง และระบบนี้ก็ยังมีส่วนที่ความสมบูรณ์ต่ำ
- flow ที่พอคาดเดาได้คือ Colossal Order เดิมพันกับเทคโนโลยีใหม่ของ Unity อย่าง DOTS และได้ผลในแง่ลดคอขวด CPU กับขนาดการจำลอง แต่ในด้านกราฟิกกลับต้อง implement ระบบอย่าง culling, animation, texture streaming เอง
- มีการประเมินว่าคำกล่าวของผู้พัฒนาที่ว่าเกมตั้งเป้า 30FPS ตั้งแต่แรกนั้นเชื่อได้ยาก และเมื่อพิจารณาว่าเป็นเกม PC ล้วนกับคุณภาพกราฟิกแล้ว ก็ยากที่จะใช้เป็นเหตุผลรองรับเป้าหมายด้านประสิทธิภาพนี้
- พื้นที่ที่มีโอกาสแก้ได้มากคือการเพิ่ม LOD, ปรับปรุง culling, optimize การเลือก shadow caster และจัดระเบียบ asset ระดับพร็อพ ซึ่งอาจต้องแก้ asset จำนวนมากทีละรายการ จึงต้องใช้เวลา
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เป็นบทความที่น่าสนใจ จึงอยากให้คุยกันโดยเน้นว่า อะไรที่น่าสนใจอย่างเป็นรูปธรรม
เธรดแบบนี้มักไหลไปเป็นการพูดกว้าง ๆ เกี่ยวกับ
$THINGเอง ในที่นี้คือเกมโดยรวม หรือ$RELATEDในที่นี้คือเฟรมเวิร์ก หรือไม่ก็สิ่งคล้าย ๆ กันโดยรวมได้ง่ายโดยหลักการแล้วไม่ใช่เรื่องแย่ แต่ยิ่งขยับไปทางการพูดกว้าง ๆ ทีละก้าว การถกเถียงก็จะยิ่งตื้นและน่าสนใจน้อยลง เลยมีข้อในแนวทางของเว็บว่า “หลีกเลี่ยงการคุยนอกประเด็นแบบกว้าง ๆ” - https://news.ycombinator.com/newsguidelines.html
ตอนที่บอกว่า “เหตุผลที่ Colossal Order ไม่ใช้ระบบ culling ที่มีมาให้ใน Unity แล้วทำของตัวเองขึ้นมา เป็นเพราะการผสาน DOTS กับ HDRP ของ Unity ยังอยู่ระหว่างพัฒนาอย่างหนัก และถือว่าไม่เหมาะกับเกมจริงส่วนใหญ่ จึงต้องลงมือทำฝั่งกราฟิกเองค่อนข้างมาก” นั้นช่างตรงกับประสบการณ์ของผมกับเครื่องมือ Unity อย่างน่าเศร้า
DOTS ออกมาแล้วก็จริง แต่ให้ความรู้สึกเหมือนถูกปล่อยทิ้งด้านการพัฒนาเหมือนเครื่องมืออื่น ๆ ที่ Unity ไปซื้อมา บริษัทดูมีปัญหาในการบริหารอย่างหนัก และจากดราม่านโยบายราคาที่ระเบิดขึ้นต่อหน้าสาธารณะเมื่อไม่กี่สัปดาห์ก่อน ก็ดูเหมือนจะสนใจหาวิธีรีดเงินจากผู้ใช้มากกว่าปรับปรุงเอนจิน
การทำ ECS ของ Bevy ดีมากจริง ๆ และหวังว่าจะไปได้สวยในสายนี้ร่วมกับ Godex
ความสอดคล้องของแคชในชั้น gameplay ไม่สามารถไปแก้คอขวดระดับเอนจินได้
เช่น Godot ไม่ได้ยึด ECS เป็นศูนย์กลาง แต่ยึดแนวคิด “servers” ซึ่งเป็นระบบย่อยของเกมที่ทำงานค่อนข้างอิสระและรับผิดชอบโดเมนเฉพาะทางอย่างการเรนเดอร์หรือฟิสิกส์ โดยเชื่อมกับลอจิกเกมทั่วไปแบบหลวม ๆ
สถาปัตยกรรมแบบ ECS มีรากมาจากยุค PS2/PS3 ที่ CPU แย่มาก ทั้งแคชเล็ก ต้นทุน branch misprediction สูง หน่วยความจำช้า พื้นที่หน่วยความจำแตกกระจัดกระจาย และไม่มีสตอเรจแบบสุ่มเข้าถึงที่ดีพอ นักพัฒนาจึงต้องสร้างเกมให้เข้ากับรูปแบบการเข้าถึงหน่วยความจำที่คาดเดาได้ ผลก็คือโครงสร้างแบบสตรีมข้อมูลเกมเป็นก้อนเล็ก ๆ เพื่อประมวลผล
วิธีนี้ทุกวันนี้ก็ยังถือเป็นแนวปฏิบัติที่ดีโดยรวม แต่พอมี CPU ที่เร็วมาก การ speculative execution ที่ยอดเยี่ยม ตัวทำนาย branch ที่ดี และแคชระดับหลายสิบ MB มันก็ไม่ใช่ข้อบังคับเคร่งครัดอีกต่อไป โดยเฉพาะเมื่อดูจากข้อเท็จจริงที่ว่าเกมสมัยใหม่ไม่ได้ขยายสเกลสิ่งที่เกิดขึ้นบนหน้าจอไปไกลกว่าเมื่อ 10–20 ปีก่อนมากนัก เกมแอ็กชันที่ให้ผู้เล่นสู้กับศัตรูเกินสิบกว่าตัวพร้อมกันก็ยังหาได้ไม่บ่อย
เกมที่มีองค์ประกอบหลายพันชิ้นแสดงพร้อมกันบนหน้าจอมักต้องใช้ลอจิกและการประมวลผลเฉพาะทางแยกต่างหากอยู่ดี
ทิปสำหรับคนที่จะลองเล่นเกมนี้: เปลี่ยน resolution scaling จาก dynamic เป็น fixed
บน 3080 ของผม เมนูหลักจากระดับ “10fps เล่นไม่ได้” กลายเป็นว่าในเกมจริง “ลื่นเล่นได้ไม่มีปัญหา” ที่ตั้งค่ากลางค่อนสูง
ถ้าจำไม่ผิด เฟรมหนึ่งของ Crysis ในฉาก benchmark น่าจะอยู่ราว ๆ 300,000 vertices หรือ triangles ดังนั้นกองท่อนไม้นี่ก็ประมาณ 3–10 กอง ขึ้นอยู่กับว่าผมจำอะไรผิด และสัดส่วน vertices/triangles ของแต่ละโมเดลมันแย่แค่ไหน
แก้ไข: ผมเช็กจำนวน vertices และ polygons ด้วย RenderDoc แล้ว ฉากตัวอย่างในบทความประมวลผล 121 ล้าน vertices และมากกว่า 40 ล้าน triangles
สไตล์การเขียนดีมากจริง ๆ:
ประโยคอย่าง “พาสนี้ใช้เวลาประมาณ 8.2 มิลลิวินาที ซึ่งพูดได้คร่าว ๆ ว่านานแบบไร้สาระ จึงหนักอย่างน่าตกใจ...”
“เมชกองท่อนไม้นี้ยังถูกใช้แค่ใน shadow rendering pass เท่านั้น แต่มี มากกว่า 100,000 vertices” ...แต่ทำไมล่ะ?
เป็นเรื่องปกติมากที่เกม AAA จะมีบางส่วนที่ยังไม่สมบูรณ์และไม่ได้ optimize งบประมาณมีจำกัดเสมอ และเวลาพัฒนาก็สั้น อีกทั้งยังเป็นอุตสาหกรรมที่ผูกกับความสำเร็จเชิงพาณิชย์ซึ่งไม่มีอะไรรับประกัน มีวิธีเพิ่มโอกาสให้เกมประสบความสำเร็จอยู่บ้าง แต่ส่วนใหญ่ออกไปทางการบริหารมากกว่าการพัฒนา และยอดพรีออร์เดอร์ที่คาดไว้ก็มักไม่แม่นนัก เพราะงั้นจึงต้องประนีประนอมเพื่อลดความเสี่ยง ลดต้นทุนตรงที่ทำได้ และลดลำดับความสำคัญของงานพัฒนาที่แพงที่สุด การประนีประนอมเหล่านี้ใหญ่กว่าการที่เมชเดี่ยว ๆ อันหนึ่งไม่ได้ optimize มาก เมชเดียวแทบไม่ใช่อะไรเลย
ที่เมชนี้เป็น LOD0 และเมชฟันก็เป็น LOD0 นั้นเป็นข้อเท็จจริงที่น่าสนใจ แต่แค่นั้นยังไม่ทำให้ประสิทธิภาพเกมพัง และก็ดูไม่น่าจะมีโอกาสถูกแก้ก่อนการแก้ประสิทธิภาพจริง ๆ ด้วย การหมกมุ่นกับเมชพวกนี้ในเธรดนี้ดูมากเกินไปหน่อย
มีคอมเมนต์ที่ใช้อารมณ์กันเยอะแล้ว ผมเลยไม่อยากเติมเชื้อ แค่อยากเพิ่มบริบทเท่านั้น
สรุปคือ เกมใช้โมเดลที่มีรายละเอียดสูงมาก แต่ไม่มีวิธีทำ abstraction หรือ culling อย่างชาญฉลาดสำหรับสิ่งที่จริง ๆ แล้วจะไม่ปรากฏเป็นพิกเซลบนหน้าจอ ใช่ไหม?
แล้วถ้าใช่ มันเป็นเรื่องที่แก้ได้ง่ายหรือเปล่า หรือว่ามันเป็นโครงสร้างแกนหลักจนถ้าจะให้แก้จริง ๆ ต้องกลับไปออกแบบใหม่ตั้งแต่ต้น?
ตัว “เทคนิค” เองน่าจะเป็นสิ่งที่รู้จักกันดีอยู่แล้ว แต่ไม่คิดว่าการลงมือทำจะง่าย
DOTS มาจากแนวคิดของ Mike Action ดูงานพูด CppCon ปี 2014 ของเขา “Data-Oriented Design and C++” ได้ [1] แต่ตามที่เห็นใน Twitter ตอนนี้ Mike ออกจาก Unity แล้ว
[1] https://www.youtube.com/watch?v=rX0ItVEVjHc
ถ้ามี Nanite ของ UE5 อาจพอประคองไปได้บ้างไม่ทางใดก็ทางหนึ่ง แต่ geometry ที่เกินพอดีระดับนั้นน่าจะทำให้ทุกอย่างอื่นพังหมดอยู่ดี
ฉันใช้เวลา 40 นาที บน Proton Experimental พยายามดันให้แม้แต่แผนที่ว่าง ๆ ที่ 1080p วิ่งได้เกินไม่กี่ fps แต่สุดท้ายก็ยอมแพ้และขอคืนเงินไป ถ้าเขาแก้ปัญหาประสิทธิภาพที่เลวร้ายนี้ได้ ฉันคงกลับมาลองอีกครั้ง
ฉันสนุกกับภาคแรกมากจริง ๆ เลยเสียดายมากที่เล่นภาคนี้ไม่ได้
“ถ้าคุณมีปัญหาด้านประสิทธิภาพ เราแนะนำให้ลดความละเอียดหน้าจอลงเป็น 1080p ระหว่างที่เรากำลังแก้ปัญหาที่ส่งผลต่อประสิทธิภาพ รวมถึงปิด depth of field และ volumetrics และลด global illumination”
สำหรับฉัน แค่นี้ก็พอให้ได้ประสิทธิภาพที่ลื่นไหลบน AMD Radeon RX 5700 XT