search icon search icon ВЕРСИЯ ДЛЯ СЛАБОВИДЯЩИХ

Инженерный тур. 3 этап

Общая информация

На заключительном этапе инженерного тура команды создают веб-ресурс с дополненной реальностью, в которой действует цифровой ИИ-агент. Он имеет внешность, похожую на реального человека, способность вести разумный диалог на заданную тему «Люди Байкала», отвечать на вопросы, давать советы.

Легенда задачи

Компания «Альтернативное будущее» продолжает свою миссию по созданию образовательных интернет-ресурсов с использованием передовых технологий дополненной реальности (webAR). На этот раз компания предлагает командам поучаствовать в проекте «Люди Байкала», посвященный разработке веб-ресурса с дополненной реальностью, объединяющего технологии webAR и искусственного интеллекта.

Байкал — жемчужина Сибири, крупнейшее пресноводное озеро планеты и объект Всемирного наследия ЮНЕСКО. Это место не только удивительной природы, но и богатой культуры, где переплетаются истории коренных народов, научные открытия и современные экологические вызовы. Однако многие люди знают о Байкале лишь поверхностно, а его истинная ценность остается скрытой за завесой времени и расстояний.

Чтобы сделать знания о Байкале доступными каждому, компания предлагает реализовать цифрового ИИ-агента в дополненной реальности. Этот агент будет обладать внешностью реального ученого, изучающего Байкал, и сможет вести осмысленный диалог на тему его исследования.

Оборудование и программное обеспечение
Наименование Описание

Компьютер или ноутбук

(базовые характеристики Operating system version Windows: Windows 10; CPU: x86, x64 architecture with SSE2 instruction set support; Graphics API: DX10, DX11, DX12 capable)

Для разработки программных компонентов

Планшет или смартфон

(базовые характеристики Operating system: Android7, CPU: ARM 64-bit, Graphics API: OpenGL ES)

Для тестирования разработанных AR-сценариев и просмотра веб-ресурса

Редактор кода или IDE, например: VS Code (https://code.visualstudio.com/)

Sublime text (https://www.sublimetext.com/) или другие аналоги

Для разработки программных компонентов и их отладки; для развертки локального сервера
Графический редактор, например: Adobe Photoshop, Adobe Illustrator, Figma (https://www.figma.com/), Procreate, Gimp, Paint или другие аналоги Для создания маркеров, макета веб-ресурса, 2D-материалов оверлея и сайта
3D-редактор, например: Blender (https://www.blender.org/) или другие аналоги Для создания трехмерных моделей

Интернет-браузер, например: Яндекс Браузер

(https://browser.yandex.ru/) или другие аналоги

Для просмотра и отладки решений
Дополнительно:
Видеокамера, наушники, компьютерная мышь Для удобства в работе и тестировании
Доступ в интернет со скоростью около 100 Мбит/с. Для тестирования и просмотра результатов работы, при разворачивании сервисов в сети интернет
Требования к команде и компетенциям участников

Количество участников в команде — 4 человека.

Компетенции, которыми должны обладать члены команды:

  • Создание 3D-модели (3D-конструктор, дизайнер):

    • Разработка модели цифрового аватара и цифровой личности (Blender).
    • Анимация моделей.
    • Экспорт моделей в формате GLTF для удобного использования с AR.js.
  • Разработка веб-AR приложения (AR-разработчик):

    • Формирование триггера и его обработка.
    • Создание HTML-страницы, включающей необходимые библиотеки.
    • Настройка AR-сцены с использованием AR.js и добавление 3D-модели человеческого тела.
  • Реализация текстового и речевого интерфейса для ввода запросов и вывода ответов.
  • Логика обработки запросов (разработчик: бэкенд):

    • Разработка логики для обработки вводимых пользователем команд.
    • Создание базы данных, содержащей информацию о темах исследования.
    • Интеграция: ИИ-агент + БД.
  • Чат-бот с базой знаний (специалист по ИИ):

    • Создание интенций, соответствующих темам исследований ученых из задачи.
    • Предобучение модели.
    • Разработка логики обработки интенций и взаимодействие через вебхуки с AR-образом.
Описание задачи

Разработать цифрового аватара: виртуальную 3D-модель реального человека со встроенным ИИ-агентом, настроенным отвечать на вопросы в рамках темы «Люди Байкала».

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

Все предлагаемые подзадачи для реализации во время заключительного этапа описаны в таблице 1.1. Участники могут выбрать некоторые из них для реализации, в зависимости от навыков и времени, затраченного на другие подзадачи, и реализовать только часть из полного списка предполагаемых работ.

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

3D-дизайнер AR-разработчик Web-разработчик Специалист по ИИ
Обсуждение идеи, выбор компонентов для визуализации и определение необходимой информации
Изменение аватаров под личности выбранных людей Проработка сценария взаимодействия пользователя с приложением Определение формата и содержания ответа от бота
Добавление объектов одежды и волос AR-сцена не исчезает, когда маркер пропадает с камеры Обработка взаимодействия с агентом (обработка ошибок) Формирование базы знаний
Наложение материалов и текстур на аватары Разработка собственного маркера в тематике команды Обработка ответа от бота Построение схемы работы ИИ-агента (соответствует полному сценарию взаимодействия)
Создание концепций предметов Оптимизация текстового ответа от бота под пространство сцены Создание отдельного чата под каждого пользователя/ сессию Правильная интерпретация темы/ объекта разговора
Блокинг предметов Анимирование оверлея
Доработка предметов ученых Интерфейс для ввода вопроса разными способами Разработка концепции внедрения голосовых запросов и ответов и последующая реализация
Создание рига ученых Доработка сценариев взаимодействия с учетом добавления интерактивности Запоминание контекста общения (в рамках одной сессии)
Создание анимации ученых Добавление интерактивности оверлея (например, нажатия) Расширение интерфейса взаимодействия с AR-контентом через внедрение жестов и или распознавания лица Обработка вопросов вне тематики бота
Экспорт моделей Тестирование на различных устройствах и запись демонстрационного видеоролика Создание презентации Рекомендуемые вопросы/темы с учетом истории сообщений
Внедрение 3D-моделей
Система оценивания

Оценка задачи заключительного этапа делится на несколько составляющих:

  1. Реализация веб-ресурса:

    • блок 1. AR-составляющая (22,5%);
    • блок 2. Веб-разработка (22,5%);
    • блок 3. ИИ-составляющая (22,5%);
    • блок 4. 3D-моделирование (22,5%).
  2. Защита решений команды (10%): баллы за все составляющие нормируются к 100.

Все критерии оценки для всех составляющих представлены в таблицах №№ 1.21.5.

Таблица: Блок 1. AR составляющая
Критерии оценки Максимум баллов
Собственный маркер (дизайнерский — 2, не очень оригинально — 1) 2
Внедрили собственный маркер в проект и все работает 1
Отвязка оверлея от сцены (идейно обыграно — 4, с новым позиционированием — 3, без позиционирования — 2) 4
Текстовый ответ от бота (предусмотрена разбивка текста — 2, укорочен за счет промпта — 1,5, просто есть в наличии — 1) 2
Оверлей на сцене расположен с правильным позиционированием 2
Анимация оверлея (через атрибут — 1, реализован скрипт перемещения — 3) 3
Интерактивность оверлея (обработка нажатия на него — 5, взаимодействие через элементы экрана — 3) 5
Адаптивная верстка (адаптация оверлея — 2, адаптация только кнопок и поля для ввода — 1) 3
Интерфейс (отправка сообщения, включение голосовое, пауза) 2
Наличие приветствия 1
Отображение текущего статуса пользователю (ожидания ответа, нет ответа) 2
Итого баллов 27
ИТОГО НОРМИРОВАННОЕ 22,5
Таблица: Блок 2. Веб-разработка
Критерии оценки Максимум баллов
Подключение бота (API — 2, веб-чат — 1) 2
Обработка ошибок (подключение, неверный формат ответа, долгое ожидание) 2
Обработка ответа чат-бота (пустая строка, некорректный ответ) 3
Озвучка персонажа (модель — 3, заготовленные сообщения — 1) 3
Интерпретация голосового ввода вопросов 4
Создание сессий под каждого пользователя 2
Интерактивность (например, в виде жестов/распознавания лица и др.) 6
Итого баллов 22
ИТОГО НОРМИРОВАННОЕ 22,5
Таблица: Блок 3. ИИ-составляющая
Критерии оценки Максимум баллов
База знаний (объем, верная разметка, корректная информация) 3
Реализация в Langflow (условия ветки, правильные соединения) 3
Приветствие внутри бота 2
Правильная интерпретация темы/разговора (из инструкции — 1, несколько слов или предложения — 2, LLM — 2) 2
Запоминание контекста общения (только одна тема за сессию — 1, несколько тем — 3) 3
Рекомендуемые темы для общения (генерация дальнейших вопросов по контексту — 2, предложение одним сообщением без контекста — 1) 2
Итого баллов 15
ИТОГО НОРМИРОВАННОЕ 22,5
Таблица: Блок 4. 3D-моделирование
Критерии оценки Максимум баллов
Количество триссов \(\le\) 20000 (коэффициент 0,1 за каждые 500 сверху) 15
Адаптация сетки под анимацию (сетка состоит преимущественно из квадов, наличие доп. петель вокруг сгибов) 5
Правильное направление нормалей 3
Отсутствие двойной геометрии и артефактов полигонов 3
Правильные значения трансформации 3
Портретное сходство с оригиналом 5
Наличие на объекте волос 3
Наличие на объекте одежды 3
Наличие рига 4
Правильные локальные оси рига и вес 4
Анимация рига 8
Дополнительная анимация 3
Наличие развертки 2
Швы развертки расположены по правилам 3
Острова развертки оптимизированы по размеру 3
Растяжение развертки отсутствует или минимально 2
Наличие одного материала для всего объекта; коэффициенты: 0,5 от 2,0 до 4,0, если более 5. 4
Наличие текстуры цвета 6
Размер текстуры (до 2048 рх) 4
Отсутствие альфа-канала на текстуре 2
Формат текстуры адаптирован под веб 3
Наличие карты нормалей 2
Размер карты нормалей (до 2048 рх) 2

Отсутствие альфа-канала на карте нормалей

Формат нормалей адаптирован под веб

2

Формат нормалей адаптирован под веб;

коэффициенты:

  • 0,7, если .png;
  • 1,0, если .webp;
  • 0,5, если .jpg или иные.
1
В gltf-файле нет лишних объектов 3
Текстуры и материалы есть в gltf-файле (запечены при необходимости) 2
Для объектов (всего 3)
Количество триссов \(\le\) 2000 30
Адаптированная сетка под статичный объект 6
Правильное направление нормалей 6
Отсутствие двойной геометрии и артефактов полигонов 6
Правильные значения трансформации 6
Объект соотносится с аватаром 15
Наличие развертки 9
Острова развертки оптимизированы 3
Растяжение развертки отсутствует или минимально 3

Наличие одного материала для всего объекта:

  • 0,5, если от 2 до 4;
  • 0, если 5 и более
12

Наличие текстуры цвета:

  • 1,0, если есть,
  • 0,5, если 2–3,
  • 0, если отсутствует или более трех
12
Размер текстуры (до 2048 рх, иначе вводится коэффициент 0,5) 9
Отсутствие альфа-канала на текстуре 6
Формат текстуры адаптирован под веб 6
Наличие карты нормалей 6
Размер карты нормалей (до 2048 рх) 3
Отсутствие альфа-канала на карте нормалей 3
Формат нормалей адаптирован под веб 3
В gltf-файле нет лишних объектов 3
Текстуры и материалы есть в gltf-файле (запечены при необходимости) 3
Итого баллов 250
Штрафные баллы до –3
ИТОГО НОРМИРОВАННОЕ 22,5

Штрафные баллы предусмотрены:

  • за использование автоматизированных решений (прим. Avaturn),
  • за частично или полностью сгенерированные и/или заимствованные из интернета модели,
  • за модели, представляющие собой примитивы без внесения значительных изменений.
Защита решений
Таблица: Защита решений
Критерии оценки Максимум баллов
Схема взаимодействия 2
Концепт БД 3
Представлены все разработанные модели 2
Единый стиль слайдов 1
Описаны участники команды и их роли 1
Демонстрационный видеоролик (отражает полноценный сценарий взаимодействия, краткая версия работы с приложением, отдельные фрагменты) 2
Соблюдение регламента защиты 1
Ответы на вопросы (корректные ответы, неуверенные ответы) 2
Дополнительные баллы 2
Итого баллов 16
ИТОГО НОРМИРОВАННОЕ 10

Общая оценка решения инженерного тура формируется по формуле:

Для каждой части реализации веб-ресурса: \[0{,}225 \times\text{баллы за реализацию} + 0{,}1 \times\text{баллы за презентацию проекта жюри}.\]

При верном (задание проверяется на соответствие требованиям), полном (выполнены все пункты задания) и качественном (экспертная критериальная оценка) выполнении всех заданий профиля участники могут набрать 100 баллов.

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

Решение задачи

В качестве вводных данных и выбора темы для контекста создаваемого продукта командам рекомендованы следующие ученые и направления исследования их деятельности:

  • гидробиология: Стом Алина Дэвардовна;
  • геология нефти и газа: Андреева Юлия Сергеевна;
  • динамическая геология: Рассказов Сергей Васильевич;
  • зоология: Матвеев Аркадий Николаевич;
  • орнитология: Фефелов Игорь Владимирович;
  • астрофизика: Танаев Андрей Борисович;
  • метеорология: Латышева Инна Валентиновна;
  • экология: Карнаухов Дмитрий;
  • генетика: Дроздова Полина;
  • гидробиология: Верещагина Ксения Петровна.

По каждому ученому приводится краткая биография, направление исследования, достижения и фотография. Пример представлен ниже.

Зоология: Матвеев Аркадий Николаевич

Матвеев Аркадий Николаевич — доктор биологических наук, профессор, декан биолого-почвенного факультета Иркутского государственного университета, заведующий кафедрой зоологии позвоночных и экологии. Отвечает за учебные программы кафедры водных ресурсов ЮНЕСКО.

Родился 2 октября 1957 года в Иркутске. В 1980 году окончил биолого-почвенный факультет ИГУ по специальности «Ихтиология». С 1980 по 1996 года работал лаборантом, старшим лаборантом и инженером на кафедре зоологии позвоночных.

В 1993 году защитил кандидатскую диссертацию на тему «Структура рыбного населения литорали Северного Байкала», а в 2006 году стал доктором наук, защитив работу «Структура рыбного населения горных водоемов Байкальской рифтовой зоны».

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

Основные научные интересы связаны:

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

А. Н. Матвеев является автором и соавтором множества научных статей и монографий, посвященных экологии и ихтиологии. Среди публикаций можно выделить работы:

  • «Гидроэнергетика и состояние экосистемы озера Байкал» (1999 г.),
  • «Биота Витимского заповедника: структура биоты водных экосистем». (2006 г.).

Рис. 7.1.

После того как выбрана тема, работа в команде может вестись параллельно по четырем направлениям:

  • блок 1. AR-составляющая;
  • блок 2. веб-разработка;
  • блок 3. ИИ-составляющая;
  • блок 4. 3D-моделирование.

Описание решения задачи далее будет представлено в соответствии с данными направлениями.

Блок 1. AR-составляющая

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

Постановка задачи: AR-бот для ознакомления с зоологией в экосистеме Байкала от лица Матвеева Аркадия Николаевича.

WrbAR-сервис с интеллектуальным ассистентом-ученым для ответов на вопросы по теме его исследования. Пользователи могут:

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

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

Пример алгоритма работы (взаимодействия) AR-бота:

  1. При наведении на QR-код на экране устройства отображается 3D-модель аватара ученого, с которым общается пользователь, отображается приветствие и описание возможных тематик общения чат-бота.
  2. Пользователь может:

    1. ввести вопрос в отображаемый интерфейс, например, «Что такое зоология?» и нажать кнопку для отправки вопроса;
    2. выбрать пункт в меню «расскажите о себе», «расскажите о гидробионтах», «10 интересных фактах о рыбах Байкала» или «Байкальская рифтовая зона»;
    3. выбрать пункт «фотобудка» для перехода в интерактивную страницу сервиса.
  3. Бот делает запрос в базу знаний:

    1. при нахождении ответа выводит информацию на экран устройства, а также отображает 3D-модель объекта, о котором идет речь, если она была создана;
    2. если информация не найдена в базе знаний, то пользователю выводится информация об этом и предлагается выбрать тематику общения из предложенных ранее (см. п. 2.2).

Для защиты следует представить алгоритм в виде схемы в свободной форме. Ниже приводятся два примера (см. рис. 7.27.3).

Рис. 7.2. Пример схемы взаимодействия из решения команды Инспаир AR

Рис. 7.3. Пример схемы взаимодействия из решения команды RTX

Для отображения дополненной реальности потребуется тематический маркер.

Рис. 7.4. Тематический маркер

Пример маркера из решения команды ARTeam_PSh_MaGN_V2.

Алгоритм его создания.

  1. Выбор изображения для маркера

    (примечание: рекомендуется выбирать контрастные изображения с четкими границами, чтобы AR.js мог легко их распознать).

    Советы по выбору маркера:

    • используйте изображения с четкими деталями, избегайте размытия;
    • предпочитайте черно-белую палитру или минимализм;
    • избегайте слишком сложных узоров, которые могут путать алгоритм распознавания.
  2. Создание маркера через AR.js Studio.

    Используйте AR.js Studio Marker Generator: https://jeromeetienne.github.io/AR.js/three.js/examples/marker-training/examples/generator.html.

    Рис. 7.5.

    1. загрузите изображение (формат: PNG или JPEG);
    2. настройте параметры генерации, такие как размер маркера;
    3. скачайте сгенерированные файлы: .patt (шаблон маркера) и изображение маркера для печати.
  3. Подключение маркера к сцене.

    Для работы с разработанным маркером поместите .patt файл в папку проекта и используйте компонент a-marker для добавления маркера.

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

    HTML
    <!DOCTYPE html>
    <html>
      <head>
        <script src="https://aframe.io/releases/1.6.0/aframe.min.js"></script>
        <script src="https://raw.githack.com/AR-js-org/AR.js/master/ aframe/build/aframe-ar.js"></script>
      </head>
      <body style="margin: 0; overflow: hidden;">
        <a-scene embedded arjs>
          <!-- Маркер с кастомным шаблоном -->
          <a-marker type="pattern" url="path/to/your-marker.patt">
            <!-- 3D-объект, привязанный к маркеру -->
            <a-box position="0 0.5 0" material="color: red;"></a-box>
          </a-marker>
    
          <!-- Камера для AR -->
          <a-entity camera></a-entity>
        </a-scene>
      </body>
    </html>

Для удобства использования сервиса рекомендуется добавить следующие составляющие:

  • Оптимизация текстового ответа от бота под пространство сцены для того, чтобы длинные ответы от чат-бота помещались на экран устройства пользователя.
  • AR-сцена не исчезает, когда маркер пропадает с камеры для реализации сценариев с плохой детекцией маркера (недостаточной освещенностью, неудобным расположением маркера, скоплением людей возле него и т. п.). В таком случае оверлей отвязывается от марки и продолжает отображаться, а также сохраняет возможность взаимодействия с ним.
  • Интерфейс для ввода вопроса разными способами, а именно: текстовым и голосовым.
  • Отображения «состояния» сервиса: «передача запроса», «формирование ответа» и т. п.
CSS
./src/css/style.css
html, body { margin:0; padding:0; overflow:hidden; }
.ar-control {
  position:absolute; top:10px; left:10px;
  background:rgba(255,255,255,0.8);
  padding:6px 10px; border-radius:4px; font-size:14px;
  z-index:100;
}
#chat-container {
  position:absolute; top:10px; right:10px;
  width:320px; max-width:90%;
  background:rgba(255,255,255,0.9);
  border-radius:8px; display:flex; flex-direction:column;
  z-index:100;
}
#chat-messages { flex:1; padding:8px; overflow-y:auto; height:200px; }
#chat-input  { display:flex; padding:8px; }
#chat-input input { flex:1; padding:4px; margin-right:4px; }
.error { border:1px solid red; }
#history-modal, #error-modal {
  display:none; position:fixed; top:50%; left:50%;
  transform:translate(-50%,-50%);
  background:rgba(0,0,0,0.8); color:white;
  padding:10px; border-radius:8px; z-index:200;
  max-width:90%;
}
#history-messages { max-height:300px; overflow-y:auto; margin-bottom:8px; }

.microphone-button {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  transition: transform 0.1s ease;
}
.microphone-button.recording {
  color: red;
  transform: scale(1.2);
}
.chat-status {
  padding: 4px 8px;
  font-size: 14px;
  color: #555;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 4px;
  margin: 4px;
  text-align: center;
}
JavaScript
./src/js/ar-component.js
AFRAME.registerComponent('marker-persistence', {
  schema: {
    worldPos: { type: 'vec3', default: { x:0, y:0, z:0 } },
    worldRot: { type: 'vec3', default: { x:0, y:0, z:0 } }
  },
  init: function() {
    this.worldEntity = document.createElement('a-entity');
    this.worldEntity.id = 'world-entity';
    this.worldEntity.setAttribute('visible', false);
    if (this.el.sceneEl) {
      this.el.sceneEl.appendChild(this.worldEntity);
    }
    this.modelEntity = document.querySelector('#model-entity');
    this.el.addEventListener('markerFound', this.onMarkerFound.bind(this));
    this.el.addEventListener('markerLost',   this.onMarkerLost.bind(this));
    this.markerVisible = false;
  },
  onMarkerFound: function() {
    this.worldEntity.setAttribute('visible', false);
    this.modelEntity.setAttribute('visible', true);
    this.markerVisible = true;
  },
  onMarkerLost: function() {
    updateModelPosition();
    if (!this.worldEntity.hasChildNodes()) {
      const clone = this.modelEntity.cloneNode(true);
      clone.id = 'world-model';
      this.worldEntity.appendChild(clone);
    }
    this.worldEntity.setAttribute('visible', true);
    this.markerVisible = false;
  }
});
./scr/js/chat.js
document.addEventListener('DOMContentLoaded', () => {
  const form       = document.getElementById('MyForm');
  
  form.addEventListener('submit', async e => {
    /*
    здесь реализована логика обработки ошибок
    */
    ...
    const statusEl = document.getElementById('chat-status');
    statusEl.textContent = 'Запрос отправлен… Ждем ответа бота';
    statusEl.style.display = 'block';
    try {
      /*
      здесь реализована логика отправки запросов
      */
    ...
      statusEl.textContent = 'Ответ получен';
      setTimeout(() => { statusEl.style.display = 'none'; }, 1500);
    ...
      /*
      здесь реализована логика сохранения истории запросов
      */
    } catch {
      statusEl.textContent = 'Ошибка сервиса. Попробуйте еще раз';
      statusEl.style.display = 'block';
      errMod.style.display = 'block';    
    }
  });
  /*
  остальная логика работы с сообщениями
  */
  ...
   window.addEventListener('offline', ()=> {
    inputChat.placeholder = 'Нет соединения';
  });
  window.addEventListener('online', ()=> {
    inputChat.placeholder = 'Введите сообщение...';
  });
  ...
./src/js/main.js
document.addEventListener('DOMContentLoaded', () => {
  const marker    = document.getElementById('marker');
  const modelEnt  = document.getElementById('model-entity');
  const chatCont  = document.getElementById('chat-container');
  const slider    = document.getElementById('scale-slider');
  const centerPos = { x:0, y:0, z:0 };
  let   markerVisible = false;
  let   boolModel     = false;
  marker.addEventListener('markerFound', () => {
    chatCont.style.display = 'flex';
    modelEnt.setAttribute('visible', true);
    markerVisible = true;
    modelEnt.setAttribute('animation-mixer', boolModel ? 'timeScale:1' : 'timeScale:0');
    updateCurrentModelScale();
  });
  marker.addEventListener('markerLost', () => {
    markerVisible = false;
    modelEnt.setAttribute('position', centerPos);
    modelEnt.setAttribute('animation-mixer', boolModel ? 'timeScale:1' : 'timeScale:0');
    updateCurrentModelScale();
  });
  function updateModelPosition() {
    if (markerVisible) {
      const wp = new THREE.Vector3();
      marker.object3D.getWorldPosition(wp);
      modelEnt.object3D.position.copy(wp);
    }
    requestAnimationFrame(updateModelPosition);
  }
  updateModelPosition();
HTML
<!DOCTYPE html>
<html lang="ru">
<head>
#погрузка библиотек и дополнительных скриптов
  <script src="src/js/ar-components.js" defer></script>
  <script src="src/js/main.js"          defer></script>
</head>
<body>
<a-scene>
</a-scene>
<div id="chat-container">
    <div id="chat-status" class="chat-status"></div>
    <div id="chat-messages"></div>
    <div id="chat-input">
      <form id="MyForm">
        <input id="input-chat" type="text" placeholder="Введите сообщение..." />
        <button id="send-button"    type="submit">Отправить</button>
        <button id="history-button" type="button">История</button>
      </form>
      <button id="mic-button" class="microphone-button" aria-label="Начать запись">
        ????
      </button>
       
    </div>
    <div id="history-modal">
      <div id="history-messages"></div>
      <button id="close-history">Закрыть</button>
    </div>
    <div id="error-modal">
      <span>Ошибка соединения</span>
      <button class="close-modal">OK</button>
    </div>
  </div>
</body>

Кроме этого, в данный блок входило еще несколько задач:

  • Анимирование оверлея.
  • Добавление интерактивности оверлея (например, нажатия).
  • Адаптивность под различные устройства.
CSS
./src/css/styles.css
/* ----------------------------------------------------------------
   Планшеты (<=768px)
-----------------------------------------------------------------*/
@media (max-width: 768px) {
  /* Чат чуть шире, опускаем вниз */
  #chat-container {
    width: 80%;
    top: auto;
    bottom: 10px;
    right: 10px;
  }
  /* Масштаб-контрол сбрасываем вниз */
  .ar-control {
    top: auto;
    bottom: 60px;
    left: 10px;
  }
}
/* ------------------------------------------------------------------
   Мобильные (<=480px)
-------------------------------------------------------------------*/
@media (max-width: 480px) {
  /* Чат занимает всю ширину внизу */
  #chat-container {
    width: 100%;
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    border-radius: 0;
    max-height: 40%;
    background: rgba(255,255,255,0.95);
  }
  /* Вход и кнопки в столбик */
  #chat-input {
    flex-direction: column;
  }
  #chat-input input {
    width: 100%;
    margin: 0 0 4px 0;
  }
  #chat-input button {
    width: 100%;
  }
  /* Скрываем контрол масштабирования на телефоне */
  #scale-control {
    display: none;
  }
  /* Уменьшаем размер текста */
  #chat-messages, .chat-status {
    font-size: 14px;
  }
}
HTML
./index.html
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width,initial-scale=1.0"/>
  <title>AR + Chat</title>

<!--
Подгрузка необходимых остальных скриптов
-->
 
  <script src="src/js/ar-components.js" defer></script>
  <script src="src/js/main.js"          defer></script>
  <script src="src/js/chat.js"          defer></script>
  <script src="src/js/voice.js"         defer></script>
  <script src="src/js/gesture_texture.js" defer></script>
  <script src="src/js/components/rotate-on-add.js" defer></script>

</head>
<body>
  <a-scene
    id="scene"
    embedded
    arjs="sourceType: webcam; debugUIEnabled: false; trackingMethod: best;">
   
    <a-assets>
      <a-asset-item id="yourModel"        src="assets/models/character.glb"></a-asset-item>
      <canvas id="gestureCanvas" style="display:none;"></canvas>
    </a-assets>
    <a-plane
    position="0 1.5 -2"
    width="1.6" height="0.9"
    gesture-texture>
    </a-plane>
    <a-marker
      id="marker"
      type="pattern"
      url="assets/patts/pattern-marker.patt"
      marker-persistence>
      <a-entity
        id="model-entity"
        gltf-model="#yourModel"
        scale="1 1 1"
        animation-mixer>
      </a-entity>
      <a-entity
        id="model-object"
        gltf-model="#model2"
        scale="1 1 1"
        rotate-on-add="speed: 60; axis: y">
      </a-entity>
    </a-marker>
    <a-entity camera></a-entity>
  </a-scene>
JavaScript
./src/js/main.js
document.addEventListener('DOMContentLoaded', () => {
. . .
  // выше инициализация остальных переменных
  const sceneEl = document.getElementById('scene');
    sceneEl.addEventListener('click', () => {
      sceneEl.querySelectorAll('[animation-mixer]').forEach(ent => {
        ent.setAttribute('animation-mixer', 'timeScale: 1');
      });
    });
  // дальнейшая логика
. . . 
});

./scr/js/rotate_odd_add.js
document.addEventListener('DOMContentLoaded', () => {
AFRAME.registerComponent('rotate-on-add', {
    schema: {
      speed:  { type: 'number', default: 30 },   // градусы в секунду
      axis:   { type: 'string', default: 'y' }    // 'x', 'y' или 'z'
    },
    init() {
      // Переводим скорость из градусов в радианы на миллисекунду
      this.radPerMs = THREE.Math.degToRad(this.data.speed) / 1000;
      this.elapsed = 0;
    },
    tick(time, timeDelta) {
      // Вращаем вокруг выбранной оси
      const obj3d = this.el.object3D;
      obj3d.rotation[this.data.axis] += this.radPerMs * timeDelta;
    }
  });
});
Блок 2. Веб-разработка

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

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

Решение данного блока представлено в виде фрагментов кода к соответствующим задачам.

Обработка взаимодействия с агентом (обработка ошибок)

JavaScript
function validateBotResponse(data) {
  if (
    !data ||
    !Array.isArray(data.outputs) ||
    data.outputs.length === 0 ||
    !Array.isArray(data.outputs[0].outputs) ||
    data.outputs[0].outputs.length === 0 ||
    !data.outputs[0].outputs[0].results ||
    !data.outputs[0].outputs[0].results.message ||
    typeof data.outputs[0].outputs[0].results.message.text !== 'string'
  ) {
    throw new Error('Неправильная структура ответа от бота');
  }
  const text = data.outputs[0].outputs[0].results.message.text.trim();
  if (!text) {
    throw new Error('Пустой ответ от бота');
  }
  return text;
}

Обработка ответа от бота

JavaScript
const form       = document.getElementById('MyForm');
  const inputChat  = document.getElementById('input-chat');
  const btnSend    = document.getElementById('send-button');
  const btnHist    = document.getElementById('history-button');
  const histMod    = document.getElementById('history-modal');
  const histMsgs   = document.getElementById('history-messages');
  const closeHist  = document.getElementById('close-history');
  const errMod     = document.getElementById('error-modal');
  const closeErr   = document.querySelector('.close-modal');
  form.addEventListener('submit', async e => {
    e.preventDefault();
    const text = inputChat.value.trim();
    if (!text) {
      inputChat.classList.add('error');
      return;
    }
    inputChat.value = '';
    const statusEl = document.getElementById('chat-status');
    statusEl.textContent = 'Запрос отправлен… Ждем ответа бота';
    statusEl.style.display = 'block';
    try {
      const resp = await fetch(
        "http://10.10.2.30:7860/api/v1/run/ f2ad3722-8930-406c-b06d-54ddd58b8a18?stream=false", {
        method: "POST",
        headers: {"x-api-key":"sk-…", "Content-Type":"application/json"},
        body: JSON.stringify({
          input_value: text,
          session_id: sessionId,
          input_type:"chat",
          output_type:"chat",
          tweaks:{}
        })
      });
      if (!resp.ok) throw new Error(resp.status);
      const data = await resp.json();
      statusEl.textContent = 'Ответ получен';
      setTimeout(() => { statusEl.style.display = 'none'; }, 1500);
      const botText = validateBotResponse(data);
      // Успех
      statusEl.textContent = 'Ответ получен';
      setTimeout(() => { statusEl.style.display = 'none'; }, 1500);
   
      addMessage('Вы', text);
      addMessage('Бот', botText);
      speakBotResponse(botText);
      saveHistory(text, botText);
    } catch {
      statusEl.textContent = 'Ошибка сервиса. Попробуйте еще раз';
      statusEl.style.display = 'block';
      errMod.style.display = 'block';    }
  });
  function addMessage(who, msg) {
    const d = document.createElement('div');
    d.innerHTML = `<strong>${who}:</strong> ${msg}<hr/>`;
    document.getElementById('chat-messages').appendChild(d);
  }
  btnHist.addEventListener('click', displayHistory);
  closeHist.addEventListener('click', ()=> histMod.style.display='none');
  closeErr .addEventListener('click', ()=> errMod.style.display='none');
  function saveHistory(u,b) {
    const h = JSON.parse(localStorage.getItem('chatHistory')||'[]');
    h.push({user:u,bot:b});
    localStorage.setItem('chatHistory', JSON.stringify(h));
  }
  function displayHistory() {
    const h = JSON.parse(localStorage.getItem('chatHistory')||'[]');
    histMsgs.innerHTML = h.length
      ? h.map(e=>`<strong>Вы:</strong> ${e.user}<br><strong>Бот:</strong> ${e.bot}<hr/>`).join('')
      : 'История пуста';
    histMod.style.display = 'block';
  }
  window.addEventListener('offline', ()=> {
    inputChat.placeholder = 'Нет соединения';
  });
  window.addEventListener('online', ()=> {
    inputChat.placeholder = 'Введите сообщение...';
  });

Под каждого пользователя / сессию  создается отдельный чат
./src/main.js
document.addEventListener('DOMContentLoaded', () => {
  // Инициализируем sessionId (для трекинга)
  let sessionId = localStorage.getItem('sessionId');
  if (!sessionId) {
    sessionId = 'chat-' + Date.now();
    localStorage.setItem('sessionId', sessionId);
  }
...

Разработка концепции внедрения голосовых запросов и ответов и последующая реализация

JavaScript
./src/js/voice.js
document.addEventListener('DOMContentLoaded', () => {
  if (!('SpeechRecognition' in window) && !('webkitSpeechRecognition' in window)) {
    console.warn('SpeechRecognition не поддерживается в этом браузере');
    return;
  }
  console.log('SpeechRecognition доступен:', window.SpeechRecognition || window.webkitSpeechRecognition);
 
  let recognition, maleVoice;
  window.speechSynthesis.onvoiceschanged = () => {
    maleVoice = speechSynthesis.getVoices().find(v => /Male|мужской/i.test(v.name)) || speechSynthesis.getVoices()[0];
  };
  const micBtn = document.querySelector('.microphone-button');
  const inputChat = document.getElementById('input-chat');
  micBtn.addEventListener('mousedown', startRec);
  micBtn.addEventListener('mouseup',   stopRec);
  micBtn.addEventListener('touchstart',startRec);
  micBtn.addEventListener('touchend',  stopRec);
  function startRec() {
    if (!('SpeechRecognition' in window) && !('webkitSpeechRecognition' in window)) return;
    micBtn.classList.add('recording');
    recognition = new (window.SpeechRecognition||window.webkitSpeechRecognition)();
    recognition.lang = 'ru-RU'; recognition.interimResults = false;
    recognition.onresult = e => {
      inputChat.value = e.results[0][0].transcript;
    };
    recognition.onend = () => micBtn.classList.remove('recording');
    recognition.start();
  }
  function stopRec() {
    recognition?.stop();
    micBtn.classList.remove('recording');
  }
});
HTML
./index.html
  <div id="chat-container">
    <div id="chat-status" class="chat-status"></div>
    <div id="chat-messages"></div>
    <div id="chat-input">
      <form id="MyForm">
        <input id="input-chat" type="text" placeholder="Введите сообщение..." />
        <button id="send-button"    type="submit">Отправить</button>
        <button id="history-button" type="button">История</button>
      </form>
      <button id="mic-button" class="microphone-button" aria-label="Начать запись">
        ||????||
      </button>
       
    </div>
    <div id="history-modal">
      <div id="history-messages"></div>
      <button id="close-history">Закрыть</button>
    </div>
    <div id="error-modal">
      <span>Ошибка соединения</span>
      <button class="close-modal">OK</button>
    </div>
  </div>

Расширение интерфейса взаимодействия с AR-контентом через внедрение жестов

JavaScript
./src/js/gestures.js
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('gestureCanvas');
  const ctx    = canvas.getContext('2d');
  function onResults(results) {
    // синхронизируем размеры с видео
    canvas.width  = results.image.width;
    canvas.height = results.image.height;
    ctx.save();
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(results.image, 0, 0, canvas.width, canvas.height);
    // отрисовка коннекторов/точек
    if (results.multiHandLandmarks) {
      for (const landmarks of results.multiHandLandmarks) {
        drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {color:'#00FF00', lineWidth:4});
        drawLandmarks(canvasCtx, landmarks, {color:'#FF0000', lineWidth:2});
       
        // пример: детектим «пинч» …
       
        // пример: детектим «пинч» …
       const [thumbTip, indexTip] = [landmarks[4], landmarks[8]];
       const dx = (thumbTip.x - indexTip.x) * canvasElement.width;
       const dy = (thumbTip.y - indexTip.y) * canvasElement.height;
       const dist = Math.hypot(dx, dy);
       
       if (dist < 30) {
         const chat = document.getElementById('chat-container');
         chat.style.display = (chat.style.display === 'none') ? 'flex' : 'none';
       }
        // теперь вычисляем ориентацию руки и поворачиваем 3D‑модель
        // берем вектор между запястьем (landmarks[0]) и основанием указательного (landmarks[5])
        const wrist = landmarks[0];
        const indexMCP = landmarks[5];
        // в координатах канваса (X вправо, Y вниз) считаем угол
        const vx = (indexMCP.x - wrist.x);
        const vy = (indexMCP.y - wrist.y);
        // atan2(dx, dy) даст угол поворота вокруг Y
        const angleY = Math.atan2(vx, vy);
        // применяем к модели
        const modelEl = document.getElementById('model-entity');
        if (modelEl && modelEl.object3D) {
          modelEl.object3D.rotation.y = -angleY;
        }
      }
    ctx.restore();
  }
  }
  const hands = new Hands({locateFile: f=>`https://cdn.jsdelivr.net/npm/@mediapipe/hands/${f}`});
  hands.setOptions({maxNumHands:1, minDetectionConfidence:0.8, minTrackingConfidence:0.5});
  hands.onResults(onResults);
  const camera = new Camera(document.createElement('video'), {
    onFrame: ()=>hands.send({image: canvas}), width:640, height:480
  });
  camera.start();
});
./src/js/gestures_texture.js
document.addEventListener('DOMContentLoaded', () => {
AFRAME.registerComponent('gesture-texture', {
    init() {
      const canvas = document.getElementById('gestureCanvas');
      // создаем три.js текстуру из canvas
      this.texture = new THREE.CanvasTexture(canvas);
      // ждем, когда данный entity будет иметь меш
      this.el.addEventListener('model-loaded', () => {
        const mesh = this.el.getObject3D('mesh');
        mesh.material = new THREE.MeshBasicMaterial({
          map: this.texture,
          transparent: true
        });
      });
    },
    tick() {
      // каждую итерацию обновляем текстуру
      if (this.texture) this.texture.needsUpdate = true;
    }
  });
});

Полное решение по описанным выше задачам можно посмотреть здесь: https://disk.360.yandex.ru/d/NoysxxGXYuQ78w/ar_chat_project.zip.

Блок 3. ИИ-составляющая

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

База знаний не должна быть перегружена большими количеством информации, иначе бот в ней долго будет искать ответы и путаться. Кроме того, она должна иметь структуру заголовков (желательно несколько уровней). Дополнительно можно выделить специальными символами важные моменты (например **).

Фрагмент базы знаний с информацией по нерпам представлен ниже.

md
# Байкальская нерпа
Байкальская нерпа (лат. Pusa sibirica) - единственный в мире пресноводный тюлень, эндемик озера Байкал. Это одно из самых загадочных животных, поскольку до сих пор точно неизвестно, как она оказалась в этом изолированном водоеме.
# Внешний вид и особенности байкальская нерпа
Нерпы достигают размеров от 1,1 до 1,4 м в длину и весят порядка 50-130 кг. Обычно они темно-серого или серовато-бурого окраса, иногда с небольшими светлыми пятнами. Нерпы имеют круглую голову с большими выразительными глазами. Живут эти замечательные животные около 50-55 лет, что делает их одними из долгожителей среди тюленей.
# Интересные факты о байкальской нерпе
## Физические способности
- Глубоководное погружение:
  Нерпы могут погружаться на глубину **до 300 метров**.
  - Для сравнения: максимальная глубина погружения человека с использованием газовых смесей составляет 100 м.
  - Нерпа способна задерживать дыхание до 25 мин.
- Скорость передвижения:
  Несмотря на кажущуюся неповоротливость, нерпа может разгоняться в открытом Байкале до 25 км/ч.
## Особенности зрения
- У нерп невероятно развитое зрение: они видят под водой **лучше, чем на суше.
## Питание
- Миф: нерпа питается омулем.
- Реальность:
  - Основной рацион: голомянка и другие виды широколобок и рачки гаммариды.
  - Установлено благодаря находке отолитов (твердых образований из ушных проходов рыб) в кишечнике нерп.
  - Скорость нерпы недостаточна для охоты на быстрого омуля.
  - **Хитрость нерп**: ночью голомянка поднимается ближе к поверхности воды, что облегчает охоту.
## Жировая прослойка и метаболизм
- Жировая прослойка нерпы достигает 14 см.
- Быстрый метаболизм позволяет эффективно использовать пищу, поэтому желудок нерп часто оказывается пустым при исследовании.
## Размножение и развитие детенышей
- Период размножения: конец января - начало февраля.
- Логовища: нерпы строят их внутри торосов на замерзшем Байкале.
- Детеныши:
  - Вес новорожденного нерпенка: **3-4 кг** (как у человеческого ребенка).
  - За первые 2-2,5 месяца нерпенок набирает 25 кг.
  - Молоко нерпы: жирность около 60% (можно резать ножом).
##Эволюционный механизм эмбриональной диапаузы
- Миф: нерпа может «заморозить» беременность до следующего брачного периода.
- Реальность:
  - Существует механизм **эмбриональной диапаузы**, который позволяет разнести по времени процессы оплодотворения и активного развития эмбриона.
  - Продолжительность диапаузы: 1-1,5 месяца.
  - Версия о вынашивании сразу двух детенышей не имеет научного обоснования.
## Культурное значение
- Байкальская нерпа - единственный эндемик млекопитающего озера Байкал.
- Легенды и мифы вокруг нерпы делают ее популярным объектом исследований и лекций среди студентов.

Для презентации результатов необходимо отразить концепт базы знаний — изображение с краткой информацией, содержащейся в базе знаний. В качестве примера концепции приведено решение команды RTX (см. рис. 7.6).

Рис. 7.6. Концептуальное решение команды RTX

Далее необходимо составить структуру чат-бота, соответствующую сценарию взаимодействия, в том числе обработку ответов вне тематики базы знаний бота (см. рис. 7.77.9).

Рис. 7.7.

Рис. 7.8.

Рис. 7.9.

После этого — дополнить базовую схему дополнительными элементами, а именно:

  • Для правильной интерпретация темы или объекта воспользоваться следующей логикой:

    1. погрузить все файлы сформированной базы данных;
    2. переконвертировать их с помощью специального блока в векторные данные;
    3. с помощью метрики сравнить полученные векторы косинусного расстояния с вопросом пользователя;
    4. полученной метрикой выбрать контекст, на основе которого бот должен выдать ответ пользователю;
    5. загрузить контекст и шаблон агенту и получить ответ.

    Рис. 7.10.

    Рис. 7.11.

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

    Рис. 7.12.

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

Рис. 7.13.

Ссылка на итоговую версию чат-бота: https://disk.360.yandex.ru/d/NoysxxGXYuQ78w/Chat-bot.json.

Блок 4. 3D-моделирование

Ссылки на решение:

Участникам необходимо изменить представленные модели (https://disk.360.yandex.ru/d/Qbnff7wr7NMALA) в соответствии с референсами личностей и анимировать аватаров. Помимо аватара необходимо также создать три объекта, соотносящихся с выбранной личностью по тематике и соответствующих требованиям.

Аватар создается с помощью изменения одного из готовых мешей на выбор, либо сбора своего аватара из представленных частей (см. рис. 7.14).

Рис. 7.14.

Далее следует адаптировать аватара для веба и добавить меш одежды. Суммарное количество треугольников у модели не должно превышать 20000.

В первую очередь аватар изменяется в соответствии с предоставленными справочными материалами и референсами выбранной личности (см. рис. 7.15).

Рис. 7.15.

Количество полигонов уменьшается. В эталонном решении задействован модификатор Decimate и ручная доработка. После этого добавляются меши одежды и волос. Итоговый внешний вид эталонного решения без материалов и текстур (см. рис. 7.16).

Рис. 7.16.

После создания базы аватара осуществляется проверка аватара на наличие проблем с трансформациями и направлением нормалей. Модель должна быть окрашена при проверке в синий цвет, не должно быть красных полигонов; значения поворота равны (0, 0, 0), масштаба (1, 1, 1) (см. рис. 7.17).

Рис. 7.17.

После этого создается развертка, благодаря которой на аватара накладываются материалы. В оценке развертки учитывается:

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

Для всего объекта должна быть предусмотрена общая развертка (рис. 7.18), создание отдельной развертки, текстуры и материала под каждый объект снижает общий балл.

Рис. 7.18.

Для переноса детализации на текстуры необходимо осуществить запекание, сохраняющее детализацию на карте нормалей (см. рис. 7.197.20).

Рис. 7.19.

Рис. 7.20.

Это позволяет сохранить внешний вид модели, а также адаптировать ее для дальнейшего использования (см. рис. 7.21).

Рис. 7.21.

После добавления нормалей накладывается карта цвета, содержащая основную информацию об объекте (см. рис. 7.22). Карта может быть создана из референсов, вручную, с помощью нейросети и т. д. Выходной формат — webp, обеспечивающий сохранение качества и малый размер.

В эталонном решении предусмотрено две текстуры:

  • текстура цвета,
  • текстура нормалей,

а также один материал для всего объекта, даже если объект разделен на несколько частей.

Рис. 7.22.

После создания основы аватара добавляется риг. Учитывается способ создания рига (вручную, автоматически, сторонним приложением), а также распределение веса при деформации объекта. В эталонном решении риг создан вручную, веса откорректированы и при деформации элементов они не искажают соседние (см. рис. 7.23).

Рис. 7.23.

Завершением работы над аватаром является добавление анимаций. Оценивается наличие анимации (см. рис. 7.24), а также ее качество и соответствие концепции. Дополнительные анимации оцениваются отдельным критерием.

Рис. 7.24.

По окончании работы аватар экспортируется в формате gltf/glb с включенными в файл текстурами и анимациями. Дополнительные объекты (референсы, источники освещения, камеры) не должны попасть в финальный файл.

Для предметов применяется аналогичный подход. Требования по количеству триссов — не более 2000.

Создается меш выбранного объекта (см. рис. 7.25), проверяются его значения трансформации и направление нормалей. В эталонном решении используется меш ленка с 1176 триссами.

Рис. 7.25.

Далее создается UV-развертка объекта (см. рис. 7.26), заполняется максимальная область текстуры с минимумом пустот.

Рис. 7.26.

Формируется более детализированная версия объекта, и детали переносятся с помощью запекания на изображение текстуры нормалей (см. рис. 7.27).

Рис. 7.27.

В заключение, накладывается текстура цвета (см. рис. 7.28). Для всего объекта используется одна текстура цвета, одна текстура нормалей и один материал. Все текстуры сохранены в формате .webp.

Рис. 7.28.

Итоговый результат экспортируется в формате gltf/glb, с выделением только объекта, без посторонних элементов (камера, свет, референс и т. д.).

text slider background image text slider background image
text slider background image text slider background image text slider background image text slider background image