Pengalaman Hobbit

Merevitalisasi Middle-Earth dengan WebGL Seluler

Daniel Isaksson
Daniel Isaksson

Secara historis, menghadirkan pengalaman interaktif berbasis web dan multimedia ke perangkat seluler dan tablet merupakan sebuah tantangan. Hambatan utamanya adalah performa, ketersediaan API, keterbatasan audio HTML5 pada perangkat, dan kurangnya pemutaran video inline yang lancar.

Awal tahun ini, kami memulai proyek dengan teman dari Google dan Warner Bros. untuk membuat pengalaman web yang berorientasi seluler untuk film Hobbit baru, The Hobbit: The Desolation of Smaug. Membuat Eksperimen Chrome seluler untuk multimedia yang sangat menarik adalah 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 Audio Web. Namun, sebagian besar pengalaman dapat diakses di perangkat dan browser non-WebGL juga berkat pengomposisian yang dipercepat perangkat keras dan animasi CSS.

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

Tantangan WebGL di perangkat seluler

Pertama, istilah "perangkat seluler" sangat luas. Spesifikasi untuk berbagai perangkat sangat bervariasi. Jadi, sebagai developer, Anda harus memutuskan apakah ingin mendukung lebih banyak perangkat dengan pengalaman yang lebih sederhana atau, seperti yang kita lakukan dalam kasus ini, batasi perangkat yang didukung hanya pada perangkat yang dapat menampilkan dunia 3D yang lebih realistis. Untuk “Journey through Middle-earth”, kami berfokus pada perangkat Nexus dan lima ponsel cerdas Android yang populer.

Dalam eksperimen, kami menggunakan three.js seperti yang dilakukan untuk beberapa project WebGL sebelumnya. Kami memulai penerapan dengan membuat versi awal game Trollshaw yang dapat berjalan dengan baik di tablet Nexus 10. Setelah beberapa pengujian awal di perangkat, kami memiliki daftar pengoptimalan yang mirip dengan yang biasanya kami gunakan untuk laptop spesifikasi rendah:

  • Menggunakan model poli rendah
  • Menggunakan tekstur resolusi rendah
  • Kurangi jumlah drawcall sebanyak mungkin dengan menggabungkan geometri
  • Sederhanakan bahan dan pencahayaan
  • Hapus efek postingan dan nonaktifkan antialiasing
  • Mengoptimalkan performa JavaScript
  • Merender kanvas WebGL menjadi setengah dan meningkatkan skala dengan CSS

Setelah menerapkan pengoptimalan ini ke versi kasar pertama game kami, kami memiliki kecepatan frame stabil sebesar 30 FPS yang sesuai dengan keinginan kami. Pada saat itu, tujuan kami adalah meningkatkan kualitas visual tanpa berdampak negatif pada kecepatan frame. Kami mencoba banyak trik: beberapa ternyata benar-benar berdampak pada performa; beberapa tidak memiliki pengaruh 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 melakukan inisialisasi scene. Kami mendapati bahwa kami dapat banyak meningkatkan kerumitan tanpa banyak memengaruhi performa. Model troll yang kami gunakan dalam game ini adalah sekitar 5K wajah dan adegannya sekitar 40 ribu wajah dan berfungsi dengan baik.

Salah satu troll hutan Trollshaw
Salah satu troll hutan Trollshaw

Untuk lokasi lainnya (yang belum dirilis) dalam pengalaman, kami melihat dampak yang lebih besar pada performa dari pengurangan poligon. Dalam hal ini, kita memuat objek poligon yang lebih rendah untuk perangkat seluler daripada objek yang dimuat untuk desktop. Membuat kumpulan model 3D yang berbeda memerlukan pekerjaan tambahan dan tidak selalu diwajibkan. Hal ini sangat bergantung pada seberapa rumit model awal Anda.

Saat mengerjakan adegan besar dengan banyak objek, kami mencoba menggunakan strategi untuk membagi geometri. Hal ini memungkinkan kami untuk 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 di JavaScript saat runtime untuk pengoptimalan dinamis atau menggabungkannya dalam praproduksi untuk menyimpan permintaan.

Menggunakan tekstur resolusi rendah

Untuk mengurangi waktu pemuatan di perangkat seluler, kami memilih untuk memuat tekstur berbeda yang berukuran setengah dari ukuran 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. Ukuran total tekstur harus muat dalam memori GPU agar tekstur tidak terus-menerus muncul dan didownload, tetapi ini mungkin bukan masalah besar bagi sebagian besar pengalaman web. Namun, menggabungkan tekstur menjadi sesedikit mungkin spritesheet penting untuk mengurangi jumlah drawcall - ini adalah sesuatu yang memiliki dampak besar pada performa pada perangkat seluler.

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

Sederhanakan bahan dan pencahayaan

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

Untuk melihat pengaruh material yang Anda gunakan terhadap performa scene, Anda dapat mengganti material scene dengan MeshBasicMaterial . Ini akan memberi Anda perbandingan yang baik.

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

Optimalkan performa JavaScript

Saat membuat game untuk perangkat seluler, GPU tidak selalu menjadi rintangan terbesar. Banyak waktu dihabiskan di CPU, terutama fisika dan animasi kerangka. Salah satu trik yang terkadang membantu, tergantung pada simulasi, adalah dengan hanya menjalankan perhitungan mahal ini setiap {i>frame<i} lainnya. Anda juga dapat menggunakan teknik pengoptimalan JavaScript yang tersedia untuk penggabungan objek, pembersihan sampah memori, dan pembuatan objek.

Memperbarui objek yang telah dialokasikan sebelumnya dalam loop, bukan membuat objek baru merupakan langkah penting untuk menghindari "gangguan" pembersihan sampah memori selama game.

Misalnya, pertimbangkan kode seperti ini:

var currentPos = new THREE.Vector3();

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

Versi loop yang ditingkatkan ini menghindari pembuatan objek baru yang harus dibersihkan sampah memorinya:

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 stage.

Tips lainnya adalah mengoptimalkan dan/atau menghitung sebelumnya operasi pelemparan sinar. Misalnya, jika Anda perlu melekatkan objek ke mesh selama pergerakan jalur statis, Anda dapat "merekam" posisi selama satu loop, lalu membaca dari data ini, bukan ray-casting pada mesh. Atau, seperti yang kita lakukan dalam pengalaman Rivendell, ray-cast untuk mencari interaksi mouse dengan mesh tak terlihat poli rendah yang lebih sederhana. Mencari tabrakan pada mesh poli tinggi sangat lambat dan harus dihindari dalam game loop secara umum.

Merender kanvas WebGL menjadi setengah dan meningkatkan skala dengan CSS

Ukuran kanvas WebGL mungkin merupakan parameter tunggal paling efektif yang dapat Anda ubah untuk mengoptimalkan performa. Semakin besar kanvas yang Anda gunakan untuk menggambar adegan 3D, semakin banyak piksel yang harus digambar di setiap bingkai. Hal ini tentu saja memengaruhi kinerja.Nexus 10 dengan layar berkepadatan tinggi 2560x1600 piksel harus mendorong 4 kali jumlah piksel sebagaimana tablet berkepadatan rendah. Untuk mengoptimalkannya di perangkat seluler, kami menggunakan trik dengan menyetel kanvas menjadi setengah ukuran (50%), lalu menskalakannya ke ukuran yang diinginkan (100%) dengan transformasi CSS 3D dengan akselerasi hardware. Kelemahannya adalah gambar {i>pixel<i} di mana garis tipis bisa menjadi masalah, tetapi pada layar resolusi tinggi, efeknya tidak terlalu buruk. Performa ekstra ini memang sepadan.

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

Objek sebagai elemen penyusun

Untuk dapat membuat labirin besar kastil Dol Guldur dan lembah Rivendell yang tak pernah berakhir, kami membuat satu set model 3D blok bangunan yang dapat kami gunakan kembali. Menggunakan kembali objek memungkinkan kita memastikan bahwa objek dibuat instance-nya dan diupload di awal pengalaman, bukan di tengah-tengahnya.

Balok bangunan objek 3D yang digunakan di labirin Dol Guldur.
Blok bangunan objek 3D yang digunakan di labirin Dol Guldur.

Di Rivendell, kami memiliki sejumlah bagian dasar yang terus kami posisikan ulang secara mendalam seiring berjalannya perjalanan pengguna. Saat pengguna melewati bagian, ini diposisikan ulang pada kejauhan.

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

Menggabungkan seluruh struktur menjadi satu mesh besar dari awal akan menghasilkan adegan yang sangat besar dan performa yang buruk. Untuk mengatasinya, kami memutuskan untuk menyembunyikan dan menampilkan elemen penyusun bergantung pada apakah elemen tersebut terlihat atau tidak. Sejak awal, kami memiliki ide untuk menggunakan skrip raycaster 2D, tetapi pada akhirnya kami menggunakan pemusnahan frustrum tiga.js bawaan. Kami menggunakan kembali skrip raycaster untuk memperbesar bagian "bahaya" yang dihadapi pemain.

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

Menggunakan interaksi sentuh dalam pengalaman web seluler

Menambahkan dukungan sentuh tidak sulit. Ada artikel bagus yang dapat dibaca tentang topik tersebut. Tetapi ada beberapa hal kecil yang bisa membuatnya lebih rumit.

Anda dapat menggunakan kedua mode sentuh dan mouse. Chromebook Pixel dan laptop lain yang mendukung sentuhan memiliki dukungan mouse dan sentuh. Salah satu kesalahan umum adalah memeriksa apakah perangkat diaktifkan sentuhan dan kemudian hanya menambahkan pemroses peristiwa sentuh dan tidak ada untuk mouse.

Jangan memperbarui 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.

Ingatlah bahwa ini multitouch: 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 gesekan, kami menggunakan penundaan sebelum kami memeriksa apakah sentuhan telah dipindahkan (geser) atau apakah sentuhan masih (ketuk). Untuk mendapatkan cubit, kita mengukur jarak antara dua sentuhan awal dan bagaimana hal itu berubah dari waktu ke waktu.

Dalam dunia 3D, Anda harus menentukan reaksi kamera 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 pada perangkat seluler seperti {i>browser<i} desktop. Kami melakukan pengujian secara ekstensif untuk memutuskan mana yang dirasa tepat untuk setiap versi.

Ketika menggunakan layar yang berukuran lebih kecil dan layar sentuh, Anda akan mendapati bahwa jari pengguna dan grafik interaksi UI sering kali menjadi tujuan yang ingin Anda tampilkan. Ini adalah sesuatu yang kita gunakan saat mendesain aplikasi native, tetapi belum benar-benar perlu memikirkannya sebelumnya dengan pengalaman web. Ini adalah 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 pada perangkat kelas atas yang lebih baru. Jika menyangkut kinerja, tampaknya jumlah poligon dan ukuran tekstur sebagian besar memengaruhi waktu download dan inisialisasi. Bahan, shader, dan ukuran kanvas WebGL adalah bagian terpenting yang harus dioptimalkan untuk performa seluler. Namun, ini adalah jumlah bagian yang memengaruhi performa sehingga semua yang dapat Anda lakukan untuk mengoptimalkan jumlah.

Menarget perangkat seluler juga berarti Anda harus membiasakan diri untuk memikirkan interaksi sentuh dan ini bukan hanya tentang ukuran piksel - tetapi juga ukuran fisik layar. Dalam beberapa kasus, kami harus mendekatkan kamera 3D untuk benar-benar melihat apa yang terjadi.

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

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