Kiếm 100.000 sao

Michael Chang
Michael Chang

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

Các chủ đề được thảo luận ở đây sẽ khá rộng và đòi hỏi bạn phải có một số kiến thức về THREE.js. Tuy nhiên, tôi hy vọng bạn vẫn có thể tận hưởng bài viết này như một bài đánh giá kỹ thuật sau khi hoàn thành. Bạn có thể chuyển đến một phần mà mình 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ẽ cho thấy phần kết xuất của dự án, tiếp theo là phần 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 ngôi sao, một thử nghiệm của Chrome do Nhóm nghệ thuật dữ liệu thực hiện
100,000 Stars sử dụng THREE.js để trực quan hoá các ngôi sao lân cận trong Dải Ngân hà

Khám phá Space

Ngay sau khi chúng tôi hoàn thành Small Arms Globe (Trái đất vũ khí nhỏ), tôi đã thử nghiệm một bản minh hoạ hạt THREE.js có độ 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 lượng hiệu ứng được áp dụng. Khi hiệu ứng độ sâu trường thực sự cực đoan, các vật thể ở xa trở nên thực sự mờ tương tự như cách chụp ảnh nghiêng-chuyển đổi tạo ra ảo giác như đang nhìn vào một cảnh vi mô. Ngược lại, việc giảm hiệu ứng này sẽ tạo cảm giác như 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 có thể 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 3 nguồn dữ liệu (Hipparcos, Yale Bright Star Catalog và Gliese/Jahreiss Catalog) cùng với toạ độ Descartes xyz được tính toán trước. Hãy bắt đầu!

Lập biểu đồ dữ liệu về ngôi sao.
Bước đầu tiên là vẽ mọi ngôi sao trong danh mục dưới dạng một hạt.
Các ngôi sao được đặt tên.
Một số ngôi 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ờ để kết hợp một số thứ đặt dữ liệu về các ngôi sao trong không gian 3D. Có đúng 119.617 ngôi sao trong tập dữ liệu,vì vậy, việc biểu thị mỗi ngô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 một 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 phim Mass Effect. Trong trò chơi, người chơi được mời khám phá thiên hà và quét nhiều hành tinh cũng như đọc về lịch sử hoàn toàn hư cấu, nghe giống như trên 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 vô số dữ liệu thực tế về các ngôi sao, người ta có thể trình bày thông tin thực tế về thiên hà theo cách tương tự. Mục tiêu cuối cùng của dự án này là làm sống động dữ liệu này, cho phép người xem khám phá thiên hà theo phong cách Mass Effect, tìm hiểu về các ngôi sao và sự phân bố của chúng, đồng thời hy vọng sẽ truyền cảm hứng cho họ về sự kỳ diệu và đáng kinh ngạc của không gian. Chà!

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 không phải là một nhà thiên văn học và đây là công việc nghiên cứu nghiệp dư dựa trên 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à cách diễn giải của nghệ sĩ về không gian.

Xây dựng Galaxy

Kế hoạch của tôi là tạo một mô hình thiên hà theo quy trình có thể đặt dữ liệu về các ngôi sao vào ngữ cảnh – và hy vọng sẽ cung cấp một cái nhìn tuyệt vời về vị trí của chúng ta trong Dải Ngân hà.

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

Để tạo dải Ngân hà, tôi đã tạo ra 100.000 hạt và đặt chúng theo hình 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ề thông tin cụ thể về quá trình hình thành cánh xoắn ốc vì đây sẽ là mô hình biểu diễn thay vì mô hình toán học. Tuy nhiên, tôi đã cố gắng làm cho số lượng cánh xoắn ít nhiều chính xác và xoay theo "hướng đúng".

Trong các phiên bản sau của mô hình Ngân hà, tôi đã giảm bớt việc sử dụng các hạt để ưu tiên hình ảnh phẳng của một thiên hà đi kèm với các hạt, hy vọng sẽ mang lại cho mô hình này một hình ảnh chụp ảnh hơn. 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 xử lý để trông giống như dải Ngân hà.

Xác định tỷ lệ 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,hình cầu có chiều rộng 110.000 năm ánh sáng, bao gồm cả hệ thống hạt.

Ngay từ đầu, tôi đã quyết định biểu thị một đơn vị GL, về cơ bản là một pixel trong 3D, dưới dạng một năm ánh sáng – một quy ước hợp nhất vị trí cho mọi thứ được hình ảnh hoá, và thật không may, sau này tôi gặp phải 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 máy ảnh, điều này tôi đã làm trong một số dự án khác. Một ưu điểm là mọi thứ được đặt vào một "bàn xoay" để thao tác kéo chuột sang trái và phải sẽ xoay đối tượng có liên quan, nhưng việc thu phóng chỉ là vấn đề thay đổi camera.position.z.

Trường nhìn (hoặc FOV) của máy ảnh cũng là một giá trị động. Khi một người kéo ra ngoài, trường nhìn sẽ mở rộng, thu vào nhiều thiên hà hơn. Ngược lại, khi di chuyển vào phía trong một ngôi sao, trường nhìn sẽ thu hẹp lại. Điều này cho phép máy ảnh xem những vật thể vô cùng nhỏ (so với thiên hà) bằng cách thu hẹp FOV xuống còn một cái kính lúp giống như thần mà không phải xử lý các vấn đề cắt gần mặt phẳng.

Các cách kết xuất thiên hà.
(ở trên) Thiên hà hạt nhân ban đầu. (dưới đây) Các hạt đi kèm với một mặt phẳng hình ảnh.

Từ đây, tôi có thể "đặt" Mặt Trời ở một số đơn vị cách lõi thiên hà. Tôi cũng có thể hình dung 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 hình dung Mây Oort). Trong hệ mặt trời mô hình này, tôi cũng có thể hình dung quỹ đạo đơn giản của Trái đất và bán kính thực tế của Mặt trời để so sánh.

Hệ mặt trời.
Mặt trời có các hành tinh quay quanh và một hình cầu đại diện cho Vành đai Kuiper.

Mặt trời khó kết xuất. Tôi đã phải gian lận bằng nhiều kỹ thuật đồ hoạ theo thời gian thực mà tôi biết. Bề mặt của Mặt Trời là một bọt plasma nóng và cần phải nhấp nháy và thay đổi theo thời gian. Điều này được mô phỏng thông qua hoạ tiết bitmap của hình ảnh hồng ngoại về bề mặt của mặt trời. Chương trình đổ bóng bề mặt sẽ 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 dải màu riêng biệt. Khi bảng tra cứu này bị dịch chuyển theo thời gian, nó sẽ tạo ra sự biến dạng giống như dung nham.

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

Kết xuất Sol.
Phiên bản đầu tiên của Sun.

Các luồng phun trào mặt trời được tạo thông qua chương trình đổ bóng đỉnh và mảnh áp dụng cho một hình xuyến, xoay quanh cạnh của bề mặt mặt trời. Chương trình đổ bóng đỉnh có một hàm nhiễu khiến nó đan theo kiểu giống như một khối.

Tại đây, tôi bắt đầu gặp phải một số vấn đề về z-fighting 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 THREE.js, vì vậy, tôi không thể tăng độ chính xác một cách thực tế mà không cần phải làm việc rất nhiều. Các vấn đề về độ chính xác không nghiêm trọng như gần gốc. Tuy nhiên, khi tôi bắt đầu lập mô hình các hệ thống sao khác, vấn đề này đã trở thành vấn đề.

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

Tôi đã sử dụng một số thủ thuật để giảm thiểu hiện tượng z-fighting. Material.polygonoffset của THREE là một thuộc tính cho phép kết xuất đa giác ở một vị trí khác được nhận biết (theo như tôi hiểu). Phương thức này được dùng để buộc mặt phẳng corona luôn kết xuất trên bề mặt của Mặt trời. Bên dưới, một "vòng hào quang" của Mặt trời được kết xuất để tạo ra các 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 bị giật khi cảnh được thu phóng. Để khắc phục vấn đề này, tôi phải "đặt về 0" chế độ xoay cảnh và xoay riêng mô hình sao và bản đồ môi trường để tạo ảo giác rằng bạn đang quay quanh sao.

Tạo hiệu ứng loé sáng

Quyền lực càng lớn thì trách nhiệm càng cao.
Sức mạnh càng lớn thì trách nhiệm càng cao.

Hình ảnh không gian là nơi tôi cảm thấy có thể sử dụng quá nhiều hiệu ứng lấp lánh. THREE.LensFlare phục vụ mục đích này, tất cả những gì tôi cần làm là thêm một số hình lục giác anamorphic và một chút JJ Abrams. Đoạn mã dưới đây cho biết cách tạo các đối tượng này trong cảnh.

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

}
}

Một 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 hướng không gian", một THREE.CylinderGeometry() khổng lồ đã được tạo và đặt ở giữa Mặt trời. Để tạo "luồng ánh sáng" tỏa ra ngoài, tôi đã sửa đổi độ lệch hoạ tiết 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ề chất liệu, nhận được một hàm onUpdate mà bạn có thể ghi đè. Việc đặt độ dời sẽ khiến hoạ tiết "cuộn" dọc theo trục đó và việc gửi đi hàng loạt needsUpdate = true sẽ buộc hành vi này lặp lại.

Sử dụng dải màu

Mỗi ngôi sao có một màu khác nhau dựa trên "chỉ số màu" mà các nhà thiên văn học đã chỉ định cho chúng. Nhìn chung, sao đỏ có nhiệt độ thấp hơn và sao xanh/tím có nhiệt độ cao hơn. Một dải màu trắng và màu cam trung gian xuất hiện trong hiệu ứng chuyển màu này.

Khi kết xuất các ngôi sao, tôi muốn tạo màu riêng cho từng hạt dựa trên dữ liệu này. Cách thực hiện việc này là bằng "thuộc tính" được cung cấp cho chất liệu chương trình đổ bóng áp dụng cho các hạt.

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

Việc điền vào mảng colorIndex sẽ cung cấp cho mỗi hạt màu riêng biệt trong chương trình đổ bóng. Thông thường, bạn sẽ truyền vào một vec3 màu, nhưng trong trường hợp này, tôi sẽ truyền vào một float để tra cứu dải màu cuối cùng.

Dải màu.
Đường chuyển 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.

Dải màu trô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 dải màu đó từ JavaScript. Cách tôi thực hiện việc này là trước tiên, hãy tải hình ảnh lên DOM, vẽ hình ảnh đó vào một 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 thức này được dùng để tô màu cho từng ngôi sao trong chế độ xem mô hình ngôi sao.

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

Xử lý 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 để hoàn thành tất 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 có chương trình đổ bóng 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 (dự kiến là .fsh cho mảnh và .vsh cho chương trình đổ bóng đỉnh), cố gắng tải dữ liệu của các tệp đó, sau đó chỉ thay thế danh sách bằng các đối tượng. Kết quả cuối cùng nằm trong các bộ đồng phục THREE.js mà bạn có thể truyền chương trình đổ bóng vào như sau:

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

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

Nhãn văn bản CSS trên THREE.js

Trong dự án gần đây nhất của chúng tôi, Small Arms Globe, tôi đã thử tạo nhãn văn bản xuất hiện trên đầu 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 vị trí tôi muốn văn bản xuất hiện, sau đó phân giải 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 ở vị trí mong muốn.

Các lần lặp lại ban đầu của dự án này đã sử dụng chính kỹ thuật này, tuy nhiên, tôi đã rất muốn thử phương thức khác do Luis Cruz mô tả.

Ý tưởng cơ bản: so khớp phép biến đổi ma trận của CSS3D với máy ảnh và cảnh của THREE, bạn có thể "đặt" các phần tử CSS ở chế độ 3D như thể nó nằm trên cảnh của THREE. Tuy nhiên, phương thức này cũng có một số hạn chế, chẳng hạn như bạn sẽ không thể đặt văn bản bên dưới đối tượng THREE.js. Cách 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ác thuộc tính CSS "top" (trên cùng) và "left" (trái).

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

Bạn có thể xem bản minh hoạ (và mã trong nguồn thành phần hiển thị) cho nội dung này tại đây. Tuy nhiên, tôi nhận thấy thứ tự ma trận đã thay đổi đối với THREE.js. Hà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 chuyển đổi, nên văn bản không còn hướng về 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 từ cảnh. Kỹ thuật này được gọi là "billboarding" (quảng cáo trên biển báo) và con quay hồi chuyển là công cụ hoàn hảo để thực hiện việc này.

Điều thực sự thú vị là tất cả DOM và CSS thông thường vẫn hoạt động, chẳng hạn như bạn có thể di chuột qua nhãn văn bản 3D và làm cho nhãn đó sáng lên với bóng đổ.

Nhãn văn bản.
Luôn hướng nhãn văn bản về phía máy ảnh bằng cách đính kèm 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ữ đang gây ra vấn đề về vị trí. Có phải điều này là do khoảng cách giữa các chữ cái và khoảng đệm của văn bản không? Một vấn đề khác là văn bản bị mờ khi phóng to vì trình kết xuất DOM coi văn bản đã kết xuất là một hình tứ giác có kết cấu. Bạn cần lưu ý điều này khi sử dụng phương thức này. Nhìn lại, tôi có thể chỉ cần sử dụng văn bản có kích thước phông chữ khổng lồ và có lẽ đây là điều cần 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 "top/left" (trên cùng/bên trái) được mô tả trước đó cho các phần tử thực sự nhỏ đi kèm với các hành tinh trong hệ mặt trời.

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

Bản nhạc phát trong "Bản đồ thiên hà" của Mass Effect là của các nhà soạn nhạc Sam Hulick và Jack Wall của Bioware, và bản nhạc này có 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ó một số bản nhạc trong dự án vì cảm thấy đó là một phần quan trọng của bầu không khí, giúp tạo ra cảm giác kinh ngạc và kỳ diệu mà chúng tôi đang hướng đến.

Nhà sản xuất Valdean Klump của chúng tôi đã liên hệ với Sam. Anh ấy có một loạt bản nhạc "bị cắt" trong Mass Effect và rất vui lòng cho phép chúng tôi sử dụng. Bản nhạc có tiêu đề "In a Strange Land" (Ở một vùng đất xa lạ).

Tôi đã sử dụng thẻ âm thanh để phát nhạc, tuy nhiên ngay cả trong Chrome, thuộc tính "lặp lại" cũng không đáng tin cậy – đôi khi thuộc tính này không lặp lại được. Cuối cùng, bản hack thẻ âm thanh kép này được dùng để kiểm tra thời điểm kết thúc phát và chuyển sang thẻ khác để phát. Điều đáng thất vọng là ảnh tĩnh này không phải lúc nào cũng lặp lại một cách hoàn hảo, nhưng tôi cảm thấy đây là điều tốt nhất 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();

Có thể cải thiện

Sau khi làm việc với THREE.js một thời gian, tôi cảm thấy mình đã đến lúc dữ liệu của mình bị trộn lẫn quá nhiều với mã. Ví dụ: khi xác định chất liệu, hoạ tiết và hướng dẫn hình học cùng dòng, về cơ bản tôi đã "mô hình hoá 3D bằng mã". Điều này thật tệ và là một khía cạnh mà các nỗ lực trong tương lai với THREE.js có thể cải thiện đáng kể, chẳng hạn như xác định dữ liệu chất liệu trong một tệp riêng biệt, tốt nhất là có thể xem và điều chỉnh trong một số ngữ cảnh, đồng thời có thể đư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ố "âm thanh không gian" tạo sinh tuyệt vời. Tuy nhiên, chúng tôi đã phải cắt bỏ những âm thanh này do API âm thanh trên web không ổn định, thường xuyên làm Chrome gặp sự cố. Thật đáng tiếc… nhưng điều này chắc chắn đã khiến chúng tôi suy nghĩ nhiều hơn về không gian âm thanh cho các dự án trong tương lai. Tại thời điểm viết bài này, tôi được thông báo rằng Web Audio API đã được vá nên có thể API này đang hoạt động. Đây là điều cần chú ý trong tương lai.

Các thành phần kiểu chữ kết hợp với WebGL vẫn là một thách thức và tôi không chắc 100% rằng những gì chúng ta đang làm ở đây là cách chính xác. Nó vẫn giống như một hành vi xâm nhập. Có thể các phiên bản sau này của THREE, với Trình kết xuất CSS sắp ra mắt, có thể được dùng để kết hợp hai thế giới này hiệu quả hơn.

Ghi công

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

Tài liệu tham khảo