Örnek Olay - Oz'a Giden Yolu Bulun

Giriş

"Oz'a Giden Yolu Bulun", Disney tarafından web'e getirilen yeni bir Google Chrome Deneyidir. Bu oyun, Kansas sirki boyunca etkileşimli bir yolculuğa çıkmanızı sağlıyor. Dev bir fırtınadan sıyrılıp sizi Oz'a götürüyor.

Hedefimiz, kullanıcıların güçlü bağ kurabileceği eğlenceli, sürükleyici bir deneyim oluşturmak için sinemanın zenginliğini tarayıcının teknik özellikleriyle birleştirmekti.

İş, bu parçada tümüyle ele alınamayacak kadar büyük olduğundan, işin içine daldık ve ilginç olduğunu düşündüğümüz teknoloji hikayesiyle ilgili bazı bölümler çıkardık. Bu süreçte, zorluğun arttığına dair bazı eğitimler çıkardık.

Bu deneyimi sağlamak için çok sayıda kişi çok çalıştı: Burada listelenemeyecek kadar fazla. Lütfen siteyi ziyaret ederek haberin tamamı için menü bölümünün altındaki jenerik sayfasına göz atın.

Gelişmiş Seçenekler

Masaüstünde Oz'a Giden Yolu Bulun, zengin ve etkileyici bir dünyadır. 3D ve geleneksel film yapımı esintili efektler bir araya gelerek neredeyse gerçekçi bir sahne oluşturuyoruz. En önemli teknolojiler, Three.js içeren WebGL, özel oluşturulmuş gölgelendiriciler ve CSS3 özelliklerini kullanan DOM animasyonlu öğelerdir. Bunun ötesinde, kullanıcının 3D ses için Web kamerasından ve WebAudio'dan resimlerini doğrudan eklemesine olanak tanıyan etkileşimli deneyimler için getUserMedia API'sı (WebRTC).

Ancak böyle bir teknolojik deneyimin sihri böyle bir araya geliyor. Bu da başlıca zorluklardan biridir: Tutarlı bir bütün oluşturmak için görsel efektleri ve etkileşimli öğeleri tek bir sahnede nasıl bir araya getirmelisiniz? Bu görsel karmaşayı yönetmek zordu. O zamanlar geliştirmenin hangi aşamasında olduğumuzu anlamamızı zorlaştırıyordu.

Birbiriyle bağlantılı görsel efektler ve optimizasyon sorununu çözmek için, o sırada incelediğimiz tüm alakalı ayarları yakalayacak bir kontrol panelinden yoğun bir şekilde yararlandık. Sahne parlaklık, alan derinliği, gama gibi her şey için tarayıcıda canlı olarak ayarlanabilir. Herkes deneyimdeki önemli parametrelerin değerlerinde değişiklik yapmayı deneyebilir ve en iyi nelerin işe yaradığını ortaya çıkarmaya katkıda bulunabilir.

Sırrımızı paylaşmadan önce, örneğin bir araba motorunun içine girersiniz sanki kazayla karşılaşabileceğine dair sizi uyarmak isteriz. Önemli bir şey bulunmadığından emin olun ve sitenin ana URL'sine gidip adrese ?debug=on ifadesini ekleyin. Sitenin yüklenmesini bekleyin. İçeriye girdikten sonra (basın?) Ctrl-I tuşuna bastığınızda sağ tarafta bir açılır menü görüntülenir. "Kamera yolundan çık" seçeneğinin işaretini kaldırırsanız alanda serbestçe dolaşmak için A, W, S, D tuşlarını ve fareyi kullanabilirsiniz.

Kamera yolu.

Burada tüm ayarların üzerinden geçmeyeceğiz, ancak denemeler yapmanızı öneririz: Tuşlar farklı sahnelerde farklı ayarları gösterir. Son fırtına sekansında ek bir anahtar vardır: Ctrl-A ile animasyon oynatmayı açıp kapatabilir ve etrafta gezinebilirsiniz. Bu sahnede, Esc (fare kilidi işlevinden çıkmak için) ve tekrar Ctrl-I tuşuna basarsanız, fırtına sahnesine özel ayarlara erişebilirsiniz. Etrafınıza bakın ve aşağıdaki gibi güzel kartpostal görünümleri yakalayın.

Fırtına sahnesi

Bunu gerçekleştirmek ve ihtiyaçlarımıza uygun esneklikte olmasını sağlamak için dat.gui adlı güzel bir kitaplık kullandık (bu kitaplığın nasıl kullanılacağıyla ilgili eski bir eğitim için buraya bakın). Sitenin ziyaretçilerine gösterilen ayarları hızlı bir şekilde değiştirmemize olanak tanıdı.

Mat tablo gibi

Birçok klasik Disney filminde ve animasyonunda sahne oluşturmak için farklı katmanları birleştirmek gerekiyordu. Canlı aksiyon, hücre animasyonu, hatta fiziksel setler ve cam üzerine boyama ile oluşturulmuş üst katmanlar vardı. Üst katmanlarda, mat boyama adı verilen bir teknik bulunuyordu.

Bazı "katmanlar" statik görsellerden çok daha fazlasını oluştursa da oluşturduğumuz deneyimin yapısı birçok açıdan benzer. Hatta daha karmaşık hesaplamalara göre nesnelerin görünümünü etkiler. Bununla birlikte, en azından büyük resim düzeyinde ele aldığımız görüntüleme sayısı, biri diğerinin üzerine birleştirilmiştir. Üst kısımda, altında 3D sahne bulunan bir kullanıcı arayüzü katmanı görünür: Kendisi farklı sahne bileşenlerinden oluşmaktadır.

Üst arayüz katmanı, DOM ve CSS 3 kullanılarak oluşturuldu. Bu da, etkileşimlerin, belirli bir etkinlik listesine göre ikisi arasındaki iletişimle ilgili 3D deneyiminden bağımsız olarak birçok şekilde düzenlenebildiği anlamına geliyordu. Bu iletişim, hangi alanın giriş/çıkma animasyonu gerektiğini kontrol eden Omurga Yönlendirici + onHashChange HTML5 etkinliğini kullanır. (proje kaynağı: /develop/coffee/router/Router.coffee).

Eğitim: Sprite E-Tablolar ve Retina desteği

Arayüz için kullandığımız eğlenceli optimizasyon tekniklerinden biri, sunucu isteklerini azaltmak amacıyla çok sayıda arayüz bindirme resmini tek bir PNG'de birleştirmekti. Bu projede arayüz, web sitesinin gecikmesini azaltmak için tamamen önden yüklenen 70'ten fazla görüntüden (3D dokular hariç) oluşuyordu. Canlı model sayfasını burada görebilirsiniz:

Normal Görüntüleme - http://findyourwaytooz.com/img/home/interface_1x.png Retina Ekran - http://findyourwaytooz.com/img/home/interface_2x.png

Burada, İmge Sayfaları kullanımından nasıl yararlandığımıza, ayrıca bunları retina cihazlar için nasıl kullanabileceğimize ve arayüzü mümkün olduğunca net ve derli toplu hale getirmeye ilişkin bazı ipuçları verilmiştir.

Model sayfaları oluşturma

SpriteE-Tablolar'ı oluşturmak için ihtiyacınız olan biçimde çıkış yapan TexturePacker'ı kullandık. Bu örnekte, oldukça temiz olan ve animasyonlu imgeler oluşturmak için de kullanılabilecek olan EaselJS olarak dışa aktardık.

Oluşturulan Model Sayfasını kullanma

Model Sayfanızı oluşturduktan sonra şuna benzer bir JSON dosyası görürsünüz:

{
   "images": ["interface_2x.png"],
   "frames": [
       [2, 1837, 88, 130],
       [2, 2, 1472, 112],
       [1008, 774, 70, 68],
       [562, 1960, 86, 86],
       [473, 1960, 86, 86]
   ],

   "animations": {
       "allow_web":[0],
       "bottomheader":[1],
       "button_close":[2],
       "button_facebook":[3],
       "button_google":[4]
   },
}

Burada:

  • resim, model sayfasının URL'sini belirtir
  • çerçeveler, her bir kullanıcı arayüzü öğesinin koordinatlarıdır [x, y, genişlik, yükseklik]
  • animasyonlar her bir öğenin

İmge sayfasını oluşturmak için yüksek yoğunluklu resimleri kullandığımızı, ardından normal sürümü oluşturduğumuzu, boyutu yarısı kadar yeniden boyutlandırdığımızı unutmayın.

Konuyu toparlamak gerekirse

Artık hazır olduğumuza göre, bunu kullanmak için tek bir JavaScript snippet'ine ihtiyacımız var.

var SSAsset = function (asset, div) {
  var css, x, y, w, h;

  // Divide the coordinates by 2 as retina devices have 2x density
  x = Math.round(asset.x / 2);
  y = Math.round(asset.y / 2);
  w = Math.round(asset.width / 2);
  h = Math.round(asset.height / 2);

  // Create an Object to store CSS attributes
  css = {
    width                : w,
    height               : h,
    'background-image'   : "url(" + asset.image_1x_url + ")",
    'background-size'    : "" + asset.fullSize[0] + "px " + asset.fullSize[1] + "px",
    'background-position': "-" + x + "px -" + y + "px"
  };

  // If retina devices

  if (window.devicePixelRatio === 2) {

    /*
    set -webkit-image-set
    for 1x and 2x
    All the calculations of X, Y, WIDTH and HEIGHT is taken care by the browser
    */

    css['background-image'] = "-webkit-image-set(url(" + asset.image_1x_url + ") 1x,";
    css['background-image'] += "url(" + asset.image_2x_url + ") 2x)";

  }

  // Set the CSS to the DIV
  div.css(css);
};

Bunu şu şekilde kullanırsınız:

logo = new SSAsset(
{
  fullSize     : [1024, 1024],               // image 1x dimensions Array [x,y]
  x            : 1790,                       // asset x coordinate on SpriteSheet         
  y            : 603,                        // asset y coordinate on SpriteSheet
  width        : 122,                        // asset width
  height       : 150,                        // asset height
  image_1x_url : 'img/spritesheet_1x.png',   // background image 1x URL
  image_2x_url : 'img/spritesheet_2x.png'    // background image 2x URL
},$('#logo'));

Değişken Piksel Yoğunlukları hakkında daha fazla bilgi için Boris Smus'un bu makalesini okuyabilirsiniz.

3D İçerik Ardışık Düzeni

Ortam deneyimi bir WebGL katmanında ayarlanır. 3D sahneleri oluştururken en zor sorulardan biri modelleme, animasyon ve efektler açısından mümkün olan en yüksek ifade potansiyelini sağlayacak içerikleri oluşturabileceğinizden nasıl emin olacağınızla ilgilidir. Bu sorunun temelinde birçok açıdan içerik ardışık düzeni yatar. Bu süreç, 3B sahneye yönelik içerik oluşturmak için izlenmesi gereken üzerinde anlaşmaya varılmış bir süreçtir.

Hayranlık uyandıran bir dünya oluşturmak istiyorduk. Bu nedenle, 3D sanatçılarının bunu yapmasını sağlayacak sağlam bir sürece ihtiyacımız vardı. Bu kullanıcılara, 3D modelleme ve animasyon yazılımlarında mümkün olduğunca fazla ifade özgürlüğü tanınmalıydı ve bizim de bunu ekranda kodla oluşturmamız gerekiyordu.

Geçmişte bir 3D site oluşturduğumuzda, kullanabileceğimiz araçlarda sınırlamalar bulduğumuz için bir süredir bu tür sorunlar üzerinde çalışıyorduk. Bu nedenle, 3D Kitaplık adlı bu aracı oluşturduk: Bir iç araştırma parçası. Gerçek bir işe uygulanmak üzereydi.

Bu aracın bir geçmişi vardı: Başlangıçta Flash içindi ve çalışma zamanını paketi açmak için optimize edilmiş tek bir sıkıştırılmış dosya halinde büyük bir Maya sahnesini oluşturmanıza olanak tanıyordu. Bu yöntemin optimum olmasının nedeni, sahneyi oluşturma ve animasyon sırasında değiştirilen veri yapısıyla aynı veri yapısında etkili bir şekilde doldurmasıydı. Yüklendiğinde dosyada çok az ayrıştırma yapılması gerekir. Dosya AMF biçiminde olduğundan Flash'ta paket açma işlemi oldukça hızlı oldu. Bu dosya, Flash'ın yerel olarak paketinden çıkarılabildi. WebGL'de aynı biçimi kullanmak, CPU üzerinde biraz daha fazla çalışma yapılmasını gerektirir. Aslında bu dosyaları açıp WebGL'nin çalışması için gereken veri yapılarını yeniden oluşturacak bir veri paketi açma JavaScript kod katmanını yeniden oluşturmamız gerekiyordu. 3D sahnenin tamamının paketten çıkarılması, biraz CPU ağırlığında bir işlemdir: Oz'a Giden Yolu Bulun bölümündeki 1. sahnenin paketten çıkarılması, orta ile ileri teknoloji bir makinede yaklaşık 2 saniye sürer. Dolayısıyla bu, "sahne kurulumu" sırasında (sahne başlatılmadan önce) Web İşçileri teknolojisi kullanılarak yapılır. Böylece, kullanıcıya deneyimin sekteye uğramaması sağlanır.

Bu kullanışlı araç, 3D sahnenin çoğunu içe aktarabilir: modeller, dokular, kemik animasyonları. Daha sonra 3D motoru tarafından yüklenebilecek tek bir kitaplık dosyası oluşturursunuz. İhtiyacınız olan tüm modelleri bu kitaplıktaki sahnenize yerleştiriyor ve işte bu modelleri sahnenize yerleştiriyorsunuz.

Ancak karşılaştığımız bir sorun da şu anda WebGL'yle, yani bloktaki yeni çocukla uğraşıyor olmamızdı. Bu oldukça zor bir çocuktu. Tarayıcı tabanlı 3D deneyimler için standardı belirliyordu. Bu nedenle, 3D Kitaplık tarafından sıkıştırılmış 3D sahne dosyalarını alıp WebGL'nin anlayacağı bir biçime doğru bir şekilde çevirecek özel bir JavaScript katmanı oluşturduk.

Eğitim: Rüzgar Gibi Olsun

"Oz'a Giden Yolu Bulun" filminin teması rüzgartı. Hikayenin bir zinciri, rüzgarın yükseldiği şekilde yapılandırılıyor.

Karnavalın ilk sahnesi nispeten sakin. Çeşitli sahnelerin üzerinden geçerken kullanıcı, giderek daha şiddetli bir rüzgarla karşı karşıya kalır ve en son sahne olan fırtınaya dönüşür.

Bu nedenle, kullanıcıları içine alan bir rüzgar efekti sunmak önemliydi.

Bunu oluşturmak için, 3 karnaval sahnesini yumuşak ve bu nedenle rüzgardan (ör. çadırlar) etkilenmesi gereken nesnelerle, fotoğraf kabininin ve balonun kendi yüzeyini işaretledik.

Yumuşak kumaş.

Masaüstü oyunlar günümüzde genellikle temel bir fizik motoruna dayalı olarak geliştiriliyor. Dolayısıyla, 3D dünyada bir yumuşak nesnenin simüle edilmesi gerektiğinde, bunun için eksiksiz bir fizik simülasyonu çalıştırılarak inandırıcı bir yumuşak davranış oluşturulur.

WebGL / Javascript'te (henüz) tam anlamıyla gelişmiş bir fizik simülasyonu çalıştırma lüksüne sahip değiliz. Bu nedenle Oz'da rüzgar efekti yaratmanın bir yolunu bulmamız, bunu gerçekten simüle etmemiz gerekmedi.

3D modelin kendisine her bir nesne için "rüzgar hassasiyeti" bilgilerini yerleştirdik. 3D modelin her bir köşe noktasının, köşenin rüzgardan ne kadar etkilenmesi gerektiğini belirten bir "Rüzgar Özelliği" bulunuyordu. Dolayısıyla bu, 3D Nesnelerin rüzgar hassasiyetini belirtiyordu. Sonra rüzgarın kendisini oluşturmamız gerekiyordu.

Bu işlemi, Perlin Noise'u içeren bir görüntü oluşturarak gerçekleştirdik. Bu görüntünün belirli bir “rüzgâr alanını” kapsaması amaçlanmıştır. Bu nedenle, 3D sahnenin belirli bir dikdörtgen alanının üzerine yerleşen bir bulut resmi (gürültü gibi) düşünmek iyi bir fikir olabilir. Bu resmin her bir pikseli, gri seviye değeri, "çevresindeki" 3D alanda belirli bir anda rüzgarın ne kadar güçlü olduğunu belirtir.

Rüzgar efektini üretmek için resim belirli bir yönde, yani rüzgarın yönünde belirli bir yönde, zamanda, sabit bir hızda, hareket ettirilir. Ayrıca, "rüzgarlı alanın" sahnedeki her şeyi etkilememesi için rüzgar görüntüsünü efekt alanıyla sınırlı olacak şekilde kenarlardan sarıyoruz.

Basit Bir 3D Rüzgar Eğiticisi

Şimdi, Three.js'de basit bir 3D sahnede rüzgar efekti oluşturalım.

Basit bir "prosedürel çim alanda" rüzgar oluşturacağız.

İlk olarak sahneyi oluşturalım. Basit, dokulu düz bir arazimiz olacak. Ardından, her bir çimen kütlesi ters çevrilmiş bir 3D koni ile gösterilir.

Çimlerle kaplı arazi
Çimenle kaplı arazi

Bu basit sahneyi CoffeeScript kullanarak Three.js'de nasıl oluşturabileceğiniz aşağıda açıklanmıştır.

İlk olarak Three.js'yi kuracağız ve bunu Kamera, Fare denetleyicisi ve Biraz Işık'a bağlayacağız.

constructor: ->

   @clock =  new THREE.Clock()

   @container = document.createElement( 'div' );
   document.body.appendChild( @container );

   @renderer = new THREE.WebGLRenderer();
   @renderer.setSize( window.innerWidth, window.innerHeight );
   @renderer.setClearColorHex( 0x808080, 1 )
   @container.appendChild(@renderer.domElement);

   @camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 5000 );
   @camera.position.x = 5;
   @camera.position.y = 10;
   @camera.position.z = 40;

   @controls = new THREE.OrbitControls( @camera, @renderer.domElement );
   @controls.enabled = true

   @scene = new THREE.Scene();
   @scene.add( new THREE.AmbientLight 0xFFFFFF )

   directional = new THREE.DirectionalLight 0xFFFFFF
   directional.position.set( 10,10,10)
   @scene.add( directional )

   # Demo data
   @grassTex = THREE.ImageUtils.loadTexture("textures/grass.png");
   @initGrass()
   @initTerrain()

   # Stats
   @stats = new Stats();
   @stats.domElement.style.position = 'absolute';
   @stats.domElement.style.top = '0px';
   @container.appendChild( @stats.domElement );
   window.addEventListener( 'resize', @onWindowResize, false );
   @animate()

initGrass ve initTerrain işlev çağrıları, sahneyi sırasıyla çim ve araziyle doldurur:

initGrass:->
   mat = new THREE.MeshPhongMaterial( { map: @grassTex } )
   NUM = 15
   for i in [0..NUM] by 1
       for j in [0..NUM] by 1
           x = ((i/NUM) - 0.5) * 50 + THREE.Math.randFloat(-1,1)
           y = ((j/NUM) - 0.5) * 50 + THREE.Math.randFloat(-1,1)
           @scene.add( @instanceGrass( x, 2.5, y, 5.0, mat ) )

instanceGrass:(x,y,z,height,mat)->
   geometry = new THREE.CylinderGeometry( 0.9, 0.0, height, 3, 5 )
   mesh = new THREE.Mesh( geometry, mat )
   mesh.position.set( x, y, z )
   return mesh

Burada 15x15 bit çimden oluşan bir ızgara oluşturuyoruz. Bir çim pozisyonuna bir parça rastgele seçiyoruz. Böylece çimlerin hizalanmamaları için askerler gibi tuhaf gelmiyor.

Bu arazi, çim parçalarının tabanına yerleştirilmiş (y = 2,5) yatay bir düzlemdir.

initTerrain:->
  @plane = new THREE.Mesh( new THREE.PlaneGeometry(60, 60, 2, 2), new THREE.MeshPhongMaterial({ map: @grassTex }))
  @plane.rotation.x = -Math.PI/2
  @scene.add( @plane )

Dolayısıyla, şu ana kadar yaptığımız şey basitçe bir Three.js sahnesi oluşturmak ve prosedürel olarak oluşturulan ters çevrilmiş konilerden yapılmış birkaç parça çim ve basit bir arazi eklemektir.

Şimdilik eğlenceli bir şey yok.

Şimdi rüzgar eklemeye başlama zamanı. İlk olarak, rüzgar hassasiyeti bilgilerini çimen 3D modele yerleştirmek istiyoruz.

Bu bilgileri, çim 3D modelin her bir köşe noktası için özel bir ayrıntı olarak yerleştireceğiz. Ve şu kuralı kullanacağız: Çim modelinin alt ucu (koni ucu), zemine bağlı olduğu için sıfır hassasiyete sahiptir. Çim modelinin üst kısmı (koni tabanı), yerden daha uzaktaki kısım olduğu için maksimum rüzgar hassasiyetine sahiptir.

Çimen 3D modelin özel özelliği olarak rüzgar hassasiyeti eklemek için instanceGrass işlevinin nasıl yeniden kodlandığı aşağıda gösterilmektedir.

instanceGrass:(x,y,z,height)->

  geometry = new THREE.CylinderGeometry( 0.9, 0.0, height, 3, 5 )

  for i in [0..geometry.vertices.length-1] by 1
      v = geometry.vertices[i]
      r = (v.y / height) + 0.5
      @windMaterial.attributes.windFactor.value[i] = r * r * r

  # Create mesh
  mesh = new THREE.Mesh( geometry, @windMaterial )
  mesh.position.set( x, y, z )
  return mesh

Daha önce kullandığımız MeshPhongMaterial yerine özel windMaterial malzemesini kullanıyoruz. WindMaterial, birazdan bakacağımız WindMeshShader'ı sarar.

Dolayısıyla, instanceGrass içindeki kod, çim modelinin tüm köşe noktalarından geçer ve her köşe için windFactor adlı özel bir tepe noktası özelliği ekler. Bu WindFactor, çim modelinin alt ucunda (araziye dokunması gereken yer) 0 olarak ayarlanır ve çim modelinin üst ucu için 1 değerini alır.

İhtiyacımız olan diğer bileşen, gerçek rüzgarı sahnemize eklemektir. Konuşulduğu gibi, bunun için Perlin gürültüsünden yararlanacağız. Prosedür olarak bir Perlin gürültü dokusu oluştururuz.

Netlik sağlamak adına, önceki yeşil doku yerine bu dokuyu arazinin kendisine atayacağız. Bu şekilde, rüzgarla neler olup bittiğini daha kolay hissedebilirsiniz.

Dolayısıyla, bu Perlin gürültü dokusu, arazimizin uzantısını uzamsal olarak kaplar ve dokunun her bir pikseli, o pikselin düştüğü arazi alanının rüzgar yoğunluğunu belirtir. Arazi dikdörtgeni bizim "rüzgar alanımız" olacaktır.

Perlin gürültüsü, prosedürel olarak NoiseShader adlı bir gölgelendirici üzerinden üretilir. Bu gölgelendirici, https://github.com/ashima/webgl-noise adresindeki 3D tek yönlü gürültü algoritmalarını kullanır . Bunun WebGL sürümü, http://mrdoob.github.com/three.js/examples/webgl_terrain_dynamic.html adresindeki MrDoob'un Three.js örneklerinin birinden tamamen alınmıştır.

NoiseShader; üniforma olarak zaman, ölçek ve belirli uzaklıkta kopyasını oluşturma parametrelerini kullanır ve Perlin gürültüsünün 2D olarak düzgün şekilde dağıtılmasını sağlar.

class NoiseShader

  uniforms:     
    "fTime"  : { type: "f", value: 1 }
    "vScale"  : { type: "v2", value: new THREE.Vector2(1,1) }
    "vOffset"  : { type: "v2", value: new THREE.Vector2(1,1) }

...

Perlin Gürültüsümüzü bir dokuya oluşturmak için bu Gölgelendiriciyi kullanacağız. Bu işlem initNoiseShader işlevinde gerçekleştirilir.

initNoiseShader:->
  @noiseMap  = new THREE.WebGLRenderTarget( 256, 256, { minFilter: THREE.LinearMipmapLinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat } );
  @noiseShader = new NoiseShader()
  @noiseShader.uniforms.vScale.value.set(0.3,0.3)
  @noiseScene = new THREE.Scene()
  @noiseCameraOrtho = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2,  window.innerHeight / 2, window.innerHeight / - 2, -10000, 10000 );
  @noiseCameraOrtho.position.z = 100
  @noiseScene.add( @noiseCameraOrtho )

  @noiseMaterial = new THREE.ShaderMaterial
      fragmentShader: @noiseShader.fragmentShader
      vertexShader: @noiseShader.vertexShader
      uniforms: @noiseShader.uniforms
      lights:false

  @noiseQuadTarget = new THREE.Mesh( new THREE.PlaneGeometry(window.innerWidth,window.innerHeight,100,100), @noiseMaterial )
  @noiseQuadTarget.position.z = -500
  @noiseScene.add( @noiseQuadTarget )

Yukarıdaki kodun amacı, noiseMap'i Three.js oluşturma hedefi olarak ayarlamak, NoiseShader ile donatmak ve perspektif bozulmasını önlemek için ortografik bir kamerayla oluşturmaktır.

Konuştuğumuz gibi, şimdi bu dokuyu aynı zamanda arazi için ana oluşturma dokusu olarak kullanacağız. Rüzgar etkisinin çalışması için bu gerekli değildir. Ama böyle olması güzel. Böylece rüzgar enerjisi üretiminde neler olup bittiğini daha iyi görsel olarak anlayabileceğiz.

Gürültü Haritasını doku olarak kullanan, yeniden düzenlenen initTerrain işlevini aşağıda görebilirsiniz:

initTerrain:->
  @plane = new THREE.Mesh( new THREE.PlaneGeometry(60, 60, 2, 2), new THREE.MeshPhongMaterial( { map: @noiseMap, lights: false } ) )
  @plane.rotation.x = -Math.PI/2
  @scene.add( @plane )

Artık rüzgar dokumuzu oluşturduğumuza göre, çim modellerini rüzgara göre deforme etmekten sorumlu WindMeshShader'a göz atalım.

Bu gölgelendiriciyi oluşturmak için standart Three.js MeshPhongMaterial gölgelendiricisinden başladık ve değiştirdik. Bu yöntem, işe en baştan başlamak zorunda kalmadan çalışan bir gölgelendiriciyi kullanmaya başlamanın hızlı ve kötü bir yoludur.

Büyük bir kısmı MeshPhongMaterial gölgelendiricisinin bir kopyası olacağından, gölgelendirici kodunun tamamını buraya kopyalamayacağız (kaynak kodu dosyasında koda bakabilirsiniz). Şimdi de Köşeli Gölgelendiricideki değiştirilmiş, rüzgarla ilgili parçalara bir göz atalım.

vec4 wpos = modelMatrix * vec4( position, 1.0 );
vec4 wpos = modelMatrix * vec4( position, 1.0 );

wpos.z = -wpos.z;
vec2 totPos = wpos.xz - windMin;
vec2 windUV = totPos / windSize;
vWindForce = texture2D(tWindForce,windUV).x;

float windMod = ((1.0 - vWindForce)* windFactor ) * windScale;
vec4 pos = vec4(position , 1.0);
pos.x += windMod * windDirection.x;
pos.y += windMod * windDirection.y;
pos.z += windMod * windDirection.z;

mvPosition = modelViewMatrix *  pos;

Dolayısıyla, bu gölgelendirici ilk olarak tepe noktasının 2D, xz (yatay) konumuna dayalı olarak windUV doku arama koordinatını hesaplar. Bu UV koordinatı, Perlin gürültü rüzgar dokusundan vWindForce rüzgar kuvvetini aramak için kullanılır.

Bu vWindForce değeri, köşenin ne kadar deformasyona ihtiyaç duyduğunu hesaplamak için yukarıda açıklanan, tepe noktasına özgü windFactor özel özelliğiyle birleştirilir. Ayrıca, genel rüzgar şiddetini kontrol etmek için global windScale parametresi ve rüzgar deformasyonunun hangi yönde olması gerektiğini belirten bir windDirection vektörümüz vardır.

Bu da çim parçalarımızın rüzgara dayalı olarak deforme olmasına yol açar. Ancak işimiz henüz bitmedi. Şu an olduğu gibi bu deformasyon statiktir ve rüzgarlı bir alanın etkisini yansıtmaz.

Daha önce de belirttiğimiz gibi, camlarımızın dalgalanabilmesi için gürültü dokusunu zaman içinde rüzgar alanı boyunca kaydırmamız gerekecek.

Bu işlem, NoiseShader'a iletilen vOffset tek tip biçiminin zaman içinde değiştirilmesiyle yapılır. Bu, belirli bir yönde (rüzgar yönü) gürültü ofsetini belirtmemizi sağlayan bir vec2 parametresidir.

Bu işlemi her karede çağrılan render işlevinde yapılır:

render: =>
  delta = @clock.getDelta()

  if @windDirection
      @noiseShader.uniforms[ "fTime" ].value += delta * @noiseSpeed
      @noiseShader.uniforms[ "vOffset" ].value.x -= (delta * @noiseOffsetSpeed) * @windDirection.x
      @noiseShader.uniforms[ "vOffset" ].value.y += (delta * @noiseOffsetSpeed) * @windDirection.z
...

İşte bu kadar! Kısa süre önce, rüzgardan etkilenen "prosedürel çimenlerin" olduğu bir sahne oluşturduk.

Karışıma toz ekliyor.

Şimdi sahnemize biraz renk katalım. Sahneyi daha ilginç hale getirmek için biraz uçan toz ekleyelim.

Toz ekleniyor
Toz ekleme

Sonuçta tozun rüzgardan etkilendiği düşünülür. Bu nedenle, rüzgar sahnemizde toz havada uçuşmak en mantıklı şeydir.

Toz, initDust işlevinde parçacık sistemi olarak kurulmuştur.

initDust:->
  for i in [0...5] by 1
      shader = new WindParticleShader()
      params = {}
      params.fragmentShader = shader.fragmentShader
      params.vertexShader   = shader.vertexShader
      params.uniforms       = shader.uniforms
      params.attributes     = { speed: { type: 'f', value: [] } }

      mat  = new THREE.ShaderMaterial(params)
      mat.map = shader.uniforms["map"].value = THREE.ImageUtils.loadCompressedTexture("textures/dust#{i}.dds")
      mat.size = shader.uniforms["size"].value = Math.random()
      mat.scale = shader.uniforms["scale"].value = 300.0
      mat.transparent = true
      mat.sizeAttenuation = true
      mat.blending = THREE.AdditiveBlending
      shader.uniforms["tWindForce"].value      = @noiseMap
      shader.uniforms[ "windMin" ].value       = new THREE.Vector2(-30,-30 )
      shader.uniforms[ "windSize" ].value      = new THREE.Vector2( 60, 60 )
      shader.uniforms[ "windDirection" ].value = @windDirection            

      geom = new THREE.Geometry()
      geom.vertices = []
      num = 130
      for k in [0...num] by 1

          setting = {}

          vert = new THREE.Vector3
          vert.x = setting.startX = THREE.Math.randFloat(@dustSystemMinX,@dustSystemMaxX)
          vert.y = setting.startY = THREE.Math.randFloat(@dustSystemMinY,@dustSystemMaxY)
          vert.z = setting.startZ = THREE.Math.randFloat(@dustSystemMinZ,@dustSystemMaxZ)

          setting.speed =  params.attributes.speed.value[k] = 1 + Math.random() * 10
          
          setting.sinX = Math.random()
          setting.sinXR = if Math.random() < 0.5 then 1 else -1
          setting.sinY = Math.random()
          setting.sinYR = if Math.random() < 0.5 then 1 else -1
          setting.sinZ = Math.random()
          setting.sinZR = if Math.random() < 0.5 then 1 else -1

          setting.rangeX = Math.random() * 5
          setting.rangeY = Math.random() * 5
          setting.rangeZ = Math.random() * 5

          setting.vert = vert
          geom.vertices.push vert
          @dustSettings.push setting

      particlesystem = new THREE.ParticleSystem( geom , mat )
      @dustSystems.push particlesystem
      @scene.add particlesystem

Burada 130 toz parçacığı oluşur. Her birinde özel bir WindParticleShader olduğunu da unutmayın.

Şimdi, her karede CoffeeScript kullanarak rüzgardan bağımsız olarak taneciklerin etrafında biraz hareket edeceğiz. Kod aşağıdaki gibidir.

moveDust:(delta)->

  for setting in @dustSettings

    vert = setting.vert
    setting.sinX = setting.sinX + (( 0.002 * setting.speed) * setting.sinXR)
    setting.sinY = setting.sinY + (( 0.002 * setting.speed) * setting.sinYR)
    setting.sinZ = setting.sinZ + (( 0.002 * setting.speed) * setting.sinZR) 

    vert.x = setting.startX + ( Math.sin(setting.sinX) * setting.rangeX )
    vert.y = setting.startY + ( Math.sin(setting.sinY) * setting.rangeY )
    vert.z = setting.startZ + ( Math.sin(setting.sinZ) * setting.rangeZ )

Buna ek olarak, her tanecik konumunu rüzgara göre belirliyoruz. Bu işlem WindParticleShader'da yapılır. Özellikle köşe gölgelendiricide.

Bu gölgelendiricinin kodu, Three.js ParticleMaterial'ın değiştirilmiş bir sürümüdür ve temel şu şekilde görünür:

vec4 mvPosition;
vec4 wpos = modelMatrix * vec4( position, 1.0 );
wpos.z = -wpos.z;
vec2 totPos = wpos.xz - windMin;
vec2 windUV = totPos / windSize;
float vWindForce = texture2D(tWindForce,windUV).x;
float windMod = (1.0 - vWindForce) * windScale;
vec4 pos = vec4(position , 1.0);
pos.x += windMod * windDirection.x;
pos.y += windMod * windDirection.y;
pos.z += windMod * windDirection.z;

mvPosition = modelViewMatrix *  pos;

fSpeed = speed;
float fSize = size * (1.0 + sin(time * speed));

#ifdef USE_SIZEATTENUATION
    gl_PointSize = fSize * ( scale / length( mvPosition.xyz ) );
#else,
    gl_PointSize = fSize;
#endif

gl_Position = projectionMatrix * mvPosition;

Bu tepe gölgelendiricisi, rüzgara dayalı çimen deformasyonu için kullandığımızdan çok farklı değil. Girdi olarak Perlin gürültü dokusunu alır ve tozun dünyadaki konumuna bağlı olarak gürültü dokusunda bir vWindForce değeri arar. Daha sonra, toz parçacığının konumunu değiştirmek için bu değeri kullanır.

Fırtınadaki Sürücüler

WebGL sahnelerimizin en heyecanlısı muhtemelen son sahneydi. Buradaki yolculuğunuzun sonuna ulaşmak için balondan geçerek hortumun gözüne doğru ilerlerseniz bunu görebilirsiniz.

Balonla yolculuk sahnesi

Bu sahneyi oluşturduğumuzda, bu deneyimin etkili olacak merkezi bir özelliği olması gerektiğini biliyorduk. Dönen kasırganın odak noktası olacak ve diğer içeriklerin katmanları bu özelliği şekillendirip etkileyici bir etki yaratacak. Bunu başarmak için, bu garip gölgelendiricinin etrafında bir film stüdyosu setine eşdeğer bir şey oluşturduk.

Gerçekçi kompozit oluşturmak için karma bir yaklaşım kullandık. Bunların bazıları, lens parlaması efekti yaratmak için ışık şekilleri veya baktığınız sahnenin üzerinde katmanlar halinde canlanan yağmur damlaları gibi görsel püf noktalarıydı. Başka durumlarda da, alçaktan uçan bulut katmanlarının bir parçacık sistem koduna göre hareket etmesi gibi düz yüzeylerin etrafta hareket ediyormuş gibi göründüğü düz yüzeyler çiziliyordu. Hortum etrafında dönen moloz parçaları, hortumun önünde ve arkasında hareket etmek üzere sıralanmış 3D sahne katmanlarından oluşuyordu.

Ortamı bu şekilde oluşturmamızın ana nedeni, hortum gölgelendiricisini, uyguladığımız diğer efektlerle dengeli bir şekilde ele almak için yeterli GPU'ya sahip olmamızdı. Başlangıçta büyük GPU dengeleme sorunları yaşıyorduk, ancak daha sonra bu sahne optimize edildi ve ana sahnelere göre daha hafif hale geldi.

Eğitim: Storm Shader

Son fırtına dizisini oluşturmak için birçok farklı teknik birleştirildi, ancak bu çalışmanın merkezinde hortuma benzeyen özel bir GLSL gölgelendiricisi bulunmaktadır. İlginç geometrik girdaplar oluşturmak için, parçacık tabanlı animasyonlara ve hatta bükülmüş geometrik şekillerin 3D animasyonlarına kadar, tepe gölgelendiricilerinden birçok farklı teknik denedik. Efektlerin hiçbiri hortum hissini yeniden oluşturmadı veya işleme açısından çok fazla işlem gerektiriyordu.

Sonunda tamamen farklı bir proje bu sorunun cevabını verdi. Max Planck Enstitüsü (brainflight.org) tarafından farenin beyninin haritasını çıkarmak amacıyla bilimsel oyunlar içeren paralel bir proje, ilginç görsel efektler oluşturmuştu. Özel bir hacimsel gölgelendirici kullanarak bir fare nöronunun içinde filmler oluşturmayı başardık.

Özel hacimsel gölgelendirici kullanan bir fare nöronunun içi
Özel bir hacimsel gölgelendirici kullanarak bir fare nöronunun içi

Beyin hücresinin içinin hortumun hunisine biraz benzediğini fark ettik. Hacimsel bir teknik kullandığımızdan bu gölgelendiriciyi uzayda her yönden görüntüleyebileceğimizi biliyorduk. Gölgelendiricinin oluşturulmasını, özellikle bulut katmanlarının altında ve dramatik bir arka planın üzerinde yerleştirilmişse fırtına sahnesiyle birleştirecek şekilde ayarlayabiliriz.

Gölgelendirici tekniği, bir uzaklık alanıyla ışın marş oluşturma adı verilen basitleştirilmiş bir oluşturma algoritmasıyla bütün bir nesneyi oluşturmak için temelde tek bir GLSL gölgelendiricisini kullanan bir ipucu içerir. Bu teknikte, ekrandaki her nokta için bir yüzeye en yakın mesafeyi tahmin eden bir piksel gölgelendirici oluşturulur.

Genel bakış bölümünde algoritmaya ilişkin iyi bir referans bulabilirsiniz: Rendering Worlds With Two Triangles - Iñigo Quilez. Ayrıca, glsl.heroku.com adresindeki gölgelendiriciler galerisini keşfederken, bu tekniğin deneyebileceğiniz birçok örneği bulunmaktadır.

Gölgelendiricinin kalbi ana işlevle başlar. Kamera dönüşümlerini ayarlar ve bir yüzeye olan uzaklığı sürekli olarak değerlendiren bir döngüye girer. RaytraceFoggy( yön_vektör, maks_iterasyonlar, renk, renk_çarpıcısı ) çağrısı, çekirdek ışın yürüyüşü hesaplamasının yapıldığı yerdir.

for(int i=0;i < number_of_steps;i++) // run the ray marching loop
{
  old_d=d;
  float shape_value=Shape(q); // find out the approximate distance to or density of the tornado cone
  float density=-shape_value;
  d=max(shape_value*step_scaling,0.0);// The max function clamps values smaller than 0 to 0

  float step_dist=d+extra_step; // The point is advanced by larger steps outside the tornado,
  //  allowing us to skip empty space quicker.

  if (density>0.0) {  // When density is positive, we are inside the cloud
    float brightness=exp(-0.6*density);  // Brightness decays exponentially inside the cloud

    // This function combines density layers to create a translucent fog
    FogStep(step_dist*0.2,clamp(density, 0.0,1.0)*vec3(1,1,1), vec3(1)*brightness, colour, multiplier); 
  }
  if(dist>max_dist || multiplier.x < 0.01) { return;  } // if we've gone too far stop, we are done
  dist+=step_dist; // add a new step in distance
  q=org+dist*dir; // trace its direction according to the ray casted
}

Buradaki fikir, kasırganın şekline ilerledikçe pikselin nihai renk değerine düzenli olarak renk katkısı eklemek ve ışın boyunca opaklığa katkı sağlamaktır. Bu, hortum dokusunda katmanlı yumuşak bir kalite oluşturur.

Hortumun bir diğer temel yönü, bir dizi fonksiyon kompozisyonuyla ortaya çıkan gerçek şeklin kendisi. Organik pürüzlü bir kenar oluşturmak üzere gürültü kullanılarak oluşturulan ve ardından ana ekseni boyunca bükülüp zaman içinde döndürülen bir koni ile başlar.

mat2 Spin(float angle){
  return mat2(cos(angle),-sin(angle),sin(angle),cos(angle)); // a rotation matrix
}

// This takes noise function and makes ridges at the points where that function crosses zero
float ridged(float f){ 
  return 1.0-2.0*abs(f);
}

// the isosurface shape function, the surface is at o(q)=0 
float Shape(vec3 q) 
{
    float t=time;

    if(q.z < 0.0) return length(q);

    vec3 spin_pos=vec3(Spin(t-sqrt(q.z))*q.xy,q.z-t*5.0); // spin the coordinates in time

    float zcurve=pow(q.z,1.5)*0.03; // a density function dependent on z-depth

    // the basic cloud of a cone is perturbed with a distortion that is dependent on its spin 
    float v=length(q.xy)-1.5-zcurve-clamp(zcurve*0.2,0.1,1.0)*snoise(spin_pos*vec3(0.1,0.1,0.1))*5.0; 

    // create ridges on the tornado
    v=v-ridged(snoise(vec3(Spin(t*1.5+0.1*q.z)*q.xy,q.z-t*4.0)*0.3))*1.2; 

    return v;
}

Bu tür bir gölgelendiricinin oluşturulması için yapılması gereken işler karmaşıktır. Oluşturduğunuz operasyonların soyutlanmasıyla ilgili sorunların ötesinde, çalışmayı üretimde kullanmadan önce takip etmeniz ve çözmeniz gereken ciddi optimizasyon ve platformlar arası uyumluluk sorunları vardır.

Sorunun ilk kısmı: Bu gölgelendiriciyi sahnemiz için optimize etme. Bununla başa çıkmak için, gölgelendiricinin fazla ağır olacağı durumlarda “güvenli” bir yaklaşım benimsememiz gerekiyordu. Bunu yapmak için, hortum gölgelendiricisini sahnenin geri kalanından farklı bir örneklenmiş çözünürlükte bir araya getirdik. Bu sonuç, strodaytest.coffee dosyasından (evet, bu bir testti).

Hortum gölgelendiricisinin sahneye çözünürlüğünden bağımsız olarak, sahne genişliği ve yüksekliğiyle eşleşen birrenderTarget ile başlarız. Ardından, fırtına gölgelendiricisinin çözünürlüğünün alt örneklemesinin, aldığımız kare hızına dinamik olarak bağlı olduğuna karar veririz.

...
Line 1383
@tornadoRT = new THREE.WebGLRenderTarget( @SCENE_WIDTH, @SCENE_HEIGHT, paramsN )

... 
Line 1403 
# Change settings based on FPS
if @fpsCount > 0
    if @fpsCur < 20
        @tornadoSamples = Math.min( @tornadoSamples + 1, @MAX_SAMPLES )
    if @fpsCur > 25
        @tornadoSamples = Math.max( @tornadoSamples - 1, @MIN_SAMPLES )
    @tornadoW = @SCENE_WIDTH  / @tornadoSamples // decide tornado resWt
    @tornadoH = @SCENE_HEIGHT / @tornadoSamples // decide tornado resHt

Son olarak, stromakktest.coffee'de @line 1107 şeklinde, basitleştirilmiş bir sal2x algoritması kullanarak kasırgayı eleme işlemi gerçekleştiriyoruz. Bu da daha kötü durumda kasırganın daha bulanık çıkması, ancak en azından kontrolü kullanıcının elinden almadan çalıştığı anlamına geliyor.

Bir sonraki optimizasyon adımı, algoritmanın ayrıntısına inmeyi gerektirir. Gölgelendiricideki sürüş hesaplama faktörü, yüzey işlevinin uzaklığını, yani ışınlama döngüsünün yineleme sayısını yaklaşık olarak tahmin etmek için her bir pikselde gerçekleştirilen iterasyondur. Daha büyük bir adım boyutu kullanarak, bulutlu yüzeyin dışındayken daha az yineleme içeren bir hortum yüzeyi tahmini elde edebiliriz. İçerideyken, hassasiyet için adım boyutunu azaltır ve puslu etkisi yaratmak amacıyla değerleri karıştırabiliriz. Ayrıca, dökülen ışın için derinlik tahmini almak üzere sınırlayıcı silindir oluşturmak da iyi bir hız sağladı.

Sorunun bir sonraki bölümü, bu gölgelendiricinin farklı video kartlarında çalıştığından emin olmaktı. Her seferinde bazı testler yaptık ve karşılaşabileceğimiz uyumluluk sorunlarının türü hakkında sezgi oluşturmaya başladık. Hatalarla ilgili her zaman iyi hata ayıklama bilgileri alamadığımız için sezgiden çok daha iyisini yapamadık. Tipik bir senaryoda devam eden bir GPU hatası, hatta bir sistem kilitlenmesi söz konusu olabilir.

Video panoları arası uyumluluk sorunlarının benzer çözümleri vardır: Tanımlandığı gibi tam veri türünde statik sabit değerler girildiğinden emin olun, kayan nokta için IE: 0.0 ve int için 0. Daha uzun fonksiyonlar yazarken dikkatli olun; derleyiciler belirli durumları doğru işleyemediğinden birden çok basit fonksiyona ve ara değişkene bölmek tercih edilir. Dokuların 2'nin gücü olduğundan, çok büyük olmadığından ve her durumda bir döngü içinde doku verilerini ararken "dikkatli" olduğundan emin olun.

Uyum konusunda yaşadığımız en büyük sorunlar fırtınanın ışıklandırma efektiyle ilgiliydi. Hortumlarını renklendirmek için kasırganın etrafına sarılmış hazır bir doku kullandık. Harika bir efekt oldu ve hortumun sahne renklerine kolayca karışmasını sağladı, ancak diğer platformlarda koşmaya çalışmak uzun zaman aldı.

kasırga

Mobil Web Sitesi

Teknoloji ve işleme gereksinimleri çok ağır olduğundan mobil deneyim, masaüstü sürümün doğrudan çevirisi olamazdı. Özellikle mobil kullanıcıyı hedefleyen yeni bir şey oluşturmamız gerekiyordu.

Carnival Photo-Booth'un, kullanıcının mobil kamerasını kullanacak bir mobil web uygulaması olarak masaüstünden oluşturulmasının çok güzel olacağını düşündük. Şimdiye kadar bunu görmediğimiz bir işlem gerçekleştirdik.

Renk katmak için, CSS3'te 3D dönüşümleri kodladık. Onu jiroskop ve ivme ölçerle ilişkilendirerek deneyime çok derinlik kattık. Site, telefonunuzu tutmanıza, hareket ettirmenize ve telefona bakma şeklinize yanıt verir.

Bu makaleyi yazarken, mobil geliştirme sürecinin sorunsuz şekilde yürütülmesiyle ilgili bazı ipuçları vermeye değer olduğunu düşündük. İşte hazırlar! Bu eğitimden neler öğrenebileceğinizi görün.

Mobil platformla ilgili ipuçları ve püf noktaları

Ön yükleyici gerekli bir şeydir, kaçınılması gereken bir şey değildir. Bazen ikinci durumun yaşandığını biliyoruz. Bunun başlıca nedeni, projeniz büyüdükçe önceden yüklediğiniz şeylerin listesini saklamaya devam etmenizin gerekmesidir. Daha da kötüsü, çoğunun aynı anda kullanıldığı farklı kaynakları alıyorsanız yükleme ilerlemesini nasıl hesaplayacağınız çok açık değildir. Özel ve son derece genel "Görev" soyut sınıfımız burada işinize yarayacaktır. Ana fikri; bir Görevin kendi alt görevleri olabileceği, sonsuz şekilde iç içe geçmiş bir yapıya izin vermektir. Bu yapıda, her görev kendi alt görevlerine de sahip olabilir. Ayrıca her görevin ilerlemesi, alt görevlerin ilerlemesine göre hesaplanır (ana görevin ilerlemesi hesaba katılmaz). Tüm MainPreloadTask, AssetPreloadTask ve TemplatePreFetchTask öğelerinin Task'ten türetilmesiyle aşağıdaki gibi bir yapı oluşturduk:

Ön yükleyici

Bu yaklaşım ve Görev sınıfı sayesinde genel ilerlemeyi (MainPreloadTask) veya sadece öğelerin ilerleme durumunu (AssetPreloadTask) ya da şablon yükleme işleminin ilerlemesini (TemplatePreFetchTask) kolayca anlayabiliriz. Belirli bir dosyanın eşit ilerleme durumu. Nasıl yapıldığını görmek için /m/javascripts/raw/util/Task.js konumundaki Görev sınıfına ve /m/javascripts/preloading/task adresindeki asıl görev uygulamalarına göz atın. Örneğin, en kapsamlı önceden yükleme sarmalayıcımız olan /m/javascripts/preloading/task/MainPreloadTask.js sınıfını oluşturma yöntemimizden alınmış bir alıntıdır:

Package('preloading.task', [
  Import('util.Task'),
...

  Class('public MainPreloadTask extends Task', {

    _public: {
      
  MainPreloadTask : function() {
        
    var subtasks = [
      new AssetPreloadTask([
        {name: 'cutout/cutout-overlay-1', ext: 'png', type: ImagePreloader.TYPE_BACKGROUND, responsive: true},
        {name: 'journey/scene1', ext: 'jpg', type: ImagePreloader.TYPE_IMG, responsive: false}, ...
...
      ]),

      new TemplatePreFetchTask([
        'page.HomePage',
        'page.CutoutPage',
        'page.JourneyToOzPage1', ...
...
      ])
    ];
    
    this._super(subtasks);

      }
    }
  })
]);

/m/javascripts/preloading/task/subtask/AssetPreloadTask.js sınıfında, MainPreloadTask ile nasıl iletişim kurduğunun (paylaşılan Görev uygulaması aracılığıyla) yanı sıra, platforma bağlı olan öğeleri nasıl yüklediğimizi de belirtmemiz gerekir. Temel olarak dört tür resmimiz vardır. Mobil standart (.ext, dosya uzantısıdır, genellikle .png veya .jpg), mobil retina (-2x.ext), tablet standardı (-tab.ext) ve tablet retinası (-tab-2x.ext). MainPreloadTask'te algılama işlemi gerçekleştirmek ve dört öğe dizisinin kodunu gömmek yerine, önceden yüklenecek öğenin adının ve uzantısının ne olduğunu ve öğenin platforma bağlı olup olmadığını söylememiz yeterli olur (duyarlı = true / false). Ardından, AssetPreloadTask bizim için dosya adını oluşturur:

resolveAssetUrl : function(assetName, extension, responsive) {
  return AssetPreloadTask.ASSETS_ROOT + assetName + (responsive === true ? ((Detection.getInstance().tablet ? '-tab' : '') + (Detection.getInstance().retina ? '-2x' : '')) : '') + '.' +  extension;
}

Sınıf zincirinin altlarında, öğe önceden yüklemeyi yapan asıl kod şu şekilde görünür (/m/javascripts/raw/util/ImagePreloader.js):

loadUrl : function(url, type, completeHandler) {
  if(type === ImagePreloader.TYPE_BACKGROUND) {
    var $bg = $('<div>').hide().css('background-image', 'url(' + url + ')');
    this.$preloadContainer.append($bg);
  } else {
    var $img= $('<img />').attr('src', url).hide();
    this.$preloadContainer.append($img);
  }

  var image = new Image();
  this.cache[this.generateKey(url)] = image;
  image.onload = completeHandler;
  image.src = url;
}

generateKey : function(url) {
  return encodeURIComponent(url);
}

Eğitim: HTML5 Photo Booth (iOS6/Android)

OZ mobil uygulamasını geliştirirken, aslında çalışmak yerine fotoğraf kabininde oyun oynamaya epey zaman harcadığımızı fark ettik. Sizin için bir demo hazırladık.

Mobil fotoğraf kabini
Mobil fotoğraf kabini

Burada canlı bir demo görüntüleyebilirsiniz (iPhone veya Android telefonunuzda çalıştırın):

http://u9html5rocks.appspot.com/demos/mobile_photo_booth

Kurulum için arka ucu çalıştırabileceğiniz ücretsiz bir Google App Engine uygulama örneğine ihtiyacınız vardır. Ön uç kodu karmaşık değildir, ancak birkaç olası sonuç söz konusudur. Şimdi bunların üzerinden geçelim:

  1. İzin verilen resim dosyası türü Kullanıcıların yalnızca resim yükleyebilmesini istiyoruz (video kabini değil, fotoğraf kabini olması nedeniyle). Teoride, filtreyi HTML'de aşağıdaki gibi belirtebilirsiniz: input id="fileInput" class="fileInput" type="file" name="file" accept="image/*" Ancak bu yalnızca iOS'ta çalıştığından, dosya seçildikten sonra RegExp'e ek bir kontrol eklememiz gerekir:
   this.$fileInput.fileupload({
          
   dataType: 'json',
   autoUpload : true,
   
   add : function(e, data) {
     if(!data.files[0].name.match(/(\.|\/)(gif|jpe?g|png)$/i)) {
      return self.onFileTypeNotSupported();
     }
   }
   });
  1. Yükleme veya dosya seçimini iptal etme Geliştirme sürecinde fark ettiğimiz bir diğer tutarsızlık, farklı cihazların iptal edilen bir dosya seçimini bildirmesidir. iOS telefonlar ve tabletler hiçbir şey yapmaz, hiçbir şey bildirmez. Dolayısıyla bu durum için özel bir işlem yapmamız gerekmez. Ancak Android telefonlar herhangi bir dosya seçili olmasa bile add() işlevini tetikler. Bunu şu şekilde yapabilirsiniz:
    add : function(e, data) {

    if(data.files.length === 0 || (data.files[0].size === 0 && data.files[0].name === "" && data.files[0].fileName === "")) {
            
    return self.onNoFileSelected();

    } else if(data.files.length > 1) {

    return self.onMultipleFilesSelected();            
    }
    }

Geri kalan her platformda sorunsuz bir şekilde çalışır. Eğlenin

Sonuç

Oz'a Giden Yolu Bulun'un devasa boyutu ve farklı teknolojilerin çeşitliliği göz önünde bulundurulduğunda, bu makalede kullandığımız yaklaşımlardan yalnızca birkaçını ele alabildik.

Enchilada'nın tamamını keşfetmek istiyorsanız Bu bağlantıdaki Oz'a Giden Yolu Bulun kaynak kodunun tamamına göz atabilirsiniz.

Kredi

Kredi listesinin tamamı için burayı tıklayın

Referanslar