Membangun dunia terbuka di browser, bagian 14: Dunia menjadi hidup
Oleh Oleg Sidorkin, CTO dan Salah Satu Pendiri Cinevva
Baru mengikuti seri ini? Gunakan panduan seri. Panduan tersebut menjelaskan apa itu spike dan menautkan semua bagiannya.
Setelah bagian 13, kami memiliki fitur pemahatan. Meninggikan medan, mengukir gua, menghaluskan tebing, semuanya secara waktu nyata dengan sambungan yang tetap utuh. Namun, dunianya tampak seperti demo teknologi. Shading datar dengan warna debug, overlay wireframe, serta geometri abu-abu dengan warna LOD untuk membedakan chunk. Anda bisa mengeditnya. Namun, Anda belum bisa merasakannya.
Tiga spike mengubah hal itu. Bukan arsitekturnya. Pipeline, buffer, dan penyambungan seam-nya sama persis seperti di Bagian 13. Kami hanya menambahkan lapisan-lapisan yang membuat medan terasa seperti sebuah tempat: permukaan yang merespons bentuknya, kehidupan yang tumbuh di permukaan tersebut, dan tubuh yang berjalan di atasnya.
Perbedaan antara "secara teknis berfungsi" dan "saya ingin tetap berada di sini" ternyata sangat kecil.
Pahat tebing dan saksikan permukaannya berubah menjadi batu
Spike 28 mengajukan pertanyaan yang spesifik: bisakah material empat lapis dengan pemetaan triplanar berjalan pada medan hasil komputasi tanpa menghabiskan anggaran frame? Jawabannya ya, tetapi bagian yang menarik adalah apa yang terjadi setelahnya.
Buka Spike 28 di tab baru ↗ · Lihat kode sumber
Empat tekstur prosedural (rumput, batu, pasir, salju) dihasilkan dari noise FBM saat startup. Tanpa file eksternal, tanpa pipeline aset, hanya matematika dan sebuah DataTexture. Bobot material berasal dari permukaannya sendiri: kemiringan dan ketinggian. Area datar di bawah garis pepohonan mendapatkan rumput. Permukaan curam mendapatkan batu. Dataran rendah mendapatkan pasir. Puncak tinggi mendapatkan salju. Setiap bobot mentah berasal dari smoothstep pada kemiringan dan ketinggian, lalu dinormalisasi per fragmen sehingga
Momen yang benar-benar meyakinkan kami: tinggikan medan dengan kuas untuk membuat tebing curam, lalu tekstur batu muncul pada permukaan baru di frame yang sama. Ratakan kembali dan rumput mengambil alih permukaan itu. Materialnya tidak mengetahui apa pun tentang kuas. Material hanya membaca posisi dunia dan normal permukaan, data yang sama dengan yang digunakan untuk membangun geometri. Siklus umpan balik dari pemahatan ke visual berlangsung seketika dan tanpa skrip khusus.
Spike 8 menguji biaya material medan satu setengah bulan lalu dengan mesh statis di WebGL. Spike 28 membuktikan bahwa material tersebut berfungsi pada medan dinamis hasil komputasi dengan seluruh pipeline pemahatan berjalan di bawahnya. Kami sudah mengalokasikan anggaran untuk ini, tetapi melihat semuanya tetap berjalan pada 60 fps masih terasa melegakan.
80.000 rumpun rumput dan langit-langit gua
Spike 29 adalah saat naluri pengembangan game mengambil alih.
Buka Spike 29 di tab baru ↗ · Lihat kode sumber
Kami menginginkan rumput seperti di Breath of the Wild. Bukan jumlah poligonnya, melainkan rasanya. Rumput yang menutupi tempat-tempat yang tepat, bergerak tertiup angin, dan membuat Anda ingin berlari menerobosnya.
Setiap rumpun rumput terdiri dari tiga quad yang saling berpotongan pada sudut 60 derajat, dengan empat segmen vertikal agar dapat menekuk. Bentuk silang ini tampak bervolume dari sudut mana pun tanpa trik billboard. Satu InstancedMesh per chunk, 80.000 rumpun di seluruh dunia, dengan total sekitar 2,4 juta verteks rumput.
Penempatan adalah bagian yang penting. CPU menyusuri grid yang diberi jitter di setiap chunk dan mengevaluasi logika kemiringan/ketinggian yang sama dengan yang digunakan shader material GPU. Di tempat sistem material mengatakan "rumput", rumput tumbuh. Di tempat batu atau pasir mendominasi, kepadatannya turun menjadi nol. Transisinya mulus karena bobot smoothstep yang mendasarinya menghasilkan gradien kontinu di tepi bioma. Anda tidak melihat batas karena memang tidak ada batas.
Lalu kami melakukan sesuatu yang belum tentu saya yakini akan berhasil. Rumput pada permukaan SDF. Fungsi penyebaran menyusuri setiap kolom volume SDF dan mencari zero-crossing di antara voxel yang bersebelahan. Ketika SDF beralih dari negatif ke positif, di sanalah terdapat permukaan. Normal permukaannya hanyalah gradien medan yang dinormalisasi,
Ukir gua dengan kuas SDF di Spike 27. Kembali ke Spike 29 dan Anda akan melihat rumput tumbuh di atas langit-langit gua. Kode penyebaran tidak tahu apa itu gua. Kode itu hanya melihat permukaan dengan kemiringan dan ketinggian yang tepat. Perilaku emergen semacam inilah yang membuat sistem dunia terbuka memuaskan untuk dibangun.
Angin berupa gelombang sinus di shader verteks TSL, yang dimodulasi oleh koordinat V bilah rumput sehingga ujungnya bergoyang sementara akarnya tetap tertanam. Tekan V untuk beralih antara lembut, kuat, dan mati. Angin bekerja pada seluruh 80 ribu rumpun secara bersamaan tanpa biaya CPU karena semuanya dijalankan di shader verteks.
Spike 7 menguji 50 ribu instans rumput dan kami khawatir akan mencapai batasnya. Spike 29 menjalankan 80 ribu instans di atas medan komputasi, penyambungan seam, tekstur multi-material, dan kuas. Mengelompokkan semuanya ke dalam lebih sedikit draw call InstancedMesh tetap lebih penting daripada mengurangi jumlah verteks per bilah. Pelajaran dari Spike 7 tetap terbukti.
Satu hal yang kami tunda: rumput tidak diperbarui ketika Anda memahat medan di bawahnya. Matriks instans ditetapkan saat penyebaran dilakukan. Pahat bukit menjadi lembah dan rumput akan melayang di udara sampai Anda menekan G untuk menyebarkannya kembali. Cukup baik untuk sebuah spike. Versi produksi akan memerlukan penyebaran ulang untuk chunk yang ditandai kotor.
Langkah kaki pertama
Spike 30 adalah spike yang terus ingin saya buka kembali.
Buka Spike 30 di tab baru ↗ · Lihat kode sumber
Tanpa pustaka fisika. Kapsul khusus dengan timestep tetap 120 Hz. Basis kode produksi menggunakan Rapier di web worker (Spike 2 membuktikan latensinya tidak bermasalah), tetapi spike ini perlu membuktikan kueri tabrakannya sendiri. Bisakah karakter berjalan melintasi medan heightmap, melangkah ke medan SDF hasil pemahatan, dan tidak terjatuh menembusnya?
Intinya adalah fungsi bernama terrainQuery(x, y, z). Fungsi ini memeriksa apakah posisi berada di dalam chunk yang dikunci ke MC. Jika ya, fungsi tersebut melakukan interpolasi trilinear pada salinan SDF di CPU dan mengembalikan gradien sebagai normal permukaan. Jika tidak, fungsi melakukan pencarian heightmap dengan normal beda-tengah. Karakter tidak tahu sistem medan mana yang sedang dipijaknya. Karakter hanya meminta posisi tanah dan mendapatkan jawaban.
Tabrakan SDF adalah bagian yang saya perkirakan akan sulit. Tujuh titik probe di sekitar kapsul (bawah, tengah, atas, dan empat offset arah mata angin). Probe pada posisi
sehingga hanya tersisa komponen yang meluncur di sepanjang permukaan. Ini bukan mesin fisika. Hanya kueri geometri dan respons sederhana. Namun, sistem ini menangani gua, overhang, dan terowongan hasil pemahatan tanpa kode kasus khusus untuk tiap bentuk. Anda berjalan memasuki gua yang diukir sepuluh detik lalu dan kapsul mengikuti kontur langit-langitnya dengan tepat.
Model pergerakannya bermula dari sesuatu yang praktis, lalu menjadi menyenangkan. Berjalan, berlari, sprint, melompat, dan meluncur di lereng dengan kemiringan di atas 45 derajat. Kemudian saya menambahkan paraglider ala BotW dan spike ini berubah menjadi sesuatu yang tidak ingin saya tutup.
Tekan Space saat berada di udara. Gravitasi turun dari -30 menjadi -4. Kecepatan jatuh dibatasi pada -3. Mesh sayap delta terbuka dari kapsul dengan interpolasi skala. Berbelok ke kiri dan sayapnya ikut miring. Arah hadap kapsul secara otomatis sejajar dengan vektor kecepatan sehingga Anda selalu melihat ke arah gerak. Lepaskan Space untuk jatuh. Sentuh tanah dan Anda langsung kembali berlari.
Kamera mundur ke sudut pandang orang ketiga (tekan P untuk beralih). Kamera mengikuti pemain dari belakang dengan penghalusan yaw. Ray-march dari pemain ke kamera menguji medan dalam 20 langkah. Terbang memasuki gua dan lengan kamera memendek secara halus, bukan menembus batu. Keluar dari sisi lain dan lengan kamera kembali memanjang.
Inilah momen yang membuat spike ini sepadan: pahat tebing tinggi dengan kuas heightmap. Beralih ke kamera orang ketiga. Berlari ke tepinya. Lompat. Buka glider. Berbelok ke kiri di atas medan yang baru saja Anda pahat, dengan rumput yang Anda tumbuhkan di Spike 29 bergoyang di bawah, tekstur batu dari Spike 28 di permukaan tebing, serta seam Transvoxel yang tetap tersambung di setiap batas chunk. Mendarat di sisi lain. Semuanya bekerja bersama dalam satu tab browser.
Memahat medan di bawah kaki sendiri
Satu hal yang sempat saya ragukan: apa yang terjadi ketika Anda memahat medan yang sedang dipijak karakter? Perubahan heightmap langsung diteruskan melalui terrainQuery() karena fungsi tersebut membaca buffer CPU secara langsung. Perubahan SDF diteruskan melalui salinan SDF di CPU. Kapsul menyelesaikan penetrasi pada tick fisika berikutnya, yang pada 120 Hz terjadi dalam
Semuanya langsung berfungsi. Tinggikan tanah di bawah pemain dan pemain ikut terangkat. Ukir tanah di bawahnya dan pemain akan jatuh. Tanpa penanganan khusus. Fisika berjalan cukup cepat sehingga perubahan medan dalam satu frame tidak pernah menghasilkan penetrasi besar. Ini adalah keuntungan tak terduga dari pemilihan timestep 120 Hz. Kami memilihnya untuk pergerakan yang mulus, dan ternyata pilihan itu juga membuat pengeditan medan aman tanpa biaya tambahan.
Setelah 30 spike
Kami memulai seri ini dengan heightmap datar dan 500 kubus. Kini kami memiliki medan hasil pemahatan dengan gua volumetrik, penyambungan seam Transvoxel, tekstur multi-material yang merespons bentuk permukaan, 80.000 rumpun rumput yang bergoyang tertiup angin, serta karakter yang dapat berjalan, sprint, melompat, dan melayang melintasi semuanya.
Belum ada satu pun dari semua ini dalam versi produksi. Basis kode world/client/ masih menjalankan WebGL dengan chunk heightmap sederhana. Semua hasil dari 30 spike ini berada di halaman HTML mandiri. Berikutnya adalah pekerjaan integrasi: bermigrasi ke WebGPURenderer, menghubungkan kebijakan hibrida HM/MC ke pengelola chunk, serta menghubungkan sistem kuas dan material ke mode multipemain.
Namun, sistem rendering bukan lagi risikonya. Pertanyaan yang masih terbuka kini berkaitan dengan aliran data: persistensi edit, sinkronisasi jaringan untuk sapuan kuas, dan pemahatan kolaboratif. Hal-hal yang terjadi di antara pemain, bukan di antara segitiga.
Jika Anda mengikuti seri ini sejak Bagian 1, terima kasih sudah bertahan melewati bagian-bagian yang berantakan. Jika Anda baru menemukannya, kembalilah ke Bagian 1. Pelajaran justru berada di jalan-jalan yang keliru.
Teknologi yang dirujuk dalam bab ini
TSL (Three Shading Language). Sistem shader berbasis node milik Three.js untuk renderer WebGPU. Material disusun dari node (positionWorld, normalWorld, smoothstep, triplanarTexture) menggunakan komposisi fungsi dalam JavaScript. Graf shader dikompilasi menjadi WGSL saat runtime. TSL menggantikan ShaderMaterial GLSL mentah untuk target WebGPU dan menyediakan interoperabilitas antara fitur material standar Three.js (cahaya, bayangan, kabut) dan logika kustom per fragmen.
Pemetaan triplanar. Teknik proyeksi tekstur yang mengambil sampel tekstur tiga kali (bidang XY, XZ, YZ) lalu mencampurkannya berdasarkan arah normal permukaan. Teknik ini menghilangkan peregangan UV pada geometri mesh arbitrer, yang sangat penting untuk keluaran marching cubes karena segitiganya tidak memiliki koordinat UV bermakna. TSL menyediakan triplanarTexture() sebagai node bawaan.
Vegetasi terinstans dengan bilah cross-quad. Setiap rumpun rumput terdiri dari tiga quad yang saling berpotongan pada sudut 60 derajat, sehingga menciptakan tampilan bervolume dari arah pandang mana pun. Empat segmen vertikal per quad memungkinkan tekukan halus untuk animasi angin. Seluruh hamparan dirender sebagai satu InstancedMesh per chunk. Kapasitas dialokasikan 25% lebih besar agar instans baru dapat mengisi slot yang telah dicadangkan ketika medan diedit tanpa mengalokasikan ulang buffer GPU. Lihat panduan pembuatan lanskap kami tentang vegetasi. Tabrakan kapsul vs SDF. Tabrakan karakter dengan medan volumetrik tanpa mesin fisika. Kapsul diuji pada beberapa titik terhadap SDF. Saat nilai medan lebih kecil daripada radius kapsul, gradien memberikan normal ke arah luar dan selisihnya menghasilkan kedalaman penetrasi. Pendekatan ini menangani gua, bagian menjorok, dan terowongan tanpa kode khusus untuk setiap bentuk. Lihat tabrakan medan SDF.
Pengontrol karakter dengan timestep tetap. Fisika melangkah pada 120 Hz terlepas dari frame rate, dengan mengakumulasi waktu nyata dan memprosesnya dalam langkah berukuran tetap. Jumlah substep maksimum mencegah spiral kematian pada frame yang lambat. Penempelan ke tanah menjaga kapsul tetap menyentuh permukaan saat melintasi lereng. Timestep tetap memastikan perilaku deterministik untuk replay multipemain di masa mendatang.
Bagian 14 dari 29. Sebelumnya: Bagian 13 - Memahat medan dan matinya fungsi matematika Berikutnya: Bagian 15 - Ganti baseline, lalu sinkronkan Panduan seri: /blog/2026-02-25-open-world-browser-series-guide