Запись видео с canvas через MediaRecorder

Для записи видео с Three.js необходимо корректно настроить сцену, камеру и рендерер. В стандартной схеме используется WebGLRenderer с указанием canvas, который будет использоваться для вывода и последующей записи.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

Ключевой момент: preserveDrawingBuffer: true — этот параметр необходим для того, чтобы содержимое канваса оставалось доступным для MediaRecorder после каждого кадра.


Подготовка анимации

Three.js поддерживает анимацию через функцию requestAnimationFrame. Для записи важно, чтобы каждый кадр рендерился синхронно с MediaRecorder:

function animate() {
    requestAnimationFrame(animate);

    // Простейшая анимация
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
}

animate();

Сцена может содержать любые объекты: геометрию, материалы, свет и тени. Например, добавление куба:

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

camera.position.z = 5;

Создание потока для MediaRecorder

Для записи видео используется поток из канваса методом captureStream(). Поток передается конструктору MediaRecorder:

const canvasStream = renderer.domElement.captureStream(60); // 60 FPS
const options = { mimeType: 'video/webm; codecs=vp9' };
const mediaRecorder = new MediaRecorder(canvasStream, options);

Параметр 60 задает частоту кадров. Поддерживаемые MIME-типы зависят от браузера: video/webm; codecs=vp9, video/webm; codecs=vp8 или video/mp4.


Сбор записанных данных

MediaRecorder работает асинхронно и генерирует данные через события dataavailable:

const recordedChunks = [];

mediaRecorder.ondataavaila ble = function(event) {
    if (event.data.size > 0) {
        recordedChunks.push(event.data);
    }
};

mediaRecorder.ons top = function() {
    const blob = new Blob(recordedChunks, { type: 'video/webm' });
    const url = URL.createObjectURL(blob);

    // Создание ссылки для скачивания
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = 'threejs_animation.webm';
    document.body.appendChild(a);
    a.click();
    URL.revokeObjectURL(url);
};

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


Управление записью

MediaRecorder предоставляет методы start(), pause(), resume() и stop() для управления процессом:

mediaRecorder.start(); // Начало записи

setTimeout(() => {
    mediaRecorder.stop(); // Остановка через 5 секунд
}, 5000);

Можно синхронизировать остановку записи с завершением анимации или любым событием в приложении.


Синхронизация рендеринга и записи

Для плавной записи рекомендуется:

  1. Использовать фиксированный FPS для потока (captureStream(60)).
  2. Не перегружать сцену тяжелыми эффектами, чтобы не падала частота кадров.
  3. Включить preserveDrawingBuffer, иначе MediaRecorder может получать пустые кадры.

Для сложных сцен можно рендерить кадры вручную и вызывать ctx.drawImage на отдельный OffscreenCanvas, но в большинстве случаев стандартного рендерера достаточно.


Продвинутая настройка кодека и качества

  • MediaRecorder поддерживает videoBitsPerSecond для управления качеством:
const mediaRecorder = new MediaRecorder(canvasStream, {
    mimeType: 'video/webm; codecs=vp9',
    videoBitsPerSecond: 5_000_000 // 5 Мбит/с
});
  • Выбор кодека зависит от целевой платформы: vp8 и vp9 подходят для веба, h264 доступен не во всех браузерах.

Пример комплексной сцены для записи

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.TorusKnotGeometry(1, 0.4, 128, 32);
const material = new THREE.MeshStandardMaterial({ color: 0xff6347, metalness: 0.5, roughness: 0.1 });
const torusKnot = new THREE.Mesh(geometry, material);
scene.add(torusKnot);

const light = new THREE.PointLight(0xffffff, 1);
light.position.set(10, 10, 10);
scene.add(light);

camera.position.z = 5;

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

animate();

const stream = renderer.domElement.captureStream(60);
const recorder = new MediaRecorder(stream, { mimeType: 'video/webm; codecs=vp9', videoBitsPerSecond: 5_000_000 });
const chunks = [];

recorder.ondataavaila ble = e => { if (e.data.size > 0) chunks.push(e.data); };
recorder.ons top = () => {
    const blob = new Blob(chunks, { type: 'video/webm' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'torusKnot.webm';
    a.click();
    URL.revokeObjectURL(url);
};

recorder.start();
setTimeout(() => recorder.stop(), 10000);

Этот пример демонстрирует полную цепочку: создание сцены, анимация объекта, захват канваса, запись и сохранение видео.