Tôi rất vui khi nhóm Google Data Arts tiếp cận Moniker và tôi về việc cùng nhau khám phá những khả năng mà WebVR mang lại. Tôi đã theo dõi các sản phẩm của nhóm họ trong nhiều năm và các dự án của họ luôn thu hút tôi. Kết quả của sự cộng tác này là Dance Tonite, một trải nghiệm nhảy múa không ngừng thay đổi trong thực tế ảo với LCD Soundsystem và người hâm mộ của họ. Sau đây là cách chúng tôi làm điều đó.
Khái niệm
Chúng tôi bắt đầu bằng việc phát triển một loạt nguyên mẫu sử dụng WebVR, một tiêu chuẩn mở cho phép chuyển sang thực tế ảo bằng cách truy cập một trang web bằng trình duyệt của bạn. Mục tiêu là giúp mọi người dễ dàng tiếp cận trải nghiệm thực tế ảo hơn, bất kể bạn dùng thiết bị nào.
Chúng tôi đã xem xét kỹ lưỡng vấn đề này. Mọi nội dung chúng tôi tạo ra đều hoạt động trên tất cả các loại thiết bị VR, từ tai nghe VR hoạt động với điện thoại di động như Daydream View, Cardboard và Gear VR của Samsung đến các hệ thống theo tỷ lệ phòng như HTC VIVE và Oculus Rift phản ánh các chuyển động thực tế của bạn trong môi trường ảo. Có lẽ quan trọng nhất là chúng tôi cảm thấy cần phải tạo ra một sản phẩm phù hợp với tinh thần của web, tức là sản phẩm đó cũng hoạt động được cho những người không sở hữu thiết bị VR.
1. Tự làm công nghệ ghi hình chuyển động
Vì muốn người dùng tham gia một cách sáng tạo, nên chúng tôi bắt đầu tìm hiểu các khả năng để người dùng tham gia và thể hiện bản thân bằng công nghệ thực tế ảo. Chúng tôi rất ấn tượng về khả năng di chuyển và quan sát xung quanh của bạn trong VR, cũng như độ chân thực của hình ảnh. Điều này đã gợi cho chúng tôi một ý tưởng. Thay vì yêu cầu người dùng xem hoặc tạo nội dung, bạn có thể ghi lại chuyển động của họ.
Chúng tôi đã tạo một nguyên mẫu để ghi lại vị trí của kính VR và tay điều khiển trong khi nhảy. Chúng tôi đã thay thế các vị trí đã ghi bằng các hình dạng trừu tượng và ngạc nhiên trước kết quả. Kết quả rất nhân văn và chứa đựng rất nhiều tính cách! Chúng tôi nhanh chóng nhận ra rằng có thể sử dụng WebVR để quay lại chuyển động giá rẻ tại nhà.
Với WebVR, nhà phát triển có quyền truy cập vào hướng và vị trí đầu của người dùng thông qua đối tượng VRPose. Phần cứng thực tế ảo cập nhật giá trị này cho mọi khung hình để mã của bạn có thể kết xuất các khung hình mới từ góc nhìn chính xác. Thông qua GamePad API với WebVR, chúng ta cũng có thể truy cập vào vị trí/hướng của tay điều khiển của người dùng thông qua đối tượng GamepadPose. Chúng tôi chỉ cần lưu trữ tất cả các giá trị vị trí và hướng này trong mỗi khung hình, từ đó tạo ra một "bản ghi" chuyển động của người dùng.
2. Phong cách tối giản và trang phục
Với thiết bị thực tế ảo theo tỷ lệ phòng hiện nay, chúng ta có thể theo dõi 3 điểm trên cơ thể người dùng: đầu và hai tay. Trong Dance Tonite, chúng tôi muốn tập trung vào tính nhân văn trong chuyển động của 3 điểm này trong không gian. Để đạt được điều này, chúng tôi đã đẩy tính thẩm mỹ về mức tối thiểu để tập trung vào chuyển động. Chúng tôi thích ý tưởng khai thác trí não của mọi người.
Video này minh hoạ công trình của nhà tâm lý học người Thuỵ Điển Gunnar Johansson là một trong những ví dụ mà chúng tôi tham khảo khi cân nhắc việc đơn giản hoá mọi thứ nhiều nhất có thể. Hình ảnh này cho thấy cách các dấu chấm trắng nổi có thể được nhận dạng ngay lập tức dưới dạng các vật thể khi nhìn thấy chuyển động.
Về mặt hình ảnh, chúng tôi lấy cảm hứng từ các phòng màu sắc và trang phục hình học trong bản ghi lại của Margarete Hastings về việc tái hiện Ballet tam giác của Oskar Schlemmer vào năm 1970.
Trong khi lý do Schlemmer chọn trang phục hình học trừu tượng là để giới hạn các chuyển động của vũ công ở mức của búp bê và rối, thì chúng tôi lại có mục tiêu ngược lại cho Dance Tonite.
Cuối cùng, chúng tôi đã chọn hình dạng dựa trên lượng thông tin mà hình dạng đó truyền tải bằng cách xoay. Một quả cầu trông giống nhau bất kể nó xoay như thế nào, nhưng hình nón thực sự chỉ về hướng mà nó nhìn và trông khác với mặt trước so với mặt sau.
3. Bàn đạp lặp lại để di chuyển
Chúng tôi muốn cho thấy những nhóm lớn người được ghi hình đang nhảy và di chuyển cùng nhau. Việc này sẽ không khả thi khi phát trực tiếp vì số lượng thiết bị VR chưa đủ lớn. Nhưng chúng tôi vẫn muốn có các nhóm người phản ứng với nhau thông qua chuyển động. Chúng tôi nghĩ đến màn trình diễn lặp lại của Norman McClaren trong tác phẩm video "Canon" năm 1964.
Màn trình diễn của McClaren có một loạt các động tác được biên đạo rất kỹ lưỡng bắt đầu tương tác với nhau sau mỗi vòng lặp. Tương tự như một vòng lặp trong âm nhạc, nơi các nhạc sĩ tự chơi với nhau bằng cách xếp chồng nhiều bản nhạc trực tiếp, chúng tôi muốn xem liệu có thể tạo ra một môi trường mà người dùng có thể tự do ứng biến các phiên biểu diễn theo cách thoải mái hơn hay không.
4. Phòng thông nhau
Giống như nhiều bản nhạc khác, các bản nhạc của LCD Soundsystem được tạo bằng cách sử dụng các biện pháp đo lường theo thời gian chính xác. Bản nhạc Tonite của họ xuất hiện trong dự án của chúng tôi có các đoạn nhạc dài đúng 8 giây. Chúng tôi muốn người dùng thực hiện một màn trình diễn cho mỗi vòng lặp 8 giây trong bản nhạc. Mặc dù nhịp của các bản nhạc này không thay đổi, nhưng nội dung âm nhạc của chúng lại thay đổi. Khi bài hát phát triển, có những khoảnh khắc với các nhạc cụ và giọng hát khác nhau mà người biểu diễn có thể phản ứng theo nhiều cách. Mỗi biện pháp đo lường này được biểu thị dưới dạng một phòng, trong đó mọi người có thể tạo một màn trình diễn phù hợp với phòng đó.
Tối ưu hoá hiệu suất: không bỏ khung hình
Việc tạo trải nghiệm thực tế ảo đa nền tảng chạy trên một cơ sở mã duy nhất với hiệu suất tối ưu cho từng thiết bị hoặc nền tảng không phải là một việc đơn giản.
Khi ở chế độ VR, một trong những điều khó chịu nhất mà bạn có thể gặp phải là do tốc độ khung hình không theo kịp chuyển động của bạn. Nếu bạn xoay đầu nhưng hình ảnh mà mắt bạn nhìn thấy không khớp với chuyển động mà tai trong cảm nhận được, thì điều này sẽ khiến dạ dày bạn cồn cào ngay lập tức. Vì lý do này, chúng tôi cần tránh mọi độ trễ ở tốc độ khung hình lớn. Sau đây là một số biện pháp tối ưu hoá mà chúng tôi đã triển khai.
1. Hình học bộ đệm được tạo bản sao
Vì toàn bộ dự án của chúng ta chỉ sử dụng một số ít đối tượng 3D, nên chúng tôi có thể tăng hiệu suất đáng kể bằng cách sử dụng Hình học vùng đệm được tạo bản sao. Về cơ bản, lớp này cho phép bạn tải đối tượng lên GPU một lần và vẽ bao nhiêu "phiên bản" của đối tượng đó tuỳ ý trong một lệnh gọi vẽ. Trong Dance Tonite, chúng ta chỉ có 3 đối tượng khác nhau (một hình nón, một hình trụ và một căn phòng có lỗ), nhưng có thể có hàng trăm bản sao của các đối tượng đó. Hình học vùng đệm thực thể là một phần của ThreeJS, nhưng chúng tôi đã sử dụng c riêng thử nghiệm và đang tiến hành của Dusan Bosnjak để triển khai THREE.InstanceMesh
, giúp việc xử lý Hình học vùng đệm thực thể dễ dàng hơn nhiều.
2. Tránh trình thu gom rác
Giống như nhiều ngôn ngữ tập lệnh khác, JavaScript tự động giải phóng bộ nhớ bằng cách tìm ra những đối tượng đã được phân bổ nhưng không còn được sử dụng nữa. Quá trình này gọi là thu gom rác.
Nhà phát triển không kiểm soát được thời điểm xảy ra việc này. Bộ thu gom rác có thể xuất hiện bất cứ lúc nào và bắt đầu dọn rác, dẫn đến tình trạng bị rớt khung hình vì mất nhiều thời gian.
Giải pháp cho vấn đề này là tạo ra ít rác nhất có thể bằng cách tái chế các đối tượng. Thay vì tạo một đối tượng vectơ mới cho mỗi phép tính, chúng ta đã đánh dấu các đối tượng tạm thời để sử dụng lại. Vì chúng tôi giữ lại các tệp này bằng cách di chuyển tham chiếu đến các tệp đó ra khỏi phạm vi của chúng tôi, nên các tệp này không bị đánh dấu để xoá.
Ví dụ: sau đây là mã của chúng ta để chuyển đổi ma trận vị trí của đầu và tay người dùng thành mảng các giá trị vị trí/xoay mà chúng ta lưu trữ mỗi khung hình. Bằng cách sử dụng lại SERIALIZE_POSITION
, SERIALIZE_ROTATION
và SERIALIZE_SCALE
, chúng ta tránh được việc phân bổ bộ nhớ và thu gom rác sẽ xảy ra nếu tạo các đối tượng mới mỗi khi hàm được gọi.
const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
return SERIALIZE_POSITION.toArray()
.concat(SERIALIZE_ROTATION.toArray())
.map(compressNumber);
};
3. Tạo tuần tự chuyển động và phát tiến bộ
Để ghi lại chuyển động của người dùng trong VR, chúng tôi cần chuyển đổi tuần tự vị trí và góc xoay của tai nghe và tay điều khiển, đồng thời tải dữ liệu này lên máy chủ. Chúng tôi bắt đầu chụp các ma trận biến đổi đầy đủ cho mỗi khung hình. Điều này hoạt động tốt, nhưng với 16 số nhân với 3 vị trí mỗi vị trí ở tốc độ 90 khung hình/giây, điều này dẫn đến các tệp rất lớn và do đó sẽ phải chờ nhiều thời gian trong khi tải lên và tải dữ liệu xuống. Bằng cách chỉ trích xuất dữ liệu vị trí và xoay vòng từ các ma trận biến đổi, chúng tôi có thể giảm các giá trị này từ 16 xuống 7.
Vì khách truy cập trên web thường nhấp vào một đường liên kết mà không biết chính xác nội dung sẽ xuất hiện, nên chúng ta cần hiển thị nội dung hình ảnh nhanh chóng nếu không họ sẽ rời khỏi trang trong vòng vài giây.
Vì lý do này, chúng tôi muốn đảm bảo dự án của mình có thể bắt đầu phát càng sớm càng tốt. Ban đầu, chúng ta sử dụng JSON làm định dạng để tải dữ liệu chuyển động. Vấn đề là chúng ta phải tải toàn bộ tệp JSON trước khi có thể phân tích cú pháp tệp đó. Không quá tiến bộ.
Để duy trì việc hiển thị một dự án như Dance Tonite ở tốc độ khung hình cao nhất có thể, trình duyệt chỉ có một khoảng thời gian nhỏ cho mỗi khung hình để tính toán JavaScript. Nếu bạn mất quá nhiều thời gian, ảnh động sẽ bắt đầu bị giật. Ban đầu, chúng tôi gặp phải tình trạng giật khi trình duyệt giải mã các tệp JSON khổng lồ này.
Chúng tôi đã tìm thấy một định dạng dữ liệu truyền trực tuyến thuận tiện có tên là NDJSON hoặc JSON phân tách bằng dòng mới. Mẹo ở đây là tạo một tệp có một loạt chuỗi JSON hợp lệ, mỗi chuỗi trên một dòng riêng. Điều này cho phép bạn phân tích cú pháp tệp trong khi tệp đang tải, giúp chúng tôi hiển thị hiệu suất trước khi tệp được tải đầy đủ.
Dưới đây là giao diện của một phần một trong các bản ghi của chúng tôi:
{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...
Việc sử dụng NDJSON cho phép chúng tôi giữ nội dung biểu diễn dữ liệu của các khung riêng lẻ của hiệu suất dưới dạng chuỗi. Chúng ta có thể đợi đến khi đạt đến thời gian cần thiết, trước khi giải mã các giá trị đó thành dữ liệu vị trí, từ đó trải dài quá trình xử lý cần thiết theo thời gian.
4. Di chuyển nội suy
Vì chúng tôi muốn hiển thị từ 30 đến 60 màn trình diễn chạy cùng lúc, nên chúng tôi cần giảm tốc độ dữ liệu xuống thấp hơn nữa. Nhóm Nghệ thuật dữ liệu đã giải quyết vấn đề tương tự trong dự án Virtual Art Sessions (Phiên nghệ thuật ảo), trong đó họ phát lại bản ghi các nghệ sĩ vẽ trong VR bằng Tilt Brush. Họ đã giải quyết vấn đề này bằng cách tạo các phiên bản trung gian của dữ liệu người dùng với tốc độ khung hình thấp hơn và nội suy giữa các khung hình trong khi phát lại. Chúng tôi rất ngạc nhiên khi nhận thấy rằng khó có thể phát hiện sự khác biệt giữa bản ghi nội suy chạy ở tốc độ 15 khung hình/giây so với bản ghi gốc ở tốc độ 90 khung hình/giây.
Để tự mình xem, bạn có thể buộc Dance Tonite phát lại dữ liệu ở nhiều tốc độ bằng cách sử dụng chuỗi truy vấn ?dataRate=
. Bạn có thể sử dụng tính năng này để so sánh chuyển động được ghi ở tốc độ 90 khung hình/giây, 45 khung hình/giây hoặc 15 khung hình/giây.
Đối với vị trí, chúng ta thực hiện nội suy tuyến tính giữa khung hình chính trước và khung hình chính tiếp theo, dựa trên khoảng thời gian giữa các khung hình chính (tỷ lệ):
const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
x1 + (x2 - x1) * ratio,
y1 + (y2 - y1) * ratio,
z1 + (z2 - z1) * ratio
);
Đối với hướng, chúng ta thực hiện nội suy tuyến tính hình cầu (slerp) giữa các khung hình chính. Hướng được lưu trữ dưới dạng Quaternion.
const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
getQuaternion(next, performanceIndex, limbIndex),
ratio
);
5. Chuyển động theo nhạc
Để biết nên phát lại khung hình nào của ảnh động đã ghi, chúng ta cần biết thời gian hiện tại của bản nhạc tính bằng mili giây. Hoá ra là mặc dù phần tử Âm thanh HTML hoàn hảo để tải và phát dần âm thanh, nhưng thuộc tính thời gian mà phần tử này cung cấp không thay đổi đồng bộ với vòng lặp khung của trình duyệt. Nó luôn sai một chút. Đôi khi sớm một phần mili giây, đôi khi trễ một phần mili giây.
Điều này dẫn đến tình trạng giật trong các bản ghi vũ đạo đẹp mắt mà chúng ta muốn tránh bằng mọi giá. Để khắc phục vấn đề này, chúng tôi đã triển khai trình hẹn giờ của riêng mình trong JavaScript. Bằng cách này, chúng ta có thể chắc chắn rằng khoảng thời gian thay đổi giữa các khung hình chính xác là khoảng thời gian đã trôi qua kể từ khung hình gần nhất. Bất cứ khi nào đồng hồ của chúng ta bị lệch hơn 10 mili giây so với nhạc, chúng ta sẽ đồng bộ hoá lại.
6. Loại bỏ và sương mù
Mỗi câu chuyện đều cần có một kết thúc tốt đẹp và chúng tôi muốn làm điều gì đó bất ngờ cho những người dùng đã biến nó đến cuối trải nghiệm của chúng tôi. Khi rời khỏi phòng cuối cùng, bạn sẽ bước vào một không gian yên tĩnh với hình ảnh các hình nón và hình trụ. Bạn tự hỏi: "Đây có phải là kết thúc không?" Khi bạn di chuyển sâu hơn vào cánh đồng, đột nhiên các tông nhạc sẽ khiến nhiều nhóm hình nón và hình trụ hình thành thành các vũ công. Bạn đang ở giữa một bữa tiệc lớn! Sau đó, khi nhạc dừng đột ngột, mọi thứ rơi xuống đất.
Tuy điều này mang lại cảm giác tuyệt vời cho người xem, nhưng nó cũng đặt ra một số trở ngại về hiệu suất cần giải quyết. Các thiết bị VR theo tỷ lệ phòng và dàn máy chơi trò chơi cao cấp của chúng hoạt động hoàn hảo với 40 hiệu suất bổ sung cần thiết cho phần kết thúc mới của chúng tôi. Tuy nhiên, tốc độ khung hình trên một số thiết bị di động đã giảm đi một nửa.
Để khắc phục vấn đề này, chúng tôi đã đưa ra hiệu ứng sương mù. Sau một khoảng cách nhất định, mọi thứ sẽ dần chuyển sang màu đen. Vì chúng ta không cần tính toán hoặc vẽ những giá trị không hiển thị, nên chúng ta chọn lọc hiệu suất trong các phòng không hiển thị và điều này cho phép chúng ta tiết kiệm công việc cho cả CPU và GPU. Nhưng làm thế nào để xác định đúng khoảng cách?
Một số thiết bị có thể xử lý mọi thứ bạn đưa vào, còn một số thiết bị khác thì bị hạn chế hơn. Chúng tôi đã chọn triển khai thang điểm trượt. Bằng cách liên tục đo số khung hình mỗi giây, chúng tôi có thể điều chỉnh khoảng cách sương mù cho phù hợp. Miễn là tốc độ khung hình chạy trơn tru, chúng ta sẽ cố gắng thực hiện thêm nhiều công việc kết xuất bằng cách đẩy sương mù ra ngoài. Nếu tốc độ khung hình không chạy đủ mượt mà, chúng ta sẽ đưa sương mù lại gần hơn để có thể bỏ qua hiệu suất kết xuất trong bóng tối.
// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
frames++;
const time = (performance || Date).now();
if (prevTime == null) prevTime = time;
if (time > prevTime + interval) {
fps = Math.round((frames * 1000) / (time - prevTime));
frames = 0;
prevTime = time;
const lastCullDistance = settings.cullDistance;
// if the fps is lower than 52 reduce the cull distance
if (fps <= 52) {
settings.cullDistance = Math.max(
settings.minCullDistance,
settings.cullDistance - settings.roomDepth
);
}
// if the FPS is higher than 56, increase the cull distance
else if (fps > 56) {
settings.cullDistance = Math.min(
settings.maxCullDistance,
settings.cullDistance + settings.roomDepth
);
}
}
// gradually increase the cull distance to the new setting
cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;
// mask the edge of the cull distance with fog
viewer.fog.near = cullDistance - settings.roomDepth;
viewer.fog.far = cullDistance;
}
Mọi người đều có thể tìm thấy ứng dụng/trò chơi mình cần tại đây: xây dựng công nghệ thực tế ảo cho web
Việc thiết kế và phát triển trải nghiệm không đối xứng trên nhiều nền tảng đồng nghĩa với việc tính đến nhu cầu của từng người dùng tuỳ thuộc vào thiết bị của họ. Và với mỗi quyết định thiết kế, chúng tôi cần xem được điều đó có thể tác động đến những người dùng khác như thế nào. Làm cách nào để đảm bảo rằng nội dung bạn thấy trong VR cũng thú vị như khi không dùng VR và ngược lại?
1. Quả cầu màu vàng
Vì vậy, người dùng thực tế ảo quy mô phòng của chúng tôi sẽ thực hiện các màn trình diễn, nhưng người dùng thiết bị thực tế ảo di động (như Cardboard, Daydream View hoặc Samsung Gear) sẽ trải nghiệm dự án như thế nào? Để làm được điều này, chúng tôi đã đưa một phần tử mới vào môi trường: quả cầu màu vàng.
Khi xem dự án ở chế độ VR, bạn sẽ xem từ góc độ của quả cầu màu vàng. Khi bạn di chuyển từ phòng này sang phòng khác, các vũ công sẽ phản ứng với sự hiện diện của bạn. Các nhân vật này sẽ vẫy tay với bạn, nhảy múa xung quanh bạn, làm những cử chỉ hài hước sau lưng bạn và nhanh chóng tránh đường để không va vào bạn. Quả cầu màu vàng luôn là tâm điểm của sự chú ý.
Lý do là trong khi ghi lại màn trình diễn, quả cầu màu vàng sẽ di chuyển qua trung tâm của phòng theo nhịp nhạc và lặp lại. Vị trí của quả cầu cho người biểu diễn biết họ đang ở đâu trong thời gian và còn bao nhiêu thời gian trong vòng lặp. Điều này giúp họ có một tiêu điểm tự nhiên để xây dựng hiệu suất.
2. Một quan điểm khác
Chúng tôi không muốn bỏ qua những người dùng không có thực tế ảo, đặc biệt vì họ có thể là đối tượng lớn nhất của chúng tôi. Thay vì tạo trải nghiệm thực tế ảo giả, chúng tôi muốn mang đến cho các thiết bị dựa trên màn hình trải nghiệm riêng. Chúng tôi đã có ý tưởng hiển thị các màn trình diễn từ trên cao theo góc nhìn đẳng cự. Chế độ phối cảnh này có một quá trình phát triển phong phú trong các trò chơi điện tử. Nó được sử dụng lần đầu trong Zaxxon, một trò chơi bắn súng vào không gian từ năm 1982. Trong khi người dùng VR đang ở giữa sự kiện, thì phối cảnh isometric lại cho thấy một góc nhìn toàn cảnh về hành động. Chúng tôi chọn mở rộng quy mô mô hình một chút để tạo cảm giác thẩm mỹ như nhà búp bê.
3. Bóng: giả vờ cho đến khi bạn làm được
Chúng tôi nhận thấy một số người dùng gặp khó khăn khi nhìn thấy chiều sâu trong góc nhìn đẳng cự. Tôi khá chắc chắn rằng đó là lý do khiến Zaxxon cũng là một trong những trò chơi máy tính đầu tiên trong lịch sử chiếu một bóng động dưới các vật thể bay của trò chơi.
Hóa ra việc tạo bóng trong 3D rất khó. Đặc biệt là đối với các thiết bị bị hạn chế như điện thoại di động. Ban đầu, chúng tôi phải đưa ra quyết định khó khăn là loại bỏ các đối tượng này khỏi phương trình, nhưng sau khi hỏi ý kiến tác giả của Three.js và hacker bản minh hoạ giàu kinh nghiệm Mr doob, anh đã đưa ra ý tưởng mới mẻ là… giả mạo các đối tượng này.
Thay vì phải tính toán cách mỗi đối tượng nổi che khuất ánh sáng và do đó tạo bóng có nhiều hình dạng, chúng ta vẽ cùng một hình ảnh hoạ tiết mờ hình tròn bên dưới mỗi đối tượng. Vì hình ảnh của chúng ta không cố gắng mô phỏng thực tế ngay từ đầu, nên chúng ta có thể dễ dàng khắc phục vấn đề này chỉ bằng một vài điều chỉnh. Khi các đối tượng đến gần mặt đất hơn, kết cấu sẽ trở nên tối hơn và nhỏ hơn. Khi các mục di chuyển lên trên, chúng ta sẽ làm cho hoạ tiết trở nên trong suốt và lớn hơn.
Để tạo các hiệu ứng này, chúng tôi đã sử dụng hiệu ứng kết cấu này với hiệu ứng chuyển màu trắng sang đen mềm mại (không có độ trong suốt alpha). Chúng ta đặt chất liệu thành trong suốt và sử dụng chế độ kết hợp trừ. Điều này giúp các lớp phủ kết hợp với nhau một cách hài hoà khi chúng chồng lên nhau:
function createShadow() {
const texture = new THREE.TextureLoader().load(shadowTextureUrl);
const material = new THREE.MeshLambertMaterial({
map: texture,
transparent: true,
side: THREE.BackSide,
depthWrite: false,
blending: THREE.SubtractiveBlending,
});
const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
const plane = new THREE.Mesh(geometry, material);
return plane;
}
4. Hiện diện
Bằng cách nhấp vào đầu của một nghệ sĩ biểu diễn, những khách truy cập không có thiết bị VR có thể xem mọi thứ từ góc nhìn của vũ công. Từ góc này, nhiều chi tiết nhỏ trở nên rõ ràng. Để cố gắng giữ cho màn trình diễn của mình đồng bộ, các vũ công nhanh chóng nhìn nhau. Khi quả cầu bay vào phòng, bạn thấy họ nhìn theo hướng quả cầu một cách lo lắng. Mặc dù với tư cách là người xem, bạn không thể ảnh hưởng đến những chuyển động này, nhưng nó thực sự mang lại cảm giác sống động đến bất ngờ. Xin nhắc lại, chúng tôi ưu tiên làm như vậy thay vì cung cấp cho người dùng một phiên bản VR giả sử sử dụng chuột.
5. Chia sẻ bản ghi âm
Chúng tôi biết bạn sẽ tự hào như thế nào khi thực hiện một bản ghi hình có vũ đạo phức tạp với 20 lớp diễn viên phản ứng với nhau. Chúng tôi biết người dùng có thể muốn giới thiệu nó cho bạn bè của họ. Nhưng hình ảnh tĩnh về thành tích này không truyền tải đủ thông tin. Thay vào đó, chúng tôi muốn cho phép người dùng chia sẻ video về màn trình diễn của họ. Thực ra, tại sao không phải là ảnh GIF? Ảnh động của chúng tôi được tô màu phẳng, hoàn hảo cho bảng màu hạn chế của định dạng.
Chúng tôi đã chuyển sang GIF.js, một thư viện JavaScript cho phép bạn mã hoá ảnh GIF động từ trong trình duyệt. Công cụ này giảm tải việc mã hoá khung hình cho trình chạy web có thể chạy ở chế độ nền dưới dạng các quy trình riêng biệt, nhờ đó có thể tận dụng nhiều bộ xử lý hoạt động song song.
Đáng tiếc là với số lượng khung hình cần thiết cho ảnh động, quá trình mã hoá vẫn quá chậm. GIF có thể tạo tệp nhỏ bằng cách sử dụng bảng màu hạn chế. Chúng tôi nhận thấy hầu hết thời gian đều dành cho việc tìm màu gần nhất cho mỗi pixel. Chúng tôi có thể tối ưu hoá quy trình này gấp 10 lần bằng cách xâm nhập vào một lối tắt nhỏ: nếu màu của pixel giống với màu cuối cùng, hãy sử dụng cùng một màu trong bảng màu như trước.
Giờ đây, chúng tôi đã có bộ mã hoá nhanh, nhưng các tệp GIF thu được lại có kích thước quá lớn. Định dạng GIF cho phép bạn chỉ định cách hiển thị từng khung hình trên khung hình cuối cùng bằng cách xác định phương thức huỷ của khung hình đó. Để có các tệp nhỏ hơn, thay vì cập nhật từng pixel từng khung hình, chúng tôi chỉ cập nhật các pixel đã thay đổi. Trong khi làm chậm quá trình mã hoá một lần nữa, nhưng điều này đã làm giảm đáng kể kích thước tệp của chúng tôi.
6. Nền tảng vững chắc: Google Cloud và Firebase
Phần phụ trợ của trang web "nội dung do người dùng tạo" thường phức tạp và dễ hỏng, nhưng chúng tôi đã tạo ra một hệ thống đơn giản và mạnh mẽ nhờ Google Cloud và Firebase. Khi một vũ công tải một điệu nhảy mới lên hệ thống, họ sẽ được xác thực ẩn danh bằng tính năng Xác thực Firebase. Họ được cấp quyền tải bản ghi lên một không gian tạm thời bằng Cloud Storage cho Firebase. Khi quá trình tải lên hoàn tất, máy khách sẽ gọi trình kích hoạt HTTP Cloud Functions cho Firebase bằng mã thông báo Firebase của máy khách. Thao tác này sẽ kích hoạt một quy trình máy chủ xác thực nội dung gửi, tạo bản ghi cơ sở dữ liệu và chuyển bản ghi vào một thư mục công khai trên Google Cloud Storage.
Tất cả nội dung công khai của chúng tôi được lưu trữ trong một loạt tệp phẳng trong một Bộ chứa trên Cloud Storage. Điều này có nghĩa là dữ liệu của chúng tôi có thể nhanh chóng truy cập được trên khắp thế giới và chúng tôi không cần phải lo lắng về việc tải lưu lượng truy cập cao sẽ ảnh hưởng đến tính sẵn có của dữ liệu theo bất kỳ cách nào.
Chúng tôi đã sử dụng Cơ sở dữ liệu theo thời gian thực Firebase và các điểm cuối của Hàm trên đám mây để xây dựng một công cụ kiểm duyệt/chọn lọc đơn giản cho phép chúng tôi xem từng nội dung gửi mới trong VR và xuất bản danh sách phát mới từ bất kỳ thiết bị nào.
7. Trình chạy dịch vụ
Trình chạy dịch vụ là một tính năng đổi mới khá mới giúp quản lý việc lưu vào bộ nhớ đệm các thành phần trang web. Trong trường hợp của chúng tôi, worker dịch vụ tải nội dung của chúng tôi nhanh như chớp cho khách truy cập cũ và thậm chí cho phép trang web hoạt động ngoại tuyến. Đây là những tính năng quan trọng vì nhiều khách truy cập của chúng ta sẽ sử dụng kết nối di động có chất lượng khác nhau.
Bạn có thể dễ dàng thêm trình chạy dịch vụ vào dự án nhờ một trình bổ trợ webpack tiện dụng giúp bạn xử lý hầu hết các công việc phức tạp. Trong cấu hình bên dưới, chúng ta tạo một worker dịch vụ sẽ tự động lưu tất cả các tệp tĩnh vào bộ nhớ đệm. Tệp này sẽ lấy tệp danh sách phát mới nhất từ mạng (nếu có) vì danh sách phát sẽ luôn cập nhật. Tất cả các tệp json ghi lại sẽ được lấy từ bộ nhớ đệm nếu có, vì các tệp này sẽ không bao giờ thay đổi.
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
new SWPrecacheWebpackPlugin({
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js',
minify: true,
navigateFallback: 'index.html',
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
runtimeCaching: [{
urlPattern: /playlist\.json$/,
handler: 'networkFirst',
}, {
urlPattern: /\/recordings\//,
handler: 'cacheFirst',
options: {
cache: {
maxEntries: 120,
name: 'recordings',
},
},
}],
})
);
Hiện tại, trình bổ trợ này không xử lý các tài sản nội dung nghe nhìn được tải dần, chẳng hạn như tệp nhạc. Vì vậy, chúng tôi đã giải quyết vấn đề này bằng cách đặt tiêu đề Cache-Control
của Cloud Storage trên các tệp này thành public, max-age=31536000
để trình duyệt sẽ lưu tệp vào bộ nhớ đệm trong tối đa một năm.
Kết luận
Chúng tôi rất hào hứng được xem các nghệ sĩ sẽ bổ sung trải nghiệm này như thế nào và sử dụng nó như một công cụ để thể hiện sự sáng tạo bằng chuyển động. Chúng tôi đã phát hành tất cả các mã nguồn mở. Bạn có thể tìm thấy mã này tại https://github.com/puckey/dance-tonite. Trong giai đoạn đầu của công nghệ thực tế ảo, đặc biệt là WebVR, chúng tôi rất mong được xem những hướng đi sáng tạo và bất ngờ mà phương tiện mới này sẽ mang lại. Bật vũ đạo.