Перейти к содержанию

Цвет, свет и материалы товара

Оглавление серии «Форма» · Урок 5 из 10.

Собственная модель уже загружается из glTF. Теперь свяжем её материалы с палитрой товара. У лампы «Форма» посетитель сможет выбрать фиолетовый, зелёный или песочный плафон и тёмное либо светлое основание. Ожидаемый результат — изменение выбранной поверхности без повторной загрузки геометрии. Стойка сохранит металлический вид, а свет и положение камеры останутся одинаковыми для всех вариантов. Это поможет сравнивать именно отделки, а не разные условия представления.

До изменения материалов введём отдельный модуль параметров. Он хранит допустимые варианты, исходную комплектацию и функцию нормализации. Хотя полноценную стоимость разберём позднее, форма уже должна работать с предметными именами, а не с произвольными цветами. Значение violet означает вариант продукта; шестнадцатеричный цвет является способом его экранного представления. Если позже понадобится уточнить палитру, ссылки на выбранную комплектацию смогут сохранить прежние предметные имена.

Палитра как ограниченные данные

В полном config.js перечислены допустимые цвета и исходные параметры. normalize возвращает новое нормализованное значение либо null. Она не копирует произвольные поля входного объекта. Это помогает ограничить состояние теми свойствами, которые приложение действительно понимает. Сначала проверяется версия схемы, затем варианты плафона, основания и размера. В этом уроке размер остаётся исходным; его управление появится после знакомства с выбором деталей.

export const palette = Object.freeze({violet:'#8044ca',green:'#44b46d',sand:'#d6bd97',charcoal:'#272532',pale:'#dad8e2'});
export const defaults = Object.freeze({v:1,shade:'violet',base:'charcoal',size:'standard'});
export function normalize(value) {
  if (!value || value.v !== 1 || !['violet','green','sand'].includes(value.shade)
      || !['charcoal','pale'].includes(value.base) || !['standard','wide'].includes(value.size)) return null;
  return {v:1,shade:value.shade,base:value.base,size:value.size};
}
export function quote(config) {
  const price = 9900 + (config.base === 'pale' ? 400 : 0) + (config.size === 'wide' ? 1200 : 0);
  return {sku:`FORMA-${config.shade}-${config.base}-${config.size}`,price};
}

Object.freeze защищает верхний уровень исходных словарей от обычного изменения. Он не делает все данные приложения автоматически неизменяемыми и не заменяет нормализацию. Текущая комплектация будет отдельным объектом, который можно заменить после действия пользователя. Такое разделение сохраняет известную исходную точку: сброс или неправильная ссылка не должны восстанавливать случайно изменённый объект, который раньше использовался как значение по умолчанию.

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

Как найти нужную поверхность

Функция apply проходит по модели через traverse. Нас интересуют только объекты с isMesh, потому что материал принадлежит видимым поверхностям, а не любому узлу. Роль берётся из userData.role или имени объекта. Для shade и base выбираем цвет из словаря. Стойка в это условие не входит, поэтому её первоначальный металлический материал сохраняется. Так код выражает правило товара, а не случайное положение элемента в массиве потомков.

После загрузки glTF вызываем apply с последним известным состоянием. Это нужно потому, что посетитель может изменить форму раньше готовности модели. Если применять параметры только в обработчике change, раннее действие потеряется: обработчик увидит пустой model и завершится, а загруженная лампа останется в исходной отделке. Состояние хранится независимо от изображения; появившееся представление должно прочитать именно актуальное значение, а не давнее значение начала запроса.

Заменяем цвет существующего материала через material.color.set. Новый материал для каждого движения поля не создаётся. Это сокращает ненужную смену ресурсов и сохраняет уже заданные свойства поверхности. У собственного glTF три независимых материала, поэтому изменение основания не перекрашивает плафон. В импортированной коммерческой модели материалы могут разделяться между несколькими mesh. Прежде чем изменять такой ресурс, выясните, должны ли связанные поверхности действительно меняться вместе.

Цвет и характер поверхности

Материалы учебной модели имеют параметры metallicFactor и roughnessFactor, которые загрузчик переносит в соответствующее представление Three.js. Высокая металлическая составляющая стойки и сравнительно низкая шероховатость дают иной ответ на свет, чем у основания. Цвет — только один параметр. Простое изменение RGB не превращает пластик в металл и не описывает ткань, стекло или покрытие с дополнительной текстурой. Для таких вариантов нужны отдельные соглашения о материале.

У металлических материалов особенно заметно окружение: они отражают его свойства. В лаборатории используем небольшой набор источников света и не добавляем HDR-карту. Поэтому металлическая стойка показана упрощённо. Нельзя сделать вывод о физической правдоподобности по одному удачному скриншоту. Сейчас задача — сохранить понятный путь от выбора варианта до нужной поверхности. Более сложное освещение можно добавить после фиксации этой связи и бюджета ресурсов карточки.

Цветовое пространство вывода задано как sRGB, а цветовые значения в палитре записаны привычными CSS-строками. Three.js учитывает цветовое управление при работе с ними. Не следует вручную применять дополнительную гамму только потому, что освещённый предмет кажется темнее цветной кнопки. Кнопка выводит однотонное значение, поверхность получает освещение. Для согласованного сравнения оставляем свет неизменным и рассматриваем отражение на гранях, а не требуем одинакового цвета каждой точки предмета.

Форма остаётся обычным интерфейсом

В HTML добавлены два select с подписью. Обработчик берёт выбранное значение, создаёт кандидат конфигурации и проверяет его через normalize. Лишь после успешной проверки заменяет состояние и применяет его к изображению. У select ограниченный список, однако проверка всё равно полезна: позднее данные начнут поступать из ссылки и localStorage. Один путь нормализации уменьшает различия между ручным выбором и восстановленным состоянием.

Поля находятся вне canvas, поэтому работают обычной клавиатурой и предоставляют видимую подпись. Изменение материала является дополнительным представлением выбора, а не единственным способом узнать значение. Если WebGL недоступен, в форме остаются предметные названия вариантов. Когда подключим стоимость и артикул, они тоже появятся в HTML. Так можно развивать эффектное изображение, не делая карточку зависимой от способности посетителя различать небольшие оттенки на 3D-поверхности.

Наблюдение за независимостью параметров

Самостоятельно смените плафон на зелёный, затем основание на светлое. Ожидается, что прежний цвет плафона сохранится при втором действии. Стойка останется металлической. Если меняются сразу две поверхности, проверьте роли mesh и общность материалов, а не добавляйте ещё один случайный обход сцены. Если ранний выбор пропадает после загрузки, проверьте вызов apply на границе готовности и используемое в нём последнее состояние.

Полный main.js этого состояния находится в архиве; кроме обработчиков формы он сохраняет камеру, наблюдатель размеров и загрузчик предыдущих уроков. Пример при подготовке не запускался, поэтому описанные эффекты являются ожидаемыми. Свойства поверхности представлены в MeshStandardMaterial, особенности цвета — в разделе цветового управления. Теперь выбор варианта имеет устойчивое предметное имя и известную поверхность назначения. Следующий урок добавит выбор самой детали, сохранив управление цветом в обычной форме.

Исходники этого шага

Архив серии содержит полную папку lesson-05. Возьмите именно её; переносить файлы из соседних папок не требуется. Версии и самостоятельная подготовка описаны в README. Пример, установка зависимостей и его выполнение при написании не запускались; результаты в тексте ожидаются по разбору исходников. Материал сохранён локально как Markdown-черновик.