Pengalaman Hobbit

Menghidupkan Middle-Earth dengan WebGL Seluler

Daniel Isaksson
Daniel Isaksson

Secara historis, menghadirkan pengalaman interaktif, berbasis web, dan multimedia yang berat ke perangkat seluler dan tablet merupakan tantangan. Batasan utamanya adalah performa, ketersediaan API, batasan audio HTML5 di perangkat, dan kurangnya pemutaran video inline yang lancar.

Awal tahun ini, kami memulai sebuah project bersama teman-teman dari Google dan Warner Bros. untuk membuat pengalaman web yang mengutamakan perangkat seluler untuk film Hobbit baru, The Hobbit: The Desolation of Smaug. Membuat Eksperimen Chrome seluler yang sarat multimedia merupakan tugas yang sangat menginspirasi dan menantang.

Pengalaman ini dioptimalkan untuk Chrome untuk Android di perangkat Nexus baru tempat kami kini memiliki akses ke WebGL dan Web Audio. Namun, sebagian besar pengalaman ini juga dapat diakses di perangkat dan browser non-WebGL berkat komposisi yang dipercepat hardware dan animasi CSS.

Seluruh pengalaman ini didasarkan pada peta Middle-earth serta lokasi dan karakter dari film Hobbit. Dengan menggunakan WebGL, kami dapat mendramatisasi dan menjelajahi dunia yang kaya dari trilogi Hobbit dan memungkinkan pengguna mengontrol pengalaman tersebut.

Tantangan WebGL di perangkat seluler

Pertama, istilah "perangkat seluler" sangat luas. Spesifikasi untuk perangkat sangat bervariasi. Jadi, sebagai developer, Anda perlu memutuskan apakah ingin mendukung lebih banyak perangkat dengan pengalaman yang tidak terlalu kompleks atau, seperti yang kita lakukan dalam hal ini, membatasi perangkat yang didukung hanya untuk perangkat yang dapat menampilkan dunia 3D yang lebih realistis. Untuk “Perjalanan melalui Middle-earth”, kami berfokus pada perangkat Nexus dan lima smartphone Android populer.

Dalam eksperimen ini, kami menggunakan three.js seperti yang telah kami lakukan untuk beberapa project WebGL sebelumnya. Kami memulai implementasi dengan mem-build versi awal game Trollshaw yang akan berjalan dengan baik di tablet Nexus 10. Setelah beberapa pengujian awal pada perangkat, kami memiliki daftar pengoptimalan yang terlihat seperti yang biasanya kami gunakan untuk laptop dengan spesifikasi rendah:

  • Menggunakan model poli rendah
  • Menggunakan tekstur beresolusi rendah
  • Mengurangi jumlah drawcall sebanyak mungkin dengan menggabungkan geometri
  • Menyederhanakan bahan dan pencahayaan
  • Menghapus efek pasca-pengeditan dan menonaktifkan anti-aliasing
  • Mengoptimalkan performa JavaScript
  • Merender kanvas WebGL dengan ukuran setengah dan menskalakan dengan CSS

Setelah menerapkan pengoptimalan ini ke versi kasar pertama game, kami memiliki kecepatan frame stabil 30 FPS yang memuaskan. Pada saat itu, sasaran kami adalah meningkatkan visual tanpa memengaruhi kecepatan frame secara negatif. Kami telah mencoba banyak trik: beberapa ternyata benar-benar berdampak pada performa; beberapa tidak berdampak sebesar yang kami harapkan.

Menggunakan model poli rendah

Mari kita mulai dengan model. Menggunakan model low-poly tentu membantu waktu download, serta waktu yang diperlukan untuk menginisialisasi tampilan. Kami mendapati bahwa kami dapat meningkatkan kompleksitas cukup banyak tanpa banyak memengaruhi performa. Model troll yang kami gunakan dalam game ini memiliki sekitar 5 ribu wajah dan latarnya memiliki sekitar 40 ribu wajah dan semuanya berfungsi dengan baik.

Salah satu troll di hutan Trollshaw
Salah satu troll di hutan Trollshaw

Untuk lokasi lain (belum dirilis) dalam pengalaman, kami melihat lebih banyak dampak pada performa dari pengurangan poligon. Dalam hal ini, kita memuat objek poligon yang lebih rendah untuk perangkat seluler daripada objek yang kita muat untuk desktop. Membuat kumpulan model 3D yang berbeda memerlukan beberapa pekerjaan tambahan dan tidak selalu diperlukan. Hal ini benar-benar bergantung pada seberapa kompleks model Anda.

Saat mengerjakan tampilan besar dengan banyak objek, kami mencoba bersikap strategis dalam membagi geometri. Hal ini memungkinkan kami mengaktifkan dan menonaktifkan mesh yang kurang penting dengan cepat, untuk menemukan setelan yang berfungsi untuk semua perangkat seluler. Kemudian, kita dapat memilih untuk menggabungkan geometri dalam JavaScript saat runtime untuk pengoptimalan dinamis atau menggabungkannya dalam praproduksi untuk menghemat permintaan.

Menggunakan tekstur beresolusi rendah

Untuk mengurangi waktu pemuatan di perangkat seluler, kami memilih untuk memuat tekstur yang berbeda dengan ukuran setengah dari tekstur di desktop. Ternyata semua perangkat dapat menangani ukuran tekstur hingga 2048x2048 piksel dan sebagian besar dapat menangani 4096x4096 piksel. Pencarian tekstur pada setiap tekstur tampaknya tidak menjadi masalah setelah diupload ke GPU. Total ukuran tekstur harus sesuai dengan memori GPU agar tekstur tidak terus-menerus diupload dan didownload, tetapi hal ini mungkin bukan masalah besar bagi sebagian besar pengalaman web. Namun, menggabungkan tekstur ke dalam spritesheet sesedikit mungkin penting untuk mengurangi jumlah drawcall - ini adalah sesuatu yang memiliki dampak besar pada performa di perangkat seluler.

Tekstur untuk salah satu troll di hutan Trollshaw
Tekstur untuk salah satu troll di hutan Trollshaw
(ukuran asli 512x512 piksel)

Menyederhanakan bahan dan pencahayaan

Pilihan materi juga dapat sangat memengaruhi performa dan harus dikelola dengan bijak di perangkat seluler. Menggunakan MeshLambertMaterial (per penghitungan cahaya vertex) di three.js, bukan MeshPhongMaterial (per penghitungan cahaya texel) adalah salah satu hal yang kami gunakan untuk mengoptimalkan performa. Pada dasarnya, kita mencoba menggunakan shader yang sederhana dengan penghitungan pencahayaan sesedikit mungkin.

Untuk melihat pengaruh material yang Anda gunakan terhadap performa tampilan, Anda dapat mengganti material tampilan dengan MeshBasicMaterial . Hal ini akan memberi Anda perbandingan yang baik.

scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});

Mengoptimalkan performa JavaScript

Saat mem-build game untuk perangkat seluler, GPU tidak selalu menjadi kendala terbesar. Banyak waktu yang dihabiskan di CPU, terutama fisika dan animasi kerangka. Satu trik yang terkadang membantu, bergantung pada simulasi, adalah hanya menjalankan penghitungan yang mahal ini setiap frame. Anda juga dapat menggunakan teknik pengoptimalan JavaScript yang tersedia dalam hal penggabungan objek, pengumpulan sampah, dan pembuatan objek.

Memperbarui objek yang dialokasikan sebelumnya dalam loop, bukan membuat objek baru, adalah langkah penting untuk menghindari "gangguan" pengumpulan sampah selama game.

Misalnya, pertimbangkan kode seperti ini:

var currentPos = new THREE.Vector3();

function gameLoop() {
  currentPos = new THREE.Vector3(0+offsetX,100,0);
}

Versi yang ditingkatkan dari loop ini menghindari pembuatan objek baru yang harus di-garbage collection:

var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
  currentPos.copy(originPos).x += offsetX;
  //or
  currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}

Sebisa mungkin, pengendali peristiwa hanya boleh memperbarui properti, dan membiarkan loop render requestAnimationFrame menangani pembaruan panggung.

Tips lainnya adalah mengoptimalkan dan/atau menghitung operasi ray-casting terlebih dahulu. Misalnya, jika Anda perlu melampirkan objek ke mesh selama gerakan jalur statis, Anda dapat "merekam" posisi selama satu loop, lalu membaca dari data ini, bukan melakukan ray-casting terhadap mesh. Atau seperti yang kita lakukan di pengalaman Rivendell, ray-cast untuk mencari interaksi mouse dengan mesh tak terlihat low-poly yang lebih sederhana. Menelusuri tabrakan pada mesh poli tinggi sangat lambat dan harus dihindari dalam loop game secara umum.

Merender kanvas WebGL dengan ukuran setengah dan menskalakannya dengan CSS

Ukuran kanvas WebGL mungkin adalah satu-satunya parameter paling efektif yang dapat Anda sesuaikan untuk mengoptimalkan performa. Makin besar kanvas yang Anda gunakan untuk menggambar tampilan 3D, makin banyak piksel yang harus digambar pada setiap frame. Hal ini tentu saja memengaruhi performa.Nexus 10 dengan layar piksel 2560x1600 berkepadatan tinggi harus mendorong 4 kali jumlah piksel seperti tablet berkepadatan rendah. Untuk mengoptimalkannya untuk perangkat seluler, kita menggunakan trik dengan menetapkan kanvas ke setengah ukuran (50%), lalu menskalakannya ke ukuran yang diinginkan (100%) dengan transformasi 3D CSS yang dipercepat hardware. Kelemahannya adalah gambar yang berpiksel, di mana garis tipis dapat menjadi masalah, tetapi pada layar beresolusi tinggi, efeknya tidak terlalu buruk. Performa ekstra ini benar-benar sepadan.

Adegan yang sama tanpa penskalaan kanvas di Nexus 10 (16FPS) dan diskalakan menjadi 50% (33FPS)
Adegan yang sama tanpa penskalaan kanvas di Nexus 10 (16 FPS) dan diskalakan menjadi 50% (33 FPS).

Objek sebagai elemen penyusun

Agar dapat membuat labirin besar di kastil Dol Guldur dan lembah Rivendell yang tidak pernah berakhir, kami membuat serangkaian model 3D blok bangunan yang akan digunakan kembali. Dengan menggunakan kembali objek, kita dapat memastikan bahwa objek dibuat instance-nya dan diupload di awal pengalaman, bukan di tengah-tengahnya.

Blok penyusun objek 3D yang digunakan di labirin Dol Guldur.
Elemen penyusun objek 3D yang digunakan di labirin Dol Guldur.

Di Rivendell, kami memiliki sejumlah bagian tanah yang terus-menerus kami posisikan ulang dalam kedalaman Z seiring perjalanan pengguna. Saat pengguna melewati bagian, bagian tersebut akan diposisikan ulang di kejauhan.

Untuk kastil Dol Guldur, kami ingin labirin dibuat ulang untuk setiap game. Untuk melakukannya, kita membuat skrip yang membuat ulang labirin.

Menggabungkan seluruh struktur menjadi satu mesh besar sejak awal akan menghasilkan tampilan yang sangat besar dan performa yang buruk. Untuk mengatasi hal ini, kami memutuskan untuk menyembunyikan dan menampilkan elemen penyusun, bergantung pada apakah elemen penyusun tersebut terlihat. Sejak awal, kami memiliki ide untuk menggunakan skrip raycaster 2D, tetapi pada akhirnya kami menggunakan pemangkasan frustrum three.js bawaan. Kami menggunakan kembali skrip raycaster untuk memperbesar "bahaya" yang dihadapi pemain.

Hal besar berikutnya yang harus ditangani adalah interaksi pengguna. Di desktop, Anda memiliki input mouse dan keyboard; di perangkat seluler, pengguna berinteraksi dengan sentuhan, geser, cubit, orientasi perangkat, dll.

Menggunakan interaksi sentuh dalam pengalaman web seluler

Menambahkan dukungan sentuh tidaklah sulit. Ada artikel bagus untuk dibaca tentang topik ini. Namun, ada beberapa hal kecil yang dapat membuatnya lebih rumit.

Anda dapat menggunakan keduanya, yaitu sentuh dan mouse. Chromebook Pixel dan laptop lain yang mendukung sentuh memiliki dukungan mouse dan sentuh. Salah satu kesalahan umum adalah memeriksa apakah perangkat mengaktifkan sentuh, lalu hanya menambahkan pemroses peristiwa sentuh dan tidak ada untuk mouse.

Jangan perbarui rendering di pemroses peristiwa. Simpan peristiwa sentuh ke variabel dan bereaksi terhadapnya dalam loop render requestAnimationFrame. Hal ini meningkatkan performa dan juga menggabungkan peristiwa yang bertentangan. Pastikan Anda menggunakan kembali objek, bukan membuat objek baru di pemroses peristiwa.

Ingat bahwa ini adalah multi-sentuh: event.touches adalah array dari semua sentuhan. Dalam beberapa kasus, akan lebih menarik untuk melihat event.targetTouches atau event.changedTouches dan hanya bereaksi terhadap sentuhan yang Anda minati. Untuk memisahkan ketukan dari geser, kita menggunakan penundaan sebelum memeriksa apakah sentuhan telah bergerak (geser) atau masih diam (ketuk). Untuk mendapatkan cubit, kita mengukur jarak antara dua sentuhan awal dan perubahannya dari waktu ke waktu.

Di dunia 3D, Anda harus memutuskan bagaimana kamera bereaksi terhadap tindakan mouse vs. geser. Salah satu cara umum untuk menambahkan gerakan kamera adalah dengan mengikuti gerakan mouse. Hal ini dapat dilakukan dengan kontrol langsung menggunakan posisi mouse atau dengan gerakan delta (perubahan posisi). Anda tidak selalu menginginkan perilaku yang sama di perangkat seluler seperti browser desktop. Kami melakukan pengujian secara ekstensif untuk memutuskan apa yang terasa tepat untuk setiap versi.

Saat menangani layar dan layar sentuh yang lebih kecil, Anda akan mendapati bahwa jari pengguna dan grafik interaksi UI sering kali menghalangi apa yang ingin Anda tampilkan. Hal ini sudah biasa kita lakukan saat mendesain aplikasi native, tetapi belum pernah kita pikirkan sebelumnya dengan pengalaman web. Hal ini merupakan tantangan nyata bagi desainer dan desainer UX.

Ringkasan

Pengalaman kami secara keseluruhan dari project ini adalah WebGL di perangkat seluler berfungsi dengan sangat baik, terutama di perangkat baru kelas atas. Dalam hal performa, tampaknya jumlah poligon dan ukuran tekstur sebagian besar memengaruhi waktu download dan inisialisasi, serta materi, shader, dan ukuran kanvas WebGL adalah bagian terpenting untuk dioptimalkan guna meningkatkan performa seluler. Namun, jumlah bagian yang memengaruhi performa adalah semua hal yang dapat Anda lakukan untuk mengoptimalkan jumlah.

Menargetkan perangkat seluler juga berarti Anda harus terbiasa memikirkan interaksi sentuh dan tidak hanya ukuran piksel, tetapi juga ukuran fisik layar. Dalam beberapa kasus, kami harus mendekatkan kamera 3D untuk benar-benar melihat apa yang terjadi.

Eksperimen ini telah diluncurkan dan merupakan perjalanan yang luar biasa. Semoga Anda menyukainya.

Ingin mencobanya? Lakukan Perjalanan ke Middle-Earth Anda sendiri.