Membangun dunia terbuka di browser, bagian 21: Renderer lebih cepat yang ternyata tidak lebih cepat
Oleh Oleg Sidorkin, CTO dan Co-Founder Cinevva
Baru mengikuti seri ini? Gunakan panduan seri. Panduan itu menjelaskan apa yang dimaksud dengan spike dan menautkan semua bagiannya.
Bagian 20 menyimulasikan kedalaman permukaan pada quad datar. Bagian ini membahas keputusan arsitektur rendering, dan inilah spike ketika jawaban dari buku teks ternyata keliru untuk perangkat keras kami. Pertanyaannya: untuk rerumputan alpha-tested dengan kepadatan sinematik di bawah kamera orang ketiga, apakah kami memerlukan visibility buffer sebelum meningkatkan kepadatan untuk 200 pemain? Saran yang umum beredar adalah ya, dengan sangat tegas. Kami membuatnya, mengukurnya, dan jawabannya ternyata tidak.
Teknik yang direkomendasikan semua orang
Buka Spike 40 di tab baru ↗ · Lihat kode sumber
Visibility buffer membagi rendering menjadi dua pass. Pass 1 merasterisasi geometri dan hanya menulis ID segitiga serta instans ke target integer ringkas beserta depth, tanpa melakukan shading sama sekali. Pass 2 adalah pass layar penuh yang membaca ID pada setiap piksel yang tercakup, mengambil kembali vertex segitiga tersebut, merekonstruksi atribut hasil interpolasi, lalu melakukan shading tepat satu kali pada setiap piksel yang terlihat. Keunggulan yang dijanjikan adalah penolakan overdraw secara sempurna: depth test dijalankan terhadap fragmen yang belum mengerjakan shading apa pun, sehingga material yang mahal hanya dijalankan pada bagian yang benar-benar terlihat.
Spike ini menjalankan dua jalur pada satu kanvas dan satu perangkat sehingga satu-satunya variabel adalah tempat shading dilakukan. Jalur forward menggunakan MeshStandardNodeMaterial biasa melalui three.js. Jalur vis-buffer menggunakan pipeline WebGPU dua pass mentah yang berjalan di luar three.js, membaca tekstur rumput three.js langsung dari backend, menulis (instanceId, triId) ke target RG32Uint pada pass 1, lalu menyelesaikan pencahayaan pada pass 2. Keduanya berbagi satu konfigurasi pencahayaan acuan.
Ada dua catatan implementasi yang layak dipertahankan. WebGPU masih belum memiliki builtin primitive_index portabel dalam fragment shader, sehingga triknya adalah menanamkan ID segitiga per vertex ke geometri non-indeks dan membacanya dengan interpolasi flat. Ini menambah jumlah vertex hingga 3×, tetapi dampaknya dapat diabaikan pada kartu rumput dengan 12 vertex. Selain itu, berbagi kanvas dengan renderer three.js pada dasarnya tidak menimbulkan masalah selama Anda tidak pernah mengonfigurasi ulang context atau menyentuh dimensi kanvas, karena keduanya dikelola oleh three.js. Mengukur waktu jalur forward adalah bagian yang merepotkan karena three.js tidak menyediakan hook untuk menyisipkan GPU timestamp query ke dalam render pass-nya. Solusinya adalah mengapit pekerjaannya dengan dua timestamp pass tanpa operasi yang dikirimkan sebelum dan sesudahnya, yang dijalankan GPU sesuai urutan pengiriman.
Angkanya bergerak ke arah yang salah
Pada Mac seri M dengan resolusi sekitar 1080p, menggunakan bilah rumput berbentuk kartu silang di lahan seluas 80 m:
Pada 50.000 instans, jalur vis-buffer unggul 25%, yakni 4,13 md dibandingkan 5,51 md untuk forward. Pada 100.000 instans, keduanya berimbang. Pada 200.000 instans, forward unggul 44%, yakni 5,44 md dibandingkan 7,80 md untuk vis-buffer. Jalur vis-buffer menjadi relatif lebih buruk saat kepadatan meningkat, persis berkebalikan dengan anggapan umum bahwa teknik ini unggul justru ketika overdraw tinggi.
Mengapa forward tetap tangguh
GPU Apple Silicon adalah tile-based deferred renderer, dan itu mengubah seluruh perhitungannya. Forward shading pada TBDR memiliki tahap penghapusan permukaan tersembunyi yang berjalan sebelum fragment shader: rasterizer mengumpulkan setiap fragmen yang dipetakan ke sebuah tile, mengurutkannya berdasarkan depth, dan hanya fragmen yang bertahan (setelah alpha test) yang mencapai fragment shader. Jadi, jalur forward sebenarnya sudah mendapatkan sebagian besar janji visibility buffer untuk “melakukan shading satu kali per piksel” secara gratis di dalam perangkat keras. Saat bilah rumput semakin memenuhi layar, semakin banyak fragmen ditolak oleh HSR sebelum shading apa pun dijalankan, dan biaya efektif forward per piksel tetap kurang lebih datar alih-alih meningkat bersama overdraw.
Pass 1 pada jalur vis-buffer mendapatkan manfaat TBDR yang sama. Masalahnya sepenuhnya ada pada pass 2. Pass 2 membaca matriks instans setiap piksel dari sebuah buffer yang, pada 200.000 instans, berukuran 12,8 MB, jauh lebih besar daripada cache GPU mana pun. Piksel yang bersebelahan di layar biasanya berasal dari instans rumput yang berbeda (penyebarannya menggunakan grid berjitter, sehingga bilah yang bertetangga memiliki ID instans acak), jadi setiap wave yang mengakses buffer tersebut mengalami cache miss secara divergen. Akses acak yang tidak koheren itu sendiri menghabiskan sekitar 4 md per frame. Forward sepenuhnya menghindarinya karena matriks instans tiba bersama vertex melalui jalur atribut per instans. Dengan demikian, saat fragment shader berjalan, data vertex yang telah ditransformasikan sudah berada di register lokal tile, tanpa memerlukan pembacaan acak berskala megabyte.
Inilah tepatnya biaya yang ingin diamortisasi oleh pass klasifikasi material Nanite: mengelompokkan piksel berdasarkan instans dan menjalankan wave komputasi yang telah diurutkan agar pembacaan setiap wave menjadi koheren. Kami tidak memiliki itu. Perhitungan kasar menunjukkan bahwa mengurutkan piksel berdasarkan instans akan menurunkan biaya 4 md tersebut menjadi mungkin 1,5 hingga 2 md dan mendorong titik peralihan ke 400.000 hingga 500.000 instans. Namun, itu berarti menumpuk optimasi pada arsitektur yang sejak awal tidak unggul dalam kasus ini.
Kesimpulan yang jujur, serta audit yang membuatnya layak dipercaya
Untuk dedaunan berbentuk kartu silang alpha-tested pada WebGPU di Apple Silicon, jalur forward dengan pipeline TSL three.js sudah setara atau lebih murah daripada vis-buffer. Infrastruktur vis-buffer tidak menghasilkan manfaat yang terlihat hingga jauh melampaui 200.000 instans, itu pun hanya jika Anda juga menambahkan pass pengurutan atau pengelompokan. Keputusan praktis untuk engine produksi adalah mempertahankan tumpukan forward, LOD, dan imposter dari spike sebelumnya, serta tidak berinvestasi pada infrastruktur vis-buffer sampai kami menargetkan GPU diskret NVIDIA atau AMD sebagai platform penerapan utama—yang biaya overdraw-nya lebih linear—atau beralih ke arsitektur meshlet, tempat vis-buffer memang menjadi output alaminya.
Karena hasil tersebut berlawanan dengan intuisi, kesimpulannya hanya bernilai jika perbandingannya adil. Karena itu, spike ini menjalani audit menyeluruh. Beberapa bug nyata ditemukan dan diperbaiki: slider skala bilah yang diam-diam membuat kedua jalur tidak sinkron, separuh bilah forward dirender sangat gelap akibat normal yang anti-paralel (diperbaiki dengan trik standar dedaunan ber-normal ke atas), serta pembacaan vis-buffer yang sekitar 2× terlalu terang akibat faktor Lambert yang dipilih secara manual alih-alih 1/π yang mempertahankan energi, komponen ambient yang di-hardcode, dan tone mapping yang hilang. Perbaikannya menyalin kurva filmik ACES three.js yang persis sama ke WGSL serta membaca warna dan intensitas cahaya dari objek lampu yang sebenarnya di scene pada setiap frame. Kekurangan yang masih diketahui, yaitu tidak adanya specular langsung pada pass 2, membuat perbandingan menjadi lebih menguntungkan vis-buffer. Artinya, forward melakukan lebih banyak pekerjaan per piksel tetapi tetap unggul pada kepadatan tinggi. Hal ini membuat kesimpulan utama kami konservatif, bukan optimistis. Satu peringatan yang tetap berlaku: semua ini khusus untuk seri M, dan titik peralihannya sangat mungkin terbalik pada GPU diskret. Jadi, pengujian ulang layak dilakukan sebelum menetapkan tumpukan teknologi untuk target non-Apple.
Teknologi yang dirujuk dalam bab ini
Rendering visibility buffer. Pass 1 merasterisasi geometri dan hanya menulis ID segitiga dan instans beserta depth, tanpa melakukan shading. Pass 2 adalah penyelesaian layar penuh yang membaca ID per piksel yang tercakup, mengambil kembali segitiga sumber, merekonstruksi atribut barycentric yang dikoreksi perspektif, lalu melakukan shading satu kali pada setiap piksel yang terlihat. Karena WebGPU tidak memiliki primitive_index fragmen yang portabel, ID segitiga ditanamkan sebagai atribut per vertex berinterpolasi flat pada geometri non-indeks.
Penghapusan permukaan tersembunyi TBDR dibandingkan deferred resolve. Pada GPU tile-based deferred (Apple Silicon), forward shading sudah menolak fragmen yang tertutup sebelum fragment shader berjalan. Karena itu, forward memperoleh sebagian besar manfaat visibility buffer berupa shading satu kali secara gratis, dan biaya per pikselnya tetap kurang lebih datar saat overdraw meningkat. Sebaliknya, pass resolve vis-buffer menanggung biaya akses acak yang tidak koheren ke buffer per instans berukuran besar (12,8 MB pada 200 ribu instans), yang menjadi faktor dominan pada kepadatan tinggi kecuali piksel terlebih dahulu diurutkan atau dikelompokkan berdasarkan instans, seperti yang dilakukan klasifikasi material Nanite.
Berbagi kanvas dengan WebGPURenderer milik three.js. Command buffer WebGPU mentah dapat diselingi dengan benar di antara pengiriman three.js pada antrean bersama selama Anda tidak pernah memanggil context.configure() lagi atau menulis ke canvas.width/height, karena keduanya dikelola oleh renderer. Pengukuran waktu GPU pada jalur forward, yang tidak memiliki hook terbuka di three.js, dapat diapit oleh dua timestamp render pass tanpa operasi yang dikirimkan sebelum dan sesudah pemanggilan render-nya, karena GPU menjalankan command buffer sesuai urutan pengiriman.
Memvalidasi benchmark yang berlawanan dengan intuisi. Hasil performa yang mengejutkan hanya dapat dipercaya sejauh perbandingannya adil. Audit terhadap kedua jalur agar memiliki konten scene dan shading yang identik—tone mapping ACES yang cocok, Lambert yang mempertahankan energi, cahaya yang dibaca dari objek yang sama, serta skala bilah yang identik—mengubah “vis-buffer lebih lambat” dari kemungkinan artefak pengukuran menjadi kesimpulan yang dapat dipertanggungjawabkan, dengan satu-satunya asimetri yang tersisa justru condong ke arah konservatif.
Bagian 21 dari 29. Sebelumnya: Bagian 20 - Menyimulasikan kedalaman pada bidang datar Berikutnya: Bagian 22 - Awan yang bisa diterobos, dan culling yang sepadan Panduan seri: /id/blog/2026-02-25-open-world-browser-series-guide