Введение в минималистичную 3D-графику

Забыли, как работают тяжелые движки вроде Unreal или Blender, когда под рукой всего один HTML5 Canvas и чистый JavaScript? Прямо сейчас, когда веб-приложения обрастают гигабайтами зависимостей ради простой кнопки, понимание фундаментального кода становится главным скиллом инженера. Представьте: вы настраиваете свой портфолио или прототипируете интерактивный редактор интерьеров для заказчика, где каждая библиотека третья сторона замедляет загрузку. Написание компактного, но полноценного 3D-моделлера позволяет отбросить избыточные абстракции и сосредоточиться на фундаментальных вещах: матричных преобразованиях, проекции пространства, обработке пользовательского ввода и базовом растеризаторе.

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

Цель этой статьи — разобрать устройство учебного 3D-моделлера, который укладывается всего в 500 строк кода, работает прямо в браузере и демонстрирует, как устроен конвейер трехмерной графики изнутри.

Математический фундамент и векторная алгебра

От концепта виртуальной комнаты до первых пикселей на экране лежит пропасть из формул, но перешагнуть её проще, чем кажется. Любой 3D-моделлер начинается с линейной алгебры. Чтобы оперировать трехмерными объектами на двумерном экране монитора, нам необходим базовый набор математических структур: векторы (Vector3) и матрицы (Matrix4x4). Без сторонних библиотек вроде GLM или Three.js нам придется реализовать их самостоятельно.

Вектор в трехмерном пространстве задается тремя координатами (x, y, z). Над ними должны быть определены стандартные операции:

  • Сложение и вычитание векторов
  • Умножение вектора на скаляр
  • Скалярное и векторное произведения
  • Нормализация (приведение длины к единице)

Для работы с матрицами преобразований нам понадобится матрица размером 4х4, поддерживающая операции трансляции (перемещения), масштабирования и вращения по трем осям (Pitch, Yaw, Roll), а также перспективную проекцию.

Пример реализации базового класса для работы с точками на JavaScript выглядит следующим образом:

class Vector3 {
    constructor(x = 0, y = 0, z = 0) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    add(v) {
        return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z);
    }

    multiplyScalar(s) {
        return new Vector3(this.x * s, this.y * s, this.z * s);
    }
}

Проекция и графический конвейер

Имея на руках математический базис, мы готовы оживить наши данные и превратить абстрактные цифры в осязаемые формы сцены. Классический мини-моделлер оперирует примитивами: вершинами (vertices), ребрами (edges) и полигонами (faces). Наша задача — взять массив точек в мировых координатах и спроецировать их на экранную плоскость.

Графический конвейер в таком приложении состоит из нескольких последовательных шагов:

  1. Локальные координаты: вершины объекта задаются относительно его собственного центра.
  2. Мировые преобразования: объект перемещается, вращается и масштабируется в общем виртуальном пространстве сцены.
  3. Видовое преобразование (Camera View): сцена пересчитывается относительно позиции и направления взгляда виртуальной камеры.
  4. Проекция (Projection): трехмерные координаты трансформируются в двумерные с учетом перспективы.
  5. Растеризация: отрисовка линий и полигонов на холсте (Canvas).

На этапе растеризации критически важна производительность. Код должен эффективно пересчитывать тысячи полигонов в реальном времени, сохраняя стабильный фреймрейт в 60 кадров в секунду.

Интерфейс и обработка пользовательского ввода

Статичная картинка — это скучно, поэтому следующий шаг заставляет наш мини-мир реагировать на каждое движение пользователя. Интерактивность — ключевой элемент любого моделлера. Нам нужно отслеживать события перемещения мыши (mousemove) и нажатия кнопок (mousedown, mouseup), чтобы изменять углы поворота камеры или выбранного объекта.

В минималистичной архитектуре обработка ввода сводится к простому автомату состояний:

  • Флаг isDragging переключается в true при зажатой левой кнопке мыши.
  • Разница между текущим и предыдущим положением курсора преобразуется в угловую скорость вращения сцены.
  • Колесико мыши (wheel event) отвечает за зум (приближение/удаление), меняя коэффициент масштабирования в матрице проекции.

Реализация каркасного (Wireframe) рендерера

Связав воедино математику и реакцию на мышь, мы подходим к самому зрелищному этапу — зарисовке геометрии на холст. Полноценный пиксельный рендеринг с текстурами и освещением по Фонгу занял бы слишком много места. Поэтому в концепции «500 Lines or Less» используется каркасный (wireframe) рендеринг. Мы рисуем только ребра объектов, соединяющие вершины.

Для реализации рендерера нам понадобится стандартный 2D Context HTML5 Canvas. Алгоритм отрисовки кадра:

  1. Очистить экран (залить холст фоновым цветом).
  2. Пройти циклом по всем объектам сцены.
  3. Применить матрицу трансформации к массиву вершин.
  4. Выполнить перспективную проекцию полученных 3D-точек в 2D-координаты экрана.
  5. Соединить вершины линиями с помощью метода ctx.lineTo().

Ниже представлен фрагмент кода, отвечающий за отрисовку ребер на холсте:

function renderWireframe(ctx, transformedVertices, edges, width, height) {
    ctx.clearRect(0, 0, width, height);
    ctx.strokeStyle = '#00ff00';
    ctx.lineWidth = 1.5;

    for (let edge of edges) {
        let p1 = transformedVertices[edge[0]];
        let p2 = transformedVertices[edge[1]];

        let x1 = p1.x + width / 2;
        let y1 = -p1.y + height / 2;
        let x2 = p2.x + width / 2;
        let y2 = -p2.y + height / 2;

        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
    }
}

Заключение

Создание 3D-моделлера в рамках 500 строк кода — это прекрасное упражнение для разработчика, желающего глубоко понять внутреннее устройство компьютерной графики. Мы убедились, что за сложными трехмерными эффектами в современных приложениях стоят базовые принципы линейной алгебры и матричного умножения.

Минималистичный код не только учит нас эффективному использованию ресурсов, но и доказывает, что для создания работающего прототипа сложного ПО не нужны гигантские зависимости. Главное — четкое понимание архитектуры и математической основы проекта. Откройте редактор кода, соберите этот пайплайн своими руками и заставьте трехмерный куб вращаться прямо в браузере уже сегодня!