Kiếm 100.000 sao

Michael Chang
Michael Chang

Xin chào! Tôi là Michael Chang, tôi làm việc với Nhóm nghệ thuật dữ liệu tại Google. Gần đây, chúng tôi đã hoàn thành 100.000 sao, một Thử nghiệm Chrome hiển thị hình ảnh về các ngôi sao ở gần. Dự án được xây dựng bằng THREE.js và CSS3D. Trong nghiên cứu điển hình này, tôi sẽ trình bày về quá trình khám phá, chia sẻ một số kỹ thuật lập trình và cuối cùng là một số ý tưởng để cải thiện trong tương lai.

Các chủ đề được thảo luận ở đây sẽ khá rộng và yêu cầu một số kiến thức về THREE.js, mặc dù tôi hy vọng rằng bạn vẫn có thể thưởng thức điều này dưới dạng phương pháp phân tích kỹ thuật sau khi thất bại. Vui lòng chuyển đến khu vực bạn quan tâm bằng cách sử dụng nút mục lục ở bên phải. Trước tiên, tôi sẽ trình bày phần kết xuất của dự án, tiếp theo là quản lý chương trình đổ bóng và cuối cùng là cách sử dụng nhãn văn bản CSS kết hợp với WebGL.

100.000 sao, một thử nghiệm Chrome của nhóm Data Arts
100.000 Stars sử dụng THREE.js để hình dung các sao lân cận trong Dải Ngân Hà

Khám phá không gian

Ngay sau khi hoàn thành Small Arms Globe, tôi đã thử nghiệm bản minh họa hạt ba.js với độ sâu trường. Tôi nhận thấy mình có thể thay đổi "tỷ lệ" được diễn giải của cảnh bằng cách điều chỉnh mức độ hiệu ứng được áp dụng. Khi hiệu ứng độ sâu trường ảnh thực sự quá lớn, các vật thể ở xa trở nên rất mờ, tương tự như cách chụp ảnh dịch chuyển nghiêng mang lại cho người dùng ảo giác đang nhìn vào một khung cảnh qua kính hiển vi. Ngược lại, việc giảm hiệu ứng làm cho hiệu ứng trông như thể bạn đang nhìn vào không gian sâu thẳm.

Tôi bắt đầu tìm kiếm dữ liệu mà tôi có thể sử dụng để chèn vị trí hạt, một đường dẫn đưa tôi đến cơ sở dữ liệu HYG của astronexus.com, một tập hợp gồm ba nguồn dữ liệu (Hipparcos, Yale Bright Star Catalog và Gliese/Jahreiss Catalog) kèm theo toạ độ xyz Descartes được tính toán trước. Hãy bắt đầu nào!

Lập biểu đồ dữ liệu sao.
Bước đầu tiên là vẽ đồ thị tất cả sao trong danh mục dưới dạng một hạt duy nhất.
Những ngôi sao được đặt tên.
Một số sao trong danh mục có tên riêng, được gắn nhãn tại đây.

Mất khoảng một giờ để ghép nối với nhau một cái gì đó đặt dữ liệu sao trong không gian 3D. Có chính xác 119.617 sao trong tập dữ liệu,vì vậy, việc biểu diễn mỗi sao bằng một hạt không phải là vấn đề đối với GPU hiện đại. Ngoài ra còn có 87 ngôi sao được xác định riêng lẻ, vì vậy, tôi đã tạo lớp phủ điểm đánh dấu CSS bằng chính kỹ thuật mà tôi mô tả trong Small Arms Globe.

Trong thời gian này, tôi vừa hoàn thành loạt video Mass Effect. Trong trò chơi, người chơi được mời khám phá thiên hà và quét qua nhiều hành tinh và đọc về lịch sử hoàn toàn hư cấu, đầy âm thanh của wikipedia: những loài nào đã phát triển mạnh trên hành tinh, lịch sử địa chất của hành tinh đó, v.v.

Khi biết được rất nhiều dữ liệu thực tế về sao, người ta có thể hình dung ra thông tin thực về thiên hà theo cách tương tự. Mục tiêu sau cùng của dự án này là biến dữ liệu này thành hiện thực, cho phép người xem khám phá thiên hà à Hiệu ứng khối lượng, tìm hiểu về các vì sao và sự phân bố của chúng, và hy vọng là khơi dậy cảm giác kinh ngạc và kỳ vọng về vũ trụ. Chà!

Có lẽ tôi nên mở đầu phần còn lại của nghiên cứu điển hình này bằng cách nói rằng tôi hoàn toàn không phải là nhà thiên văn học và đây là công trình nghiên cứu nghiệp dư có sự hỗ trợ của một số lời khuyên của các chuyên gia bên ngoài. Dự án này chắc chắn phải được hiểu là một cách thể hiện của nghệ sĩ về không gian.

Xây dựng Thiên hà

Kế hoạch của tôi là tạo ra một mô hình thiên hà theo quy trình để có thể đặt dữ liệu về sao vào bối cảnh -- và hy vọng có thể đưa ra khung cảnh tuyệt đẹp về vị trí của chúng ta trong Dải Ngân Hà.

Một nguyên mẫu ban đầu của thiên hà.
Nguyên mẫu ban đầu của hệ hạt trong Dải Ngân Hà.

Để tạo ra Dải Ngân Hà, tôi đã sinh ra 100.000 hạt và đặt chúng theo dạng xoắn ốc bằng cách mô phỏng cách các nhánh thiên hà được hình thành. Tôi không quá lo lắng về các chi tiết cụ thể về sự hình thành nhánh xoắn ốc vì đây sẽ là một mô hình biểu diễn thay vì một mô hình toán học. Tuy nhiên, tôi đã cố gắng làm cho số lượng nhánh xoắn ốc chính xác hơn hoặc ít hơn và quay theo "đúng hướng".

Trong các phiên bản sau của mô hình Dải Ngân Hà, tôi đã giảm mức độ nhấn mạnh vào việc sử dụng các hạt mà thay vào đó là hình ảnh phẳng của thiên hà đi cùng với các hạt, với hy vọng giúp nó trông giống như ảnh chụp. Hình ảnh thực tế là của thiên hà xoắn ốc NGC 1232 cách chúng ta khoảng 70 triệu năm ánh sáng, đã được chỉnh sửa để hình ảnh trông giống như Dải Ngân Hà.

Xác định quy mô của thiên hà.
Mỗi đơn vị GL là một năm ánh sáng. Trong trường hợp này, quả cầu có chiều rộng 110.000 năm ánh sáng bao gồm hệ hạt.

Tôi sớm quyết định đại diện cho một đơn vị GL, về cơ bản là một pixel trong 3D, như một năm ánh sáng - một quy ước thống nhất vị trí cho mọi thứ được trực quan hóa và thật không may sau này đã mang đến cho tôi những vấn đề nghiêm trọng về độ chính xác.

Một quy ước khác mà tôi quyết định là xoay toàn bộ cảnh thay vì di chuyển camera, điều mà tôi đã thực hiện trong một vài dự án khác. Một ưu điểm là mọi thứ được đặt trên "bàn xoay" để thao tác kéo chuột trái và phải xoay đối tượng được đề cập, nhưng phóng to chỉ là vấn đề thay đổi camera.position.z.

Trường nhìn (hay FOV) của máy ảnh cũng là trường động. Khi một người đưa ra ngoài, trường nhìn sẽ mở rộng để chụp vào càng nhiều thiên hà. Điều ngược lại là đúng khi di chuyển vào phía trong về phía một ngôi sao, trường nhìn thu hẹp lại. Điều này cho phép camera quan sát những vật thể vô hạn (so với thiên hà) bằng cách phóng to FOV xuống mức tương tự như kính lúp mà không phải xử lý các sự cố cắt cận cảnh.

Các cách kết xuất thiên hà khác nhau.
(bên trên) Thiên hà hạt sớm. (bên dưới) Các hạt đi kèm với mặt phẳng ảnh.

Từ đây tôi có thể "đặt" Mặt trời tại một số đơn vị cách lõi thiên hà. Tôi cũng có thể hình dung ra kích thước tương đối của hệ mặt trời bằng cách lập bản đồ bán kính của Vách đáKuiper (Cuối cùng, tôi đã chọn trực quan hoá Đám mây Oort). Trong hệ mặt trời mô hình này, tôi cũng có thể hình dung ra quỹ đạo được đơn giản hoá của Trái đất và bán kính thực tế của Mặt trời so với bán kính thực tế.

Hệ mặt trời.
Mặt trời quay quanh bởi các hành tinh và một hình cầu tượng trưng cho Vành đai Kuiper.

Mặt trời rất khó kết xuất hình ảnh. Tôi đã phải gian lận nhiều kỹ thuật đồ hoạ theo thời gian thực như tôi biết. Bề mặt của Mặt trời là một lớp bọt nóng của plasma và cần thiết để đập và thay đổi theo thời gian. Hình ảnh này được mô phỏng thông qua hoạ tiết bitmap của hình ảnh hồng ngoại của bề mặt mặt trời. Chương trình đổ bóng bề mặt thực hiện tra cứu màu dựa trên thang màu xám của hoạ tiết này và thực hiện tra cứu trong một đoạn dốc màu riêng biệt. Khi việc tra cứu này thay đổi theo thời gian, nó sẽ tạo ra sự biến dạng giống như dung nham này.

Một kỹ thuật tương tự đã được sử dụng cho vành thiên hà của Mặt trời, ngoại trừ việc nó là một thẻ sprite phẳng luôn hướng vào máy ảnh bằng https://github.com/mrdoob/three.js/blob/master/src/extras/core/Gyroscope.js.

Kết xuất Sol.
Phiên bản sớm của Mặt trời.

Các tia sáng mặt trời được tạo ra nhờ các chương trình đổ bóng mảnh và đỉnh được áp dụng cho một hình xoắn, xoay quanh cạnh của bề mặt mặt trời. Chương trình đổ bóng đỉnh (vertex) có chức năng nhiễu khiến chương trình này dệt thành một giọt nước.

Tại đây, tôi bắt đầu gặp phải một số vấn đề về z-Battle do độ chính xác của GL. Tất cả các biến cho độ chính xác đều được xác định trước trong ba.js, vì vậy thực tế là tôi không thể tăng độ chính xác nếu không phải làm rất nhiều. Khi gần nơi xuất phát, vấn đề về độ chính xác không quá tệ. Tuy nhiên, khi tôi bắt đầu lập mô hình các hệ thống sao khác, điều này trở thành một vấn đề.

Mô hình sao.
Mã để kết xuất Mặt trời sau đó được tổng quát hoá để kết xuất các sao khác.

Tôi đã dùng một số thủ thuật để giảm thiểu tình trạng Z-Battle. Material.polygonoffset của BA là một thuộc tính cho phép kết xuất đa giác ở một vị trí quan sát khác (theo tôi hiểu). Điều này được dùng để buộc mặt phẳng vành luôn kết xuất trên bề mặt của Mặt trời. Bên dưới hình ảnh này, một "vầng sáng" của Mặt trời được tạo ra để tạo ra những tia sáng sắc nét di chuyển ra khỏi hình cầu.

Một vấn đề khác liên quan đến độ chính xác là các mô hình sao sẽ bắt đầu dao động khi cảnh được phóng to. Để khắc phục sự cố này, tôi đã phải "loại bỏ" chế độ xoay cảnh và xoay riêng mô hình sao và bản đồ môi trường để ảo tưởng rằng bạn đang quay quanh ngôi sao.

Đang tạo Ống kính loá

Sức mạnh đi đôi với trách nhiệm.
Sức mạnh đi đôi với trách nhiệm.

Hình ảnh không gian là nơi khiến tôi cảm thấy mình có thể thoát ra ngoài nếu sử dụng quá nhiều ánh sáng loé sáng. THREE.LensFlare phục vụ cho mục đích này. Tôi chỉ cần ném một số hình lục giác biến hình và một nét gạch ngang của JIT Abrams. Đoạn mã dưới đây cho biết cách tạo các thành phần này trong cảnh của bạn.

// This function returns a lesnflare THREE object to be .add()ed to the scene graph
function addLensFlare(x,y,z, size, overrideImage){
var flareColor = new THREE.Color( 0xffffff );

lensFlare = new THREE.LensFlare( overrideImage, 700, 0.0, THREE.AdditiveBlending, flareColor );

// we're going to be using multiple sub-lens-flare artifacts, each with a different size
lensFlare.add( textureFlare1, 4096, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );

// and run each through a function below
lensFlare.customUpdateCallback = lensFlareUpdateCallback;

lensFlare.position = new THREE.Vector3(x,y,z);
lensFlare.size = size ? size : 16000 ;
return lensFlare;
}

// this function will operate over each lensflare artifact, moving them around the screen
function lensFlareUpdateCallback( object ) {
var f, fl = this.lensFlares.length;
var flare;
var vecX = -this.positionScreen.x _ 2;
var vecY = -this.positionScreen.y _ 2;
var size = object.size ? object.size : 16000;

var camDistance = camera.position.length();

for( f = 0; f < fl; f ++ ) {
flare = this.lensFlares[ f ];

flare.x = this.positionScreen.x + vecX * flare.distance;
flare.y = this.positionScreen.y + vecY * flare.distance;

flare.scale = size / camDistance;
flare.rotation = 0;

}
}

Cách dễ dàng để cuộn hoạ tiết

Lấy cảm hứng từ Homeworld.
Một mặt phẳng Descartes giúp định hướng không gian trong không gian.

Đối với "mặt phẳng định hướng không gian", một THREE.CylinderGeometry() khổng lồ đã được tạo ra và căn giữa Mặt trời. Để tạo ra "sóng ánh sáng" xòe ra ngoài, tôi đã sửa đổi độ lệch hoạ tiết của nó theo thời gian như sau:

mesh.material.map.needsUpdate = true;
mesh.material.map.onUpdate = function(){
this.offset.y -= 0.001;
this.needsUpdate = true;
}

map là hoạ tiết thuộc về vật liệu, nhận được hàm onUpdate mà bạn có thể ghi đè. Việc đặt độ lệch khiến hoạ tiết bị "cuộn" dọc theo trục đó và việc gửi spam cầnUpdate = true sẽ buộc hành vi này lặp lại.

Sử dụng đường màu

Mỗi sao có một màu khác nhau dựa trên "chỉ mục màu" mà các nhà thiên văn học đã gán cho chúng. Nói chung, các sao màu đỏ lạnh hơn còn các sao màu xanh/tím nóng hơn. Một dải màu trắng và cam trung gian tồn tại trong dải chuyển màu này.

Khi hiển thị các ngôi sao, tôi muốn gán cho mỗi hạt một màu riêng dựa trên dữ liệu này. Để làm việc này, hãy dùng "thuộc tính" cho chất liệu trong chương trình đổ bóng được áp dụng cho các phần tử.

var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: datastarUniforms,
attributes: datastarAttributes,
/_ ... etc _/
});
var datastarAttributes = {
size: { type: 'f', value: [] },
colorIndex: { type: 'f', value: [] },
};

Việc lấp đầy mảng colorIndex sẽ làm cho mỗi phần tử có màu duy nhất trong chương trình đổ bóng. Thông thường, một người sẽ chuyển vào một color vec3, nhưng trong trường hợp này, tôi sẽ truyền một số thực cho quá trình tra cứu màu sắc cuối cùng.

Lộ trình màu sắc.
Đường dốc màu dùng để tra cứu màu hiển thị từ chỉ mục màu của một ngôi sao.

Đường dẫn màu trông giống như thế này, tuy nhiên tôi cần truy cập vào dữ liệu màu bitmap của nó từ JavaScript. Cách tôi thực hiện là tải hình ảnh vào DOM, vẽ hình ảnh đó vào phần tử canvas, sau đó truy cập vào bitmap canvas.

// make a blank canvas, sized to the image, in this case gradientImage is a dom image element
gradientCanvas = document.createElement('canvas');
gradientCanvas.width = gradientImage.width;
gradientCanvas.height = gradientImage.height;

// draw the image
gradientCanvas.getContext('2d').drawImage( gradientImage, 0, 0, gradientImage.width, gradientImage.height );

// a function to grab the pixel color based on a normalized percentage value
gradientCanvas.getColor = function( percentage ){
return this.getContext('2d').getImageData(percentage \* gradientImage.width,0, 1, 1).data;
}

Sau đó, phương pháp tương tự này được sử dụng để tô màu các ngôi sao riêng lẻ trong chế độ xem mô hình sao.

Mắt của tôi!
Kỹ thuật này được dùng để tra cứu màu sắc phổ của một sao.

Xoay vòng trong chương trình đổ bóng

Trong suốt dự án, tôi nhận thấy mình cần phải viết nhiều chương trình đổ bóng hơn để đạt được tất cả các hiệu ứng hình ảnh. Tôi đã viết một trình tải chương trình đổ bóng tuỳ chỉnh cho mục đích này vì tôi đã chán việc phải đưa chương trình đổ bóng vào trong index.html.

// list of shaders we'll load
var shaderList = ['shaders/starsurface', 'shaders/starhalo', 'shaders/starflare', 'shaders/galacticstars', /*...etc...*/];

// a small util to pre-fetch all shaders and put them in a data structure (replacing the list above)
function loadShaders( list, callback ){
var shaders = {};

var expectedFiles = list.length \* 2;
var loadedFiles = 0;

function makeCallback( name, type ){
return function(data){
if( shaders[name] === undefined ){
shaders[name] = {};
}

    shaders[name][type] = data;

    //  check if done
    loadedFiles++;
    if( loadedFiles == expectedFiles ){
    callback( shaders );
    }

};

}

for( var i=0; i<list.length; i++ ){
var vertexShaderFile = list[i] + '.vsh';
var fragmentShaderFile = list[i] + '.fsh';

//  find the filename, use it as the identifier
var splitted = list[i].split('/');
var shaderName = splitted[splitted.length-1];
$(document).load( vertexShaderFile, makeCallback(shaderName, 'vertex') );
$(document).load( fragmentShaderFile,  makeCallback(shaderName, 'fragment') );

}
}

Hàm loadShaders() lấy danh sách tên tệp chương trình đổ bóng (mong đợi .fsh cho mảnh và .vsh cho chương trình đổ bóng đỉnh), cố gắng tải dữ liệu của chúng và sau đó chỉ thay thế danh sách bằng các đối tượng. Kết quả cuối cùng là trong đồng phục THREE.js, bạn có thể chuyển chương trình đổ bóng đến như sau:

var galacticShaderMaterial = new THREE.ShaderMaterial( {
vertexShader: shaderList.galacticstars.vertex,
fragmentShader: shaderList.galacticstars.fragment,
/_..._/
});

Có thể tôi đã sử dụng request.js mặc dù việc đó cần tập hợp lại một số mã cho mục đích này. Giải pháp này, mặc dù dễ dàng hơn nhiều, nhưng có thể được cải thiện theo tôi nghĩ, thậm chí có thể là tiện ích THREE.js. Nếu bạn có đề xuất hoặc cách để cải thiện việc này, vui lòng cho tôi biết!

Nhãn văn bản CSS ở đầu THREE.js

Trong dự án gần đây nhất của chúng tôi, Small Arms Globe, tôi đã cố gắng tạo nhãn văn bản xuất hiện trên cảnh THREE.js. Phương thức tôi đang sử dụng tính toán vị trí mô hình tuyệt đối của nơi tôi muốn văn bản xuất hiện, sau đó giải quyết vị trí màn hình bằng THREE.Projector() và cuối cùng sử dụng CSS "top" và "left" để đặt các phần tử CSS tại vị trí mong muốn.

Các lặp lại ban đầu trong dự án này cũng sử dụng kỹ thuật này. Tuy nhiên, tôi rất muốn thử phương pháp khác do Luis Croz mô tả.

Ý tưởng cơ bản: khớp biến đổi ma trận của CSS3D với máy ảnh và cảnh của BA và bạn có thể "đặt" các phần tử CSS trong 3D như thể nó ở trên cảnh của BA. Tuy nhiên, có những giới hạn đối với việc này, ví dụ: bạn sẽ không thể đưa văn bản vào bên dưới đối tượng THREE.js. Việc này vẫn nhanh hơn nhiều so với việc cố gắng thực hiện bố cục bằng cách sử dụng các thuộc tính CSS "top" (trên cùng) và "left" (bên trái).

Nhãn văn bản.
Sử dụng các biến đổi CSS3D để đặt nhãn văn bản lên trên WebGL.

Bạn có thể tìm thấy bản minh hoạ (và mã trong nguồn xem) cho nội dung này tại đây. Tuy nhiên, tôi đã thấy rằng thứ tự ma trận đã thay đổi cho THREE.js. Hàm mà tôi đã cập nhật:

/_ Fixes the difference between WebGL coordinates to CSS coordinates _/
function toCSSMatrix(threeMat4, b) {
var a = threeMat4, f;
if (b) {
f = [
a.elements[0], -a.elements[1], a.elements[2], a.elements[3],
a.elements[4], -a.elements[5], a.elements[6], a.elements[7],
a.elements[8], -a.elements[9], a.elements[10], a.elements[11],
a.elements[12], -a.elements[13], a.elements[14], a.elements[15]
];
} else {
f = [
a.elements[0], a.elements[1], a.elements[2], a.elements[3],
a.elements[4], a.elements[5], a.elements[6], a.elements[7],
a.elements[8], a.elements[9], a.elements[10], a.elements[11],
a.elements[12], a.elements[13], a.elements[14], a.elements[15]
];
}
for (var e in f) {
f[e] = epsilon(f[e]);
}
return "matrix3d(" + f.join(",") + ")";
}

Vì mọi thứ đều được thay đổi, nên văn bản không còn nằm trước máy ảnh nữa. Giải pháp là sử dụng THREE.Gyroscope() để buộc Object3D "mất" hướng kế thừa khỏi cảnh. Kỹ thuật này được gọi là "lướt ván" và con quay hồi chuyển là một lựa chọn hoàn hảo để thực hiện việc này.

Thật tuyệt vời là tất cả DOM và CSS thông thường vẫn được phát như vậy, chẳng hạn như có thể di chuột qua nhãn văn bản 3D và làm cho nhãn phát sáng với bóng đổ.

Nhãn văn bản.
Gắn nhãn văn bản luôn hướng về phía máy ảnh bằng cách gắn nhãn đó vào THREE.Gyroscope().

Khi phóng to, tôi nhận thấy việc điều chỉnh tỷ lệ kiểu chữ gây ra vấn đề về việc định vị. Có thể điều này là do khoảng cách giữa văn bản và khoảng đệm của văn bản? Một vấn đề khác là văn bản bị phân thành điểm ảnh khi được phóng to vì trình kết xuất DOM xem văn bản đã kết xuất là một khung hình có hoạ tiết, điều cần lưu ý khi sử dụng phương pháp này. Khi nhìn lại, tôi có thể chỉ dùng một phông chữ cỡ rất lớn và có lẽ đây là thứ để khám phá trong tương lai. Trong dự án này, tôi cũng đã sử dụng nhãn văn bản vị trí CSS "trên cùng/bên trái" như được mô tả ở trên cho các phần tử rất nhỏ đi kèm với các hành tinh trong hệ Mặt Trời.

Phát nhạc và lặp lại nhạc

Đoạn nhạc được phát trong chương trình "Galactic Map" của Hiệu ứng âm nhạc là của các nhà soạn nhạc Bioware là Sam Hulick và Jack Wall và mang đến cảm xúc mà tôi muốn khách truy cập trải nghiệm. Chúng tôi muốn có vài bản nhạc trong dự án này vì chúng tôi cảm thấy đó là một phần quan trọng trong bầu không khí, giúp tạo ra cảm giác kinh hoàng và bất ngờ mà chúng tôi đang cố gắng hướng đến.

Nhà sản xuất Valdean Klump của chúng tôi đã liên hệ với Sam. Sam đã cho chúng tôi sử dụng rất nhiều bản nhạc "sàn nhảy" trong loạt phim Khối Effect. Bản nhạc có tên là "In một vùng đất lạ".

Tôi đã sử dụng thẻ âm thanh để phát nhạc, tuy nhiên ngay cả trong Chrome, thuộc tính "loop" (vòng lặp) không đáng tin cậy -- đôi khi tính năng này không lặp lại được. Cuối cùng, vụ tấn công thẻ âm thanh kép này được dùng để kiểm tra kết thúc phát và chuyển sang thẻ khác để phát. Thật không hài lòng khi vẫn không phải lúc nào cũng lặp lại hoàn hảo. Tôi cảm thấy như đây là điều tốt nhất mà tôi có thể làm.

var musicA = document.getElementById('bgmusicA');
var musicB = document.getElementById('bgmusicB');
musicA.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playB = function(){
musicB.play();
}
// make it wait 15 seconds before playing again
setTimeout( playB, 15000 );
}, false);

musicB.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playA = function(){
musicA.play();
}
// otherwise the music will drive you insane
setTimeout( playA, 15000 );
}, false);

// okay so there's a bit of code redundancy, I admit it
musicA.play();

Khả năng cải thiện

Sau khi làm việc với THREE.js được một thời gian, tôi cảm thấy như mình đã đạt đến điểm mà dữ liệu đang trộn lẫn quá nhiều với mã. Ví dụ: khi xác định vật liệu, hoạ tiết và hướng dẫn hình học nội tuyến, về cơ bản tôi là "mô hình 3D bằng mã". Điều này thực sự rất tệ và là một khía cạnh mà những nỗ lực trong tương lai với THREE.js có thể cải thiện đáng kể, ví dụ như xác định dữ liệu Material trong một tệp riêng biệt, tốt nhất là có thể xem và điều chỉnh được trong một số ngữ cảnh, và có thể được đưa trở lại dự án chính.

Đồng nghiệp của chúng tôi, Ray McClure cũng đã dành thời gian tạo ra một số "tiếng ồn không gian" tạo sinh tuyệt vời mà vốn phải được cắt giảm do API âm thanh trên web không ổn định, thường xuyên khiến Chrome gặp sự cố. Thật không may... nhưng nó chắc chắn đã khiến chúng tôi suy nghĩ nhiều hơn về âm thanh cho công việc trong tương lai. Tại thời điểm viết thư này, tôi được thông báo rằng API Web âm thanh đã được vá, vì vậy có thể API này hiện đang hoạt động, một điểm cần lưu ý trong tương lai.

Các thành phần kiểu chữ được ghép nối với WebGL vẫn là một thách thức và tôi không chắc chắn 100% những gì chúng tôi đang làm ở đây là chính xác. Vẫn có cảm giác như bị tấn công. Có lẽ bạn sẽ có thể sử dụng các phiên bản trong tương lai của THREE với Trình kết xuất CSS sắp ra mắt để kết nối hiệu quả hơn với cả hai nền tảng này.

Ghi công

Cảm ơn Aaron Koblin đã cho tôi đến thị trấn thực hiện dự án này. Jono Brandel thiết kế giao diện người dùng + triển khai, xử lý loại và triển khai chuyến tham quan tuyệt vời. Valdean Klump đã đặt tên cho dự án và đặt toàn bộ nội dung cho dự án. Sabah Ahmed đã xoá hàng tấn quyền sử dụng đối với nguồn dữ liệu và hình ảnh. Clem Wright đã liên hệ với người phù hợp để xuất bản. Doug Fritz là một nhà sáng tạo xuất sắc về kỹ thuật. George Brower đã hướng dẫn tôi về JS và CSS. Và tất nhiên là thầy Doob cho THREE.js.

Tài liệu tham khảo