Современные веб-приложения с трёхмерной графикой сталкиваются с ограничениями основного потока браузера. JavaScript традиционно выполняется в одном потоке, который одновременно отвечает за обработку пользовательского ввода, работу интерфейса, выполнение скриптов и рендеринг. При высокой нагрузке, связанной с трёхмерной графикой, это приводит к снижению частоты кадров, задержкам интерфейса и блокировке пользовательских действий.
Механизм Offscreen Canvas позволяет перенести процесс рендеринга в отдельный поток, используя Web Workers. В сочетании с возможностями Babylon.js это открывает путь к многопоточному рендерингу, где графический движок работает независимо от пользовательского интерфейса.
В стандартной архитектуре веб-приложения происходит следующая последовательность действий:
HTMLCanvasElement.Проблема заключается в том, что рендеринг и логика приложения конкурируют за одни и те же ресурсы. Если сцена сложная, интерфейс начинает работать с задержкой.
Многопоточная архитектура решает эту проблему разделением обязанностей:
| Поток | Назначение |
|---|---|
| Main Thread | UI, DOM, пользовательский ввод |
| Worker | рендеринг сцены Babylon.js |
| GPU | выполнение WebGL команд |
Offscreen Canvas выступает связующим элементом между Worker и графическим процессором.
OffscreenCanvas — специальный объект Canvas, который может использоваться вне DOM и передаваться между потоками.
Основные возможности:
Объект создаётся двумя основными способами:
const canvas = document.getElementById("renderCanvas");
const offscreen = canvas.transferControlToOffscreen();
Метод transferControlToOffscreen() передаёт управление
канвасом в другой поток.
Web Worker представляет собой отдельный поток выполнения JavaScript, изолированный от DOM.
Инициализация Worker:
const worker = new Worker("renderWorker.js");
worker.postMessage({
canvas: offscreen
}, [offscreen]);
Передача OffscreenCanvas выполняется через Transferable Objects, что предотвращает копирование памяти.
Worker получает канвас следующим образом:
self.onmess age = function(event) {
const canvas = event.data.canvas;
};
Теперь весь рендеринг может выполняться внутри Worker.
Babylon.js поддерживает OffscreenCanvas благодаря использованию стандартного WebGL API.
Пример создания движка:
importScripts("babylon.js");
self.onmess age = function(event) {
const canvas = event.data.canvas;
const engine = new BABYLON.Engine(canvas, true);
const scene = createScene(engine);
engine.runRenderLoop(function() {
scene.render();
});
};
Функция создания сцены может выглядеть следующим образом:
function createScene(engine) {
const scene = new BABYLON.Scene(engine);
const camera = new BABYLON.ArcRotateCamera(
"camera",
Math.PI / 2,
Math.PI / 3,
5,
BABYLON.Vector3.Zero(),
scene
);
const light = new BABYLON.HemisphericLight(
"light",
new BABYLON.Vector3(0, 1, 0),
scene
);
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {
diameter: 2
}, scene);
camera.attachControl(true);
return scene;
}
В данном случае весь цикл рендеринга выполняется в Worker, не затрагивая основной поток.
Worker не имеет прямого доступа к DOM и событиям интерфейса. Поэтому ввод необходимо пересылать вручную.
Основной поток перехватывает события:
canvas.addEventListener("pointermove", function(event) {
worker.postMessage({
type: "pointerMove",
x: event.clientX,
y: event.clientY
});
});
Worker принимает сообщение:
self.onmess age = function(event) {
const data = event.data;
if (data.type === "pointerMove") {
handlePointerMove(data.x, data.y);
}
};
Для корректной работы камер Babylon.js используется система Input Manager, поэтому события могут быть преобразованы в структуру, совместимую с движком.
DOM остаётся доступным только в основном потоке, поэтому изменение размеров необходимо синхронизировать.
Основной поток:
function resize() {
const rect = canvas.getBoundingClientRect();
worker.postMessage({
type: "resize",
width: rect.width,
height: rect.height
});
}
window.addEventListener("resize", resize);
Worker:
if (data.type === "resize") {
engine.setSize(data.width, data.height);
}
Это обеспечивает корректное масштабирование сцены.
Babylon.js поддерживает загрузку моделей, текстур и других ресурсов непосредственно внутри Worker.
Пример загрузки glTF модели:
BABYLON.SceneLoader.Append(
"models/",
"scene.glb",
scene,
function () {
console.log("Model loaded");
}
);
Браузер автоматически выполняет сетевые запросы из Worker, поэтому ограничений на загрузку ассетов нет.
Несмотря на преимущества, технология имеет ряд ограничений.
Worker не может:
Любая операция, связанная с DOM, должна выполняться в основном потоке.
OffscreenCanvas поддерживается не во всех браузерах одинаково.
Наиболее стабильная поддержка наблюдается в:
Некоторые платформы могут ограничивать WebGL в Worker.
Некоторые расширения WebGL могут быть недоступны в Worker, что влияет на:
Babylon.js автоматически проверяет доступность возможностей GPU.
Основные преимущества многопоточного рендеринга:
1. Разгрузка основного потока
Интерфейс остаётся отзывчивым даже при сложной сцене.
2. Параллельная обработка
Логика приложения может выполняться одновременно с рендерингом.
3. Более стабильный FPS
Исключаются блокировки UI-потока.
Наибольший эффект достигается в следующих сценариях:
Типичная архитектура приложения с Babylon.js и OffscreenCanvas:
Main Thread
├─ UI
├─ DOM
├─ обработка событий
└─ отправка сообщений Worker
Worker
├─ Babylon Engine
├─ Scene
├─ Physics
└─ Render Loop
GPU
└─ WebGL execution
Связь между потоками осуществляется через
postMessage.
При многопоточном рендеринге важна минимизация коммуникации между потоками.
Рекомендуемые практики:
Пример передачи массива:
worker.postMessage({
vertices: vertexArray
}, [vertexArray.buffer]);
Буфер передаётся без копирования, что значительно ускоряет обмен.
В сложных приложениях логика может выполняться в основном потоке, а сцена — в Worker.
Для синхронизации используется система сообщений:
Main Thread → Worker
управление камерой
пользовательский ввод
Worker → Main Thread
состояние сцены
события столкновений
Babylon.js не накладывает ограничений на эту архитектуру.
Отладка усложняется из-за разделения потоков.
Рекомендации:
console.log внутри WorkerНекоторые браузеры позволяют открывать отдельную консоль для Worker.
Пример структуры файлов:
/project
index.html
main.js
renderWorker.js
babylon.js
/models
/textures
Такое разделение упрощает поддержку проекта.
OffscreenCanvas часто используется вместе с:
WebAssembly
Высокопроизводительные вычисления могут выполняться параллельно с рендерингом.
Physics Engines
Физические движки могут работать в отдельном Worker.
Procedural Generation
Генерация ландшафтов и геометрии может выполняться асинхронно.
Развитие многопоточных технологий в веб-графике продолжается.
Основные направления:
Babylon.js активно адаптируется к этим изменениям, обеспечивая поддержку современных возможностей браузера и GPU.