Skip to content

Membangun dunia terbuka di browser, bagian 28: Rumput hingga cakrawala, dan permukaan tanah yang menyembunyikan dirinya sendiri

Oleh Oleg Sidorkin, CTO dan Co-Founder Cinevva

Baru di sini? Gunakan panduan seri. Panduan tersebut menjelaskan apa itu spike dan menautkan semua bagian.

Bagian 27 membangun pulau dan memberinya permukaan tanah yang tidak tampak berulang seperti ubin. Bagian ini membahas dua hal yang membuat medan terasa dihuni alih-alih kosong. Spike 56 adalah rumput, detail permukaan yang mengubah lereng bertekstur menjadi tempat yang ingin Anda jelajahi, dan tantangannya adalah membuat padang terlihat sebagai padang, bukan goresan-goresan yang tersebar. Spike 57 adalah kebalikan dari menggambar lebih banyak: ini tentang tidak menggambar apa yang sudah disembunyikan oleh bukit, dan hasil menariknya adalah bahwa cara cepat untuk mengujinya ternyata juga merupakan cara yang benar.

Rumput yang terlihat seperti padang, bukan konfeti

Buka Spike 56 di tab baru ↗ · Lihat kode sumber

Keputusan intinya bersifat geometris. Satu helai tipis yang meruncing berukuran subpiksel dari sebagian besar sudut kamera, sehingga setengah juta helai datar terlihat seperti konfeti hijau yang jarang, bukan sebuah padang. Solusinya adalah rumpun cross-quad: tiga quad meruncing yang diputar dengan selisih enam puluh derajat mengelilingi sumbu atas lokal, sehingga dari arah pandang mana pun setidaknya satu quad berada nyaris tegak lurus terhadap kamera dan setiap instance mencakup kira-kira tiga kali lebar helai pada area layar sebenarnya. Itulah perbedaan antara melihat goresan hijau dan melihat rumput.

Sebuah kernel komputasi WebGPU menempatkan setiap rumpun satu kali saat inisialisasi. Kernel tersebut melakukan hash pada indeks instance menjadi lima aliran acak yang tidak berkorelasi, memilih posisi XZ di dalam petak, mengambil sampel ketinggian tanah dari FBM yang sama dengan yang digunakan mesh tanah CPU (port TSL dengan mod yang mempertahankan tanda agar nilainya cocok persis dan helai rumput berada di permukaan, bukan melayang di atasnya), menghitung normal dengan beda pusat, lalu mengacak lebar, tinggi, dan rona per rumpun. Sisi rendering menggunakan graf verteks TSL yang sepenuhnya melewati matriks instance dan menulis langsung ke ruang klip: graf ini menskalakan unit cross-quad, memutar arah atas lokal ke normal tanah dengan rumus Rodrigues, lalu mentranslasikannya ke posisi rumpun. LOD berdasarkan jarak didapatkan tanpa biaya tambahan dan tanpa pass culling, karena graf verteks mengalikan tinggi rumpun dengan 1smoothstep(fadeNear,fadeFar,dist), sehingga rumpun yang jauh mengempis hingga tingginya nol dan tidak lagi membebani fill rate. Angin terdiri dari dua oktaf sin dan cos pada XZ dunia milik rumpun ditambah waktu, diterapkan secara horizontal dan dibatasi oleh fraksi tinggi agar pangkal tetap tertambat sementara ujungnya bergerak. Sedikit kemiringan yang memperhitungkan kamera mencondongkan setiap rumpun ke arah pengamat agar terbuka secara perspektif alih-alih tampak seperti kartu datar.

Resep warnanya diadaptasi dari shader URP Breath of the Wild milik NedMakesGames: flat shading dengan warna helai berupa lerp dari rona pangkal ke rona ujung sepanjang fraksi tinggi, sedangkan rona pangkal dan ujung itu sendiri merupakan hasil lerp antara dua palet berdasarkan nilai rona per rumpun. Ini menghasilkan tampilan dua rona berbintik-bintik seperti yang dimiliki padang rumput BotW sungguhan. Diffuse menggunakan normal tanah terhadap matahari dengan batas bawah ambient, karena normal per quad milik cross-quad terlalu berisik jika di-shading satu per satu untuk tampilan bergaya. Seluruh padang dirender dalam satu draw call, dengan penempatan dan animasi yang sepenuhnya dilakukan di GPU.

Empat cara untuk menyisihkan apa yang disembunyikan bukit

Buka Spike 57 di tab baru ↗ · Lihat kode sumber

Spike 57 menguji performa empat jalur culling pada dunia prosedural yang sama dengan yang dialirkan oleh klien produksi, diskalakan 1,8x secara vertikal agar bukit benar-benar mengoklusi vegetasi. T0 hanya menggunakan jarak, sesuai dengan mekanisme visibilitas chunk pada versi yang dirilis. T1 menambahkan frustum culling, peningkatan murah terbesar, dengan membuang semua yang berada di luar kerucut pandang. T2 menambahkan pengujian cakrawala yang memperhitungkan medan: menelusuri sinar dari mata ke setiap instance dan menolaknya jika heightmap naik melewati sinar di titik mana pun sepanjang jalur, sehingga pohon di balik punggung bukit disisihkan meskipun berada di dalam frustum. T3 mempertahankan pengujian cakrawala yang sama, tetapi mempercepatnya dengan piramida ketinggian maksimum. T4 memindahkan seluruh pengujian jarak-plus-frustum-plus-cakrawala ke kernel komputasi TSL yang menulis skala visibilitas per instance, yang kemudian dibaca material vegetasi untuk mengempiskan verteks tersembunyi. Penghitung tetap-terlihat selama dua frame meredam kedipan satu frame ketika titik sampel jatuh tepat di dalam atau di luar sebuah texel saat kamera sedikit bergerak. Durasinya sengaja dibuat singkat karena durasi yang lebih panjang hanya menyamarkan bug algoritmis alih-alih memperbaikinya.

Piramida inilah yang membuat spike ini berharga, dan pelajarannya adalah menyesuaikan pengujian dengan apa yang benar-benar tampil di layar. Medan yang dirender adalah mesh segitiga dengan verteks pada grid tetap berjarak dua meter, dan di antara verteks, rasterizer melakukan interpolasi linear. Karena itu, ketinggian nyata yang dirender di dalam persegi panjang mana pun adalah nilai maksimum dari verteks di dalamnya, bukan puncak noise kontinu di antara verteks tersebut. Jika piramida oklusi mengambil sampel noise pada grid yang lebih rapat, piramida akan menemukan puncak semu yang tidak pernah ditampilkan mesh dan mulai menghalangi sinar yang jelas-jelas dapat ditembus pandangan kamera. Jadi, texel dasar piramida ditempatkan tepat pada grid verteks, masing-masing menyimpan nilai maksimum dari empat verteks sudutnya, sementara level atas menggunakan reduksi maksimum 2x2 standar. Ini membuatnya presisi terhadap mesh yang dirender. Untuk pengujian sinar per langkah, kode sengaja mengambil sampel titik pada mesh menggunakan interpolasi bilinear alih-alih mengkueri piramida, karena kueri AABB subtexel mengembalikan nilai maksimum seluruh texel dan menaikkan ketinggian beberapa meter pada punggung bukit yang curam—persis jenis oklusi berlebihan yang akan menyembunyikan prop yang sebenarnya terlihat.

Hasil yang mengejutkan adalah T2, referensi brute-force, justru merupakan jalur yang keliru. Karena T2 mengambil sampel titik langsung dari noise kontinu, jalur ini mendeteksi puncak di antara verteks mesh yang tidak pernah dirender. Akibatnya, T2 sedikit melakukan oklusi berlebihan dan menyembunyikan vegetasi yang sebenarnya dapat dilihat pemain. Jalur piramida lebih cepat berkat reduksi AABB O(logN) untuk pengujian tingkat chunk, sekaligus lebih benar secara geometris karena hanya dapat mengembalikan ketinggian yang benar-benar ditampilkan mesh. Itulah inti spike ini: struktur yang dipercepat bukanlah kompromi kualitas demi kecepatan, melainkan versi yang sesuai dengan kenyataan. Culling chunk menjalankan pengujian lima titik per chunk (empat sudut atas ditambah titik tengah pada ketinggian maksimum chunk) dan mengecualikan area chunk itu sendiri dari kumpulan pengoklusi agar chunk tidak pernah mengoklusi dirinya sendiri. Jalur produksi yang direkomendasikan adalah T4: masukkan metadata instance dan medan ketinggian ke storage buffer, lalu jalankan loop yang sama dalam compute shader dengan indirect draw, karena renderer memang sedang beralih ke WebGPU.

Teknologi yang dirujuk dalam bab ini

Rumput GPU cross-quad. Tiga quad meruncing yang diputar dengan selisih enam puluh derajat per rumpun menjamin adanya satu quad yang nyaris tegak lurus dari sudut mana pun, sehingga setengah juta instance terlihat seperti padang, bukan konfeti subpiksel. Sebuah kernel komputasi menempatkan setiap rumpun (ketinggian tanah FBM yang diambil sampelnya dengan mod yang mempertahankan tanda seperti pada mesh CPU, normal beda pusat, serta ukuran dan rona per rumpun), sementara graf verteks TSL melewati matriks instance untuk menskalakan, melakukan rotasi Rodrigues ke normal, mentranslasikan, dan menerapkan angin yang dibatasi oleh tinggi. LOD berdasarkan jarak didapatkan tanpa biaya tambahan: rumpun mengempis hingga tingginya nol berdasarkan 1smoothstep(fadeNear,fadeFar,dist). Warnanya mengikuti resep BotW dari NedMakesGames, yaitu gradasi pangkal-ke-ujung pada dua palet yang dicampur berdasarkan rona. Lihat LOD berbasis GPU.

Culling oklusi cakrawala medan. Empat jalur diuji performanya pada dunia produksi: jarak, ditambah frustum, ditambah pengujian sinar heightmap yang menolak instance di balik punggung bukit, ditambah piramida ketinggian maksimum yang mempercepatnya, lalu port komputasi TSL. Piramida mengambil sampel tepat pada grid verteks dua meter yang digunakan untuk teselasi mesh (texel dasar = nilai maksimum dari empat verteks sudut, dengan reduksi maksimum 2x2 ke atas), sehingga hanya mengembalikan ketinggian yang benar-benar ditampilkan rasterizer.

Dipercepat sekaligus benar, bukan sebuah kompromi. Pengambilan sampel titik pada noise kontinu (jalur brute-force T2) menemukan puncak di antara verteks mesh yang tidak pernah dirender, sehingga melakukan oklusi berlebihan terhadap vegetasi yang terlihat. Piramida grid verteks lebih cepat (reduksi AABB O(logN)) sekaligus presisi secara geometris. Karena itu, pengujian sinar per langkah mengambil sampel bilinear dari mesh alih-alih mengkueri nilai maksimum per texel milik piramida, yang akan menaikkan ketinggian beberapa meter pada punggung bukit curam. Culling chunk menggunakan pengujian lima titik dan mengecualikan area chunk itu sendiri agar tidak pernah mengoklusi dirinya sendiri. Jalur produksi memasukkan metadata dan medan ketinggian ke storage buffer untuk culling dengan compute shader serta indirect draw.


Bagian 28 dari 29. Sebelumnya: Bagian 27 - Pulau dari noise, permukaan tanah yang tampak seperti tanah Berikutnya: Bagian 29 - Satu pengontrol, tubuh apa pun Panduan seri: /id/blog/2026-02-25-open-world-browser-series-guide