Hobbit Deneyimi

Mobil WebGL ile Orta Dünya'ya Hayata Geçme

Daniel Isaksson
Daniel Isaksson

Geçmişte, mobil cihazlara ve tabletlere etkileşimli, web tabanlı, multimedya açısından yoğun deneyimler sunmak zordu. Temel kısıtlamalar performans, API kullanılabilirliği, cihazlarda HTML5 ses sınırlamaları ve sorunsuz satır içi video oynatmanın olmamasıdır.

Bu yılın başlarında, Google ve Warner Bros.'un arkadaşlarıyla yeni Hobbit filmi Hobbit: Smaug'un Viranesi için mobil öncelikli web deneyimi oluşturmak üzere bir proje başlattık. Multimedya ağırlıklı bir mobil Chrome Denemesi oluşturmak gerçekten ilham verici ve zorlu bir görev oldu.

Artık WebGL ve Web Audio'ya erişebildiğimiz yeni Nexus cihazlarda deneyim, Android için Chrome'a göre optimize edilmiştir. Bununla birlikte, donanım hızlandırmalı birleştirme ve CSS animasyonları sayesinde deneyimin büyük bir bölümüne WebGL olmayan cihazlarda ve tarayıcılarda da erişilebilir.

Tüm bu deneyim Orta Dünya haritasına ve Hobbit filmlerindeki yerler ve karakterlere dayanıyor. WebGL'yi kullanmak, Hobbit üçlemesinin zengin dünyasını dramatize edip keşfetmemizi ve kullanıcıların deneyimi kontrol etmesine izin vermemizi mümkün kıldı.

Mobil cihazlarda WebGL zorlukları

İlk olarak, "mobil cihazlar" terimi oldukça kapsamlıdır. Cihazların teknik özellikleri birbirinden oldukça farklıdır. Dolayısıyla, bir geliştirici olarak daha az karmaşık bir deneyime sahip daha fazla cihazı desteklemek mi, yoksa bu örnekte yaptığımız gibi, desteklenen cihazları daha gerçekçi bir 3D dünya görüntüleyebilecek cihazlarla sınırlamak mı istediğinize karar vermeniz gerekir. "Orta Dünya'da bir yolculuk" için Nexus cihazlara ve beş popüler Android akıllı telefona odaklandık.

Denemede, önceki WebGL projelerimizden bazılarında olduğu gibi three.js dosyasını kullandık. Uygulamaya, Trollshaw oyununun Nexus 10 tablette sorunsuz çalışacak ilk sürümünü oluşturarak başladık. Cihaz üzerinde yaptığımız ilk testlerin ardından aklımızda bir optimizasyon listesi vardı. Bu liste, normalde düşük kaliteli bir dizüstü bilgisayar için kullandığımız duruma çok benziyor:

  • Düşük poli modelleri kullanma
  • Düşük çözünürlüklü dokular kullanın
  • Geometriyi birleştirerek çizim çağrısı sayısını mümkün olduğunca azaltın
  • Malzeme ve ışıklandırmayı sadeleştirin
  • Yayın efektlerini kaldırın ve kenar yumuşatmayı kapatın
  • JavaScript performansını optimize edin
  • WebGL tuvalini yarı boyutta oluşturun ve CSS ile ölçeklendirin

Bu optimizasyonları oyunun ilk kaba sürümünde uyguladıktan sonra, 30FPS'lik sabit bir kare hızı elde ettik ve bu bizi memnun etti. Bu noktada hedefimiz, kare hızını olumsuz etkilemeden görselleri iyileştirmekti. Birçok hile denedik: Bazılarının performans üzerinde gerçekten etkisi olduğu ortaya çıktı, bazıları ise umduğumuz kadar büyük etki yaratmadı.

Düşük poli modelleri kullanma

Modellerle başlayalım. Düşük çoklu modelleri kullanmak, kesinlikle indirme süresine ve sahneyi başlatmak için gereken süreye yardımcı olur. Performansı fazla etkilemeden karmaşıklığı büyük ölçüde artırabileceğimizi gördük. Bu oyunda kullandığımız trol modelleri yaklaşık 5 bin yüz, sahnede ise yaklaşık 40 bin yüz var ve bu sorunsuz çalışıyor.

Trolbükü ormanının trollerinden biri
Tolçu ormanındaki trollerden biri

Deneyimde, (henüz yayınlanmamış) başka bir konumda, poligonların azaltılmasının performans üzerinde daha fazla etki yarattığını gördük. Bu örnekte, mobil cihazlar için masaüstü için yüklediğimiz nesnelerden daha küçük boyutlu nesneler yükledik. Farklı 3D model grupları oluşturmak için fazladan bazı işlemler yapılması gerekir ve bu her zaman gerekmez. Bu, başlangıçta modellerinizin ne kadar karmaşık olduğuna bağlıdır.

Çok sayıda nesnenin bulunduğu büyük sahneler üzerinde çalışırken, geometriyi nasıl böleceğimiz konusunda stratejik davranmaya çalıştık. Bu, tüm mobil cihazlarda çalışan bir ayar bulmak için daha az önemli ağları hızlı bir şekilde açıp kapatmamıza olanak tanıdı. Daha sonra, dinamik optimizasyon için çalışma zamanında JavaScript'teki geometriyi birleştirmeyi veya istekleri kaydetmek için üretim öncesinde birleştirmeyi seçebiliriz.

Düşük çözünürlüklü dokular kullanın

Mobil cihazlarda yükleme süresini azaltmak için, masaüstündeki dokuların yarısı kadar farklı dokular yüklemeyi tercih ettik. Tüm cihazların 2048x2048 piksele kadar doku boyutlarını işleyebildiği ve çoğu cihazın 4096x4096 piksel işleyebildiği görülmüştür. Bağımsız dokularda doku araması, GPU'ya yüklendikten sonra sorun teşkil etmiyor. Dokuların sürekli olarak yüklenip indirilmesini önlemek için dokuların toplam boyutu GPU belleğine sığmalıdır, ancak bu büyük olasılıkla çoğu web deneyimi için büyük bir sorun değildir. Bununla birlikte, dokuları mümkün olduğunca az model sayfasında birleştirmek, çizim çağrısı sayısını azaltmak için önemlidir. Bu, mobil cihazlarda performans üzerinde büyük etkisi olan bir şeydir.

Trolbükü ormanındaki trollerden birinin dokusu
Truva ormanlarının trollerinden birinin dokusu
(orijinal boyut 512x512 piksel)

Malzeme ve ışıklandırmayı sadeleştirin

Malzeme seçimi de performansı büyük ölçüde etkileyebilir ve mobil cihazlarda akıllıca yönetilmelidir. Üç.js'de MeshPhongMaterial (tekel ışık hesaplaması başına) yerine MeshLambertMaterial (köşe ışık hesaplaması başına) yöntemini kullanmak, performansı optimize etmek için kullandığımız yöntemlerden biridir. Esas olarak, mümkün olduğunca az ışık hesaplamasıyla basit gölgelendiriciler kullanmaya çalıştık.

Kullandığınız malzemelerin bir sahnenin performansını nasıl etkilediğini görmek için, sahnedeki malzemeleri bir MeshBasicMaterial ile geçersiz kılabilirsiniz . Böylece iyi bir karşılaştırma yapabilirsiniz.

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

JavaScript performansını optimize edin

Mobil cihazlar için oyun oluştururken GPU her zaman en büyük engel değildir. CPU, özellikle fizik ve iskelet animasyonları için çok fazla zaman harcanmaktadır. Simülasyona bağlı olarak, bazen işe yarayan bir başka ipucu da bu pahalı hesaplamaları yalnızca iki karede de çalıştırmaktır. Nesne havuzu, çöp toplama ve nesne oluşturma konularında mevcut JavaScript optimizasyon tekniklerini de kullanabilirsiniz.

Yeni nesneler oluşturmak yerine döngüler içinde önceden ayrılmış nesnelerin güncellenmesi, oyun sırasında çöp toplama "hücrelerini" önlemek için önemli bir adımdır.

Örneğin, aşağıdaki gibi bir kod düşünün:

var currentPos = new THREE.Vector3();

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

Bu döngünün iyileştirilmiş bir sürümü, atık toplanması gereken yeni nesnelerin oluşturulmasını önler:

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);
}

Etkinlik işleyiciler mümkün olduğunca yalnızca özellikleri güncellemeli ve requestAnimationFrame oluşturma döngüsünün sahnenin güncellenmesine izin vermelidir.

Verebileceğim bir diğer ipucu ise ışın yayınlama operasyonlarını optimize etmek ve/veya önceden hesaplamaktır. Örneğin, statik yol hareketi sırasında bir ağa nesne eklemeniz gerekirse, bir döngü sırasında konumları "kaydedebilir" ve daha sonra ağa karşı ışın yayınlamak yerine bu verilerden okuyabilirsiniz. Bunun yerine Rivendell deneyiminde yaptığımız gibi, ışınla yayın kullanarak daha basit bir düşük poli görünür örgülü fare etkileşimlerini arayın. Yüksek çoklu örgüde çakışmaları aramak çok yavaştır ve genel olarak bir oyun döngüsünde bundan kaçınılmalıdır.

WebGL tuvalini yarı boyutta oluşturun ve CSS ile ölçeklendirin

WebGL tuvalinin boyutu, muhtemelen performansı optimize etmek için değiştirebileceğiniz en etkili parametredir. 3D sahnenizi çizmek için kullandığınız tuval ne kadar büyükse, her karede o kadar çok piksel çizilmesi gerekir. Bu elbette performansı etkiler.Yüksek yoğunluklu 2560x1600 piksel ekranına sahip Nexus 10, düşük yoğunluklu bir tablette 4 kat fazla piksel itmek zorunda kalır. Bunu mobil cihazlar için optimize etmek amacıyla bir hikaye kullanarak kanvası yarı boyuta (%50) ayarlıyor, ardından donanım hızlandırmalı CSS 3D dönüşümleriyle istenen boyutuna (%100) ulaşıyoruz. Bunun olumsuz tarafı, ince çizgilerin sorun yaratabildiği pikselleştirilmiş bir resimdir. Ancak yüksek çözünürlüklü ekranlarda efekt o kadar da kötü değildir. Sunduğu ekstra performansa kesinlikle değer.

Nexus 10'da (16FPS) kanvas ölçeklendirme olmadan aynı sahne, %50'ye (33FPS) göre ölçeklendirilmiştir
Nexus 10'da (16FPS) kanvas ölçeklendirme olmadan, %50'ye (33FPS) ölçeklenmiş aynı sahne.

Yapı taşı gibi nesneler

Dol Guldur kalesinin büyük labirentini ve Rivendell'in sonu gelmeyen vadiyi oluşturabilmek için, yeniden kullandığımız 3D yapı taşlarından oluşan bir set hazırladık. Nesneleri tekrar kullanmak, nesnelerin örneklenmesini ve deneyimin ortasında değil, deneyimin ortasında yüklenmesini sağlamamıza olanak tanır.

Dol Guldur labirentinde kullanılan 3D nesne yapı taşları.
Dol Guldur'un labirentinde kullanılan 3D nesne yapı taşları.

Ayrıkvadi'de kullanıcının yolculuğu ilerledikçe sürekli olarak Z derinliğinde yeniden konumlandırdığımız çok sayıda zemin bölümümüz var. Kullanıcı bölümleri geçtikçe bunlar uzak mesafelere yeniden yerleştirilir.

Dol Guldur şatosu için labirentin her oyunda yeniden oluşturulmasını istedik. Bunu yapmak için, labirenti yeniden oluşturan bir komut dosyası oluşturduk.

Tüm yapıyı başlangıçtan itibaren büyük tek bir ağ halinde birleştirmek, çok büyük bir sahneye ve kötü performansa neden olur. Bu sorunu çözmek için yapı taşlarını, görünümde olup olmadıklarına göre gizlemeye ve göstermeye karar verdik. En başından beri, 2D raycaster komut dosyası kullanma konusunda bir fikrimiz vardı, ancak sonunda yerleşik trip.js frustrum culling özelliğini kullandık. Oyuncunun karşı karşıya olduğu "tehlike"yi yakınlaştırmak için raycaster komut dosyasını yeniden kullandık.

Ele alınması gereken bir sonraki önemli nokta kullanıcı etkileşimi. Masaüstünde fare ve klavye girişi; mobil cihazlarda ise kullanıcılarınız dokunma, kaydırma, sıkıştırma, cihaz yönü gibi işlevlerle etkileşimde bulunurlar.

Mobil web deneyimlerinde dokunma etkileşimini kullanma

Dokunma desteği eklemek zor değildir. Konu hakkında okunacak harika makaleler bulunmaktadır. Ancak bunu daha karmaşık hale getirebilecek bazı küçük şeyler vardır.

Dokunma ve farenin ikisini de kullanabilirsiniz. Chromebook Pixel ve dokunma özellikli diğer dizüstü bilgisayarlarda hem fare hem de dokunma desteği vardır. Yaygın olarak yapılan bir hata, cihazda dokunma özelliğinin etkin olup olmadığını kontrol edip ardından sadece dokunma etkinlik işleyicileri eklemeli, fare için hiç işlem yapılmamasıdır.

Etkinlik işleyicilerdeki oluşturma işlemini güncellemeyin. Dokunma etkinliklerini değişkenlere kaydedin ve requestAnimationFrame oluşturma döngüsünde bunlara tepki verin. Bu, performansı artırır ve çakışan etkinlikleri bir araya getirir. Etkinlik işleyicilerde yeni nesneler oluşturmak yerine nesneleri yeniden kullandığınızdan emin olun.

Bu özelliğin çok noktalı olduğunu unutmayın: event.touches tüm dokunmaları içeren bir dizidir. Bazı durumlarda bunun yerine event.targetTouches veya event.changedTouches öğelerine bakmak ve yalnızca ilgilendiğiniz dokunmalara tepki vermek daha ilgi çekicidir. Dokunmaları kaydırma işlemlerinden ayırmak için, dokunmanın hareket edip etmediğini (kaydırma) veya hareketsiz olup olmadığını (dokunma) kontrol etmeden önce bir gecikme uygularız. Parmaklarınızı birbirine yaklaştırmak için ilk dokunuş arasındaki mesafeyi ve bunun zaman içinde nasıl değiştiğini ölçeriz.

3D dünyada kameranızın kaydırma hareketlerine karşı fareye nasıl tepki vereceğine karar vermeniz gerekir. Kamera hareketi eklemek için yaygın olarak kullanılan bir yöntem de fare hareketini izlemektir. Bu, fare konumunu kullanarak doğrudan kontrol edilerek veya bir delta hareketle (konum değişikliği) yapılabilir. Mobil cihazda her zaman masaüstü tarayıcısıyla aynı davranışta bulunmak istemezsiniz. Her sürümde neyin doğru olduğunu belirlemek üzere kapsamlı testler yaptık.

Daha küçük ekranlarla veya dokunmatik ekranlarla çalışırken, kullanıcının parmaklarının ve kullanıcı arayüzü etkileşim grafiklerinin genellikle göstermek istediğiniz şeye karıştığını göreceksiniz. Bu, yerel uygulamalar tasarlarken alışık olduğumuz ancak web deneyimleri konusunda daha önce düşünmek zorunda olmadığımız bir şey. Bu, tasarımcılar ve UX tasarımcıları için gerçek bir zorluk teşkil ediyor.

Özet

Bu projedeki genel deneyimimize göre, mobil cihazlarda WebGL, özellikle yeni ve ileri teknoloji cihazlarda gerçekten iyi çalışıyor. Performans söz konusu olduğunda, poligon sayısı ve doku boyutu çoğunlukla indirme ve başlatma sürelerini etkiler. Mobil performans için optimize edilmesi gereken en önemli parçalar ise materyaller, gölgelendiriciler ve WebGL tuvalinin boyutudur. Ancak, performansı etkileyen parçaların toplamı olduğu için sayıları optimize etmek üzere yapabileceğiniz her şey budur.

Mobil cihazları hedeflemek, aynı zamanda dokunma etkileşimlerini göz önünde bulundurmaya alışmanız gerektiği anlamına gelir. Bu etkileşim, yalnızca piksel boyutuyla ilgili değildir, ekranın fiziksel boyutuyla da ilgilidir. Bazı durumlarda, olup biteni gerçekten görebilmek için 3D kamerayı yaklaştırmamız gerekti.

Deneme başladı ve harika bir yolculuk oldu. Umarız beğenmişsinizdir!

Denemek ister misiniz? Kendi Orta Dünya Yolculuğunuza çıkın.