Giới thiệu
“Find Your Way to Oz” (Tìm đường đến xứ Oz) là một Thử nghiệm mới của Google Chrome do Disney mang đến web. Ứng dụng này cho phép bạn tham gia một hành trình tương tác qua một rạp xiếc ở Kansas, đưa bạn đến xứ sở Oz sau khi bạn bị cuốn vào một cơn bão lớn.
Mục tiêu của chúng tôi là kết hợp sự phong phú của rạp chiếu phim với các khả năng kỹ thuật của trình duyệt để tạo ra trải nghiệm thú vị, sống động mà người dùng có thể kết nối mạnh mẽ.
Công việc này hơi lớn nên không thể trình bày toàn bộ trong bài viết này. Vì vậy, chúng tôi đã tìm hiểu và chọn ra một số chương trong câu chuyện công nghệ mà chúng tôi cho là thú vị. Trong quá trình này, chúng tôi đã trích xuất một số hướng dẫn tập trung có độ khó tăng dần.
Có rất nhiều người đã nỗ lực để mang đến trải nghiệm này, nhiều đến mức không thể liệt kê hết tại đây. Vui lòng truy cập vào trang web để xem trang ghi công trong phần trình đơn để biết toàn bộ câu chuyện.
Cơ chế hoạt động
Find Your Way to Oz trên máy tính là một thế giới sống động và hấp dẫn. Chúng tôi sử dụng công nghệ 3D và một số lớp hiệu ứng lấy cảm hứng từ kỹ thuật làm phim truyền thống để kết hợp tạo ra một cảnh gần như thực tế. Các công nghệ nổi bật nhất là WebGL với Three.js, chương trình đổ bóng tuỳ chỉnh và các phần tử ảnh động DOM sử dụng các tính năng CSS3. Ngoài ra, getUserMedia API (WebRTC) cho trải nghiệm tương tác cho phép người dùng thêm hình ảnh trực tiếp từ Webcam và WebAudio để có âm thanh 3D.
Nhưng điều kỳ diệu của trải nghiệm công nghệ như thế này là cách kết hợp các yếu tố. Đây cũng là một trong những thách thức chính: làm cách nào để kết hợp hiệu ứng hình ảnh và các thành phần tương tác với nhau trong một cảnh để tạo ra một tổng thể nhất quán? Sự phức tạp về hình ảnh này rất khó quản lý: khiến chúng tôi khó xác định được giai đoạn phát triển tại bất kỳ thời điểm nào.
Để giải quyết vấn đề về hiệu ứng hình ảnh và tối ưu hoá liên kết, chúng tôi đã sử dụng nhiều bảng điều khiển để ghi lại tất cả các chế độ cài đặt liên quan mà chúng tôi đang xem xét tại thời điểm đó. Bạn có thể điều chỉnh trực tiếp cảnh trong trình duyệt cho mọi thứ, từ độ sáng đến độ sâu trường, gamma, v.v. Bất kỳ ai cũng có thể thử điều chỉnh các giá trị của các thông số quan trọng trong trải nghiệm này và tham gia khám phá những thông số nào hoạt động hiệu quả nhất.
Trước khi chia sẻ bí mật, chúng tôi muốn cảnh báo rằng ứng dụng có thể gặp sự cố, giống như khi bạn thọc vào bên trong động cơ ô tô. Đảm bảo bạn không có gì quan trọng rồi truy cập vào URL chính của trang web và thêm ?debug=on vào địa chỉ. Chờ trang web tải và khi bạn đã vào bên trong (nhấn phím?) Ctrl-I
, bạn sẽ thấy một trình đơn thả xuống xuất hiện ở bên phải. Nếu bỏ đánh dấu tuỳ chọn "Exit camera path" (Thoát khỏi đường dẫn máy ảnh), bạn có thể sử dụng các phím A, W, S, D và chuột để tự do di chuyển trong không gian.

Chúng tôi sẽ không trình bày tất cả các chế độ cài đặt ở đây, nhưng bạn nên thử nghiệm: các phím sẽ hiển thị các chế độ cài đặt khác nhau trong các cảnh khác nhau. Trong trình tự bão cuối cùng, có một phím bổ sung: Ctrl-A
. Bạn có thể bật/tắt chế độ phát ảnh động và bay xung quanh bằng phím này. Trong cảnh này, nếu nhấn Esc
(để thoát khỏi chức năng khoá chuột) rồi nhấn lại Ctrl-I
, bạn có thể truy cập vào các chế độ cài đặt dành riêng cho cảnh bão. Hãy xem xung quanh và chụp một số khung hình đẹp như hình bên dưới.

Để thực hiện việc này và đảm bảo nó đủ linh hoạt cho nhu cầu của chúng ta, chúng tôi đã sử dụng một thư viện tuyệt vời có tên là dat.gui (xem tại đây để biết hướng dẫn trước đây về cách sử dụng thư viện này). Điều này cho phép chúng tôi nhanh chóng thay đổi chế độ cài đặt hiển thị cho khách truy cập trang web.
Có phần giống với tranh mờ
Trong nhiều phim hoạt hình và phim cổ điển của Disney, việc tạo cảnh có nghĩa là kết hợp nhiều lớp. Có các lớp hình ảnh người thật, ảnh động dạng tế bào, thậm chí là các bối cảnh thực tế và các lớp trên cùng được tạo bằng cách vẽ trên kính: một kỹ thuật gọi là vẽ mờ.
Về nhiều khía cạnh, cấu trúc của trải nghiệm mà chúng tôi tạo ra cũng tương tự như vậy; mặc dù một số "lớp" không chỉ là hình ảnh tĩnh. Trên thực tế, các giá trị này ảnh hưởng đến cách mọi thứ trông như thế nào theo các phép tính phức tạp hơn. Tuy nhiên, ít nhất ở cấp độ tổng thể, chúng ta đang xử lý các thành phần hiển thị, được kết hợp chồng lên nhau. Ở trên cùng, bạn sẽ thấy một lớp giao diện người dùng, với một cảnh 3D bên dưới: chính lớp này được tạo thành từ nhiều thành phần cảnh.
Lớp giao diện trên cùng được tạo bằng DOM và CSS 3, có nghĩa là bạn có thể chỉnh sửa các lượt tương tác theo nhiều cách độc lập với trải nghiệm 3D bằng cách giao tiếp giữa hai lớp này theo một danh sách sự kiện đã chọn. Thông tin liên lạc này sử dụng Backbone Router + sự kiện HTML5 onHashChange để kiểm soát khu vực sẽ tạo ảnh động vào/ra. (nguồn dự án: /develop/coffee/router/Router.coffee).
Hướng dẫn: Hỗ trợ trang Sprite và màn hình Retina
Một kỹ thuật tối ưu hoá thú vị mà chúng tôi dựa vào giao diện là kết hợp nhiều hình ảnh lớp phủ giao diện trong một tệp PNG duy nhất để giảm các yêu cầu máy chủ. Trong dự án này, giao diện được tạo thành từ hơn 70 hình ảnh (không tính hoạ tiết 3D) được tải trước để giảm độ trễ của trang web. Bạn có thể xem trang tính sprite trực tiếp tại đây:
Màn hình thông thường – http://findyourwaytooz.com/img/home/interface_1x.png Màn hình Retina – http://findyourwaytooz.com/img/home/interface_2x.png
Sau đây là một số mẹo về cách chúng tôi tận dụng việc sử dụng Trang tính Sprite và cách sử dụng các trang tính này cho các thiết bị màn hình Retina để có giao diện sắc nét và gọn gàng nhất có thể.
Tạo trangg sprite
Để tạo SpriteSheets, chúng ta đã sử dụng TexturePacker. Công cụ này sẽ xuất ra ở bất kỳ định dạng nào bạn cần. Trong trường hợp này, chúng ta đã xuất dưới dạng EaselJS. Đây là một tệp rất gọn gàng và cũng có thể dùng để tạo ảnh động sprite.
Sử dụng trang Sprite được tạo
Sau khi tạo Trang tính Sprite, bạn sẽ thấy một tệp JSON như sau:
{
"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]
},
}
Trong trường hợp:
- image đề cập đến URL của trang tính sprite
- khung là toạ độ của từng phần tử trên giao diện người dùng [x, y, width, height]
- ảnh động là tên của từng thành phần
Xin lưu ý rằng chúng ta đã sử dụng hình ảnh có mật độ cao để tạo trang Sprite, sau đó tạo phiên bản thông thường bằng cách đổi kích thước trang Sprite đó thành một nửa kích thước.
Mối tương quan giữa các yếu tố
Bây giờ, chúng ta đã sẵn sàng, chỉ cần một đoạn mã JavaScript để sử dụng.
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);
};
Sau đây là cách sử dụng:
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'));
Để hiểu thêm một chút về Mật độ pixel biến đổi, bạn có thể đọc bài viết này của Boris Smus.
Quy trình nội dung 3D
Trải nghiệm môi trường được thiết lập trên một lớp WebGL. Khi nghĩ đến một cảnh 3D, một trong những câu hỏi khó khăn nhất là làm cách nào để đảm bảo bạn có thể tạo nội dung khai thác tối đa tiềm năng biểu đạt từ các khía cạnh mô hình hoá, ảnh động và hiệu ứng. Về nhiều khía cạnh, cốt lõi của vấn đề này là quy trình nội dung: một quy trình đã thoả thuận để tạo nội dung cho cảnh 3D.
Chúng tôi muốn tạo ra một thế giới đầy cảm hứng; vì vậy, chúng tôi cần một quy trình vững chắc để các nghệ sĩ 3D có thể tạo ra thế giới đó. Họ cần được cung cấp nhiều quyền tự do thể hiện nhất có thể trong phần mềm tạo mô hình và ảnh động 3D; đồng thời, chúng ta cần kết xuất hình ảnh đó trên màn hình thông qua mã.
Chúng tôi đã giải quyết loại vấn đề này trong một thời gian vì mỗi khi tạo trang web 3D, chúng tôi đều nhận thấy các giới hạn trong công cụ có thể sử dụng. Vì vậy, chúng tôi đã tạo ra công cụ này, có tên là 3D Librarian: một nghiên cứu nội bộ. Và nó đã sẵn sàng để áp dụng cho một công việc thực tế.
Công cụ này đã có một số bản dựng trước đây: ban đầu, công cụ này dành cho Flash và cho phép bạn đưa một cảnh Maya lớn vào dưới dạng một tệp nén duy nhất được tối ưu hoá để giải nén thời gian chạy. Lý do là vì nó đóng gói hiệu quả cảnh trong cơ bản cùng một cấu trúc dữ liệu được thao tác trong quá trình kết xuất và tạo ảnh động. Bạn chỉ cần phân tích cú pháp rất ít cho tệp khi tải. Quá trình giải nén trong Flash diễn ra khá nhanh vì tệp ở định dạng AMF, Flash có thể giải nén tệp đó theo cách gốc. Việc sử dụng cùng một định dạng trong WebGL đòi hỏi CPU phải làm việc nhiều hơn một chút. Trên thực tế, chúng ta phải tạo lại một lớp mã Javascript giải nén dữ liệu, về cơ bản lớp này sẽ giải nén các tệp đó và tạo lại các cấu trúc dữ liệu cần thiết để WebGL hoạt động. Việc giải nén toàn bộ cảnh 3D là một thao tác nặng CPU ở mức độ vừa phải: giải nén cảnh 1 trong Find Your Way To Oz (Tìm đường đến xứ Oz) mất khoảng 2 giây trên máy tầm trung đến cao cấp. Do đó, việc này được thực hiện bằng công nghệ Web Worker, tại thời điểm "thiết lập cảnh" (trước khi cảnh thực sự được khởi chạy), để không làm người dùng bị treo.
Công cụ tiện dụng này có thể nhập hầu hết cảnh 3D: mô hình, hoạ tiết, ảnh động xương. Bạn tạo một tệp thư viện duy nhất, sau đó công cụ 3D có thể tải tệp đó. Bạn sẽ đưa tất cả các mô hình cần thiết vào cảnh trong thư viện này và thế là xong, bạn đã tạo các mô hình đó trong cảnh.
Tuy nhiên, chúng tôi gặp phải một vấn đề là hiện đang phải xử lý WebGL: một công nghệ mới. Đây là một thử thách khá khó khăn: thiết lập tiêu chuẩn cho trải nghiệm 3D dựa trên trình duyệt. Vì vậy, chúng tôi đã tạo một lớp JavaScript đặc biệt để lấy các tệp cảnh 3D được nén của Thư viện 3D và dịch các tệp đó đúng cách sang định dạng mà WebGL có thể hiểu.
Hướng dẫn: Let There Be Wind
Chủ đề lặp lại trong "Find Your Way To Oz" là gió. Một luồng của cốt truyện được cấu trúc để trở thành một cơn gió mạnh dần.
Cảnh đầu tiên của lễ hội tương đối yên bình. Khi xem qua các cảnh, người dùng sẽ cảm nhận được gió ngày càng mạnh, đỉnh điểm là cảnh cuối cùng – cơn bão.
Do đó, điều quan trọng là phải cung cấp hiệu ứng gió sống động.
Để tạo hiệu ứng này, chúng tôi đã điền 3 cảnh lễ hội bằng các đối tượng mềm và do đó sẽ chịu ảnh hưởng của gió, chẳng hạn như lều, cờ trên mặt quầy chụp ảnh và chính quả bóng bay.

Ngày nay, các trò chơi dành cho máy tính thường được xây dựng dựa trên một công cụ vật lý cốt lõi. Vì vậy, khi cần mô phỏng một đối tượng mềm trong thế giới 3D, hệ thống sẽ chạy một mô phỏng vật lý đầy đủ cho đối tượng đó, tạo ra hành vi mềm đáng tin cậy.
Trong WebGL / Javascript, chúng ta (chưa) có thể chạy một mô phỏng vật lý đầy đủ. Vì vậy, trong Oz, chúng tôi phải tìm cách tạo hiệu ứng gió mà không cần mô phỏng thực tế.
Chúng tôi đã nhúng thông tin về "độ nhạy cảm với gió" cho từng đối tượng trong chính mô hình 3D. Mỗi đỉnh của mô hình 3D có một "Thuộc tính gió" chỉ định mức độ ảnh hưởng của gió đến đỉnh đó. Do đó, độ nhạy cảm với gió của Đối tượng 3D được chỉ định này. Sau đó, chúng ta cần tạo chính gió.
Chúng tôi đã thực hiện việc này bằng cách tạo một hình ảnh chứa Perlin Noise. Hình ảnh này dùng để che phủ một "khu vực gió" nhất định. Vì vậy, một cách hay để suy nghĩ về hình ảnh này là tưởng tượng một bức ảnh đám mây như nhiễu được đặt trên một khu vực hình chữ nhật nhất định của cảnh 3D. Mỗi pixel, giá trị cấp độ màu xám, của hình ảnh này chỉ định mức độ mạnh của gió tại một thời điểm nhất định trong vùng 3D "bao quanh nó".
Để tạo hiệu ứng gió, hình ảnh được di chuyển theo thời gian, với tốc độ không đổi, theo một hướng cụ thể; hướng của gió. Và để đảm bảo "vùng có gió" không ảnh hưởng đến mọi thứ trong cảnh, chúng ta sẽ bao bọc hình ảnh gió xung quanh các cạnh, giới hạn trong vùng hiệu ứng.
Hướng dẫn đơn giản về gió 3D
Bây giờ, hãy tạo hiệu ứng gió trong một cảnh 3D đơn giản trong Three.js.
Chúng ta sẽ tạo gió trong một "cánh đồng cỏ quy trình" đơn giản.
Trước tiên, hãy tạo cảnh. Chúng ta sẽ có một địa hình phẳng, đơn giản và có kết cấu. Sau đó, mỗi cọng cỏ sẽ được biểu thị bằng một hình nón 3D lộn ngược.

Sau đây là cách tạo cảnh đơn giản này trong Three.js bằng CoffeeScript.
Trước tiên, chúng ta sẽ thiết lập Three.js và kết nối với Camera (Máy ảnh), Mouse controller (Bộ điều khiển chuột) và Some Light (Một số ánh sáng):
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()
Các lệnh gọi hàm initGrass và initTerrain lần lượt điền cỏ và địa hình vào cảnh:
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
Ở đây, chúng ta sẽ tạo một lưới cỏ 15x15 bit. Chúng ta thêm một chút ngẫu nhiên vào mỗi vị trí cỏ để chúng không xếp hàng như những người lính, trông sẽ rất kỳ quặc.
Địa hình này chỉ là một mặt phẳng nằm ngang, được đặt ở gốc của các cọng cỏ (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 )
Cho đến nay, chúng ta chỉ cần tạo một cảnh Three.js và thêm một vài cỏ, được tạo bằng các hình nón đảo ngược theo quy trình và một địa hình đơn giản.
Đến đây thì chưa có gì đặc biệt.
Bây giờ, đã đến lúc bắt đầu thêm gió. Trước tiên, chúng ta muốn nhúng thông tin về độ nhạy cảm với gió vào mô hình cỏ 3D.
Chúng ta sẽ nhúng thông tin này dưới dạng một thuộc tính tuỳ chỉnh cho mỗi đỉnh của mô hình cỏ 3D. Và chúng ta sẽ sử dụng quy tắc: đầu dưới cùng của mô hình cỏ (đầu nón) có độ nhạy bằng 0, vì nó được gắn với mặt đất. Phần trên cùng của mô hình cỏ (đáy hình nón) có độ nhạy gió tối đa, vì đó là phần cách mặt đất xa nhất.
Dưới đây là cách mã hoá lại hàm instanceGrass để thêm độ nhạy với gió làm thuộc tính tuỳ chỉnh cho mô hình cỏ 3D.
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
Bây giờ, chúng ta sử dụng một chất liệu tuỳ chỉnh, windMaterial, thay vì MeshPhongMaterial mà chúng ta đã sử dụng trước đó. WindMaterial gói WindMeshShader mà chúng ta sẽ thấy trong một phút nữa.
Vì vậy, mã trong instanceGrass lặp lại qua tất cả các đỉnh của mô hình cỏ và đối với mỗi đỉnh, mã này sẽ thêm một thuộc tính đỉnh tuỳ chỉnh, được gọi là windFactor. windFactor này được đặt thành 0 cho phần dưới cùng của mô hình cỏ (nơi cỏ chạm vào địa hình) và giá trị 1 cho phần trên cùng của mô hình cỏ.
Thành phần khác mà chúng ta cần là thêm gió thực tế vào cảnh. Như đã thảo luận, chúng ta sẽ sử dụng nhiễu Perlin cho việc này. Chúng ta sẽ tạo một hoạ tiết nhiễu Perlin theo quy trình.
Để rõ ràng hơn, chúng ta sẽ gán hoạ tiết này cho chính địa hình, thay vì hoạ tiết màu xanh lục trước đó. Điều này sẽ giúp bạn dễ dàng cảm nhận được những gì đang diễn ra với gió.
Do đó, hoạ tiết nhiễu Perlin này sẽ bao phủ không gian mở rộng của địa hình và mỗi pixel của hoạ tiết sẽ chỉ định cường độ gió của khu vực địa hình nơi pixel đó rơi xuống. Hình chữ nhật địa hình sẽ là "khu vực gió" của chúng ta.
Tiếng ồn Perlin được tạo theo quy trình thông qua một chương trình đổ bóng có tên là NoiseShader. Chương trình đổ bóng này sử dụng thuật toán nhiễu đơn giản 3D từ: https://github.com/ashima/webgl-noise . Phiên bản WebGL của mã này được lấy nguyên văn từ một trong các mẫu Three.js của MrDoob tại: http://mrdoob.github.com/three.js/examples/webgl_terrain_dynamic.html.
NoiseShader lấy thời gian, tỷ lệ và một tập hợp tham số chênh lệch dưới dạng đồng nhất và xuất ra một phân phối 2D đẹp mắt của nhiễu 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) }
...
Chúng ta sẽ sử dụng chương trình đổ bóng này để kết xuất Perlin Noise thành hoạ tiết. Bạn có thể thực hiện việc này trong hàm 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 )
Mã trên thực hiện việc thiết lập noiseMap làm mục tiêu kết xuất Three.js, trang bị cho nó NoiseShader, sau đó kết xuất bằng máy ảnh orthographic để tránh méo hình phối cảnh.
Như đã thảo luận, chúng ta sẽ sử dụng kết cấu này làm kết cấu kết xuất chính cho địa hình. Điều này không thực sự cần thiết để hiệu ứng gió hoạt động. Nhưng bạn nên có để có thể hiểu rõ hơn về những gì đang diễn ra trong quá trình tạo điện gió.
Dưới đây là hàm initTerrain được làm lại, sử dụng noiseMap làm hoạ tiết:
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 )
Giờ đây, khi đã có hoạ tiết gió, hãy xem WindMeshShader, lớp này chịu trách nhiệm biến dạng các mô hình cỏ theo gió.
Để tạo chương trình đổ bóng này, chúng ta bắt đầu từ chương trình đổ bóng MeshPhongMaterial Three.js chuẩn và sửa đổi chương trình đó. Đây là một cách nhanh chóng và hiệu quả để bắt đầu với một chương trình đổ bóng hoạt động mà không cần phải bắt đầu lại từ đầu.
Chúng ta sẽ không sao chép toàn bộ mã chương trình đổ bóng tại đây (bạn có thể xem mã này trong tệp mã nguồn), vì hầu hết mã này sẽ là bản sao của chương trình đổ bóng MeshPhongMaterial. Tuy nhiên, hãy xem xét các phần đã sửa đổi liên quan đến gió trong chương trình đổ bóng đỉnh.
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;
Vì vậy, việc đầu tiên mà chương trình đổ bóng này thực hiện là tính toán toạ độ tra cứu hoạ tiết windUV, dựa trên vị trí 2D, xz (ngang) của đỉnh. Toạ độ UV này được dùng để tra cứu lực gió, vWindForce, từ hoạ tiết gió nhiễu Perlin.
Giá trị vWindForce này được kết hợp với windFactor cụ thể của đỉnh, thuộc tính tuỳ chỉnh đã thảo luận ở trên, để tính toán mức độ biến dạng mà đỉnh cần. Chúng ta cũng có tham số windScale chung để kiểm soát cường độ tổng thể của gió và vectơ windDirection chỉ định hướng cần xảy ra biến dạng do gió.
Vì vậy, điều này tạo ra sự biến dạng dựa trên gió của các cọng cỏ. Tuy nhiên, chúng ta vẫn chưa hoàn tất. Hiện tại, biến dạng này là tĩnh và sẽ không truyền tải hiệu ứng của một khu vực có gió.
Như đã đề cập, chúng ta sẽ cần trượt hoạ tiết nhiễu theo thời gian, trên khu vực gió để kính có thể lắc.
Việc này được thực hiện bằng cách dịch chuyển theo thời gian, vOffset đồng nhất được truyền đến NoiseShader. Đây là tham số vec2, cho phép chúng ta chỉ định độ lệch nhiễu dọc theo một hướng nhất định (hướng gió của chúng ta).
Chúng ta thực hiện việc này trong hàm render (hiển thị) được gọi ở mọi khung hình:
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
...
Vậy là xong! Chúng ta vừa tạo một cảnh có "cỏ quy trình" chịu ảnh hưởng của gió.
Thêm bụi vào hỗn hợp
Bây giờ, hãy thêm chút gia vị cho cảnh của chúng ta. Hãy thêm một chút bụi bay để làm cho cảnh này trở nên thú vị hơn.

Xét cho cùng, bụi phải chịu ảnh hưởng của gió, vì vậy, việc bụi bay xung quanh trong cảnh gió là điều hoàn toàn hợp lý.
Bụi được thiết lập trong hàm initDust dưới dạng hệ thống hạt.
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
Tại đây, 130 hạt bụi được tạo ra. Và lưu ý rằng mỗi hạt đều được trang bị một WindParticleShader đặc biệt.
Bây giờ, ở mỗi khung hình, chúng ta sẽ di chuyển các hạt một chút bằng CoffeeScript, độc lập với gió. Sau đây là mã.
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 )
Ngoài ra, chúng ta sẽ bù trừ từng vị trí hạt theo gió. Việc này được thực hiện trong WindParticleShader. Cụ thể là trong chương trình đổ bóng đỉnh chóp.
Mã cho chương trình đổ bóng này là một phiên bản sửa đổi của ParticleMaterial Three.js và đây là nội dung cốt lõi của chương trình:
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;
Chương trình đổ bóng đỉnh này không khác nhiều so với chương trình đổ bóng đỉnh mà chúng ta đã có cho việc biến dạng cỏ dựa trên gió. Hàm này lấy hoạ tiết nhiễu Perlin làm dữ liệu đầu vào và tuỳ thuộc vào vị trí bụi trong thế giới, hàm này sẽ tra cứu giá trị vWindForce trong hoạ tiết nhiễu. Sau đó, ứng dụng sẽ sử dụng giá trị này để sửa đổi vị trí của hạt bụi.
Riders On The Storm
Cảnh cuối cùng có lẽ là cảnh phiêu lưu nhất trong các cảnh WebGL của chúng tôi. Bạn có thể xem cảnh này nếu nhấp vào quả bóng bay vào tâm lốc xoáy để kết thúc hành trình của mình trong trang web và xem video độc quyền về bản phát hành sắp tới.

Khi tạo cảnh này, chúng tôi biết rằng mình cần có một tính năng trung tâm để mang lại trải nghiệm ấn tượng. Lốc xoáy đang quay sẽ đóng vai trò là tâm điểm và các lớp nội dung khác sẽ định hình tính năng này để tạo hiệu ứng ấn tượng. Để đạt được điều này, chúng tôi đã tạo một bối cảnh tương đương với một xưởng phim xung quanh chương trình đổ bóng kỳ lạ này.
Chúng tôi đã sử dụng phương pháp kết hợp để tạo thành phần kết hợp chân thực. Một số là thủ thuật hình ảnh như hình dạng ánh sáng để tạo hiệu ứng flare (bùm sáng) hoặc các hạt mưa tạo hiệu ứng ảnh động dưới dạng lớp phủ trên cảnh bạn đang xem. Trong các trường hợp khác, chúng ta đã vẽ các bề mặt phẳng để có vẻ như di chuyển xung quanh, chẳng hạn như các lớp mây bay thấp di chuyển theo mã hệ thống hạt. Trong khi các mảnh vụn quay quanh lốc xoáy là các lớp trong cảnh 3D được sắp xếp để di chuyển ở phía trước và phía sau lốc xoáy.
Lý do chính khiến chúng ta phải xây dựng cảnh theo cách này là để đảm bảo có đủ GPU xử lý chương trình đổ bóng lốc xoáy một cách cân bằng với các hiệu ứng khác mà chúng ta đang áp dụng. Ban đầu, chúng tôi gặp phải vấn đề lớn về việc cân bằng GPU, nhưng sau đó cảnh này đã được tối ưu hoá và trở nên nhẹ hơn so với các cảnh chính.
Hướng dẫn: Chương trình đổ bóng Storm
Để tạo cảnh bão cuối cùng, nhiều kỹ thuật khác nhau đã được kết hợp, nhưng điểm trung tâm của tác phẩm này là một chương trình đổ bóng GLSL tuỳ chỉnh trông giống như một cơn lốc xoáy. Chúng tôi đã thử nhiều kỹ thuật khác nhau, từ chương trình đổ bóng đỉnh để tạo các xoáy hình học thú vị cho đến ảnh động dựa trên hạt và thậm chí là ảnh động 3D của các hình dạng hình học xoắn. Không có hiệu ứng nào có vẻ như tái tạo được cảm giác về một cơn lốc xoáy hoặc yêu cầu quá nhiều về việc xử lý.
Cuối cùng, một dự án hoàn toàn khác đã cung cấp cho chúng tôi câu trả lời. Một dự án song song liên quan đến các trò chơi khoa học để lập bản đồ não chuột của Max Planck Institute (brainflight.org) đã tạo ra các hiệu ứng hình ảnh thú vị. Chúng tôi đã tạo được các đoạn phim về bên trong tế bào thần kinh của chuột bằng cách sử dụng chương trình đổ bóng thể tích tuỳ chỉnh.

Chúng tôi nhận thấy bên trong tế bào não có hình dạng hơi giống phễu lốc xoáy. Và vì chúng ta đang sử dụng một kỹ thuật thể tích, nên chúng ta biết rằng có thể xem chương trình đổ bóng này từ mọi hướng trong không gian. Chúng ta có thể thiết lập kết xuất của chương trình đổ bóng để kết hợp với cảnh bão, đặc biệt là nếu được đặt giữa các lớp mây và phía trên một phông nền ấn tượng.
Kỹ thuật chương trình đổ bóng sử dụng một thủ thuật cơ bản là sử dụng một chương trình đổ bóng GLSL để kết xuất toàn bộ đối tượng bằng thuật toán kết xuất đơn giản được gọi là kết xuất quét tia bằng trường khoảng cách. Trong kỹ thuật này, một chương trình đổ bóng pixel được tạo để ước tính khoảng cách gần nhất đến một bề mặt cho mỗi điểm trên màn hình.
Bạn có thể tham khảo tài liệu về thuật toán này trong phần tổng quan của iq: Rendering Worlds With Two Triangles – Iñigo Quilez (Hiển thị thế giới bằng hai tam giác – Iñigo Quilez). Ngoài ra, bạn có thể khám phá thư viện chương trình đổ bóng trên glsl.heroku.com. Tại đây, bạn có thể tìm thấy nhiều ví dụ về kỹ thuật này để thử nghiệm.
Phần cốt lõi của chương trình đổ bóng bắt đầu bằng hàm chính, hàm này thiết lập các phép biến đổi máy ảnh và chuyển sang một vòng lặp đánh giá lặp đi lặp lại khoảng cách đến một bề mặt. Lệnh gọi RaytraceFoggy( direction_vector, max_iterations, color, color_multiplier ) là nơi diễn ra phép tính chính của phương pháp quét tia.
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
}
Ý tưởng là khi tiến vào hình dạng của lốc xoáy, chúng ta thường xuyên thêm các giá trị đóng góp về màu sắc vào giá trị màu cuối cùng của pixel, cũng như các giá trị đóng góp vào độ mờ dọc theo tia. Điều này tạo ra một chất lượng mềm theo lớp cho hoạ tiết của lốc xoáy.
Cốt lõi tiếp theo của lốc xoáy là hình dạng thực tế được tạo bằng cách kết hợp một số hàm. Ban đầu, hình ảnh này là một hình nón, được tạo thành từ nhiễu để tạo ra một cạnh thô hữu cơ, sau đó được xoắn dọc theo trục chính và xoay theo thời gian.
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;
}
Việc tạo loại chương trình đổ bóng này khá phức tạp. Ngoài các vấn đề liên quan đến việc trừu tượng hoá các thao tác mà bạn đang tạo, bạn cần theo dõi và giải quyết các vấn đề nghiêm trọng về tối ưu hoá và khả năng tương thích trên nhiều nền tảng trước khi có thể sử dụng công việc trong quá trình sản xuất.
Phần đầu tiên của vấn đề: tối ưu hoá chương trình đổ bóng này cho cảnh của chúng ta. Để giải quyết vấn đề này, chúng ta cần có một phương pháp "an toàn" trong trường hợp chương trình đổ bóng quá nặng. Để làm việc này, chúng tôi đã kết hợp chương trình đổ bóng lốc xoáy ở độ phân giải lấy mẫu khác với phần còn lại của cảnh. Đây là mã từ tệp stormTest.coffee (đúng vậy, đây là một mã kiểm thử!).
Chúng ta bắt đầu với một renderTarget khớp với chiều rộng và chiều cao của cảnh để có thể độc lập với độ phân giải của chương trình đổ bóng lốc xoáy cho cảnh. Sau đó, chúng ta quyết định giảm độ phân giải của chương trình đổ bóng bão một cách linh động tuỳ thuộc vào tốc độ khung hình mà chúng ta nhận được.
...
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
Cuối cùng, chúng ta kết xuất lốc xoáy lên màn hình bằng cách sử dụng thuật toán sal2x được đơn giản hoá (để tránh giao diện bị khối) @dòng 1107 trong stormTest.coffee. Điều này có nghĩa là trong trường hợp xấu nhất, chúng ta sẽ có một cơn lốc xoáy mờ hơn nhưng ít nhất nó vẫn hoạt động mà không lấy đi quyền kiểm soát của người dùng.
Bước tối ưu hoá tiếp theo yêu cầu bạn phải tìm hiểu thuật toán. Hệ số tính toán chính trong chương trình đổ bóng là phép lặp được thực hiện trên mỗi pixel để cố gắng ước tính khoảng cách của hàm bề mặt: số lần lặp của vòng lặp raymarching. Khi sử dụng bước lớn hơn, chúng ta có thể ước tính bề mặt lốc xoáy với ít vòng lặp hơn khi ở bên ngoài bề mặt có mây của lốc xoáy. Khi ở bên trong, chúng ta sẽ giảm kích thước bước để tăng độ chính xác và có thể kết hợp các giá trị để tạo hiệu ứng sương mù. Ngoài ra, việc tạo một hình trụ giới hạn để ước tính độ sâu cho tia được chiếu cũng giúp tăng tốc đáng kể.
Phần tiếp theo của vấn đề là đảm bảo chương trình đổ bóng này sẽ chạy trên nhiều loại thẻ video. Mỗi lần, chúng tôi đã kiểm thử một số vấn đề và bắt đầu xây dựng trực giác về loại vấn đề về khả năng tương thích mà chúng tôi có thể gặp phải. Lý do chúng ta không thể làm tốt hơn là vì không phải lúc nào chúng ta cũng có thể nhận được thông tin gỡ lỗi tốt về các lỗi. Một trường hợp điển hình chỉ là lỗi GPU với một số vấn đề khác hoặc thậm chí là sự cố hệ thống!
Các vấn đề về khả năng tương thích của bo mạch video chéo có các giải pháp tương tự: đảm bảo rằng các hằng số tĩnh được nhập thuộc kiểu dữ liệu chính xác như đã xác định, tức là 0,0 cho float và 0 cho int. Hãy cẩn thận khi viết các hàm dài hơn; tốt hơn là bạn nên chia nhỏ các hàm và biến tạm thời thành nhiều hàm đơn giản hơn vì trình biên dịch có vẻ như không xử lý chính xác một số trường hợp. Đảm bảo tất cả hoạ tiết đều là luỹ thừa của 2, không quá lớn và trong mọi trường hợp, hãy thực hiện "thận trọng" khi tra cứu dữ liệu hoạ tiết trong một vòng lặp.
Vấn đề lớn nhất mà chúng tôi gặp phải về khả năng tương thích là do hiệu ứng ánh sáng của cơn bão. Chúng tôi đã sử dụng một hoạ tiết tạo sẵn bao quanh lốc xoáy để có thể tô màu cho các luồng khí xoáy. Đây là một hiệu ứng tuyệt đẹp và giúp dễ dàng kết hợp lốc xoáy vào màu sắc của cảnh, nhưng mất nhiều thời gian để cố gắng chạy trên các nền tảng khác.

Trang web dành cho thiết bị di động
Trải nghiệm trên thiết bị di động không thể là bản dịch trực tiếp của phiên bản dành cho máy tính vì các yêu cầu về công nghệ và xử lý quá nặng. Chúng tôi phải xây dựng một sản phẩm mới, nhắm đến người dùng thiết bị di động.
Chúng tôi nghĩ rằng sẽ rất tuyệt nếu có một ứng dụng web dành cho thiết bị di động sử dụng máy ảnh của người dùng để tạo ra một quầy chụp ảnh Carnival trên máy tính. Một điều mà chúng tôi chưa thấy được thực hiện cho đến nay.
Để thêm hương vị, chúng tôi đã lập trình các phép biến đổi 3D trong CSS3. Sau khi liên kết với con quay hồi chuyển và gia tốc kế, chúng tôi có thể tăng độ sâu cho trải nghiệm. Trang web phản hồi cách bạn cầm, di chuyển và nhìn vào điện thoại.
Khi viết bài viết này, chúng tôi nghĩ rằng bạn nên biết một số gợi ý về cách chạy suôn sẻ quy trình phát triển ứng dụng dành cho thiết bị di động. Đây là những thông tin đó! Hãy xem bạn có thể học được gì từ đó!
Mẹo và thủ thuật dành cho thiết bị di động
Trình tải trước là một phần cần thiết, chứ không phải là một phần nên tránh. Chúng tôi biết rằng đôi khi điều này xảy ra. Điều này chủ yếu là do bạn cần duy trì danh sách những nội dung tải trước khi dự án phát triển. Tệ hơn nữa, bạn không rõ cách tính tiến trình tải nếu đang kéo nhiều tài nguyên cùng một lúc. Đây là lúc lớp trừu tượng tuỳ chỉnh và rất chung của chúng ta "Task" (Nhiệm vụ) hữu ích. Ý tưởng chính của nó là cho phép cấu trúc lồng nhau vô hạn, trong đó một Tác vụ có thể có các Tác vụ con riêng, có thể có các Tác vụ con riêng, v.v. Hơn nữa, mỗi tác vụ tính toán tiến trình của tác vụ đó liên quan đến tiến trình của các tác vụ con (nhưng không phải tiến trình của tác vụ mẹ). Để tất cả MainPreloadTask, AssetPreloadTask và TemplatePreFetchTask đều bắt nguồn từ Task, chúng tôi đã tạo một cấu trúc như sau:

Nhờ phương pháp này và lớp Task, chúng ta có thể dễ dàng biết tiến trình tổng thể (MainPreloadTask), hoặc chỉ tiến trình của các thành phần (AssetPreloadTask) hoặc tiến trình tải mẫu (TemplatePreFetchTask). Tiến trình đồng đều của một tệp cụ thể. Để xem cách thực hiện, hãy xem lớp Task tại /m/javascripts/raw/util/Task.js và cách triển khai tác vụ thực tế tại /m/javascripts/preloading/task. Ví dụ: đây là phần trích dẫn về cách chúng ta thiết lập lớp /m/javascripts/preloading/task/MainPreloadTask.js, lớp này là trình bao bọc tải trước cuối cùng của chúng ta:
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);
}
}
})
]);
Trong lớp /m/javascripts/preloading/task/subtask/AssetPreloadTask.js, ngoài việc lưu ý cách lớp này giao tiếp với MainPreloadTask (thông qua việc triển khai Task dùng chung), bạn cũng nên lưu ý cách chúng ta tải các thành phần phụ thuộc vào nền tảng. Về cơ bản, chúng ta có 4 loại hình ảnh. Tiêu chuẩn dành cho thiết bị di động (.ext, trong đó ext là đuôi tệp, thường là .png hoặc .jpg), tiêu chuẩn dành cho thiết bị di động có màn hình độ phân giải cao (-2x.ext), tiêu chuẩn dành cho máy tính bảng (-tab.ext) và tiêu chuẩn dành cho máy tính bảng có màn hình độ phân giải cao (-tab-2x.ext). Thay vì phát hiện trong MainPreloadTask và mã hoá cứng 4 mảng tài sản, chúng ta chỉ cần nói tên và đuôi của tài sản cần tải trước và liệu tài sản có phụ thuộc vào nền tảng hay không (responsive = true / false). Sau đó, AssetPreloadTask sẽ tạo tên tệp cho chúng ta:
resolveAssetUrl : function(assetName, extension, responsive) {
return AssetPreloadTask.ASSETS_ROOT + assetName + (responsive === true ? ((Detection.getInstance().tablet ? '-tab' : '') + (Detection.getInstance().retina ? '-2x' : '')) : '') + '.' + extension;
}
Tiếp theo trong chuỗi lớp, mã thực tế thực hiện việc tải trước tài sản sẽ có dạng như sau (/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);
}
Hướng dẫn: Máy ảnh chụp nhanh HTML5 (iOS6/Android)
Khi phát triển OZ dành cho thiết bị di động, chúng tôi nhận thấy mình dành nhiều thời gian để chơi với máy ảnh thay vì làm việc :D Đó chỉ đơn giản là vì nó rất thú vị. Vì vậy, chúng tôi đã tạo một bản minh hoạ để bạn có thể chơi thử.

Bạn có thể xem bản minh hoạ trực tiếp tại đây (chạy trên iPhone hoặc điện thoại Android):
http://u9html5rocks.appspot.com/demos/mobile_photo_booth
Để thiết lập, bạn cần có một thực thể ứng dụng Google App Engine miễn phí để có thể chạy phần phụ trợ. Mã xử lý phía trước không phức tạp nhưng có một vài điểm cần lưu ý. Hãy cùng xem xét các bước này:
- Loại tệp hình ảnh được phép
Chúng tôi muốn người dùng chỉ có thể tải hình ảnh lên (vì đây là một máy ảnh, chứ không phải máy quay video). Về lý thuyết, bạn chỉ cần chỉ định bộ lọc trong HTML như sau:
input id="fileInput" class="fileInput" type="file" name="file" accept="image/*"
Tuy nhiên, cách này có vẻ chỉ hoạt động trên iOS, vì vậy, chúng ta cần thêm một bước kiểm tra bổ sung đối với RegExp sau khi chọn một tệp:
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();
}
}
});
- Huỷ lựa chọn tệp hoặc tải lên Một điểm không nhất quán khác mà chúng tôi nhận thấy trong quá trình phát triển là cách các thiết bị khác nhau thông báo về việc huỷ lựa chọn tệp. Điện thoại và máy tính bảng iOS không làm gì cả, chúng hoàn toàn không thông báo. Vì vậy, chúng ta không cần làm gì đặc biệt cho trường hợp này. Tuy nhiên, điện thoại Android sẽ kích hoạt hàm add() ngay cả khi không có tệp nào được chọn. Dưới đây là cách giải quyết vấn đề này:
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();
}
}
Phần còn lại hoạt động khá trơn tru trên các nền tảng. Chúc bạn sáng tạo vui vẻ!
Kết luận
Do quy mô khổng lồ của Find Your Way To Oz và sự kết hợp đa dạng của nhiều công nghệ liên quan, trong bài viết này, chúng tôi chỉ có thể đề cập đến một số phương pháp mà chúng tôi đã sử dụng.
Nếu bạn muốn khám phá toàn bộ ứng dụng, hãy xem mã nguồn đầy đủ của Find Your Way To Oz tại đường liên kết này.
Ghi công
Nhấp vào đây để xem danh sách đầy đủ các nhà sáng tạo
Tài liệu tham khảo
- CoffeeScript – http://coffeescript.org/
- Backbone.js – http://backbonejs.org/
- Three.js – http://mrdoob.github.com/three.js/
- Viện Max Planck (brainflight.org) – http://brainflight.org/