Three.js
October 2, 2022

Текстуры (часть 1)

Вам уже надоел ваш одноцветный куб? Пришло время добавить текстуры!

Но сначала давайте узнаем, что это такое и что мы можем с ними делать.

Что такое текстуры

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

Тут я расскажу про типы текстур, ссылаясь на достаточно популярную текстуру двери от João Paulo.

Цвет или текстура альбедо

Текстура альбедо - самая понятная. Она просто берет только изображение текстуры и применяет их к созданной вами геометрии геометрии.

Альфа-текстура

Альфа-текстура - это полутоновое изображение, где белый цвет будет виден, а черный - нет.

Текстура высоты

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

Текстура нормалей

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

Ambient occlusion

Текстура ambient occlusion - это полутоновое изображение, которое будет имитировать тени в щелях поверхности. Хотя она и не является физически точной, она, безусловно, помогает создать контраст.

Текстура металлизации

Текстура металлизации - это изображение в сером спектре, которое определяет, какая часть является металлической (белой), а какая неметаллической (черной). Эта информация поможет создать отражение.

Текстура шероховатости

Шероховатость - это изображение в в сером спектре, которое идет вместе с металлизацией, и которое определяет, какая часть шероховатая (белая), а какая гладкая (черная).

Давайте приведу пример. Тканный шарф очень шершавый, и вы не увидите отражения света на нем, в то время как поверхность воды очень гладкая, и вы можете увидеть отражение света на ней.

Здесь дерево однородное, потому что на нем есть прозрачный слой.

Отрисовка на основе физических характеристик

Эти текстуры (особенно металлизация и шероховатость) соответствуют тому, что называется принципами PBR.

PBR расшифровывается как Physically Based Rendering (отрисовка на основе физических характеристик). Этот метод объединяет множество техник, которые направлены на получение реалистичных результатов.

Хотя существует множество других техник, PBR становится стандартом для реалистичного рендеринга, и многие программы, движки и библиотеки используют его.

Сейчас мы просто сосредоточимся на том, как загружать текстуры, как их использовать, какие преобразования мы можем применять и как их оптимизировать. Более подробно о PBR мы поговорим в последующих уроках.

Как загружать текстуры

Получение URL изображения

Чтобы загрузить текстуру, нам нужен URL-адрес файла изображения.

Поскольку мы используем Webpack, есть два способа получить его.

Можно поместить текстуру изображения в папку /src/ и импортировать ее, как мы импортировали бы зависимость JavaScript:

import imageSource from './image.png';

console.log(imageSource);

Или можно поместить это изображение в папку /static/ и получить к нему доступ, просто добавив путь к изображению (без /static) в URL:

const imageSource = '/image.png';

console.log(imageSource);

Будьте внимательны, эта папка /static/ работает только благодаря конфигурации шаблона Webpack. Если вы используете другие типы бандлеров, вам, возможно, придется адаптировать свой проект.

Мы будем использовать папку /static/ до конца курса.

Загружаем изображение

Текстуры дверей, которые мы только что видели, можно найти в репозитории проекта в папке /static/textures, и существует несколько способов их загрузки.

Используем нативный JavaScript

В JavaScript сначала нужно создать объект класса Image, прослушать событие load, а затем изменить его свойство src, чтобы начать загрузку изображения:

const image = new Image();

image.onload = () => {
    console.log('image loaded');
};

image.src = '/textures/door/color.jpg';

Мы не можем использовать это изображение напрямую. Сначала нам нужно создать текстуру из этого изображения.

Это связано с тем, что WebGL нужен очень специфический формат, понятный GPU, а также с тем, что к текстурам будут применены некоторые изменения, например, mipmapping, но об этом мы узнаем чуть позже.

Создайте текстуру с помощью класса Texture:

const image = new Image();

image.addEventListener('load', () => {
    const texture = new THREE.Texture(image);
});

image.src = '/textures/door/color.jpg';

Теперь нам нужно использовать эту текстуру в материале. К сожалению, переменная texture была объявлена в функции, и мы не можем получить к ней доступ вне этой функции. Это ограничение JavaScript, называемое областью видимости.

Мы могли бы создать наш куб внутри функции, но есть лучшее решение, заключающееся в создании текстуры вне функции и последующем ее обновлении после загрузки изображения путем установки свойства needsUpdate в true:

const image = new Image();
const texture = new THREE.Texture(image);

image.addEventListener('load', () => {
    texture.needsUpdate = true;
});

image.src = '/textures/door/color.jpg';

Делая это, вы можете сразу же использовать переменную texture, и изображение будет прозрачным, пока оно не загрузится.

Чтобы увидеть текстуру на кубе, замените свойство color на map и используйте texture:

const material = new THREE.MeshBasicMaterial({ map: texture });

Вы должны увидеть текстуру двери на каждой стороне вашего куба:

Используя TextureLoader

Нативная загрузка JavaScript не так сложна, но есть еще более простой способ с TextureLoader.

Создайте переменную класса TextureLoader и используйте его метод .load(...) для создания текстуры:

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('/textures/door/color.jpg');

Под капотом Three.js будет делать то же, что и мы делали раньше - загружать изображение и обновлять текстуру, когда она будет готова.

Вы можете загрузить столько текстур, сколько хотите, используя только один объект TextureLoader.

Можно прокинуть три колбека в метод .load(...) после строки, которая описывает путь до файла. Эти методы будут вызываться при следующих событиях:

  • load при успешной загрузке изображения
  • progress, если загрузка идет успешно
  • error, если что-то пошло не так
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(
    '/textures/door/color.jpg',
    () => {
        console.log('loading finished');
    },
    () => {
        console.log('loading progressing');
    },
    () => {
        console.log('loading error');
    }
);

Если текстура не работает, может быть полезно добавить эти колбеки, чтобы увидеть, что происходит, и выявить ошибки.

Используя LoadingManager

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

Создайте экземпляр класса LoadingManager и передайте его в TextureLoader:

const loadingManager = new THREE.LoadingManager();
const textureLoader = new THREE.TextureLoader(loadingManager);

Можно прослушивать различные события, заменив следующие свойства своими собственными функциями onStart, onLoad, onProgress и onError:

const loadingManager = new THREE.LoadingManager();

loadingManager.onStart = () => {
    console.log('loading started');
};

loadingManager.onLoad = () => {
    console.log('loading finished');
};

loadingManager.onProgress = () => {
    console.log('loading progressing');
};

loadingManager.onError = () => {
    console.log('loading error');
};

const textureLoader = new THREE.TextureLoader(loadingManager);

Потом вы можете начать загрузку всех необходимых изображений:

// ...
const colorTexture = textureLoader.load('/textures/door/color.jpg');
const alphaTexture = textureLoader.load('/textures/door/alpha.jpg');
const heightTexture = textureLoader.load('/textures/door/height.jpg');
const normalTexture = textureLoader.load('/textures/door/normal.jpg');
// ...

Как вы можете видеть здесь, мы переименовали переменную texture в colorTexture, поэтому не забудьте изменить ее и в материале:

const material = new THREE.MeshBasicMaterial({ map: colorTexture });

LoadingManager очень полезен, если вы хотите показать загрузчик и скрыть его только тогда, когда вся статика загружена. Как мы увидим в одном из следующих уроков, его можно использовать и с другими типами загрузчиков.

На этом сегодня все, надеюсь вам это было полезно!

🤖 Чтобы не пропустить новые уроки подпишись на телеграм канал!

🚀 Код для данного урока вы можете найти тут.

Текстуры (часть 2) ->