PerfectVector
Автор: Irene Kim8 мин чтения

Three.js SVGLoader: из плоской графики в 3D-меш

Загрузите контуры SVG в Three.js, сохраните настоящее отверстие, исправьте вертикальную ориентацию и задайте размер выдавленного меша на небольшом воспроизводимом примере.

На этой странице

Чтобы превратить плоскую графику SVG в меш Three.js, загрузите SVG, преобразуйте его контуры в фигуры, затем передайте их в ExtrudeGeometry. Проверьте отверстия, вертикальную ориентацию и размеры перед добавлением эффектов. Узнаваемый силуэт всё ещё может иметь заполненный просвет или перевёрнутый угол.

Этот пример использует собственный значок, написанный вручную, с одним квадратным отверстием и срезанным верхним правым углом. Асимметрия делает ошибку ориентации заметной. Это браузерный пример геометрии, а не результат преобразования PerfectVector или производственное испытание.

Начните с фигур, которым хотите придать глубину

Для первой попытки используйте небольшой SVG с замкнутыми залитыми контурами. Сохраните это как badge.svg:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 180">
  <path fill="#e64b35" fill-rule="evenodd"
    d="M20 20H160L220 80V160H20Z M65 60H115V110H65Z"/>
</svg>

Внутри одного элемента path находятся два замкнутых подконтура: внешняя граница и внутренний квадрат. Правило заливки evenodd превращает внутреннюю область в отверстие. Отдельный белый прямоугольник лишь закрыл бы поверхность на плоской картинке; он не описывал бы ту же пустую область.

Графика занимает 200 на 140 координатных единиц внутри viewBox 240 на 180. Учитывайте это различие при настройке размера меша. Пустые отступы — часть области просмотра SVG, а не выдавленного силуэта.

Если есть редактируемый векторный оригинал, используйте его. Если единственный исходник — PNG или JPG, процесс преобразования изображения в вектор PerfectVector может помочь подготовить графику из контуров SVG. Проверьте предпросмотр и скачанные контуры, особенно пустые области, перед загрузкой файла в Three.js. Векторизация даёт 2D-графику; код ниже даёт глубину, материалы, освещение и размещение.

Загрузите SVG и отрисуйте меш

Пример проверен с Three.js 0.186.0. Сохраняйте одинаковую версию основной библиотеки и дополнения. В браузерном проекте с импортами пакетов установите three@0.186.0, предоставьте файлы через HTTP и создайте контейнер:

<div id="viewer"></div>
<script type="module" src="./main.js"></script>

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

<script type="importmap">
{
  "imports": {
    "three": "./node_modules/three/build/three.module.js",
    "three/addons/": "./node_modules/three/examples/jsm/"
  }
}
</script>

Поместите badge.svg рядом с HTML-страницей. Относительный URL загрузки ниже разрешается относительно адреса этой страницы. Затем сохраните это как main.js:

import * as THREE from 'three';
import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
 
const data = await new SVGLoader().loadAsync('./badge.svg');
const artwork = new THREE.Group();
for (const path of data.paths) {
  if (path.userData.style.fill === 'none') continue;
  for (const shape of path.toShapes()) {
    const geometry = new THREE.ExtrudeGeometry(shape, {
      depth: 20, bevelEnabled: false, steps: 1
    });
    const material = new THREE.MeshStandardMaterial({
      color: path.color, roughness: 0.65, metalness: 0
    });
    artwork.add(new THREE.Mesh(geometry, material));
  }
}
if (!artwork.children.length) throw new Error('No filled shapes found');
 
// SVG y grows down; this scene uses y up.
artwork.scale.y = -1;
const bounds = new THREE.Box3().setFromObject(artwork);
const size = bounds.getSize(new THREE.Vector3());
const center = bounds.getCenter(new THREE.Vector3());
const scale = 4 / size.x;
artwork.scale.multiplyScalar(scale);
artwork.position.copy(center).multiplyScalar(-scale);
 
const scene = new THREE.Scene();
scene.background = new THREE.Color('#f1f3f5');
scene.add(artwork);
scene.add(new THREE.HemisphereLight(0xffffff, 0x596477, 2));
const light = new THREE.DirectionalLight(0xffffff, 3);
light.position.set(-3, 5, 7);
scene.add(light);
const camera = new THREE.PerspectiveCamera(35, 800 / 600, 0.1, 100);
camera.position.set(4, 3, 9);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(800, 600);
document.querySelector('#viewer').append(renderer.domElement);
renderer.render(scene, camera);

SVGLoader — отдельно импортируемое дополнение. loadAsync() читает файл; parse(svgText) — альтернатива, если разметка SVG уже есть в строке.

В r186 используйте path.toShapes(). Старые примеры часто вызывают SVGLoader.createShapes(path). Этот вспомогательный метод объявлен устаревшим в r185 и делегирует работу toShapes() в проверенной версии. Проверьте установленный выпуск, прежде чем смешивать фрагменты разных уроков.

Цикл намеренно пропускает fill="none". Он обрабатывает сплошные заливки этого примера, но не является полноценным рендерером SVG. Градиенты, маски, встроенные изображения, обводки, непрозрачность и перекрывающиеся цветовые слои требуют отдельных решений вместо ожидания, что плоский внешний вид автоматически станет идентичной 3D-геометрией.

Реальный браузерный снимок: сравнение исходного SVG-значка с квадратным отверстием и срезанным верхним правым углом и его выдавленного меша Three.js
Реальная WebGL-отрисовка написанного вручную образца в Three.js r186. Отверстие остаётся пустым, срезанный угол — справа вверху, а равномерное масштабирование создаёт меш 4 на 2.8 на 0.4.

Настройте ориентацию перед измерением и центрированием

Вертикальные координаты SVG увеличиваются вниз. Эта сцена смотрит на графику с положительным y, направленным вверх, поэтому artwork.scale.y = -1 восстанавливает ожидаемую ориентацию. Проверка — срезанный угол: он должен быть справа вверху.

Эта смена знака относится к данной настройке координат. Не добавляйте второе переворачивание потому, что меш всё ещё выглядит неправильно после поворота камеры или родительского объекта. Сначала сравните известный асимметричный элемент исходника с отрисованной сценой.

Следующие строки измеряют перевёрнутую группу, рассчитывают равномерный масштаб и смещают её масштабированный центр к началу координат. Box3.setFromObject() измеряет границы с учётом дочерних объектов и их преобразований. Здесь при измерении у группы нет преобразованного родителя и поворота графики.

Исходное выдавливание имеет размер 200 на 140 на 20. Установка ширины 4 даёт масштаб 4 / 200, то есть 0.02. Поскольку он применяется ко всем трём осям, результирующие границы — 4 на 2.8 на 0.4. Глубина 20 в конструкторе не остаётся 20 после масштабирования группы.

Это единицы сцены. Пример не утверждает, что одна единица — миллиметр. Если приложению нужна определённая физическая интерпретация, задайте её явно и подтвердите через целевой процесс.

Подтвердите отверстие перед украшением поверхности

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

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

Если просвет заполнился, изолируйте эту фигуру. Проверьте, использует ли исходник подконтуры, отдельные залитые объекты, контур отсечения или маску. Не пытайтесь скрыть проблему цветом материала. Руководство по заполняющимся отверстиям SVG объясняет различия исходных файлов; диагностика выдавливания разбирает исправление контуров, когда исходная графика повреждена.

Добавляйте эффекты глубины, когда простое выдавливание работает правильно

Пример отключает фаски, чтобы начальные размеры было легко проверить. Опции ExtrudeGeometry отдельно управляют глубиной, размером и толщиной фаски и детализацией кривых. Перепроверьте мелкие отверстия и границы при включении фасок. Декоративный край меняет только что измеренную геометрию.

Для изогнутой графики проверяйте силуэт меша при размере и расстоянии камеры, которые использует приложение. Увеличивайте детализацию кривых лишь при необходимости устранить угловатость; сокращение лишних узлов исходника — отдельная задача редактирования. Ни одна настройка не восстанавливает отсутствовавшую в исходнике деталь.

Рисунок только из обводок тоже требует другого решения. Загрузчик предоставляет pointsToStroke() для геометрии обводки, но цикл залитых фигур выше намеренно пропускает такие контуры. Если видимая линия должна стать выдавленной лентой, сначала определите замкнутое очертание этой ленты. Если она должна остаться линией в сцене, постройте представление линии или обводки.

В приложении React выполняйте браузерную загрузку и работу рендерера на клиенте и освобождайте принадлежащие представлению геометрию, материалы и рендерер при его удалении. Компактный пример отрисовывает один раз с фиксированным размером; рабочему просмотрщику также нужны обработка изменения размера и собственный жизненный цикл ресурсов.

Разделяйте проверки исходника и результата в целевой среде

Браузерный меш полезен для интерактивного значка, просмотра логотипа или декорации сцены. Он не подтверждает, что результат — замкнутая деталь с подходящими размерами. Если цель — изготовление, используйте отдельный процесс преобразования изображения в SVG для 3D-печати и проверяйте импортированную модель там.

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

Частые вопросы

Почему мой SVG перевёрнут в Three.js? Вертикальные координаты SVG увеличиваются вниз. В сцене с положительным y вверх и видом на лицевую сторону графики смена знака масштаба y группы может восстановить ориентацию исходника. Проверьте асимметричный элемент перед добавлением нового переворачивания или поворота.

Использовать createShapes или toShapes? Этот пример использует path.toShapes() с Three.js 0.186.0. SVGLoader.createShapes() объявлен устаревшим в r185. Используйте API, описанный для версии проекта, с одинаковым выпуском основного пакета и дополнения.

Превращает ли SVGLoader PNG внутри SVG в меш? Встроенная растровая картинка не предоставляет залитые контуры, используемые этим примером. Сначала получите подходящую графику из контуров либо используйте изображение как текстуру, если реальная цель — сохранить его внешний вид.

Сохраняет ли выдавливание ширину обводки SVG? Этот пример выдавливает залитые фигуры и пропускает контуры с fill, равным none. Геометрия обводки требует отдельного процесса; толстая нарисованная линия не является автоматически замкнутым очертанием выдавленной ленты.

Источники

  1. Three.js — SVGLoader — Описывает импорт дополнения, методы разбора и загрузки, помощник обводки и устаревание createShapes.
  2. Three.js — ShapePath — Описывает преобразование коллекций контуров в фигуры через toShapes.
  3. Three.js — ExtrudeGeometry — Определяет выдавливание фигур, глубину, фаски и детализацию кривых.
  4. Three.js — Box3 — Описывает измерение преобразованных границ объектов и получение размера и центра.

Работаете с эмблемой, которая есть только в растре? Подготовьте вариант SVG, проверьте залитые контуры и отверстия, затем протестируйте файл в этой небольшой сцене перед созданием полного просмотрщика.

Ещё из блога

Начните с более чистого SVG,
который проще редактировать