Фраза про сайт в метавселенной звучит как фантастика, пока её не разобрать на файлы и стандарты. Технически за ней стоят три слоя: трёхмерные ассеты в формате glTF, браузерный API WebXR, который выводит сцену прямо в шлем, и идентичность через кошелёк, заменяющая отдельную регистрацию. Всё это работает уже сейчас в обычных браузерах, без установки приложений, и подготовка сайта сводится к честной инженерной работе: собрать модели, уложиться в бюджет кадра, проверить поддержку устройств и связать уровни доступа с токенами. Ниже разбор каждого слоя с кодом, командами и цифрами, без обещаний и магии.

Трёхмерные ассеты по стандарту glTF вместо плоских картинок

Двумерный веб вырос на форматах JPEG и PNG, трёхмерному нужен собственный язык. Стандарт де-факто - glTF 2.0 от консорциума Khronos: сцена описывается JSON-ом, геометрия и текстуры лежат в бинарных буферах, а весь набор упаковывается в один файл .glb. Формат читают все движки: браузерные three.js и Babylon, игровые Unity и Unreal, встроенные просмотрщики операционных систем. Blender экспортирует glTF нативно, без промежуточных конвертеров: модель с материалами, анимациями и иерархией узлов выгружается в один файл командой из меню.

Размер - первое, за что платят. Сырая сетка с 4K текстурами весит сотни мегабайт и убивает мобильную загрузку, поэтому стандарт растёт расширениями. Геометрию сжимают Draco или meshopt (расширения KHR_draco_mesh_compression и EXT_meshopt_compression), текстуры переводят в GPU-сжатый KTX2 (расширение KHR_texture_basisu), и модель худеет в разы: сетка теряет девять десятых объёма, а текстуры перестают разворачиваться в памяти целиком. Правило практики простое: главная сцена не тяжелее 5-10 МБ, остальное догружается потоково по мере движения посетителя.

glTF переносит не только статичную сетку. Скелетная анимация, морф-целевые формы и анимация материалов записываются в тот же файл, поэтому идущие фигуры, машущие крылья экспонатов и меняющаяся подсветка живут рядом с геометрией. Отдельная дисциплина - запекание: сложное освещение и мелкие детали выдавливают в текстуры заранее, чтобы видеокарта не считала их во время показа. Трёхмерный контент собирают как монтаж видеоролика: сырые исходники в Blender, сборка и запекание отдельно, на сайт уезжает только готовый компактный файл.

Быстрая проверка результата без программирования - веб-компонент model-viewer: одна HTML-строчка даёт вращение мышью, зум и режим AR на телефоне. Атрибуты camera-controls и ar включают интерактив и запуск в реальности, auto-rotate оживляет витрину:


<model-viewer
  src="/assets/product.glb"
  camera-controls
  ar
  auto-rotate
  shadow-intensity="1">
</model-viewer>

Для мира Apple существует параллельный формат USDZ: модель в нём открывается в режиме быстрого просмотра дополненной реальности прямо из поисковой выдачи. Готовят оба варианта одновременно: glTF для веба и сцен WebXR, USDZ для мобильного AR, а исходник в Blender хранят один.

Сцены WebXR в браузере и проверка шлема до загрузки модели

WebXR - браузерный интерфейс к шлему и очкам дополненной реальности. Первый шаг по спецификации W3C - не запуск сцены, а тихий вопрос браузеру:


const supported = await navigator.xr.isSessionSupported("immersive-vr");
if (supported) {
  // можно предлагать кнопку входа в шлем
}

Такой порядок экономит и нервы, и трафик: страница не грузит сотню мегабайт модели человеку, у которого шлема нет. Сама сессия запрашивается методом requestSession с режимом immersive-vr или immersive-ar, а система координат выбирается из референсных пространств: local-floor ставит пол на уровень глаз вниз, что для интерьеров важнее абстрактного центра мира. Работает WebXR только в защищённом контексте браузера, то есть на страницах с шифрованным соединением, и только после явного согласия человека, поэтому кнопку входа в шлем рисуют свою, а не устраивают автозапуск.

В three.js вход в шлем занимает две строки: флаг xr.enabled у рендерера и цикл setAnimationLoop вместо старого requestAnimationFrame, потому что кадры теперь отсчитывает шлем, а не монитор:


import * as THREE from "three";
import { VRButton } from "three/addons/webxr/VRButton.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.xr.enabled = true;
document.body.appendChild(renderer.domElement);
document.body.appendChild(VRButton.createButton(renderer));

const draco = new DRACOLoader();
draco.setDecoderPath("assets/draco/"); // файлы декодера лежат рядом с сайтом

const loader = new GLTFLoader();
loader.setDRACOLoader(draco);
loader.load("assets/scene.glb", (gltf) => scene.add(gltf.scene));

renderer.setAnimationLoop(() => {
  renderer.render(scene, camera); // частоту задаёт шлем: 72, 90 или 120 Гц
});

Ввод в шлеме - это два контроллера с осями, кнопками и триггерами, события которых приходят в сессию как устройства ввода. В three.js контроллеры получают через renderer.xr.getController(0) и getController(1), модель геймпада подгружается фабрикой XRControllerModelFactory, а луч указателя рисуется обычной линией сцены. Пространственный звук - второй якорь присутствия: аудиопоток привязывается к точке сцены, и собеседник слышится слева именно потому, что стоит слева. Для AR-режимов арсенал шире: hit-test ищет точку на реальной поверхности, куда ставится модель, анкеры закрепляют объекты в пространстве, а DOM overlay выводит обычные HTML-кнопки поверх камеры. Набор возможностей сессии запрашивается списками requiredFeatures и optionalFeatures, и страница обязана честно деградировать: нет hit-test - нет и раскладки мебели, но витрина продолжает работать.

Бюджет кадра мобильного шлема и арифметика 14 миллисекунд

Виртуальная реальность прощает меньше, чем плоский веб. Кадр должен уложиться в долю секунды с точностью до миллисекунды, иначе изображение дёргается, а у человека за шлемом начинается укачивание. Арифметика жёсткая: 72 кадра в секунду означают бюджет 13.9 миллисекунды на всё - опрос контроллеров, анимацию, рендер, 90 кадров оставляют 11.1, а 120 оставляют 8.3. Мобильные шлемы работают на частотах 72, 90 или 120 в зависимости от модели, и ориентироваться нужно на худший целевой режим.

Дальше умножение: стереорендер рисует картинку дважды, по разу на каждый глаз, так что нагрузка почти удваивается. Отсюда практические бюджеты, к которым приходят команды: сцена порядка 100-150 тысяч треугольников, материалы считаются по-простому, тени и постобработка первыми выключаются, когда кадр поехал. Именно вызовы отрисовки, а не полигоны, душат мобильные сцены: двести объектов по 500 треугольников убьют кадр быстрее, чем один объект из 100 тысяч, ведь каждый вызов - отдельный разговор процессора с видеокартой. Лечение классическое: атласы текстур, объединение сеток, инстансинг повторяющихся объектов и уровни детализации, подменяющие дальние модели грубыми копиями.

Спасает и периферия: фиксированная фовеация рендерит центр поля зрения в полном разрешении, а края грубее, глаз всё равно не замечает. В three.js это настраивается одной строкой:


renderer.xr.setFoveation(1); // максимальное упрощение периферии

Замеры делают на реальных устройствах, а не в эмуляторе: статистика рендерера, счётчик кадров и подсказки браузера покажут, где именно сцена выходит за бюджет.

Токен-гейт открывает закрытые разделы держателям NFT токенов

Идентичность в трёхмерном мире повторяет клубную карту: показал токен - вошёл. Роль карты играет NFT в кошельке, а проверка - вызов balanceOf у контракта коллекции, механика знакома по блокчейн-витринам. Уровни доступа задаются порогами: один токен открывает обычный раздел, три - закрытую комнату, пять - ранний показ новинок. Код проверки умещается в пару строк:


const amount = await gate.methods.balanceOf(account).call();
const level =
  amount >= 5n ? "founder" :
  amount >= 3n ? "insider" :
  amount > 0n ? "guest" : null;
if (level) {
  // открыть разделы, соответствующие уровню
}

Деградацию строят мирно: человеку без токена показывают публичную часть мира и объяснение, как попасть внутрь, а не пустой экран с ошибкой. Пороги проверяются и на сервере при выдаче контента: клиентская проверка открывает интерфейс, но тяжёлые ассеты закрытого раздела отдаются только после серверной сверки баланса, иначе токен-гейт превращается в декорацию. Тот же кошелёк обслуживает и вход: одноразовый код с сервера подписывается в кошельке, сервер восстанавливает адрес из подписи и выдаёт сессию без паролей. Пороги сначала проверяют на тестовой сети Sepolia с номером 11155111: там токены раздаются из крана бесплатно, и можно спокойно проверить все уровни доступа до выхода в основную сеть.

Аватары по стандарту VRM и имена из кошелька вместо профилей

Житель метавселенной - это аватар, и его переносимость между мирами закреплена открытым стандартом VRM, который строится поверх glTF. Внутри файла скелет humanoid с единой картой костей, блендшейпы для эмоций, пружинные кости для физики волос и одежды, шейдер MToon для стилизованной графики. Один и тот же файл аватара загружается в разные миры, поддерживающие стандарт, и человек сохраняет внешность без переконвертации, как один пароль в менеджере открывает разные сайты. Речь и мимика достаются аватару из коробки: блендшейпы открывают улыбку и удивление, виземы подстраивают губы под фонемы, и во встрече внутри мира участники узнают друг друга по привычной внешности, а не по безликим кружкам с инициалами.

Имя решает та же связка, что и доступы: кошелёк и открытые сервисы имён. Запись вида name.eth резолвится в адрес, работает в любых приложениях сразу и не требует регистрации на каждой площадке. Для сайта это значит, что профиль посетителя собирается из открытых записей: имя, аватар, список токенов - всё читается из сети, а подпись сообщения кошельком заменяет пароль. Человек заходит в мир с той же карточкой, которой открывал закрытые разделы, и площадка не требует заполнять лишние формы.

Распределённое хранение тяжёлых ассетов и адресация по хешу

Трёхмерный контент тяжёлый, и хранение встаёт ребром. Распределённые файловые сети адресуют содержимое хешем: идентификатор вычисляется из файла, изменился байт - изменился адрес, и подменить модель под старым адресом невозможно в принципе. Это удобно для метавселенных: сцена однажды опубликована, и указатель на неё вечно ведёт ровно к той версии. Побочный эффект - неизменность: обновление модели означает новый адрес, и реестр версий ведёт либо смарт-контракт, либо сервер метаданных. Отдельный плюс для долгих проектов: старые версии не пропадают, и демо трёхлетней давности открывается в точности таким, каким его опубликовали.

Хостинг решается пиннингом: узлы сети держат копию за договорённую плату, а сеть Arweave хранит файл после однократного взноса, без ежемесячных платежей. Человеческие имена вида site.eth ссылаются на актуальный хеш через запись в реестре имён, и адрес остаётся прежним при смене версий. Для больших сцен держат два слоя: холодное хранение ассетов в сети и быстрые шлюзы с кэшированием у ребра, иначе загрузка сотни мегабайт превратится в минуты ожидания у первого же посетителя.

План подготовки сайта к трёхмерному присутствию по шагам

Сборка всех слоёв в один план выглядит так:

  1. Инвентаризировать контент и выбрать, что станет трёхмерным: продукция, пространства, интерфейсы;
  2. Собрать ассеты в glTF со сжатием Draco и текстурами KTX2, проверить в model-viewer на телефонах;
  3. Поднять WebXR сцену с проверкой поддержки шлема до загрузки и честной деградацией до плоской страницы;
  4. Связать уровни доступа с токенами и проверить пороги на тестовой сети до основного запуска;
  5. Замерить бюджет кадра на целевых шлемах и добиться стабильных 72 кадров в секунду на средней модели.

Метавселенная в этой картине - не жанр фантастики, а слой протоколов поверх обычного сайта: те же страницы, дополненные glTF-моделями, сессиями WebXR и доступом по токенам. Ни один кусок не требует пересборки бизнеса с нуля, каждый добавляется отдельно и тестируется отдельно. Владелец магазина, у которого уже есть сайт, находится ближе к трёхмерному присутствию, чем ему кажется: инвентарь ассетов, один WebGL-рендерер и честный бюджет кадра - вот и вся волшебная дверь.