WebGL и компоненты для 3D

Lit — современный фреймворк для создания Web Components, который обеспечивает реактивность, шаблонность и высокую производительность. Он идеально подходит для интеграции с WebGL, позволяя создавать интерактивные 3D-приложения на вебе с использованием компонетной архитектуры.


Создание базового 3D-компонента

Для работы с WebGL внутри Lit-компонента используется стандартный элемент. Основная структура компонента включает:

import { LitElement, html, css } from 'lit';

class ThreeDComponent extends LitElement {
  static styles = css`
    canvas {
      width: 100%;
      height: 100%;
      display: block;
    }
  `;

  firstUpdated() {
    this.initWebGL();
  }

  initWebGL() {
    const canvas = this.renderRoot.querySelector('canvas');
    const gl = canvas.getContext('webgl2');

    if (!gl) {
      console.error('WebGL2 не поддерживается этим браузером');
      return;
    }

    // Настройка сцены
    gl.clearColor(0.1, 0.1, 0.1, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
  }

  render() {
    return html``;
  }
}

customElements.define('three-d-component', ThreeDComponent);

Ключевые моменты:

  • firstUpdated() используется для инициализации WebGL после того, как DOM компонента готов.
  • this.renderRoot предоставляет доступ к Shadow DOM компонента, позволяя работать с локальными элементами.
  • Контекст webgl2 обеспечивает современные возможности OpenGL ES 3.0, включая шейдеры и буферы.

Реактивность и свойства компонента

Lit позволяет объявлять реактивные свойства, которые автоматически обновляют компонент при изменении:

import { property } from 'lit/decorators.js';

class ThreeDComponent extends LitElement {
  @property({ type: Number }) rotationX = 0;
  @property({ type: Number }) rotationY = 0;

  // Обновление сцены при изменении свойств
  updated(changedProperties) {
    if (changedProperties.has('rotationX') || changedProperties.has('rotationY')) {
      this.renderScene();
    }
  }

  renderScene() {
    const canvas = this.renderRoot.querySelector('canvas');
    const gl = canvas.getContext('webgl2');
    // Логика обновления сцены с использованием rotationX и rotationY
  }
}

Примечание: Свойства с декоратором @property автоматически отслеживаются, и updated() вызывается при любом изменении их значений.


Интеграция с Three.js

Для упрощения работы с WebGL часто используют библиотеку Three.js. Lit позволяет легко интегрировать её, сохраняя реактивность компонентов:

import * as THREE from 'three';

class ThreeJSComponent extends LitElement {
  firstUpdated() {
    const canvas = this.renderRoot.querySelector('canvas');
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 1000);
    this.renderer = new THREE.WebGLRenderer({ canvas });

    this.camera.position.z = 5;
    this.cube = this.createCube();
    this.scene.add(this.cube);

    this.animate();
  }

  createCube() {
    const geometry = new THREE.BoxGeometry();
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    return new THREE.Mesh(geometry, material);
  }

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

  render() {
    return html``;
  }
}

customElements.define('three-js-component', ThreeJSComponent);

Особенности интеграции:

  • Использование Shadow DOM не мешает работе WebGL, так как остаётся доступным для рендеринга.
  • Анимация реализуется через рекурсивный requestAnimationFrame, который поддерживает плавные обновления сцены.
  • Свойства Lit могут управлять объектами Three.js, обеспечивая реактивное взаимодействие между UI и 3D-сценой.

Управление событиями и интерактивностью

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

render() {
  return html``;
}

onMouseMove(event) {
  const rect = this.renderRoot.querySelector('canvas').getBoundingClientRect();
  this.rotationX = (event.clientY - rect.top) / rect.height * 2 - 1;
  this.rotationY = (event.clientX - rect.left) / rect.width * 2 - 1;
}

Преимущества:

  • События можно привязывать прямо в шаблоне через директиву @event.
  • Значения автоматически обновляют реактивные свойства и перерисовывают сцену.

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

Для 3D-приложений важно учитывать производительность:

  • Перерисовка только при изменении состояния: использовать реактивные свойства и updated(), чтобы не рендерить сцену лишний раз.
  • Использование requestAnimationFrame: обеспечивает синхронизацию с частотой обновления экрана.
  • Локальные буферы и объекты: хранить объекты Three.js в свойствах компонента для избежания повторной инициализации.

Структурирование больших 3D-компонентов

Для крупных приложений рекомендуется разделять логику:

  1. Компонент сцены: управляет WebGL или Three.js, анимацией и рендерингом.
  2. Компоненты объектов: отдельные Lit-компоненты для кубов, моделей, света, которые добавляются в сцену через API сцены.
  3. Контроллеры событий: отдельные методы или классы для управления вводом пользователя, камеры и интерактивности.

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