3D-model (векторизація)

sexi 3d-models

Вступ

Проект, який ми сьогодні реалізовуватимемо, має кілька практичних складових:

  • Навчитися базовим принципам роботи з бібліотекою three.js;
  • Можливість конвертувати картинки різних форматів у 3d візуал.

Створення HTML файлу

Так як ми створюємо проект саме як веб-додаток, нам необхідно створити html файл і прописати в ньому:

  1. шляхи до додаткових ресурсів (файлів);
  2. canvas– це HTML-елемент, який використовується для малювання графіки за допомогою JavaScript;
  3. тег input, необхідний для вибору картинки, що цікавить користувача.
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="lab.css" rel="stylesheet">
    <title>3D-Project</title>
</head>
<body>
    <canvas id="myscene"></canvas>
    <input type="file" id="fileInput" accept="image/*">
    <script type="module" src="./lab.js"></script>
</body>
</html>

Створення JSON та Webpack файлів

Почнемо з того, що таке JSON файл.

Файл package.json використовується в проектах JavaScript для керування залежностями, скриптами та метаданими проекту. Він зазвичай створюється в кореневій директорії нашого проекту і містить інформацію про проект, а також про необхідні пакети та команди для його складання та запуску.

Для початку встановіть Node.js, якщо у вас його ще немає, а також встановимо webpack(трохи пізніше він нам знадобиться). Команда для macOS:

brew install node

npm install --save-dev webpack webpack-cli

Після цього ми запускаємо команду npm init -y, яка створює нам файл json із базовими налаштуваннями.

Після цього, ми змінюємо вміст файлу на це:

    "name": "назва нашого файлу",
    "version": "1.0.0",
    "scripts": {
        "start": "webpack serve --open",
        "build": "webpack"
    },
    "devDependencies": {
        "html-webpack-plugin": "^5.6.2",
        "three": "^0.169.0",
        "webpack": "^5.95.0",
        "webpack-cli": "^5.1.4",
        "webpack-dev-server": "^5.1.0"
    }
} 

Щоб створити конфігураційний файл для Webpack, нам потрібно створити файл з ім’ям webpack.config.js в кореневій директорії нашого проекту. Цей файл міститиме настройки, які Webpack буде використовувати для складання нашого проекту.

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './lab.js', // Вказує вхідний файл для збирання
    output: {
        filename: 'lab.js', // Ім'я вихідного файлу
        path: path.resolve(__dirname, 'dist'), // Шлях до вихідної директорії
        clean: true, // Очищає вихідну директорію перед кожним збиранням
    },
    devServer: {
        static: {
            directory: path.join(__dirname, 'dist'), // Вказує каталог для статичних файлів
        },
        open: true, // Автоматично відкриває браузер під час запуску сервера
        port: 8080, // Порт, на якому буде запущено сервер
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './lab.html', // Шаблон HTML-файла
        }),
    ],
    mode: 'development', // Режим збирання (development чи production)
};

Створення JS файлу

Як ви вже зрозуміли з минулого блоку, нам потрібно створити файл lab.js.
Для початку рекомендую в принципі ознайомитися з бібліотекою Three.js на офіційному сайті https://threejs.org. Там чудово описані основні “3 кити Three.js” і є каталог з усіма необхідними методами.

Я постараюся коротко описати всю структуру та математику проекту, тому опишу основний алгоритм виконання:

1) Необхідно завантажити бібліотеку threeтак само через brew;

2) Імпортуємо всі з цієї бібліотеки (це вже прописуємо у файлі) import * as THREE from 'three';;

3) Для можливості маніпулювати надалі картинкою (обертати її), прописуємо:import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

4) Створюємо необхідне полотно, де проектуватиметься 3d-фігура:
let canvas = document.getElementById('myscene'); let width = 1400; let height = 800;

5) Створюємо екземпляр рендерингу, задаючи йому значення полотна та згладжування (для 3d графіки дуже важливий аспект).

6) Встановлюємо співвідношення пікселів за допомогою методу setPixelRatio(повертає коефіцієнт пікселів пристрою. Якщо він більше 1 (що означає, що пристрій має високу роздільну здатність), ми встановлюємо співвідношення пікселів в 2, інакше – в 1.). Потім задаємо розміри полотна та його колір

7) Потім створюємо сцену та встановлюємо камеру (це базові налаштування, які можна коригувати залежно від наших переваг, тому розписувати це не буду)

8) Щоб надалі не додавати по одному елементу на сцену, я створюю групу, яка зберігатиме в собі всі елементи мого малювання (далі це допоможе при обертанні та переміщенні всього об’єкта в цілому) let group = new THREE.Group();

9) Далі йде основна частина цієї програми – малювання. У цьому блоці ми звертаємося до елемента inputі задаємо йому функцію із подієм натискання, при якому він запускатиме за фактом весь функціонал нашої програми.

Розпишу це трохи докладніше.

Для початку ми створюємо змінну, яка буде використовується для отримання першого файлу, вибраного користувачем через елемент у HTML.

Після того, як ми переконалися, що файл не порожній, ми починаємо малювання:

  1. Створення об’єкта зображення: const img = new Image();Тут створюється новий об’єкт Image, який буде використовуватись для завантаження зображення.
  2. Обробник події onload:
img.onload = function() {
    // Код внутри этой функции выполнится, когда изображение будет загружено
};

Цей обробник спрацьовує, коли зображення успішно завантажено. Всередині нього буде виконуватися основний код обробки зображення.

  1. Створення канвасу та контексту:
let ctx = canvas2d.getContext('2d');
canvas2d.width = 200;
canvas2d.height = 200;

Тут створюється тимчасовий елемент, який використовується для малювання зображення у 2D. Встановлюються розміри канвасу на 200×200 пікселів.

  1. Малювання зображення на конвасі:
ctx.drawImage(img, 0, 0, 200, 200);
let data = ctx.getImageData(0, 0, size, size).data;

Зображення малюється на канвасі, а потім виходять дані пікселів за допомогою getImageData. Ці дані містять інформацію про кольори кожного пікселя у форматі RGBA.

  1. Очищення групи:
group.clear();

Тут очищається група group, щоб видалити попередні об’єкти перед додаванням нових.

  1. Цикл для створення 3D-об’єктів:
for (let i = 0; i < size; ++i) {
    let geometry = new THREE.BufferGeometry();
    let vertices = new Float32Array(size * 3);
    let colors = new Float32Array(size * 3);

У цьому циклі створюється нова геометрія кожного рядка пікселів. Vertices та colors – це масиви, які будуть містити координати вершин та кольору для кожної лінії.

  1. Цикл для обробки пікселів:
for (let j = 0; j < size; ++j) {
    let colorIndex = (j * size + i) * 4; 
    let r = data[colorIndex] / 255; 
    let g = data[colorIndex + 1] / 255; 
    let b = data[colorIndex + 2] / 255;

Внутрішній цикл проходить по кожному пікселю в рядку, витягуючи значення кольору (червоний, зелений, синій) із масиву даних пікселів. Значення нормалізуються, поділяючи на 255.

  1. Встановлення координат вершин та кольорів:
vertices[j * 3] = j - 100; 
vertices[j * 3 + 1] = i - 100; 
vertices[j * 3 + 2] = data[colorIndex] / 10; 

colors[j * 3] = r;
colors[j * 3 + 1] = g; 
colors[j * 3 + 2] = b;

Тут встановлюються координати вершин для 3D-об’єктів. z-координата встановлюється з урахуванням значення червоного каналу, поділеного на 10, щоб створити деяку висоту.

  1. Створення геометрії та додавання до групи:
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

let material = new THREE.LineBasicMaterial({ vertexColors: true });
let line = new THREE.Line(geometry, material);
group.add(line);

Після того, як масиви vertices і colors заповнені, вони встановлюються як атрибути геометрії. Потім створюється матеріал з використанням кольорів вершин, лінія додається до групи.

У принципі це все. Нам залишилося завантажити результат в об’єкт imgі відмалювати це все за допомогою функції, яка викликає саму себе та оновлює дані сцени та камери:

function animation() {
    requestAnimationFrame(animation);
    controls.update();
    renderer.render(scene, camera);
}
animation();

Залишилося лише запустити наш проект командоюnpm start

Джерело