Долой скучный и плоский мир, переезжаем в 3D!

В предыдущей статье мы с вами рассмотрели как разработать Qt приложение, используя стандартные средства для проектирования интерфейсов QtDesigner — очень хорошее средство, если вы разрабатываете вполне обычное приложение, не требующее поддержки 3-х мерной графики и эффектов. Но иногда возникает потребность в быстрой разработке интерфейса с поддержкой 3D. К счастью в Qt Creator существует специальный инструмент для решения данной задачи — инструмент для быстрого проектирования интерфейсов Qt Quick!

Создание приложения Qt Quick

Итак, давайте начнём! Первое что нам нужно сделать, так это создать новый проект используя сочетание клавиш Ctrl + N или выбрав пункт главного меню ФаилСоздать файл или проект…

Далее выберем пункт Приложение Qt Quick

Создание проекта Qt Quick

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

Создание проекта Qt Quick. Итог

Здесь я назвал проект simple_tree потому как в данном проекте я покажу как отрисовать простое дерево в 3-х мерном пространстве, пользуясь уже готовой 3D-моделью и файлами текстур. Вы же можете выбрать любой другой объект — главное чтобы он был не слишком сложным. Об этом мы ещё поговорим далее.

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

Работа над проектом

Итак, наш проект создан, в результате вы видите перед собой главное окно QtCreator:

Структура проекта Qt Quick

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

Qt Modeling Language

Qt Modeling Language (QML) — специальный язык разметки, предназначенный для создания пользовательского интерфейса. Из официальной документации:

QML — это спецификация пользовательского интерфейса и язык программирования. Он позволяет разработчикам и дизайнерам создавать высокопроизводительные, плавно анимированные и визуально привлекательные приложения. QML предлагает читаемый декларативный JSON-подобный синтаксис с поддержкой императивных выражений JavaScript в сочетании с динамическими привязками свойств объектов.

Иными словами QML это язык разметки пользовательского интерфейса для Qt приложения, чем-то напоминающий JSON по синтаксису. С помощью него вы можете создавать интерфейсы, добавлять анимацию и 3D-графику — в общем, делать ваши приложения живыми и красивыми!

Вы можете познакомиться с QML, например прочитав статью о QML на официальном сайте Qt. Для нашей работы нам потребуется всего несколько элементов данного языка, касающихся 3D-графики, но если вы хотите в действительности овладеть этим инструментом, то я советую вам подружиться с официальной документацией по языку QML — благо, что она очень хорошо и подробно написана.

Google Translate Page

Если же вас смущает обилие английского языка, то вы можете использовать плагин Google Translate Page, поставляемый с браузером Google Chrome — для перевода страницы на русский язык достаточно одного клика по кнопке в правом верхнем углу экрана:

Google Translate Page фичаДанный плагин доступен также и в браузере Firefox, но требует для этого специальной установки. В крайнем случае, если ваш браузер не поддерживает этот плагин, вы можете воспользоваться приложением Google Переводчик: если ввести URL-ссылку на страницу в окно переводчика, в окне перевода появиться гиперссылка на страницу с переводом. К примеру наша статья про QML будет выглядеть следующим образом:

Пример использования Google Translate

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

Включаем поддержку Qt 3D

Итак, прежде чем мы начнём редактирование .qml файла, нам потребуется немного изменить код нашего приложения для того, чтобы включить поддержку 3D. Откройте файл main.cpp и замените его содержимое кодом указанным ниже:

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

Копирование исходного кода

Для того чтобы заменить исходный текст в Qt Creator, достаточно просто нажать сочетание клавиш Ctrl + A в окне редактора, чтобы выделить старый текст, а затем Ctrl + V, чтобы заменить его на новый.

После того, как это будет сделано, нам так же потребуется добавить несколько строк в файл проекта нашего приложения. Откройте файл с расширением .pro и добавьте в первую строку следующие элементы:

qml 3dcore 3drender 3dinput 3dquick 3dlogic 3dquickextras

В принципе это всё, что нам потребуется для того, чтобы могли работать с 3D-графикой в нашем проекте, за исключением самой 3D-графики!

Файлы 3D-объектов

Конечно же без самих файлов 3D-объектов мы не можем обойтись, если хотим сделать что-то более менее интересное в плане графики. Хотя мы можем использовать базовые примитивы типа плоскостей, некоторых идеальных платоновых тел, таких как сфера или куб, но это всё не очень интересно по сравнению со всем многообразием природных и технических форм, которые можно получить используя 3D-редактор и моделирование.

Да, если вы хотите отрисовать что-нибудь своё, то вам будет достаточно получить все необходимые файлы ваших 3D-моделей в формате .obj, а также отдельные файлы текстур для каждого из объектов в любом из графических форматов (JPEG, PNG, TIFF, etc). Если вы уже пользовались каким-либо 3D-редактором, то вы можете сделать это самостоятельно. Ну а тем, для кого это ещё в новинку, я покажу как это сделать легко и быстро с помощью редактора Blender.

Итак, если вы не желаете устанавливать себе 3D-редактор, то вы можете пропустить этот шаг, скачав все необходимые файлы по этой ссылке. Если же вы желаете получить этот опыт, то гоу сюды. На данном сайте вы можете скачать легковесный, многофункциональный и эргономичный open source редактор 3-х мерной графики с чудным названием Blender. Чтобы оценить всю прелесть и мощь данного инструмента, предлагаю ознакомиться с этим видео. Нам же потребуется всего пара функций от этой программы.

Создание 3D-моделей — дело не из лёгких. И скорее относится не к нашей специальности, так как мы занимаемся всё-таки программированием. Но порою так хочется от души позабавиться графическими возможностями наших кремниевых братьев! Поэтому всем, кто чувствует тоже самое, я оставляю эту ссылку на один из лучших самоучителей по Blender К сожалению, к настоящему времени этот самоучитель немного устарел, но вы можете установить себе Blender версии 2.49 из архива, чтобы лучше познакомиться с его основными функциями, а затем перейти к новой версии, воспользовавшись официальной документацией — так будет гораздо легче освоить его новые возможности.

Я же например человек ленивый — не хочу изобретать велосипед, тем более что в интернете уже существует достаточно ресурсов, на которых можно скачать 3D-модели бесплатно. Как говориться — кто со мной, тот герой!

Сайт turbosquid.com. Главная страница

Заходим на сайт turbosquid.com, регистрируемся (это обязательно) и ставим фильтр на blend-файлы в разделе Formats в левой верхней части экрана. Объект можете выбрать любой, какой душе угодно, главное только, чтобы его фаил не оказался более 50 МБ, по принципу чем легче — тем лучше, потому как файлы 3D-моделей по умолчанию будут интегрированы в структуру приложения. Да, Qt Quick работает именно по этому принципу, данный инструмент предназначен для создания UI, а не тяжеловесных 3D-игр и интерактивных 3D-приложений.

Сайт turbosquid.com. Обзор 3D-модели

После того как вы выбрали нужный объект кликните на него, появиться его крупный план и кнопка Download снизу. Кликайте на неё. На следующей странице у вас будет возможность скачать нужный файл с расширением blend.

Хорошо, как только вы получили 3D-модель объекта, вы можете открыть его в Blender по двойному щелчку ЛКМ на файле.

Экспорт модели в Blender. Удаление элемента

В открытом редакторе вам будет нужно пройтись по всем элементам модели, щёлкая  ЛКМ по доступным для вас участкам и нажимая на клавишу Delete, до тех пор пока у вас не будет оставаться какой-то один элемент модели, чтобы сделать её экспорт в отдельный объектный файл с расширением .obj

Экспорт модели в Blender. Выбор формата для экспорта

Убедитесь что остался только один элемент вашей 3D-модели и экспортируйте его с помощью меню File — Export — Wavefront (.obj) Экспорт модели в Blender. Окно выбора директорииНа выходе у вас должно получиться несколько файлов с расширением .obj по количеству элементов вашей 3D-модели. И ещё, так как мы всё-таки хотим сделать что-то цветное, нам придётся позаботиться о текстурах, которые мы можем позаимствовать на сайте textures.com

Сайт textures.com. Выбор текстур

Я скачал себе набор текстур для ствола дерева и ещё одно изображение для травы. Так что добавилось ещё 6 файлов. Все файлы я поместил в созданную мной отдельную директорию data/ внутри каталога проекта приложения, а затем добавил их в файл ресурсов проекта, так что у меня получилась следующая структура:

Ах, да, чуть не забыл про файл plane.obj, вам придётся создать его самостоятельно, если вы хотите повторить тот же самый путь, что проделал и я, либо можете скачать его из репозитария на гите. И если вы забыли как создаётся файл ресурсов, то вернитесь к предыдущей моей статье и прочитайте раздел про Ресурсы Qt. Вот и всё, теперь мы смело можем приступать программированию 3D-приложения на языке QML!

Пишем 3D-приложение на языке QML

C чего начинается приложение написанное на языке QML? Первое что нужно сделать это определиться какие именно модули мы будем использовать в приложении. Это делается с помощью функции import.

Итак, откроем наш файл main.qml, в первых строках вы видите что уже используются модули QtQuick и QtQuick.Window, напротив которых указаны номер их версий. Что ж, второй модуль нам не понадобиться, так как мы не будем использовать класс QQuickWindow, зато нам понадобятся классы для работы с 3D-графикой из тех модулей, которые мы уже подключили в наш проект прописав дополнительные строки в файле .pro. В результате получится следующее:

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

Следующий код нам придётся переписывать, так как мы отказались от QQuickWindow. Поэтому вместо Window мы пишем

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

  1. id (идентификатор) позволяет ссылаться на любое свойство данного класса из любого другого класса по идентификатору, в том числе и на саму Entity
  2. components (компоненты) это массив объектов, которые входят в структуру объекта Entity

Свойство enabled не является основным, оно лишь обозначает, что данный элемент интерфейса включен и доступен для взаимодействия. В случае scene оно должно быть обязательно установлено как true, чтобы всё что мы делаем было доступно после запуска приложения.

Пришло время добавить парочку элементов без которых немыслима любая 3D-сцена, кроме чёрного квадрата (английский юмор).

Свет

Свет этот — порожденье тьмы ночной
И отнял место у нее самой.
Он с ней не сладит, как бы ни хотел.
Его удел — поверхность твердых тел.
Он к ним прикован, связан с их судьбой,
И лишь с их помощью он может быть собой…

Иоганн Вольфганг Гёте, «Фауст»

…лирическое отступление, и продолжим. Добавим следующие строки сразу после комментария:

Здесь мы объявляем новую сущность, внутри которой указываем компоненту DirectionalLight — она отвечает за создание направленного освещения в нашей сцене.

  • Освещение приходит с высоты 10 единиц над сценой немного ближе к нам от центра на 6 единиц и чуть-чуть сбоку на 2 единицы (свойство worldDirection).
  • Цвет освещения — чисто белый, ему соответствует HEX код #FFFFFF, означающий максимальную интенсивность по всем каналам RGB (свойство color).
  • Сила света стандартная в 1 единицу (свойство intensity).

Камера

3D-сцены в кантовском смысле, как вещь в себе, через компьютер мы наблюдать не можем, поэтому нам нужна хотя бы какая-то точка зрения, с которой мы сможем на неё посмотреть.

Эта точка точка всем точкам точка…

Из фольклора про Кастанеду

И здесь будет немного сложнее, чем со светом, так как потребуется создать несколько объектов:

Как видите здесь есть 2 объекта Camera и OrbitCameraController — первый объект отвечает за камеру, а второй за управление ею с помощью действий мышью.

Основные свойства объекта Camera:

  • projectionType (тип проекции) — Orthographic, Perspective, Frustum, Custom — влияет на отображение объектов, соотношения и пропорции объектов вблизи и вдали
  • fieldOfView (угол обзора) — чем больше угол, тем больше объектов попадает в камеру
  • aspectRatio (соотношение сторон) — 16/9, 4/3 и т.п. соотношения сторон для отображения картины по горизонтали и вертикали
  • nearPlane (ближний план), farPlane (дальний план) — детализация объектов ближнего и дальнего плана
  • position (позиция) — положение камеры в 3-х мерном пространстве
  • upVector (верх-вектор) — направление верхнего вектора камеры (в данном случае совпадает с осью OY)
  • viewCenter (центр просмотра) — направление фокуса камеры, то есть положение центра объекта, на который камера сориентирована.

Если ввести подобные координаты position, upVector и viewCenter для камеры в Blender, то получится следующая картина:

Как видите камера находится спереди от нашей сцены, прямо напротив нашего дерева и немного выше от основной плоскости XZ. (В Blender в отличие от Qt координаты Y и Z меняются местами.)

У объекта OrbitCameraController просто указывается id конкретной камеры в свойстве camera (для контроля её движений со стороны пользователя).

И это ещё не всё. Нужно добавить в components нашей сцены следующее:

RenderSettings (настройки отрисовки) со свойством activeFrameGraph позволяют запустить отрисовку нашей 3D-сцены. Мы задаём два свойства для класса отрисовщика ForwrardRenderer, с одним из которых вы уже знакомы, а второй clearColor (чистый цвет) — это цвет фона нашей сцены. Здесь также вы можете указать любой цвет с помощью его HEX-значения или его имени латинскими символами.

InputSettings (настройки ввода) я указываю для приличия, ознакомьтесь с ними самостоятельно на странице документации.

3D-объекты

Lights, camera, action, baby!

Перифраз обычного производственного процесса в съёмках

Теперь мы подошли к самому главному, ради чего затевался весь этот сыр-бор. Наше виртуальное пространство полностью готово для того, чтобы населить его 3D-объектами.

Из чего состоит любой 3D-объект? Во-первых это форма (Mesh), а во вторых это материал (Material). Так вот, нам нужно подготовить формы и материалы для каждого из 3D-объектов, прежде чем мы собираемся сделать их отрисовку.

Что ж, я начну с самого главного (вы можете продолжать писать свой код после OrbitCameraController и перед components в нашей сцене):

Mesh (форма) — здесь я подгружаю форму 3D-объекта из .obj файла в source и даю ему id-имя, чтобы потом сослаться на неё в отдельном Entity. Имя файла берётся из списка в файле Qt ресурсов, который мы создали ранее. Бывают разные виды мешей по типу геометрических тел: PlaneMesh для построения плоскостей, SphereMesh для шаров, CubeMesh для кубов и т.д.

Затем я создаю объект материала TexturedMetalRoughMaterial, который испольует конкретную схему отрисовки текстур на основе 5 текстурных блоков:

  1. baseColor для передачи цвета
  2. metalness для передачи блеска
  3. roughness для передачи шероховатости
  4. normal для указания нормалей поверхности
  5. ambientOcclusion для затенения

Есть и другие схемы отрисовки текстур и классы соответствующих им материалов, это весьма глубокая и специфичная тема. Для искушённых ценителей 3D-искусства скажу то, что Qt Quick поддерживает шейдеры и OpenGL и вы можете узнать о том, как использовать их из примеров — например из этого, либо вот из этой и вот этой интересных статей.

Когда форма и материал 3D-объекта готовы, для его отображения достаточно создать отдельную сущность Entity, обязательно указав форму и материал в списке его компонент:

Теперь, если вы запустите ваше приложение, то вы должны увидеть перед собой первый 3D-объект нарисованный вами на экране вашего устройства. По умолчанию этот объект будет расположен в центре с координатами (0, 0, 0), так же как и 3-х мерная форма вашего объекта записанная в файле .obj. Но если вы хотите задать новое положение, то вы можете создать дополнительный объект преобразований Transform, который имеет свойства:

  1. translation (перенос) — задаёт координаты нового центра для объекта
  2. rotation (поворот) — определяет углы поворота объекта относительно координатных осей
  3. scale (масштабирование) — изменяет размер объекта по каждой из координат

Данные свойства можно определить с помощью класса Qt.vector3d который принимает на вход значения вектора x, y, z интерпретируемые как координаты нового центра, углы поворота или коэффициенты масштаба относительно 3-х осей X, Y, Z.

На моей картине чего-то не хватает, поэтому я добавлю ещё парочку объектов: мне нужно добавить листву для дерева, чтобы оно приобрело весенний вид, поэтому:

Ну и конечно же не забуду добавить траву под ногами, иначе дерево моё дерево зависнет в воздухе.

Тайлинг

А снится нам трава, трава у дома — зелёная, зелёная трава…

Группа «Земляне»

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

Здесь у нас 3 новых класса: Texture2D, NodeInstantiator и DiffuseMapMaterial. Класс Texture2D поддерживает загрузку изображения текстуры с помощью свойства source класса TextureImage. Класс DiffuseMapMaterial позволяет использовать эту текстуру для создания материала, это несколько иной способ создания материала из текстуры, основанный на модели затенения по Фонгу. На самом деле существует множество способов решить одну и туже задачу с помощью средств математики. Кто-то очень умный заметил, что

Математика — наука обо всех возможных мирах.

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

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

Итак, что же такое этот NodeInstatiator? Эта штука предназначена для того, чтобы делать циклы специально для работы с 3D-объектами. QML поддерживает и обычные циклы for в рамках возможностей интеграции c JavaScript. Но для работы с 3D-объектами вполне достаточно обойтись этим классом. Я думаю вам нетрудно будет разобраться в том, как он устроен. По сути этот класс представляет цикл генерации отдельных объектов, свойства которого вычисляются на каждом шаге этого цикла. Доступ к свойствам осуществляется через id класса.

После всех этих пертурбаций у меня получилась следующая картина:

Рендеринг 3D-сцены на Qt Quick

Текст

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

А я прощаюсь с вами до следующей статьи, в которой мы продолжим изучать мир 3D и перенесёмся с вами в виртуальную реальность. И напоследок, держите это

Денежное дерево по фэн-шуй

И помните

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

Вождь Белое Облако

Берегите природу! Сажайте деревья! Любите Землю!


Leave a Reply

Your email address will not be published.

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.