Studi Kasus - Temukan Jalanmu ke Oz

Pengantar

“Find Your Way to Oz” adalah Eksperimen Google Chrome baru yang dihadirkan ke web oleh Disney. Game ini memungkinkanmu melakukan perjalanan interaktif melalui sirkus di Texas, yang akan membawamu ke daratan Oz setelah tersapu badai besar.

Tujuan kami adalah menggabungkan beragam bioskop dengan kemampuan teknis browser untuk menciptakan pengalaman yang menyenangkan dan mendalam sehingga pengguna dapat menjalin hubungan yang kuat.

Tugas ini terlalu besar untuk dijabarkan secara utuh dalam bagian ini, jadi kami menyelami dan menulis beberapa bab untuk kisah teknologi yang menurut kami menarik. Dalam prosesnya, kami mengekstrak beberapa tutorial terfokus untuk meningkatkan tingkat kesulitan.

Banyak orang bekerja keras untuk mewujudkan pengalaman ini: terlalu banyak untuk disebutkan di sini. Kunjungi situs untuk melihat halaman kredit di bagian menu untuk mengetahui artikel lengkapnya.

Mengintip Di Balik Terpal

Temukan Jalanmu ke Oz di desktop adalah dunia yang kaya dan imersif. Kami menggunakan efek 3D dan beberapa lapisan efek inspiratif pembuatan film tradisional yang digabungkan untuk menciptakan adegan yang nyaris realistis. Teknologi yang paling menonjol adalah WebGL dengan Three.js, shader yang dibuat khusus, dan elemen animasi DOM yang menggunakan fitur CSS3. Selain itu, getUserMedia API (WebRTC) untuk pengalaman interaktif memungkinkan pengguna menambahkan gambar langsung dari Webcam dan WebAudio untuk suara 3D.

Tetapi keajaiban pengalaman teknologi seperti ini adalah bagaimana menyatukannya. Hal ini juga merupakan salah satu tantangan utama: bagaimana cara memadukan efek visual dan elemen interaktif bersama-sama dalam satu adegan untuk menciptakan kesatuan yang konsisten? Kompleksitas visual ini sulit dikelola: sehingga sulit untuk membedakan tahap pengembangan yang mana pada suatu waktu.

Untuk mengatasi masalah pengoptimalan dan efek visual yang saling terhubung, kami banyak menggunakan panel kontrol yang akan menangkap semua setelan relevan yang kami tinjau pada saat itu. Pemandangan dapat disesuaikan langsung di browser mulai dari kecerahan, kedalaman bidang, gamma dll... Siapa pun dapat mencoba menyesuaikan nilai parameter signifikan dalam pengalaman dan mengambil bagian untuk menemukan apa yang paling baik.

Sebelum membagikan rahasia, kami ingin memperingatkan Anda bahwa mobil itu mungkin akan bertabrakan, sama seperti jika Anda mencari-cari di dalam mesin mobil. Pastikan tidak ada informasi penting di dalamnya, lalu kunjungi URL utama situs dan tambahkan ?debug=on ke alamat tersebut. Tunggu hingga situs dimuat dan setelah Anda berada di dalam (tekan?) tombol Ctrl-I dan Anda akan melihat dropdown muncul di sisi kanan. Jika Anda menghapus centang pada opsi "Exit camera path", Anda dapat menggunakan tombol A, W, S, D, dan mouse untuk bergerak bebas di sekitar ruang.

Jalur kamera.

Kami tidak akan membahas semua setelan di sini, tetapi kami mendorong Anda untuk bereksperimen: tombol menampilkan setelan yang berbeda dalam berbagai adegan. Pada urutan terakhir, ada tombol tambahan: Ctrl-A yang dapat digunakan untuk mengalihkan pemutaran animasi dan terbang. Dalam scene ini jika Anda menekan Esc (untuk keluar dari fungsi kunci mouse) dan menekan lagi Ctrl-I, Anda dapat mengakses setelan yang spesifik untuk adegan badai. Silakan lihat sekeliling kami dan rekam beberapa tampilan kartu pos yang bagus seperti di bawah ini.

Lokasi badai

Untuk mewujudkannya dan memastikannya cukup fleksibel dengan kebutuhan kami, kami menggunakan library menarik yang disebut dat.gui (lihat di sini untuk melihat tutorial sebelumnya tentang cara menggunakannya). Fitur ini memungkinkan kami dengan cepat mengubah setelan yang ditampilkan kepada pengunjung situs.

Sedikit Seperti Lukisan Matte

Dalam banyak film dan animasi Disney klasik, pembuatan adegan berarti menggabungkan berbagai lapisan. Ada banyak lapisan aksi langsung, animasi sel, bahkan set fisik, dan di lapisan atas yang dibuat dengan melukis di atas kaca: teknik yang disebut lukisan matte.

Dalam banyak hal, struktur pengalaman yang kami buat serupa; meskipun beberapa "lapisan" lebih banyak dari visual statis. Bahkan, berpengaruh pada tampilan berdasarkan komputasi yang lebih kompleks. Meskipun demikian, setidaknya pada tingkat gambaran besar kita berhadapan, yang digabungkan menjadi satu di atas yang lain. Di bagian atas, Anda akan melihat lapisan UI, dengan tampilan 3D di bawahnya: lapisan itu sendiri terbuat dari berbagai komponen tampilan.

Lapisan antarmuka atas dibuat menggunakan DOM dan CSS 3 yang berarti bahwa pengeditan interaksi dapat dilakukan dengan banyak cara secara terpisah dari pengalaman 3D dengan komunikasi di antara keduanya sesuai dengan daftar peristiwa tertentu. Komunikasi ini menggunakan Backbone Router + event HTML5 onHashChange yang mengontrol area mana yang harus dianimasikan masuk/keluar. (sumber project: /develop/coffee/router/Router.coffee).

Tutorial: Dukungan Sprite Spreadsheet dan Retina

Salah satu teknik pengoptimalan menyenangkan yang kami andalkan untuk antarmuka adalah mengombinasikan banyak gambar overlay antarmuka dalam satu PNG untuk mengurangi permintaan server. Dalam project ini, antarmuka terdiri dari 70+ gambar (tidak termasuk tekstur 3D) yang dimuat semuanya di awal untuk mengurangi latensi situs. Anda dapat melihat {i>sprite sheet<i} secara langsung di sini:

Tampilan Normal - http://findyourwaytooz.com/img/home/interface_1x.png Retina Display - http://findyourwaytooz.com/img/home/interface_2x.png

Berikut adalah beberapa tips tentang bagaimana kami memanfaatkan penggunaan Spreadsheet Sprite, dan cara menggunakannya untuk perangkat retina serta mendapatkan antarmuka yang tajam dan rapi.

Membuat Spritesheet

Untuk membuat SpriteSheets, kami menggunakan TexturePacker yang menghasilkan output dalam format apa pun yang Anda butuhkan. Dalam hal ini, kami telah mengekspor sebagai EaselJS yang benar-benar rapi dan dapat digunakan untuk membuat sprite animasi juga.

Menggunakan Sheet Sprite yang dihasilkan

Setelah membuat Sheet Sprite, Anda akan melihat file JSON seperti ini:

{
   "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]
   },
}

Dengan keterangan:

  • gambar mengacu ke URL sprite sheet
  • bingkai adalah koordinat setiap elemen UI [x, y, lebar, tinggi]
  • animasi adalah nama setiap aset

Perhatikan bahwa kita telah menggunakan gambar kepadatan tinggi untuk membuat lembar Sprite, kemudian kita telah membuat versi normal hanya dengan mengubah ukurannya menjadi setengah dari ukurannya.

Rangkuman

Setelah siap, kita hanya perlu cuplikan JavaScript untuk menggunakannya.

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

Dan beginilah cara Anda akan menggunakannya:

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

Untuk memahami Variabel Kepadatan Piksel lebih lanjut, Anda dapat membaca artikel ini oleh Boris Smus.

Pipeline Konten 3D

Pengalaman lingkungan disiapkan di lapisan WebGL. Saat Anda memikirkan adegan 3D, salah satu pertanyaan tersulit adalah bagaimana Anda memastikan dapat membuat konten yang memungkinkan potensi ekspresif maksimum dari sisi pemodelan, animasi, dan efek. Dalam banyak hal, inti dari masalah ini adalah pipeline konten: proses yang disepakati untuk diikuti guna membuat konten untuk adegan 3D.

Kami ingin menciptakan dunia yang menginspirasi, jadi kami membutuhkan proses solid yang memungkinkan seniman 3D menciptakannya. Mereka perlu sebanyak mungkin diberikan kebebasan ekspresif dalam software animasi dan pemodelan 3D; dan kita perlu merendernya di layar melalui kode.

Kami telah mengerjakan masalah semacam ini sejak lama karena setiap kali kami membuat situs 3D di masa lalu, kami menemukan keterbatasan pada alat yang dapat kami gunakan. Jadi kami menciptakan alat yang disebut Pustaka 3D ini: sebuah bagian penelitian internal. Dan sudah hampir siap untuk dilamar untuk pekerjaan yang sebenarnya.

Alat ini memiliki beberapa riwayat: awalnya untuk Flash, dan alat ini memungkinkan Anda menghadirkan adegan Maya besar sebagai satu file terkompresi tunggal yang dioptimalkan untuk membongkar runtime. Alasannya optimal adalah karena secara efektif memaketkan adegan dalam struktur data yang pada dasarnya sama yang dimanipulasi selama render dan animasi. Terdapat sangat sedikit penguraian yang perlu dilakukan pada file saat dimuat. Proses ekstrak dalam Flash cukup cepat karena file tersebut memiliki format AMF, sehingga Flash dapat mengekstraknya secara bawaan. Penggunaan format yang sama di WebGL memerlukan upaya lebih besar di CPU. Sebenarnya kami harus membuat ulang lapisan kode JavaScript ekstrak data, yang pada dasarnya akan mendekompresi file tersebut dan membuat ulang struktur data yang diperlukan agar WebGL berfungsi. Pembukaan seluruh tampilan 3D merupakan operasi yang menggunakan sedikit CPU: mengekstrak adegan 1 di Find Your Way To Oz memerlukan waktu sekitar 2 detik pada mesin kelas menengah hingga atas. Oleh karena itu, ini dilakukan dengan teknologi Web Workers, pada saat "pengaturan adegan" (sebelum adegan benar-benar diluncurkan), agar tidak menggantung pengalaman bagi pengguna.

Alat praktis ini dapat mengimpor sebagian besar tampilan 3D: model, tekstur, animasi tulang. Anda membuat satu file library yang kemudian dapat dimuat oleh mesin 3D. Anda memasukkan semua model yang dibutuhkan dalam scene dalam library ini, dan, jadi, munculkan model tersebut ke dalam scene Anda.

Masalah yang kami alami adalah saat ini kami menangani WebGL: turunan baru di blok tersebut. Anak yang cukup sulit: yang menjadi standar untuk pengalaman 3D berbasis browser. Jadi kami membuat lapisan JavaScript ad hoc yang akan mengambil file adegan 3D terkompresi Librarian 3D, dan menerjemahkannya dengan benar ke format yang dapat dipahami oleh WebGL.

Tutorial: Memungkinkan Angin

Tema yang muncul berulang dalam “Find Your Way To Oz” adalah angin. Rangkaian jalan cerita disusun menjadi crescendo angin.

Adegan pertama karnaval cukup tenang. Saat melalui berbagai adegan, pengguna mengalami angin yang semakin kencang dan berpuncak pada adegan terakhir, yaitu badai.

Oleh karena itu, penting untuk memberikan efek angin yang imersif.

Untuk membuatnya, kami mengisi 3 adegan karnaval dengan benda-benda yang lembut, sehingga seharusnya terpengaruh oleh angin, seperti tenda, bendera permukaan booth foto, dan balon itu sendiri.

Kain lembut.

Saat ini, game desktop biasanya dibuat berdasarkan mesin fisika inti. Jadi, ketika objek lunak perlu disimulasikan dalam dunia 3D, simulasi fisika penuh akan dijalankan untuk objek itu, sehingga menciptakan perilaku lembut yang masuk akal.

Di WebGL / JavaScript, kami tidak (belum) memiliki kemampuan untuk menjalankan simulasi fisika yang sangat lengkap. Jadi di Oz kami harus menemukan cara untuk menciptakan efek angin, tanpa benar-benar menyimulasikannya.

Kami menyematkan informasi "sensitivitas angin" untuk setiap objek dalam model 3D itu sendiri. Setiap verteks model 3D memiliki ”Atribut Angin” yang menentukan seberapa besar verteks tersebut seharusnya dipengaruhi oleh angin. Jadi, sensitivitas angin tertentu pada Objek 3D. Lalu, kita perlu menciptakan angin itu sendiri.

Kita melakukannya dengan membuat gambar yang berisi Perlin Noise. Gambar ini dimaksudkan untuk menutupi "area angin". Jadi, cara yang baik untuk membayangkannya adalah dengan membayangkan gambar awan seperti kebisingan yang ditempatkan di atas area persegi panjang tertentu dari adegan 3D. Setiap piksel, nilai tingkat abu-abu, pada gambar ini menentukan seberapa kuat angin pada momen tertentu di area 3D "yang mengelilinginya".

Untuk menghasilkan efek angin, gambar digerakkan, dalam waktu, dengan kecepatan konstan, ke arah tertentu; arah angin. Untuk memastikan “area berangin” tidak memengaruhi semua hal dalam ruang pandang, kami membungkus gambar angin di sekitar tepinya, terbatas pada area efek.

Tutorial Angin 3D Sederhana

Sekarang, mari kita buat efek angin dalam tampilan 3D sederhana di Three.js.

Kita akan menciptakan angin dalam "bidang rumput sesuai prosedur" sederhana.

Mari kita buat suasananya terlebih dahulu. Kita akan memiliki medan datar yang sederhana dan bertekstur. Kemudian setiap bagian rumput akan ditampilkan dengan kerucut 3D terbalik.

Medan penuh rumput
Medan penuh rumput

Berikut cara membuat suasana sederhana di Three.js menggunakan CoffeeScript.

Pertama-tama, kita akan menyiapkan Three.js, dan mengaitkannya dengan Kamera, pengontrol Mouse, dan Beberapa Cahaya, kira-kira:

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()

Fungsi initGrass, dan initTerrain akan mengisi tampilan masing-masing dengan rumput dan medan:

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

Di sini kita membuat petak rumput berukuran 15 kali 15 bit. Kami menambahkan sedikit pengacakan ke setiap posisi rumput, sehingga mereka tidak sejajar seperti tentara, yang akan terlihat aneh.

Medan ini hanyalah bidang horizontal, yang ditempatkan di dasar potongan rumput (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 )

Jadi, yang telah kita lakukan sejauh ini adalah dengan hanya membuat tampilan Three.js, dan menambahkan beberapa bagian rumput, yang dibuat dari kerucut terbalik yang dihasilkan secara prosedural, dan medan yang sederhana.

Sejauh ini tidak ada yang keren.

Sekarang, saatnya untuk mulai menambahkan angin. Pertama-tama, kita ingin menyematkan informasi sensitivitas angin ke dalam model 3D rumput.

Kita akan menyematkan informasi ini sebagai atribut khusus, untuk setiap verteks model 3D rumput. Dan kita akan menggunakan aturan bahwa: ujung bawah model rumput (ujung kerucut) memiliki sensitivitas nol, karena menempel ke tanah. Bagian atas model rumput (dasar kerucut) memiliki sensitivitas angin maksimum, karena bagian tersebut lebih jauh dari tanah.

Berikut cara fungsi instanceGrass dikodekan ulang, untuk menambahkan sensitivitas angin sebagai atribut khusus untuk model 3D rumput.

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

Sekarang kami menggunakan bahan kustom, windMaterial, bukan MeshPhongMaterial yang kita gunakan sebelumnya. WindMaterial menggabungkan WindMeshShader yang akan kita lihat sebentar lagi.

Jadi, kode dalam instanceGrass melakukan loop melalui semua verteks model rumput, dan untuk setiap verteks, kode ini menambahkan atribut verteks kustom, yang disebut windFactor. WindFactor ini disetel ke 0, untuk ujung bawah model rumput (yang seharusnya menyentuh medan), dan bernilai 1 untuk ujung atas model rumput.

Bahan lain yang kita butuhkan adalah menambahkan angin yang sebenarnya ke adegan. Seperti yang telah dibahas, kita akan menggunakan noise Perlin untuk ini. Kita akan membuat tekstur derau Perlin secara prosedural.

Agar jelas, kita akan menetapkan tekstur ini ke medan itu sendiri, sebagai pengganti tekstur hijau sebelumnya. Ini akan memudahkan Anda untuk memahami apa yang sedang terjadi dengan angin.

Jadi, tekstur derau Perlin ini akan mencakup ekstensi medan kita secara spasial, dan setiap piksel tekstur akan menentukan intensitas angin di area medan tempat piksel tersebut berada. Persegi panjang medan ini akan menjadi “area angin”.

Derau perlin dihasilkan secara prosedural melalui shader, yang disebut NoiseShader. Shader ini menggunakan algoritma noise simplex 3d dari: https://github.com/ashima/webgl-noise . Versi WebGL diambil kata demi kata dari salah satu contoh Three.js MrDoob, di: http://mrdoob.github.com/three.js/examples/webgl_terrain_dynamic.html.

NoiseShader memerlukan waktu, skala, dan serangkaian parameter offset, sebagai uniform, dan menghasilkan distribusi 2D yang bagus untuk derau Perlin.

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

...

Kita akan menggunakan Shader ini untuk merender Perlin Noise ke tekstur. Langkah ini dilakukan di fungsi initNoiseShader.

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 )

Fungsi kode di atas adalah menyiapkan noiseMap sebagai target render Three.js, melengkapinya dengan NoiseShader, lalu merendernya dengan kamera ortografis untuk menghindari distorsi perspektif.

Seperti yang telah dibahas, sekarang kita akan menggunakan tekstur ini sebagai tekstur rendering utama untuk medan. Ini tidak terlalu diperlukan agar efek angin itu sendiri bekerja. Tapi itu bagus untuk dimiliki, agar kita bisa lebih memahami secara visual apa yang terjadi dengan pembangkitan angin.

Berikut adalah fungsi initTerrain yang dikerjakan ulang, menggunakan noiseMap sebagai tekstur:

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 )

Setelah memiliki tekstur angin, mari kita lihat WindMeshShader, yang bertanggung jawab untuk mengubah model rumput menurut angin.

Untuk membuat shader ini, kami mulai dari shader MeshPhongMaterial Three.js standar, dan mengubahnya. Ini adalah cara cepat dan gampang untuk memulai shader yang berfungsi, tanpa harus memulai dari awal.

Kita tidak akan menyalin seluruh kode shader di sini (silakan lihat di file kode sumber), karena sebagian besar kode tersebut adalah replika shader MeshPhongMaterial. Tapi mari kita lihat bagian-bagian yang dimodifikasi dan terkait angin, di Vertex Shader.

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;

Jadi, yang dilakukan shader ini adalah terlebih dahulu menghitung koordinat pencarian tekstur windUV, berdasarkan posisi 2D, xz (horizontal) dari verteks tersebut. Koordinat UV ini digunakan untuk mencari gaya angin, vWindForce, dari tekstur angin derau Perlin.

Nilai vWindForce ini digabungkan dengan windFactor khusus verteks, atribut khusus yang telah dibahas di atas, untuk menghitung jumlah deformasi yang diperlukan verteks. Kita juga memiliki parameter windScale global untuk mengontrol kekuatan angin secara keseluruhan, dan vektor windDirection yang menentukan arah terjadinya deformasi angin.

Jadi, hal ini menciptakan deformasi berbasis angin dari potongan-potongan rumput kita. Namun, kami masih belum selesai. Seperti sekarang, deformasi ini bersifat statis, dan tidak akan menyampaikan efek area berangin.

Seperti yang telah kami sebutkan, kita perlu menggeser tekstur kebisingan dari waktu ke waktu, melintasi bidang angin, sehingga kaca kita dapat melambai.

Hal ini dilakukan dengan menggeser dari waktu ke waktu, seragam vOffset yang diteruskan ke NoiseShader. Ini adalah parameter vec2, yang memungkinkan kita menentukan offset kebisingan, di sepanjang arah tertentu (arah angin).

Kita melakukan ini dalam fungsi render, yang dipanggil di setiap frame:

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

Dan selesai! Kita baru saja membuat adegan dengan "rumput sesuai prosedur" yang dipengaruhi oleh angin.

Menambahkan debu ke campuran

Sekarang mari kita sedikit membuat suasananya tampak lebih menarik. Mari tambahkan sedikit debu terbang, agar adegannya lebih menarik.

Menambahkan debu
Menambahkan debu

Debu seharusnya dipengaruhi oleh angin, jadi masuk akal jika ada debu yang beterbangan di tempat angin kita.

Debu disiapkan dalam fungsi initDust sebagai sistem partikel.

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

Di sini 130 partikel debu terbentuk. Selain itu, perhatikan bahwa setiap modul dilengkapi dengan WindParticleShader khusus.

Sekarang, di setiap bingkai, kita akan sedikit bergerak mengitari partikel, menggunakan CoffeeScript, tanpa bergantung pada angin. Berikut kodenya.

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 )

Selain itu, kita akan mengimbangi setiap posisi partikel menurut angin. Langkah ini dilakukan di WindParticleShader. Khususnya di shader verteks.

Kode untuk shader ini adalah versi ParticleMaterial Three.js yang dimodifikasi, dan intinya seperti ini:

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;

Shader verteks ini tidak berbeda dengan yang kami miliki untuk deformasi rumput berbasis angin. Fungsi ini menggunakan tekstur derau Perlin sebagai input, dan bergantung pada posisi debu, tekstur akan mencari vWindForce, nilai dalam tekstur derau. Kemudian, nilai ini digunakan untuk mengubah posisi partikel debu.

Pengendara di Badai

Adegan WebGL yang paling menantang mungkin adalah adegan terakhir, yang dapat Anda lihat jika mengklik balon hingga mencapai mata tornado untuk mencapai akhir perjalanan di situs, dan video eksklusif dari rilis mendatang.

Adegan naik balon

Ketika membuat suasana ini, kami tahu bahwa kami perlu memiliki fitur utama untuk pengalaman yang akan berdampak. Tornado yang berputar akan bertindak sebagai pusat dan lapisan konten lain akan membentuk fitur ini untuk menciptakan efek yang dramatis. Untuk mencapai hal ini, kami membuat alat yang setara dengan studio film yang dibuat dengan shader aneh ini.

Kami menggunakan pendekatan campuran untuk membuat komposit yang realistis. Beberapa di antaranya adalah trik visual seperti bentuk cahaya untuk membuat efek suar lensa, atau tetesan hujan yang bergerak sebagai lapisan di atas adegan yang Anda lihat. Dalam kasus lain, kami menggambar permukaan datar agar tampak bergerak, seperti lapisan awan terbang rendah yang bergerak sesuai dengan kode sistem partikel. Sementara serpihan puing yang mengorbit di sekitar tornado adalah lapisan-lapisan dalam adegan 3D yang diurutkan untuk bergerak di depan dan di belakang tornado.

Alasan utama kami harus membangun scene dengan cara ini adalah untuk memastikan bahwa kami memiliki cukup GPU untuk menangani shader tornado secara seimbang dengan efek lain yang kami terapkan. Awalnya kami memiliki masalah besar terkait penyeimbangan GPU, tetapi kemudian adegan ini dioptimalkan dan menjadi lebih ringan dari adegan utama.

Tutorial: Storm Shader

Untuk membuat urutan badai akhir, banyak teknik berbeda yang digabungkan, tetapi inti dari pekerjaan ini adalah shader GLSL kustom yang terlihat seperti tornado. Kami telah mencoba berbagai teknik mulai dari shader verteks untuk membuat whirlpool geometris yang menarik hingga animasi berbasis partikel, dan bahkan animasi 3D dari bentuk geometris yang terpilin. Tampaknya tidak ada efek yang menciptakan kembali perasaan tornado atau membutuhkan terlalu banyak pemrosesan.

Proyek yang sama sekali berbeda pada akhirnya memberi kami jawabannya. Sebuah proyek paralel yang melibatkan game untuk sains guna memetakan otak tikus dari Max Planck Institute (brainflight.org) telah menghasilkan efek visual yang menarik. Kami telah berhasil membuat film bagian dalam neuron mouse menggunakan shader volumetrik kustom.

Di dalam neuron mouse menggunakan shader volumetrik kustom
Bagian dalam neuron mouse menggunakan shader volumetrik kustom

Kami menemukan bahwa bagian dalam sel otak tampak mirip seperti corong tornado. Dan karena menggunakan teknik volumetrik, kami tahu bahwa kami dapat melihat shader ini dari segala arah di ruang angkasa. Kita dapat mengatur render shader untuk digabungkan dengan adegan badai, terutama jika diapit di bawah lapisan awan dan di atas latar belakang yang dramatis.

Teknik shader melibatkan trik yang pada dasarnya menggunakan satu shader GLSL untuk merender seluruh objek dengan algoritma render yang disederhanakan yang disebut rendering ray marching dengan bidang jarak. Dalam teknik ini, shader piksel dibuat, yang memperkirakan jarak terdekat ke permukaan untuk setiap titik pada layar.

Referensi yang baik untuk algoritme ini dapat ditemukan dalam ringkasan oleh iq: Rendering Worlds With Two Triangles - Iñigo Quilez. Selain itu, dengan menjelajahi galeri shader di glsl.heroku.com, ada banyak contoh teknik ini yang dapat dicoba dan ditemukan di sana.

Jantung shader dimulai dengan fungsi utama, yang menyiapkan transformasi kamera dan memasuki loop yang berulang kali mengevaluasi jarak ke permukaan. Panggilan RaytraceFoggy( direct_vector, max_iterations, color, color_multiplier ) adalah tempat penghitungan sinar inti terjadi.

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
}

Idenya adalah bahwa saat kita bergerak ke bentuk tornado, secara teratur menambahkan kontribusi warna ke nilai warna akhir piksel, serta kontribusi pada opasitas di sepanjang sinar. Proses ini akan menghasilkan kualitas lembut berlapis pada tekstur tornado.

Aspek inti tornado berikutnya adalah bentuk sebenarnya yang terbentuk dengan menyusun sejumlah fungsi. Ini adalah kerucut untuk memulai, yang dikomposisi dengan noise untuk menciptakan tepi kasar organik, dan kemudian dipelintir di sepanjang sumbu utamanya dan diputar dalam waktu.

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

Pekerjaan yang diperlukan dalam membuat shader semacam ini cukup rumit. Di luar masalah yang terkait dengan abstraksi operasi yang Anda buat, ada pengoptimalan serius dan masalah kompatibilitas lintas platform yang perlu Anda lacak dan selesaikan sebelum Anda dapat menggunakan pekerjaan dalam produksi.

Bagian pertama dari masalah: mengoptimalkan shader ini untuk adegan. Untuk mengatasi hal ini, kami perlu menerapkan pendekatan yang "aman" untuk mengantisipasi jika shader akan terlalu berat. Untuk melakukannya, kami mengomposisikan shader tornado pada resolusi sampel yang berbeda dari adegan lainnya. Ini dari file stormTest.coffee (ya, ini tes!).

Kita mulai dengan renderTarget yang cocok dengan lebar dan tinggi scene sehingga kita dapat memiliki resolusi shader tornado yang tidak bergantung pada scene. Kemudian, kita memutuskan pengurangan sampel resolusi shader badai, yang secara dinamis bergantung pada kecepatan frame yang diperoleh.

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

Terakhir, kita merender tornado ke layar menggunakan algoritma sal2x yang disederhanakan, (untuk menghindari tampilan kotak-kotak) @line 1107 di stormTest.coffee. Ini berarti bahwa kasus yang lebih buruk pada akhirnya adalah tornado yang lebih buram, tetapi setidaknya hal tersebut berhasil tanpa mengambil kendali dari pengguna.

Langkah pengoptimalan berikutnya mengharuskan Anda menyelami algoritme. Faktor komputasi yang mendorong dalam shader adalah iterasi yang dilakukan pada setiap piksel untuk mencoba memperkirakan jarak fungsi permukaan: jumlah iterasi loop raymarching. Dengan ukuran langkah yang lebih besar, kita bisa mendapatkan perkiraan permukaan tornado dengan iterasi yang lebih sedikit saat berada di luar permukaannya yang berawan. Saat berada di dalam, kita akan mengurangi ukuran langkah untuk presisi dan agar dapat menggabungkan nilai untuk menciptakan efek berkabut. Selain itu, membuat silinder pembatas untuk mendapatkan perkiraan kedalaman sinar yang dicor memberikan kecepatan peningkatan yang baik.

Bagian selanjutnya dari masalah ini adalah memastikan bahwa shader ini akan berjalan pada kartu video yang berbeda. Setiap kali kami melakukan pengujian dan mulai membangun intuisi untuk jenis masalah kompatibilitas yang mungkin kami hadapi. Alasan kami tidak dapat melakukan lebih baik daripada intuisi adalah kami tidak selalu bisa mendapatkan informasi proses debug yang baik pada error. Skenario umumnya hanyalah error GPU yang masih memerlukan banyak waktu, atau bahkan sistem error.

Masalah kompatibilitas lintas board video memiliki solusi yang serupa: pastikan konstanta statis dimasukkan dari jenis data yang tepat seperti yang didefinisikan, IE: 0,0 untuk float dan 0 untuk int. Berhati-hatilah saat menulis fungsi yang lebih panjang; lebih baik memecahnya menjadi beberapa fungsi yang lebih sederhana dan variabel sementara karena compiler tampaknya tidak menangani kasus tertentu dengan benar. Pastikan tekstur memiliki nilai 2, tidak terlalu besar, dan, bagaimanapun, menerapkan "kehati-hatian" saat mencari data tekstur dalam satu loop.

Masalah terbesar yang kami miliki dalam kompatibilitas adalah dari efek pencahayaan untuk badai. Kami menggunakan tekstur siap pakai yang melilitkan tornado sehingga kami bisa mewarnai lilitannya. Efeknya yang luar biasa, dan memudahkan untuk membaurkan tornado ke warna adegan, tetapi perlu waktu lama untuk dijalankan di platform lain.

tornado

Situs Web Seluler

Pengalaman seluler tidak dapat menjadi terjemahan langsung dari versi desktop karena persyaratan teknologi dan pemrosesan yang terlalu berat. Kami harus membuat sesuatu yang baru, yang secara khusus menyasar pengguna seluler.

Menurut kami, akan keren jika memiliki Carnival Photo-Booth dari desktop sebagai aplikasi web seluler yang akan menggunakan kamera seluler pengguna. Sesuatu yang belum kami lihat sejauh ini.

Untuk menambah cita rasa, kami membuat kode transformasi 3D di CSS3. Setelah menghubungkannya dengan giroskop dan akselerometer, kami dapat menambahkan pengalaman yang lebih mendalam. Situs merespons cara Anda memegang, menggerakkan, dan melihat ponsel Anda.

Saat menulis artikel ini, ada baiknya jika Anda memberikan beberapa petunjuk tentang cara menjalankan proses pengembangan seluler dengan lancar. Ini dia! Lanjutkan dan lihat apa yang dapat Anda pelajari darinya.

Tips dan trik seluler

Pra-pemuat adalah sesuatu yang diperlukan, bukan sesuatu yang harus dihindari. Kami tahu, bahwa terkadang hal yang kedua juga terjadi. Hal ini terutama karena Anda perlu terus mempertahankan daftar hal-hal yang Anda {i>pramuat<i} seiring dengan pertumbuhan proyek Anda. Lebih buruk lagi, Anda tidak dapat menghitung progres pemuatan jika menarik resource yang berbeda, dan banyak resource sekaligus. Di sinilah class abstrak khusus dan sangat generik 'Tugas' akan berguna. Ide utamanya adalah memungkinkan struktur bertingkat tanpa henti di mana Task dapat memiliki sub-Tugasnya sendiri, yang dapat memiliki sub-Tugas, dan sebagainya... Selain itu, setiap tugas menghitung progresnya sehubungan dengan progres sub-tugasnya (tetapi tidak untuk progres induknya). Dengan membuat semua MainPreloadTask, AssetPreloadTask, dan TemplatePreFetchTask berasal dari Task, kami membuat struktur yang terlihat seperti ini:

Prapemuat

Berkat pendekatan dan class Task tersebut, progres global (MainPreloadTask) dapat dengan mudah diketahui, atau hanya progres aset (AssetPreloadTask), atau progres pemuatan template (TemplatePreFetchTask). Bahkan progres file tertentu. Untuk melihat cara melakukannya, lihat class Task di /m/javascripts/raw/util/Task.js dan implementasi tugas yang sebenarnya di /m/javascripts/preloading/task. Sebagai contoh, berikut adalah ekstrak dari cara kita menyiapkan class /m/javascripts/preloading/task/MainPreloadTask.js yang merupakan wrapper pramuat utama kita:

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

      }
    }
  })
]);

Di class /m/javascripts/Preloading/task/subtask/AssetPreloadTask.js, selain memperhatikan cara komunikasinya dengan MainPreloadTask (melalui implementasi Task bersama), perhatikan juga cara kita memuat aset yang bergantung pada platform. Pada dasarnya, kita memiliki empat jenis gambar. Standar seluler (.ext, di mana ext adalah ekstensi file, biasanya .png atau .jpg), retina seluler (-2x.ext), standar tablet (-tab.ext) dan retina tablet (-tab-2x.ext). Alih-alih melakukan deteksi di MainPreloadTask dan melakukan hardcode empat array aset, kami hanya perlu mengatakan apa nama dan ekstensi aset yang akan dimuat sebelumnya, serta apakah aset tersebut bergantung pada platform (responsive = true / false). Kemudian, AssetPreloadTask akan menghasilkan nama file untuk kita:

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

Di bagian bawah rantai class, kode sebenarnya yang melakukan pramuat aset akan terlihat seperti berikut (/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);
}

Tutorial: HTML5 Photo Booth (iOS6/Android)

Saat mengembangkan OZ mobile, kami mendapati bahwa kami menghabiskan banyak waktu untuk bermain dengan studio foto daripada bekerja :D Itu hanya karena itu menyenangkan. Jadi, kami membuat sebuah demo untuk Anda coba.

Bilik foto seluler
Boot foto seluler

Anda dapat melihat demo langsung di sini (jalankan di iPhone atau ponsel Android):

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

Untuk menyiapkannya, Anda memerlukan instance aplikasi Google App Engine gratis tempat Anda dapat menjalankan backend. Kode {i>front end<i} tidak rumit, tetapi ada beberapa kemungkinan gocha. Mari kita bahas sekarang:

  1. Jenis file Gambar yang Diizinkan Kami ingin orang-orang hanya dapat mengupload gambar (karena ini adalah bilik foto, bukan bilik video). Secara teori, Anda cukup menentukan filter dalam HTML, sebagai berikut: input id="fileInput" class="fileInput" type="file" name="file" accept="image/*" Namun, tampaknya ini hanya berfungsi di iOS, jadi kita perlu menambahkan pemeriksaan tambahan terhadap RegExp setelah file dipilih:
   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. Membatalkan upload atau pemilihan file Inkonsistensi lain yang kami lihat selama proses pengembangan adalah cara perangkat yang berbeda memberi tahu pemilihan file yang dibatalkan. Ponsel dan tablet iOS tidak melakukan apa pun, perangkat tidak memberi tahu sama sekali. Jadi, kita tidak memerlukan tindakan khusus untuk kasus ini, tetapi ponsel Android akan memicu fungsi add(), meskipun tidak ada file yang dipilih. Berikut cara mengakomodasi hal ini:
    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();            
    }
    }

Sisanya berfungsi agak lancar di seluruh platform. Bersenang-senanglah

Kesimpulan

Mengingat besarnya ukuran aplikasi Find Your Way To Oz, dan beragamnya teknologi yang terlibat, dalam artikel ini kami hanya dapat membahas beberapa pendekatan yang kami gunakan.

Jika ingin mempelajari seluruh enchilada, silakan lihat kode sumber lengkap Find Your Way To Oz di link ini.

Kredit

Klik di sini untuk melihat daftar kredit lengkap

Referensi