Cả một hành tinh gói trong tab trình duyệt
Ảnh vệ tinh thật, bốn mươi sáu nghìn điểm sáng đô thị, và đúng một dòng lượng giác cầu để trị dứt điểm cái camera, gói gọn trong khoảng 1.5 megabyte.
5 phút đọcViết từ Seoul và Nha Trang
Trang web của chúng tôi giờ có một chương mà chỉ cần cuộn chuột là camera lướt qua quỹ đạo thấp, ghé thăm bốn nơi chúng tôi đặt cơ sở hoạt động. Nhìn thì tưởng nó phải nặng năm mươi megabyte. Thực ra chỉ cỡ 1.5, và gần như được dựng hoàn toàn từ dữ liệu khoa học thuộc phạm vi công cộng.
Dữ liệu thật, bake sẵn từ đầu
- Hành tinh là ảnh NASA Blue Marble, do một generator chạy lúc build lo việc resize và nén lại. Không bao giờ chỉnh tay: muốn đổi gì thì sửa generator rồi bake lại.
- Ánh đèn đô thị không phải một texture. Một script lấy mẫu từ raster ảnh đèn đêm của NASA thành khoảng 46,000 điểm có trọng số, dồn vào một buffer nhị phân nhỏ gọn, rồi render thành các sprite phát sáng. Sắc nét ở mọi mức zoom, vì chúng là geometry chứ không phải pixel.
- Các cảnh cận dùng mẹo patch hai tầng: ảnh toàn Trái Đất chỉ đạt cỡ 10 km trên mỗi pixel, vô vọng ở độ cao 200 km, nên generator chỉ bake các patch độ phân giải cao (mịn tới 76 m trên mỗi pixel, từ dữ liệu Sentinel-2 không mây) quanh đúng bốn thành phố mà camera thực sự ghé qua.
Các patch hòa trộn trong fragment shader, được làm mờ ở viền để không bao giờ lộ đường ghép, và lớp procedural detail noise bên dưới mờ dần đi để ảnh chụp không phải chọi nhau với phần tổng hợp:
float fe = smoothstep(edge, edge - feather, dist); // patch edge feather
vec3 col = mix(base, patchTexel, fe * uPatch);
col *= 1.0 - patchAmt * 0.75; // damp noise under imagery
Một dòng lượng giác trị dứt cái camera
Cuộn chuột điều khiển một chuyến bay theo vòng tròn lớn (great-circle) giữa các điểm dừng. Cách nhắm camera hiển nhiên nhất, normalize(next - pos), lại co về vector không đúng ngay tại hai đầu mút, và đó là lý do những camera bay lướt kiểu ngây thơ hay khựng lại lúc tới nơi. Cách sửa là dùng chính tiếp tuyến giải tích của phép nội suy cầu, ổn định ở khắp nơi, kể cả t = 0 và t = 1:
// bearing along slerp(a, b) at parameter t, W = angle between a and b
tangent(t) = normalize( -cos((1 - t) * W) * a + cos(t * W) * b )
Lúc đến nơi, camera ổn định ở hướng bắc-lên, giữ yên trong khi hạ độ cao, rồi xoay về phía đích tiếp theo và cất cánh với một cú nghiêng nhẹ, tất cả đều được điều khiển bằng các góc có dấu giữa tiếp tuyến đó và hướng bắc cục bộ. Ánh sáng cố tình làm theo kiểu điện ảnh: mặt trời mọc lên đúng lúc camera vừa tới để địa hình nổi rõ, rồi lặn dần về một dải sáng bình minh cho chặng bay đêm.
Hiệu năng trung thực
ema = ema * 0.9 + frameMs * 0.1; // frame-time moving average
if (ema > 24.0 && px > FLOOR) {
px -= 0.2; // shed resolution before jank
renderer.setPixelRatio(px);
}
Một bộ điều tiết (governor) theo dõi frame time đang chạy rồi hạ dần độ phân giải nội bộ xuống trước khi một GPU yếu bắt đầu rớt frame, kèm một mức sàn để chữ và đường bờ biển vẫn sắc nét. Asset chỉ tải khi sắp đến chương, và cả cỗ máy nằm im với chi phí bằng không khi ở ngoài màn hình. Hình ảnh kiểu "câu giải thưởng" thì được phép. Còn giật lag kiểu "câu giải thưởng" thì không.