Örnek Olay - Oz'a Giden Yolu Bulun

Giriş

"Oz Büyücüsü'ne Giden Yolu Bul", Disney tarafından web'e sunulan yeni bir Google Chrome denemesi. Kansas'taki bir sirkte etkileşimli bir yolculuğa çıkarak büyük bir fırtınaya kapılır ve Oz diyarına ulaşırsınız.

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

Bu çalışma, tümüyle bu makalede ele alınamayacak kadar büyük olduğundan, teknoloji hikayesinin ilgi çekici olduğunu düşündüğümüz bazı bölümlerini ele aldık. Bu süreçte, zorluk derecesi artan bazı odaklanmış eğitimler hazırladık.

Bu deneyimi mümkün kılmak için çok sayıda kişi çok çalıştı. Burada listelenebilecekten çok daha fazla kişi var. Hikayenin tamamını öğrenmek için lütfen siteyi ziyaret edin ve menü bölümündeki jenerik sayfasına göz atın.

Testlerin işleyiş şekli

Masaüstünde Oz Büyücüsü'ne Yolculuk, zengin ve etkileyici bir dünyadır. Gerçekçiliğe yakın bir sahne oluşturmak için 3D ve geleneksel film yapımına dayalı çeşitli efekt katmanlarını kullanırız. En belirgin teknolojiler arasında Three.js ile WebGL, özel olarak oluşturulmuş gölgelendiriciler ve CSS3 özelliklerini kullanan DOM animasyonlu öğeler yer alır. Bunun yanı sıra, kullanıcının doğrudan web kamerasından resmini eklemesine olanak tanıyan etkileşimli deneyimler için getUserMedia API (WebRTC) ve 3D ses için WebAudio de mevcuttur.

Ancak bu tür teknolojik deneyimlerin büyüsü, tüm bunların bir araya gelmesidir. Bu da en önemli zorluklardan biridir: Tutarlı bir bütün oluşturmak için görsel efektleri ve etkileşimli öğeleri tek bir sahnede nasıl harmanlayabiliriz? Bu görsel karmaşıklığı yönetmek zordu. Bu da geliştirme sürecinin hangi aşamasında olduğumuzu anlamamızı zorlaştırıyordu.

Birbirine bağlı görsel efektler ve optimizasyon sorununu çözmek için, o anda incelediğimiz tüm ilgili ayarları yakalayan bir kontrol panelinden yoğun şekilde yararlandık. Sahne, parlaklık, alan derinliği, gama vb. her şey için tarayıcıda canlı olarak ayarlanabilir. Herkes deneyimdeki önemli parametrelerin değerlerini değiştirmeyi deneyebilir ve en iyi sonucun ne olduğunu keşfetmeye katılabilir.

Sırımızı paylaşmadan önce, araba motorunun içine girip kurcaladığınızda olduğu gibi, uygulamanın kilitlenebileceği konusunda sizi uyarmak isteriz. Önemli bir işiniz olmadığından emin olun ve sitenin ana URL'sini ziyaret edin. Adrese ?debug=on ifadesini ekleyin. Sitenin yüklenmesini bekleyin ve içeri girdikten sonra Ctrl-I tuşuna basın. Sağ tarafta bir açılır menü gösterilir. "Kamera yolundan çık" seçeneğinin işaretini kaldırırsanız A, W, S, D tuşlarını ve fareyi kullanarak alanda özgürce hareket edebilirsiniz.

Kamera yolu.

Burada tüm ayarları ele almayacağız ancak denemeler yapmanızı öneririz. Tuşlar, farklı sahnelerde farklı ayarları gösterir. Son fırtına sekansında ek bir tuş vardır: Ctrl-A. Bu tuşla animasyon oynatmayı değiştirebilir ve etrafta uçabilirsiniz. Bu sahnede Esc (fare kilidi işlevinden çıkmak için) ve ardından Ctrl-I tuşlarına basarak fırtına sahnesine özgü ayarlara erişebilirsiniz. Etrafınıza bakın ve aşağıdaki gibi güzel kartpostal manzaraları çekin.

Fırtına sahnesi

Bunu yapmak ve ihtiyaçlarımıza yeterince esnek olmasını sağlamak için dat.gui adlı harika bir kitaplık kullandık (Kullanımıyla ilgili geçmiş bir eğitim için buraya göz atın). Bu sayede, site ziyaretçilerine hangi ayarların gösterileceğini hızlıca değiştirebildik.

Mat Resme Biraz Benzer

Birçok klasik Disney filminde ve animasyonunda sahne oluşturmak için farklı katmanlar birleştirilirdi. Canlı çekim, hücre animasyonu ve hatta fiziksel setler içeren katmanlar vardı. En üst katmanlarda ise cam üzerine boyama yapılarak oluşturulan mat boyama tekniği kullanılıyordu.

"Katmanların" bazıları statik görsellerden çok daha fazlası olsa da, oluşturduğumuz deneyimin yapısı birçok açıdan benzer. Aslında, daha karmaşık hesaplamalara göre nesnelerin görünümünü etkilerler. Yine de en azından genel hatlarıyla, birbirleri üzerine yerleştirilmiş görünümlerle uğraşıyoruz. Üstte, altında farklı sahne bileşenlerinden oluşan bir 3D sahnenin bulunduğu bir kullanıcı arayüzü katmanı görürsünüz.

Üst arayüz katmanı DOM ve CSS 3 kullanılarak oluşturuldu. Bu sayede etkileşimlerin düzenlenmesi, belirli bir etkinlik listesine göre iki taraf arasında iletişim kurularak 3D deneyimden bağımsız olarak birçok şekilde yapılabiliyordu. Bu iletişimde, hangi alanın animasyonla açılacağını/kapatacağını kontrol eden Backbone Router + onHashChange HTML5 etkinliği kullanılır. (proje kaynağı: /develop/coffee/router/Router.coffee).

Eğitim: Sprite e-tabloları ve Retina desteği

Arayüzde kullandığımız eğlenceli bir optimizasyon tekniği, sunucu isteklerini azaltmak için birçok arayüz yer paylaşımı resmini tek bir PNG'de birleştirmekti. Bu projede arayüz, web sitesinin gecikmesini azaltmak için önceden yüklenen 70'den fazla resimden (3D dokular hariç) oluşuyordu. Canlı sprite e-tablosunu burada görebilirsiniz:

Normal Ekran: http://findyourwaytooz.com/img/home/interface_1x.png Retina Ekran: http://findyourwaytooz.com/img/home/interface_2x.png

Sprite e-tablolarından nasıl yararlandığımıza ve bunları retina cihazlarda nasıl kullanacağımıza ve arayüzü mümkün olduğunca keskin ve düzenli hale getireceğimize dair bazı ipuçları aşağıda verilmiştir.

Sprite sayfaları oluşturma

SpriteSheets oluşturmak için ihtiyacınız olan herhangi bir biçimde çıkış veren TexturePacker'ı kullandık. Bu durumda, gerçekten temiz olan ve animasyonlu sprite'ler oluşturmak için de kullanılabilecek EaselJS olarak dışa aktardık.

Oluşturulan sprite e-tablosunu kullanma

Sprite e-tablonuzu oluşturduktan sonra aşağıdaki gibi 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:

  • image, sprite sayfasının URL'sini ifade eder.
  • frames, her kullanıcı arayüzü öğesinin koordinatlarıdır [x, y, width, height]
  • animasyonlar, her öğenin adıdır

Sprite sayfasını oluşturmak için yüksek yoğunluklu resimleri kullandığımızı, ardından normal sürümü oluşturmak için boyutunu yarıya indirdiğimizi unutmayın.

Konuyu toparlamak gerekirse

Artık her şey hazır. Kullanmak için 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);
};

Bu özelliği şu şekilde kullanabilirsiniz:

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 edinmek için Boris Smus'un bu makalesini okuyabilirsiniz.

3D İçerik Akışı

Ortam deneyimi, bir WebGL katmanında ayarlanır. 3D sahneler söz konusu olduğunda en zor sorulardan biri, modelleme, animasyon ve efektler açısından maksimum ifade potansiyelini sunan içerikler oluşturma konusunda nasıl emin olabileceğinizdir. Bu sorunun temelinde, birçok açıdan içerik ardışık düzeni yatmaktadır. Bu, 3D sahne için içerik oluşturmak üzere üzerinde anlaşmaya varılan bir süreçtir.

Heyecan verici bir dünya oluşturmak istediğimizden, 3D sanatçıların bu dünyayı oluşturmasını sağlayacak sağlam bir sürece ihtiyacımız vardı. 3D modelleme ve animasyon yazılımlarında mümkün olduğunca fazla ifade özgürlüğü tanınması ve kod aracılığıyla ekranda oluşturulması gerekir.

Geçmişte 3D site oluşturduğumuz her seferinde kullanabileceğimiz araçlarda sınırlamalarla karşılaştığımız için bu tür sorunlar üzerinde bir süredir çalışıyorduk. Bu nedenle, şirket içi bir araştırma aracı olan 3D Librarian'ı geliştirdik. Artık gerçek bir işe uygulanmaya hazırdı.

Bu aracın geçmişi oldukça eskilere dayanır: Başlangıçta Flash için tasarlanan bu araç, büyük bir Maya sahnesini, çalışma zamanında açma işlemi için optimize edilmiş tek bir sıkıştırılmış dosya olarak getirmenize olanak tanır. Bu yöntemin en uygun olmasının nedeni, sahneyi temel olarak oluşturma ve animasyon sırasında manipüle edilen aynı veri yapısına etkili bir şekilde paketlemesidir. Yüklendiğinde dosyada çok az ayrıştırma işlemi yapılması gerekir. Dosya AMF biçiminde olduğu için Flash'ta paketin açılması oldukça hızlıydı. Flash, bu biçimi doğal olarak açabilir. WebGL'de aynı biçimi kullanmak, CPU'da biraz daha fazla çalışma gerektirir. Aslında, bu dosyaları sıkıştırıp WebGL'nin çalışması için gereken veri yapılarını yeniden oluşturacak bir veri açma JavaScript kod katmanı yeniden oluşturmamız gerekiyordu. 3D sahnenin tamamını açmak, CPU'ya orta düzeyde yük bindirir: Oz Büyücüsü filmindeki 1. sahnenin açılması, orta ve üst düzey bir makinede yaklaşık 2 saniye sürer. Bu nedenle, kullanıcının deneyimini aksatmamak için Web Workers teknolojisi kullanılarak "sahne kurulumu" sırasında (sahne gerçekten başlatılmadan önce) yapılır.

Bu kullanışlı araç, 3D sahnenin çoğunu (modeller, dokular, kemik animasyonlar) içe aktarabilir. 3D motor tarafından yüklenebilecek tek bir kitaplık dosyası oluşturursunuz. Sahnenizde ihtiyaç duyduğunuz tüm modelleri bu kitaplığa doldurursunuz ve işte bu kadar, onları sahnenize yerleştirirsiniz.

Ancak yeni bir teknoloji olan WebGL ile çalışmamız gerekiyordu. Bu çocuk oldukça zorluydu: Tarayıcı tabanlı 3D deneyimler için standartları belirlemişti. Bu nedenle, 3D Librarian sıkıştırılmış 3D sahne dosyalarını alıp WebGL'nin anlayacağı bir biçime doğru şekilde çevirecek özel bir JavaScript katmanı oluşturduk.

Eğitim: Rüzgar Olsun

"Find Your Way To Oz"da tekrarlanan bir tema rüzgardı. Hikayenin bir kısmı, rüzgarın giderek artması şeklinde yapılandırılmıştır.

Karnavalın ilk sahnesi nispeten sakin. Kullanıcı, çeşitli sahnelerde giderek daha güçlü bir rüzgar deneyimler ve son sahnede fırtınaya ulaşır.

Bu nedenle, sürükleyici bir rüzgar efekti sunmak önemliydi.

Bunu oluşturmak için 3 karnaval sahnesini, yumuşak ve dolayısıyla rüzgardan etkilenmesi beklenen nesnelerle (ör. çadırlar, bayraklar, fotoğraf kabininin yüzeyi ve balonun kendisi) doldurduk.

Yumuşak bir bez.

Günümüzde masaüstü oyunları genellikle temel bir fizik motoru etrafında geliştiriliyor. Bu nedenle, 3D dünyada yumuşak bir nesnenin simülasyonu gerektiğinde, bu nesne için tam bir fizik simülasyonu çalıştırılır ve inandırıcı bir yumuşak davranış oluşturulur.

WebGL / JavaScript'te henüz tam teşekküllü bir fizik simülasyonu çalıştırma lüksüne sahip değiliz. Bu nedenle Oz'da, rüzgarı simüle etmeden rüzgar etkisini yaratmanın bir yolunu bulmak zorunda kaldık.

Her nesnenin "rüzgar duyarlılığı" bilgilerini 3D modelin içine yerleştirdik. 3D modelin her bir köşesinde, söz konusu köşenin rüzgardan ne kadar etkileneceğini belirten bir "Rüzgar Özelliği" vardı. Bu, 3D nesnelerin rüzgar duyarlılığını belirtir. Ardından rüzgarı oluşturmamız gerekiyordu.

Bunu, Perlin gürültüsü içeren bir resim oluşturarak yaptık. Bu görüntünün amacı, belirli bir "rüzgar alanını" kapsamaktır. Bu nedenle, bu görüntüyü düşünmenin iyi bir yolu, 3D sahnenin belirli bir dikdörtgen alanının üzerine yerleştirilmiş bulut benzeri bir gürültü resmini hayal etmektir. Bu resmin her pikseli (gri seviye değeri), "etrafındaki" 3D alandaki belirli bir anda rüzgarın ne kadar güçlü olduğunu belirtir.

Rüzgar efekti oluşturmak için resim, zaman içinde sabit bir hızda belirli bir yönde (rüzgarın yönü) hareket ettirilir. "Rüzgarlı alanın" sahnedeki her şeyi etkilemediğinden emin olmak için rüzgar resmini, etki alanı ile sınırlı olacak şekilde kenarlara sardık.

Basit 3D Rüzgar Eğitimi

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

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

Öncelikle sahneyi oluşturalım. Basit, dokulu ve düz bir arazi kullanacağız. Ardından her çim parçası, ters çevrilmiş bir 3D koni ile temsil edilir.

Çimlerle dolu arazi
Çim kaplı arazi

Bu basit sahneyi Three.js'de CoffeeScript kullanarak nasıl oluşturacağınızı burada bulabilirsiniz.

Öncelikle Three.js'i kuracağız ve kamera, fare denetleyici ve bazı ışıklarla 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 arazi ile 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 çim parçasından oluşan bir ızgara oluşturuyoruz. Her çim konumuna biraz rastgelelik ekleriz. Böylece çim, asker gibi yan yana dizilmez ve tuhaf görünmez.

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

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 )

Şimdiye kadar bir Three.js sahnesi oluşturduk ve işlemsel olarak oluşturulmuş ters konilerden yapılmış birkaç çim parçası ve basit bir arazi ekledik.

Şimdilik çok fazla şey yapmadık.

Şimdi rüzgar eklemeye başlayın. Öncelikle rüzgar duyarlılığı bilgilerini çim 3D modeline yerleştirmek istiyoruz.

Bu bilgileri, çim 3D modelinin her bir köşe noktası için özel bir özellik olarak yerleştireceğiz. Ayrıca, çim modelinin alt ucunun (koninin ucu) yere bağlı olduğu için sıfır hassasiyete sahip olduğu kuralını kullanacağız. Çim modelinin üst kısmı (koninin tabanı), yerden daha uzak olduğu için rüzgara karşı maksimum hassasiyete sahiptir.

Aşağıda, çim 3D modeli için rüzgar duyarlılığını özel bir özellik olarak eklemek amacıyla instanceGrass işlevinin nasıl yeniden kodlandığı 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

Artık daha önce kullandığımız MeshPhongMaterial yerine windMaterial adlı özel bir malzeme kullanıyoruz. WindMaterial, bir dakika içinde göreceğimiz WindMeshShader'ı sarar.

Bu nedenle, instanceGrass içindeki kod, çim modelinin tüm köşelerini döngüden geçirir ve her köşe için windFactor adlı özel bir köşe özelliği ekler. Bu windFactor, çim modelinin alt ucu (zemine değmesi gereken yer) için 0 olarak ayarlanır ve çim modelinin üst ucu için 1 değerini alır.

İhtiyacımız olan diğer şey, sahnemize gerçek rüzgarı eklemektir. Konuştuğumuz gibi, bunun için Perlin gürültüsünü kullanacağız. Perlin gürültü dokusunu işlemsel olarak oluşturacağız.

Anlaşılır olması için bu doku, önceki yeşil doku yerine araziye atanacaktır. Böylece rüzgarın durumunu daha kolay anlayabilirsiniz.

Bu Perlin gürültü dokusu, arazimizin uzantısını mekansal olarak kapsayacak ve dokunun her pikseli, pikselin yer aldığı arazi alanının rüzgar yoğunluğunu belirtecektir. Arazi dikdörtgeni, "rüzgar alanımız" olacak.

Perlin gürültüsü, NoiseShader adlı bir gölgelendirici aracılığıyla işlemsel olarak oluşturulur. Bu gölgelendirici, https://github.com/ashima/webgl-noise adresindeki 3D basit gürültü algoritmalarını kullanır . Bunun WebGL sürümü, MrDoob'un Three.js örneklerinden birine (http://mrdoob.github.com/three.js/examples/webgl_terrain_dynamic.html) kelimesi kelimesine aktarılmıştır.

NoiseShader, tekdüzen olarak bir zaman, ölçek ve bir ofset parametre grubu alır ve Perlin gürültüsünün güzel bir 2D dağılımını döndürür.

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ümüzü bir dokuya oluşturmak için bu gölgelendiriciyi kullanacağız. Bu işlem, initNoiseShader işlevinde yapılır.

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 kod, noiseMap'i Three.js oluşturma hedefi olarak ayarlayarak NoiseShader ile donatır ve perspektif bozulmalarının önüne geçmek için ortografik kamerayla oluşturur.

Konuşulduğu gibi, bu doku artık arazi için ana oluşturma dokusu olarak da kullanılacak. Rüzgar efektinin çalışması için bu gerekli değildir. Ancak rüzgar enerjisi üretiminde neler olduğunu görsel olarak daha iyi anlayabilmemiz için bu tür verilere sahip olmak güzel bir şey.

Aşağıda, noiseMap'i doku olarak kullanan, yeniden tasarlanmış initTerrain işlevi verilmiştir:

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 )

Rüzgar dokusumuzu yerleştirdiğimize 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 yola çıkarak gölgelendiriciyi değiştirdik. Bu, sıfırdan başlamak zorunda kalmadan çalışan bir gölgelendirici kullanmaya başlamanın hızlı ve basit bir yoludur.

Çoğu MeshPhongMaterial gölgelendiricisinin bir kopyası olacağından gölgelendirici kodunun tamamını buraya kopyalamayacak (kaynak kod dosyasında bulabilirsiniz) Ancak Vertex Shader'daki rüzgarla ilgili değiştirilmiş bölümlere 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;

Bu gölgelendirici, önce köşenin 2D, xz (yatay) konumuna göre windUV doku arama koordinatını hesaplar. Bu UV koordinatı, Perlin gürültü rüzgar dokusundan rüzgar kuvvetini (vWindForce) aramak için kullanılır.

Bu vWindForce değeri, köşenin ne kadar deformasyona ihtiyacı olduğunu hesaplamak için köşeye özgü windFactor (yukarıda açıklanan özel özellik) ile birleştirilir. Ayrıca, rüzgarın genel gücünü kontrol etmek için global bir windScale parametresi ve rüzgar deformasyonunun hangi yönde gerçekleşmesi gerektiğini belirten bir windDirection vektörü de vardır.

Bu da çim parçalarımızda rüzgara bağlı deformasyonlara neden olur. Ancak işimiz henüz bitmedi. Şu anda bu deformasyon statiktir ve rüzgarlı bir alanın etkisini yansıtmaz.

Daha önce de belirttiğimiz gibi, camımızın dalgalanması için gürültü dokusunu zaman içinde rüzgar alanında kaydırmamız gerekir.

Bu işlem, NoiseShader'a iletilen vOffset birimli değişkeni zaman içinde kaydırarak yapılır. Bu, belirli bir yönde (rüzgar yönümüz) gürültü ofsetini belirtmemize olanak tanıyan bir vec2 parametresidir.

Bunu her karede çağrılan render işlevinde yaparız:

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

Hepsi bu kadar. Rüzgardan etkilenen "yöntemsel çim" içeren bir sahne oluşturduk.

Karışıma toz ekleme

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

Toz ekleme
Toz ekleme

Sonuçta tozun rüzgardan etkilenmesi gerekir. Bu nedenle, rüzgarlı sahnemizde tozların uçuşması son derece mantıklı.

Toz, initDust işlevinde parçacık sistemi olarak ayarlanır.

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. Ayrıca her birinin özel bir WindParticleShader ile donatıldığını unutmayın.

Şimdi her karede, CoffeeScript kullanarak parçacıkları rüzgardan bağımsız olarak biraz hareket ettireceğiz. Kodu burada bulabilirsiniz.

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 parçacığın konumunu rüzgara göre kaydıracağız. Bu işlem WindParticleShader'da yapılır. Özellikle de köşe gölgelendiricisinde.

Bu gölgelendiricinin kodu, Three.js ParticleMaterial'ın değiştirilmiş bir sürümüdür. Kodun çekirdeği şu şekildedir:

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 köşe birimi gölgelendirici, çimde rüzgara dayalı deformasyon için kullandığımızdan çok farklı değil. Perlin gürültü dokusunu giriş olarak alır ve toz dünya konumuna bağlı olarak gürültü dokusundaki vWindForce değerini arar. Ardından, toz parçacığının konumunu değiştirmek için bu değeri kullanır.

Riders On The Storm

WebGL sahnelerimiz arasında en maceralı olanı muhtemelen son sahneydi. Sitedeki yolculuğunuzu sonuna kadar tamamlamak için balonu tıklayarak kasırganın gözüne ulaşabilir ve yaklaşan sürümün özel videosunu görebilirsiniz.

Balon yolculuğu sahnesi

Bu sahneyi oluştururken deneyimin merkezinde yer alacak ve etkili olacak bir özellik olması gerektiğini biliyorduk. Dönen kasırga ana unsur olarak kullanılır ve diğer içerik katmanları bu unsuru etkileyici bir etki oluşturacak şekilde şekillendirir. Bunu başarmak için bu garip gölgelendiricinin etrafında bir film stüdyosu seti oluşturduk.

Gerçekçi bir kompozisyon oluşturmak için karma bir yaklaşım kullandık. Bazıları, lens parlaması efekti oluşturmak için ışık şekilleri veya baktığınız sahnenin üzerinde katmanlar halinde animasyonlu yağmur damlaları gibi görsel hilelerdi. Diğer durumlarda, hareket ediyormuş gibi görünen düz yüzeyler çizdik (ör. parçacık sistemi koduna göre hareket eden alçaktan uçan bulut katmanları). Tornadonun etrafında dönen enkaz parçaları ise 3D sahnedeki katmanlardan oluşuyordu ve tornadonun önünde ve arkasında hareket edecek şekilde sıralandı.

Sahneyi bu şekilde oluşturmamızın başlıca nedeni, kasırga gölgelendiricisini uyguladığımız diğer efektlerle dengeli bir şekilde işlemek 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 sahnelerden daha hafif hale geldi.

Eğitim: Fırtına Gölgelendiricisi

Nihai fırtına sekansını oluşturmak için birçok farklı teknik birleştirildi ancak bu çalışmanın ana unsuru, kasırga gibi görünen özel bir GLSL gölgelendiriciydi. İlginç geometrik girdaplar oluşturmak için uç nokta gölgelendiricilerden parçacık tabanlı animasyonlara ve hatta bükülmüş geometrik şekillerin 3D animasyonlarına kadar birçok farklı teknik denedik. Hiçbir efekt kasırga hissini yeniden oluşturmuyor veya işleme açısından çok fazla iş gerektirmiyordu.

Sonunda, tamamen farklı bir proje bize yanıtı verdi. Max Planck Enstitüsü'nden (brainflight.org) fare beynini haritalamak için bilime yönelik oyunlar içeren paralel bir projede ilginç görsel efektler elde edildi. Özel bir hacimsel gölgelendirici kullanarak bir fare nöronunun içinin videolarını oluşturmayı başardık.

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

Bir beyin hücresinin iç kısmının bir kasırganın hunisine benzediğini tespit ettik. Hacimsel bir teknik kullandığımız için bu gölgelendiriciyi uzayda her yönden görüntüleyebileceğimizi biliyorduk. Özellikle bulut katmanlarının arasına yerleştirilmiş ve etkileyici bir arka planın üzerindeyse gölgelendiricinin oluşturulmasını fırtına sahnesiyle birleştirecek şekilde ayarlayabiliriz.

Gölgelendirici tekniği, temel olarak bir mesafe alanı ile ışın izleme oluşturma adı verilen basitleştirilmiş bir oluşturma algoritmasıyla bir nesnenin tamamını oluşturmak için tek bir GLSL gölgelendirici kullanan bir hile içerir. Bu teknikte, ekrandaki her nokta için bir yüzeye olan en yakın mesafeyi tahmin eden bir piksel gölgelendirici oluşturulur.

Algoritma hakkında iyi bir referans, iq tarafından hazırlanan genel bakış bölümünde bulunabilir: Rendering Worlds With Two Triangles - Iñigo Quilez. Ayrıca glsl.heroku.com adresindeki gölgelendirici galerisini inceleyebilirsiniz. Bu teknikle ilgili denenebilecek birçok örnek bulabilirsiniz.

Gölgelendiricinin kalbi ana işlevle başlar, kamera dönüşümlerini ayarlar ve bir yüzeye olan mesafeyi tekrar tekrar değerlendiren bir döngüye girer. Temel ışın izleme hesaplamasının yapıldığı yer, RaytraceFoggy( direction_vector, max_iterations, color, color_multiplier ) çağrısıdır.

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 amaç, kasırganın şekline doğru ilerlerken pikselin nihai renk değerine düzenli olarak renk katkıları ve ışın boyunca opaklığa katkılar eklemektir. Bu, kasırganın dokusunda katmanlı ve yumuşak bir kalite oluşturur.

Kasırganın bir sonraki temel yönü, bir dizi işlevden oluşturulan gerçek şekildir. İlk olarak, organik bir pürüzlü kenar oluşturmak için gürültüden oluşan bir koni oluşturulur. Ardından, ana ekseni boyunca bükülerek zaman içinde döndürülür.

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ölgelendirici oluşturmak zor bir iştir. Oluşturduğunuz işlemlerin soyutlanmasıyla ilgili sorunların yanı sıra, çalışmayı üretimde kullanabilmeniz için izlemeniz ve çözmeniz gereken ciddi optimizasyon ve platformlar arası uyumluluk sorunları vardır.

Sorunun ilk kısmı: Bu gölgelendiriciyi sahnemiz için optimize etme. Bu sorunu çözmek için, gölgelendiricinin çok ağır olması ihtimaline karşı "güvenli" bir yaklaşım benimsememiz gerekiyordu. Bunu yapmak için kasırga gölgelendiricisini sahnenin geri kalanından farklı bir örneklenmiş çözünürlükte birleştirdik. Bu, stormTest.coffee dosyasından alınmıştır (evet, bu bir testti!).

Kasırga gölgelendiricisinin çözünürlüğünün sahneden bağımsız olması için sahne genişliği ve yüksekliğiyle eşleşen bir renderTarget ile başlarız. Ardından, aldığımız kare hızına bağlı olarak fırtına gölgelendiricisinin çözünürlüğünün azaltılmasına dinamik olarak 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, stormTest.coffee dosyasının 1107. satırında basitleştirilmiş bir sal2x algoritması kullanarak (bloklu görünümü önlemek için) kasırgayı ekrana oluşturuyoruz. Bu, en kötü durumda daha bulanık bir kasırga elde ettiğimiz anlamına gelir ancak en azından kullanıcının kontrolünü elinden almadan çalışır.

Sonraki optimizasyon adımı için algoritmaya girmeniz gerekir. Gölgelendiricideki temel hesaplama faktörü, yüzey işlevinin mesafesini yaklaşık olarak bulmaya çalışmak için her pikselde gerçekleştirilen iterasyondur: ışın izleme döngüsünün iterasyon sayısı. Daha büyük bir adım boyutu kullanarak, bulutlu yüzeyinin dışındayken daha az iterasyonla bir kasırga yüzeyi tahmini elde edebiliriz. İçerideyken, hassasiyet için adım boyutunu azaltır ve puslu efekti oluşturmak için değerleri karıştırabiliriz. Ayrıca, oluşturulan ışın için derinlik tahmini almak üzere bir sınırlayıcı silindir oluşturmak da hız açısından iyi bir gelişme sağladı.

Sorunun bir sonraki kısmı, bu gölgelendiricinin farklı ekran kartlarında çalışacağından emin olmaktı. Her seferinde bazı testler yaptık ve karşılaşabileceğimiz uyumluluk sorunlarıyla ilgili bir fikir edinmeye başladık. Sezgiden çok daha iyi bir sonuç elde edemememizin nedeni, hatalar hakkında her zaman iyi hata ayıklama bilgileri alamamamızdır. Tipik bir senaryo, fazla bir şeye yol açmayan bir GPU hatası veya hatta sistem kilitlenmesidir.

Farklı video kartı uyumluluğu sorunlarının benzer çözümleri vardı: Statik sabitlerin, tanımlandığı şekilde tam veri türünde girildiğinden emin olun.Örneğin: kayan nokta için 0,0 ve tam sayı için 0. Daha uzun işlevler yazarken dikkatli olun; derleyiciler belirli durumları doğru şekilde ele almadığı için işlevleri birden fazla basit işleve ve ara değişkene bölmek tercih edilir. Dokuların hepsinin 2'nin bir kuvveti olduğundan, çok büyük olmadığından ve her durumda bir döngüde doku verilerini ararken "dikkatli" davrandığınızdan emin olun.

Uyumluluk konusunda en büyük sorun, fırtınanın ışık efektinden kaynaklanıyordu. Kıvrımlarını renklendirebilmek için kasıp sarmalayan önceden hazırlanmış bir doku kullandık. Bu harika efekt, kasırgayı sahne renkleriyle kolayca harmanlamamızı sağladı ancak diğer platformlarda çalıştırmaya çalışmamız uzun sürdü.

hortum

Mobil web sitesi

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

Masaüstündeki Carnival Photo-Booth'u, kullanıcının mobil kamerasını kullanacak bir mobil web uygulaması olarak sunmanın ilgi çekici olacağını düşündük. Bu, daha önce hiç yapmadığımız bir şeydi.

Farklı bir hava katmak için CSS3'te 3D dönüşümler kodladık. Jiroskop ve ivme ölçere bağladığımızda deneyime çok fazla derinlik kattık. Site, telefonunuzu tutma, hareket ettirme ve ona bakma şeklinize yanıt verir.

Bu makaleyi yazarken, mobil geliştirme sürecinin sorunsuz bir şekilde nasıl yürütüleceğiyle ilgili bazı ipuçları vermenin faydalı olacağını düşündük. İşte başlıyoruz. Bu makaleyi inceleyerek neler öğrenebileceğinize göz atın.

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

Önyükleyici, kaçınılması gereken bir şey değil, gerekli olan bir şeydir. Bazen bu durumun yaşandığını biliyoruz. Bunun başlıca nedeni, projeniz büyüdükçe önceden yüklediğiniz öğelerin listesini güncellemeniz gerekmesidir. Daha da kötüsü, farklı kaynaklar çekiyorsanız ve bunların çoğunu aynı anda çekiyorsanız yükleme ilerleme durumunu nasıl hesaplamanız gerektiği çok net değildir. Bu noktada özel ve çok genel bir soyut sınıfımız olan "Görev" devreye girer. Ana fikri, bir görevin kendi alt görevlerine sahip olabileceği ve bunların da kendi alt görevlerine sahip olabileceği sonsuz şekilde iç içe yerleştirilmiş bir yapıya izin vermektir. Ayrıca her görev, ilerleme durumunu alt görevlerinin ilerleme durumuna göre hesaplar (ancak üst görevin ilerleme durumuna göre değil). Tüm MainPreloadTask, AssetPreloadTask ve TemplatePreFetchTask'ı Task'tan türeterek aşağıdaki gibi bir yapı oluşturduk:

Ön yükleyici

Bu yaklaşım ve Task sınıfı sayesinde genel ilerlemeyi (MainPreloadTask), yalnızca öğelerin ilerlemesini (AssetPreloadTask) veya şablonların yükleme ilerlemesini (TemplatePreFetchTask) kolayca öğrenebiliriz. Belirli bir dosyanın ilerleme durumu bile. Bunun nasıl yapıldığını görmek için /m/javascripts/raw/util/Task.js adresindeki Task sınıfına ve /m/javascripts/preloading/task adresindeki gerçek görev uygulamalarına göz atın. Örneğin, en iyi ön yükleme sarmalayıcımız olan /m/javascripts/preloading/task/MainPreloadTask.js sınıfını nasıl ayarladığımızı gösteren bir alıntı aşağıda verilmiştir:

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 (ortak Task uygulaması aracılığıyla) yanı sıra platforma bağlı öğeleri nasıl yüklediğimizi de belirtmek önemlidir. Temel olarak dört tür resim vardır. Mobil standart (.ext; burada ext, dosya uzantısıdır ve genellikle .png veya .jpg olur), mobil retina (-2x.ext), tablet standart (-tab.ext) ve tablet retina (-tab-2x.ext). MainPreloadTask'ta algılama yapmak ve dört öğe dizisini sabit kodlamak yerine, önceden yüklenecek öğenin adını ve uzantısını ve öğenin platforma bağlı olup olmadığını (responsive = true / false) belirtiriz. Ardından AssetPreloadTask, dosya adını bizim için 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 daha alt kısmında, öğe ön yüklemesini yapan asıl kod aşağıdaki gibi 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 Fotoğraf Makinesi (iOS6/Android)

OZ mobil'i geliştirirken, çalışmak yerine fotoğraf kabiniyle oynayarak çok zaman geçirdiğimizi fark ettik :D Bunun nedeni, bu özelliğin çok eğlenceli olmasıydı. Bu nedenle, denemeniz için bir demo hazırladık.

Mobil fotoğraf kabini
Mobil fotoğraf kabini

Burada canlı bir demoyu görebilirsiniz (iPhone veya Android telefonunuzda çalıştırın):

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

Bu özelliği ayarlamak için arka ucu çalıştırabileceğiniz ücretsiz bir Google App Engine uygulama örneğine ihtiyacınız vardır. Kullanıcı arabirimi kodu karmaşık değildir ancak birkaç olası sorun vardır. Şimdi bu noktalara göz atalım:

  1. İzin verilen resim dosyası türü Kullanıcıların yalnızca resim yükleyebilmesini istiyoruz (video kabini değil, fotoğraf kabini olduğu için). Teorik olarak, filtreyi HTML'de aşağıdaki gibi belirtmeniz yeterlidir: input id="fileInput" class="fileInput" type="file" name="file" accept="image/*" Ancak bu yöntem yalnızca iOS'te çalışıyor. Bu nedenle, bir dosya seçildikten sonra RegExp ile ilgili ek bir kontrol eklememiz gerekiyor:
   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 dosya seçimlerini nasıl bildirdiğiyle ilgili. iOS telefonlar ve tabletler hiçbir işlem yapmaz, bildirimde bulunmaz. Bu nedenle, bu durum için herhangi bir özel işlem yapmamız gerekmiyor. Ancak Android telefonlar, dosya seçilmemiş olsa bile add() işlevini tetikler. Bu sorunu nasıl gidereceğiniz aşağıda açıklanmıştır:
    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();            
    }
    }

Diğer işlemler ise platformlar arasında oldukça sorunsuz bir şekilde çalışır. Eğlenin

Sonuç

Oz Büyücüsü'nün devasa boyutu ve kullanılan farklı teknolojilerin geniş yelpazesi göz önüne alındığında, bu makalede kullandığımız yaklaşımlardan yalnızca birkaçını ele alabildik.

Tüm enchiladayı keşfetmek istiyorsanız bu bağlantıda Oz Büyücüsü'ne giden yolu bulma başlıklı makalenin kaynak kodunun tamamını inceleyebilirsiniz.

Kredi

Tam jenerik listesi için burayı tıklayın.

Referanslar