Передача данных в воркер: OffscreenCanvas

Three.js активно использует возможности WebGL для рендеринга 3D-сцен в браузере. При сложных сценах или интенсивных вычислениях основная нагрузка ложится на главный поток (main thread), что может приводить к задержкам интерфейса. Для решения этой проблемы используется OffscreenCanvas вместе с веб-воркерами (Web Workers), что позволяет переносить рендеринг в отдельный поток.

Создание OffscreenCanvas

OffscreenCanvas создается аналогично обычному <canvas>, но при этом не привязан напрямую к DOM. Основной способ создания:

const canvas = new OffscreenCanvas(width, height);

Его можно передавать в воркер через postMessage с указанием { transfer: [canvas] }, чтобы воркер получил полный контроль над объектом:

worker.postMessage({ canvas: canvas }, [canvas]);

Это позволяет в воркере использовать WebGL-контекст без участия главного потока.

Настройка Three.js для работы в воркере

В воркере создается рендерер, который принимает OffscreenCanvas:

import * as THREE from 'three';

self.onmess age = (event) => {
    const canvas = event.data.canvas;
    const renderer = new THREE.WebGLRenderer({ canvas });
    renderer.setSize(canvas.width, canvas.height);

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, canvas.width / canvas.height, 0.1, 1000);

    // Настройка геометрии и материалов
    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    camera.position.z = 5;

    function animate() {
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
    }

    animate();
};

Ключевой момент: requestAnimationFrame в воркере работает только если используется OffscreenCanvas, иначе кадры не будут обновляться автоматически.

Передача размеров и событий из основного потока

Чтобы сцена корректно реагировала на изменения окна и пользовательский ввод, необходимо передавать события из главного потока в воркер:

window.addEventListener('resize', () => {
    worker.postMessage({ type: 'resize', width: window.innerWidth, height: window.innerHeight });
});

canvas.addEventListener('mousemove', (event) => {
    worker.postMessage({ type: 'mousemove', x: event.clientX, y: event.clientY });
});

В воркере нужно обработать эти сообщения и обновить параметры камеры и рендерера:

self.onmess age = (event) => {
    if(event.data.type === 'resize') {
        renderer.setSize(event.data.width, event.data.height);
        camera.aspect = event.data.width / event.data.height;
        camera.updateProjectionMatrix();
    } else if(event.data.type === 'mousemove') {
        // Можно использовать координаты для управления камерой или объектами
    }
};

Ограничения и особенности

  1. Контекст WebGL в воркере создается только с OffscreenCanvas. Обычные <canvas> не поддерживают работу с воркером напрямую.
  2. Нет доступа к DOM внутри воркера, поэтому все элементы интерфейса и события нужно передавать через postMessage.
  3. Рендеринг с текстурами требует дополнительной передачи данных, например, через ImageBitmap или fetch, чтобы воркер мог загрузить изображения.
  4. Поддержка браузеров: современные версии Chrome, Edge и Firefox полностью поддерживают OffscreenCanvas с WebGL, Safari — частично, без поддержки WebGL2 в воркере.

Использование ImageBitmap и TextureLoader

Для передачи изображений в воркер рекомендуется использовать createImageBitmap, что минимизирует нагрузку на главный поток:

const img = new Image();
img.src = 'texture.png';
img.onl oad = async () => {
    const bitmap = await createImageBitmap(img);
    worker.postMessage({ type: 'texture', bitmap }, [bitmap]);
};

В воркере этот bitmap можно использовать для создания текстуры:

const texture = new THREE.CanvasTexture(event.data.bitmap);
material.map = texture;
material.needsUpdate = true;

Практический пример оптимизации

  • Большие сцены с сотнями объектов, анимацией частиц или динамическими тенями, перенесенные в воркер с OffscreenCanvas, позволяют поддерживать плавность интерфейса выше 60 FPS.
  • Можно использовать несколько воркеров, если сцена разделена на независимые слои или эффекты. Каждый воркер будет рендерить свой OffscreenCanvas, который затем объединяется на основном канвасе через drawImage.

Интеграция с основным DOM

Основной поток может отобразить результат воркера, создавая обычный <canvas> и связывая его с OffscreenCanvas:

const mainCanvas = document.getElementById('mainCanvas');
const offscreen = mainCanvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);

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

Рекомендации по производительности

  • Минимизировать пересылку больших данных между потоками, использовать transfer вместо копирования.
  • Вычисления анимации и физики объектов лучше выполнять в воркере, оставляя основной поток для UI.
  • Обновление текстур, шейдеров и геометрии следует группировать в одно сообщение, чтобы снизить количество postMessage.

Эта архитектура позволяет создавать интерактивные 3D-приложения высокой сложности без просадок FPS и зависаний интерфейса, полностью используя возможности многопоточности браузера через OffscreenCanvas и Web Workers в связке с Three.js.