Membangun dunia terbuka di browser, bagian 17: Animasi yang tidak memerlukan retargeting, dan pencarian aset langsung
Oleh Oleg Sidorkin, CTO dan Co-Founder Cinevva
Baru di sini? Gunakan panduan seri. Panduan ini menjelaskan apa itu spike dan menyediakan tautan ke semua bagian.
Bagian 16 memberi kita dunia tempat objek dapat ditempatkan. Bagian ini memberi pemain sesuatu yang lebih menarik untuk dilakukan di dalamnya: kumpulan animasi kelas pertarungan dan cara memasukkan salah satu dari seribu model CC0 ke dunia hanya dengan mengetikkan pencarian.
262 klip, satu skeleton, tiga perbaikan retargeting
Buka Spike 35 di tab baru ↗ · Lihat kode sumber
Rig pemain adalah skeleton 3MIKE bergaya CC4 dengan 213 tulang, termasuk rig wajah yang terdiri dari lebih dari 80 tulang dan sendi jari lengkap. Sumber animasinya tidak cocok dengan rig tersebut. Versi pertama melakukan retargeting pada klip pertarungan Mixamo yang dipilih secara manual, tetapi kumpulan sumbernya terbatas dan lokomosi terbagi secara canggung antara Mixamo dan beberapa animasi diam BVH dari Kimodo. Pencapaian sebenarnya terjadi ketika kami mengalihkan pustaka sumber ke dua paket Universal Animation Library dari Quaternius, dengan total 262 klip pada satu manekin bergaya UE5 yang konsisten dan memiliki 65 tulang: kombo pedang, panahan, memanjat, berlari di dinding, mengelak, reaksi terkena serangan, emote, dan kumpulan lokomosi yang jauh lebih rapi.
Artinya, kami memetakan sumber dengan 65 tulang ke target dengan 213 tulang, tanpa kesamaan nama tulang, orientasi T-pose, maupun proporsi anggota tubuh. Setiap klip dipetakan ulang dalam tiga langkah: peta nama tulang, penyelarasan bind pose agar kedua rig memiliki orientasi referensi yang sama, dan penskalaan track posisi agar rig sumber yang lebih pendek tidak membuat karakter terbenam setinggi lutut di lantai. Untuk membuatnya bekerja dengan benar, kami harus memburu serangkaian bug yang masing-masing mengajarkan sesuatu yang spesifik.
Karakter awalnya terlihat terlalu terpuntir, dengan setiap bahu dan siku berputar sekitar 30° terlalu jauh. Penyebabnya adalah ketidakcocokan pose: sumber UAL menggunakan T-pose sejati, bind CC4 berupa A-pose, dan retargeter mengasumsikan kedua rig berada dalam pose referensi yang sama. Akibatnya, selisih A-to-T ditambahkan ke setiap delta per frame. Perbaikannya memaksa rantai lengan CC4 ke T-pose sejati untuk merekam bind, lalu melakukan retargeting agar deltanya tetap kecil.
Kemudian tubuh berhenti berpindah posisi. Animasi knockback membuat tubuh bagian atas terpental, tetapi kaki tetap tertanam di tempat. UAL menempatkan perpindahan pada tulang root, bukan pelvis, sehingga membaca posisi pelvis menghasilkan gerakan yang nyaris nol. Perbaikannya menjumlahkan root.position dan pelvis.position, menskalakan komponen horizontalnya, lalu menulis satu track posisi pinggul. Saat mengerjakannya, kami menemukan offset vertikal meleset sekitar 12% karena kami menggunakan rasio proporsi anggota tubuh global untuk sumbu Y, padahal tinggi pelvis memerlukan rasio pinggul-ke-lantai. Dua rasio, dua sumbu: RL_BoneRoot.position ternyata berasal dari pemetaan root UAL (pada
Perbaikan terkecil justru paling memuaskan untuk dilihat. Karakter menggenggam pedang dengan jari-jari lemas dalam bind pose karena peta tulang tidak memiliki entri untuk jari, sedangkan retargeter hanya menulis track untuk tulang yang dipetakan. Menambahkan 30 tulang jari—lima jari, tiga segmen, dua tangan, dengan menghapus helper ujung keempat milik UAL yang tidak mendeformasi mesh—membuat tangan menutup pada gagang dan terbuka saat melepaskannya.
Membuktikan 262 klip tanpa menonton 262 klip
Pustaka berisi 262 klip tidak mungkin diverifikasi hanya dengan pengamatan visual, jadi kami membangun pengujian paritas lintasan offline: skrip Node headless yang memuat setiap paket, mengambil sampel skeleton sumber pada 60Hz, menjalankan pipeline retargeting, lalu membandingkan posisi dan rotasi dunia setiap tulang dengan sumber setelah penskalaan. Penyimpangan pelvis pada sumbu Y memiliki nilai maksimum 0,003 m. Tangan menunjukkan offset konstan sebesar 2,5° yang awalnya tampak seperti “jari tidak mengikuti animasi”, tetapi offset konstan sebenarnya merupakan delta bind antara tangan UAL yang datar dan bind CC4 yang sedikit melengkung, dan nilainya tidak berubah sepanjang klip. Kesalahan animasi yang sesungguhnya muncul sebagai penyimpangan yang berubah dari frame ke frame. Setelah hal itu dipahami, pengujian tersebut menjadi pemeriksaan regresi sekali jalan: jika penyimpangan suatu klip tidak lagi mempertahankan baseline konstan tersebut, berarti perubahan terbaru merusak retargeting.
Manekin referensi yang ditampilkan berdampingan membuat aspek visual proses debugging menjadi sangat menentukan. Menekan tombol garis miring terbalik menampilkan rig sumber pemilik klip yang sedang aktif di sebelah pemain, sehingga pertanyaan “apakah bahunya terpuntir?” berubah menjadi “apakah puntiran itu sudah ada pada sumber, atau ditambahkan oleh retargeting?” Pengujian numerik menangkap regresi, referensi visual menangkap kesalahan bind pose yang tidak terlihat dalam angka, dan keduanya bersama-sama mengakhiri permainan tebak-tebakan.
Setelah pipeline stabil, peralihan baseline WASD/lompat/berenang dari Mixamo ke UAL cukup menggunakan peta alias sederhana: FSM tetap memakai nama status generik seperti idle dan walk, yang diterjemahkan menjadi nama klip UAL saat pemutaran. Animasi berenang memerlukan offset rig per klip karena pose gaya bebas dan mengapung di tempat menempatkan pelvis pada ketinggian anatomi yang berbeda. Karena itu, kami menenggelamkan rig setengah meter untuk berenang aktif dan lebih dalam untuk mengapung di tempat, lalu melakukan blending di antaranya secara mulus pada 5Hz.
Ketik satu kata, dapatkan satu model
Buka Spike 36 di tab baru ↗ · Lihat kode sumber
Spike 34 menghabiskan satu hari untuk mengurasi paket CC0 secara manual. Solusi jangka panjangnya adalah kotak pencarian. Polyhaven menerbitkan sekitar 1.100 model CC0 melalui API JSON yang permisif dan CDN deterministik, dan spike ini menghubungkan seluruh alurnya—kueri, thumbnail, pemuatan, dan rendering—dari halaman statis tanpa tahap build, dalam sekitar 300 baris JavaScript vanilla ditambah three.js.
Saat dimulai, aplikasi mengambil seluruh katalog satu kali, sekitar 600 KB. Pencarian menggunakan penilaian sepenuhnya di sisi klien—nama memiliki prioritas di atas id, id di atas kategori, dan kategori di atas tag—dengan debounce 120 ms, lalu merender 60 kartu teratas. Thumbnail dimuat secara lazy melalui IntersectionObserver agar mengetik tidak langsung memicu 60 permintaan sekaligus. Bagian yang menarik adalah proses pemuatannya. Endpoint file Polyhaven menyediakan glTF multi-file, bukan GLB, dengan tekstur yang digunakan bersama lintas resolusi dan dipisahkan menjadi file tersendiri. Endpoint tersebut juga mengembalikan peta include dari path relatif ke URL CDN absolut. Alih-alih mengunduh dan memodifikasi JSON sendiri, kami meneruskan peta tersebut melalui LoadingManager.setURLModifier, yang dipanggil untuk setiap dependensi yang dibutuhkan loader—file .bin dan setiap tekstur—lalu menyelesaikan URL-nya melalui CDN. Satu klik, terasa seperti satu file. API dan CDN sama-sama menetapkan CORS permisif, yang diverifikasi menggunakan curl sebelum menulis kode klien apa pun, sehingga proxy tidak diperlukan. Material PBR dirender dengan benar menggunakan RoomEnvironment dan pengaturan default tone mapping ACES tanpa penyesuaian per aset, sementara tekstur 1k menjaga ukuran model umum di kisaran 2 hingga 5 MB, alih-alih 20 hingga 40 MB pada resolusi 4k.
Tahap WASM meshoptimizer melengkapinya dengan reducer non-destruktif: setiap mesh menyimpan klona geometri aslinya, dan perubahan rasio membangun ulang index buffer dari klona tersebut, bukan menyederhanakan hasil sebelumnya secara kumulatif. Geometri multi-material disederhanakan per grup dan geometry.groups dibangun ulang agar slot material tidak menyatu. Sebuah kursi berlengan berkurang dari 5.626 segitiga pada kualitas penuh menjadi 2.812 pada kualitas setengah.
Teknologi yang dirujuk dalam bab ini
Retargeting skeleton dengan penyelarasan bind pose. Memetakan animasi dari satu skeleton ke skeleton lain yang memiliki nama tulang, proporsi, dan pose referensi berbeda memerlukan tiga koreksi: peta nama tulang, penyelarasan bind pose agar kedua rig memiliki orientasi referensi yang sama—dengan memaksa rantai lengan A-pose target ke T-pose sumber—dan penskalaan track posisi. Ketidakcocokan pose menambahkan rotasi A-to-T ke setiap delta per frame sehingga menggandakan rotasi sendi. Tulang helper yang tidak dipetakan harus dibuang karena tulang root pada
Dua rasio skala untuk satu rig. Perpindahan horizontal menggunakan rasio proporsi anggota tubuh global—ukuran skeleton secara keseluruhan—tetapi offset vertikal pelvis menggunakan rasio pinggul-ke-lantai root, bukan pelvis, sehingga keduanya harus dijumlahkan ke dalam satu track posisi pinggul untuk mempertahankan gerakan pada klip knockback, memanjat, dan lokomosi.
Pengujian paritas lintasan offline. Skrip headless mengambil sampel skeleton sumber pada 60Hz, menjalankan pipeline retargeting, lalu membandingkan transformasi dunia setiap tulang dengan sumber. Offset konstan per frame merupakan delta bind yang tidak berbahaya, sedangkan penyimpangan yang berubah dari frame ke frame merupakan kesalahan nyata. Dengan demikian, pengujian ini menjadi pemeriksaan regresi yang terpicu ketika suatu perubahan menghilangkan atau mendistorsi track. Isolasi GLB per paket—memuat salinan baru dan membebaskannya sebelum paket berikutnya—mencegah perebutan cache selama pengujian menyeluruh.
LoadingManager.setURLModifier untuk graf glTF CDN. Ketika CDN mengirimkan glTF sebagai graf URI relatif yang disertai peta include—path relatif ke URL absolut—setURLModifier menyelesaikan setiap dependensi yang diminta loader melalui CDN tanpa menulis ulang JSON. Hal ini menyederhanakan distribusi multi-file dan multi-resolusi menjadi pemuatan dengan satu klik. Menghapus geometri, material, dan peta tekstur model sebelumnya sebelum memuat model berikutnya mencegah akumulasi ratusan MB memori GPU selama satu sesi penelusuran.
Penyederhanaan mesh non-destruktif. Menyimpan klona geometri asli setiap mesh dan hanya membangun ulang index buffer untuk setiap rasio penyederhanaan membuat perubahan tetap cepat serta menghindari kerusakan kumulatif akibat penyederhanaan berulang. Menjalankannya pada setiap irisan geometry.groups dan merekonstruksi grup mempertahankan penetapan multi-material. Lihat LOD dan meshoptimizer untuk mengetahui bagaimana teknik ini digunakan dalam LOD berbasis jarak.
Bagian 17 dari 29. Sebelumnya: Bagian 16 - Struktur untuk dunia yang terus berkembang Berikutnya: Bagian 18 - Kuas sebar yang terasa ditempatkan oleh AI Panduan seri: /id/blog/2026-02-25-open-world-browser-series-guide