Pull to refresh

Введение в Qt Quick3D

Qt *
Tutorial
Этот пост участвует в конкурсе „Умные телефоны за умные посты“
Не так давно фреймворк Qt Quick обзавелся дополнением Qt Quick3D, позволяющим полноценно работать с 3D объектами (поддерживается импорт из 3D Max и Blender), совершать над ними различные трансформации, анимации, применять эффекты, ну и вообще по полной использовать возможности лежащего в основе OpenGL. Работает всё это под Symbian, MeeGo, Windows\Linux\MacOs (ну и вообще везде, где Qt есть). В этом топике мы попробуем технологию «на зуб». Писать что-то сложное и серьёзное не хочется, поэтому мы сделаем хабрахолодильник, из которого по клику будет вылетать НЛО.
Сразу результат:


Что нам понадобится


Качаем и устанавливаем Qt Creator, Qt library, Qt Quick3D и Blender. Еще нам понадобятся 3D-модели, которые мы будем использовать. Я взял вот этот холодильник и эту летающую тарелку (всё бесплатное). Ну и логотип Хабра бессовестно стащил.

Поехали


Итак, запускаем Qt Creator и создаём новый проект: Файл->Новый файл или проект->Проект Qt Quick->Интерфейс пользователя на Qt Quick.



Вводим имя проекта, расположения папки, все остальные опции оставляем по-умолчанию.



Новосозданный проект выглядит как-то так:



Оставим пока его в покое.

Подготовка моделей


Открываем Blender и готовим в нём модели, которые мы будем использовать. Я не буду детально описывать работу в Blender (всё-таки, это не по нему урок, да и не спец я в Blender). Вот вкратце, что мы сделаем:
  1. Откроем модели холодильника и НЛО.
  2. Поместим НЛО в холодильник.
  3. Поцепим на холодильник логотип Хабра.
  4. Экспортируем из Blender отдельно НЛО (которое сейчас в холодильнике), дверцу холодильника (она нужна нам отдельно, чтобы её можно было открывать) и его корпус без дверцы. И того у нас должно быть на выходе 4 файла: ufo.3ds, door.3ds, refr.3ds + файл текстуры. Скопируем все эти файлы в папку проекта.


Hello world


Возвращаемся в Qt Creator к нашему проекту. При его создании в файл HabraHolod.qml был записан такой себе «Hello world», но он никакого отношения к Qt Quick3D не имеет, а потому мы его удалим. Начнём писать свой qml-код с нуля. В первой итерации он будет вот таким:
import Qt3D 1.0

Viewport {
    Mesh { id: refrigirator; source: "refr.3ds" }
    Mesh { id: ufo; source: "ufo.3ds" }
    Mesh { id: bottom_door; source: "door.3ds" }

    Item3D { mesh: refrigirator }
    Item3D { mesh: ufo;}
    Item3D { mesh: bottom_door; }
}

Давайте пройдемся по коду. В первой строке мы импортируем пакет Qt3D, который, если Вы всё установили верно, должен найтись и взяться за работу по отображению 3D-объектов. Если же Вы, как и я, что-то на этапе установки запороли (у меня была установлена другая версия библиотек Qt), то вот очень толковая статья, описывающая что и куда нужно положить руками, чтобы всё работало и еще одна, объясняющая как скомпилировать Qt Quick3D вручную.

Далее мы создаём элемент Viewport — это контейнер, в который можно добавлять 3D-объекты и задавать некоторые параметры их отображения (освещение, позицию камеры и т.д.). Дальше мы импортируем 3 наших 3ds-файла (каждый — в отдельный мэш) и создаём 3 элемента Item3D (обратите внимание на их связку с мэшами по свойству id).

Если Вы нажмёте Ctrl+R (запуск), то даже сможете увидеть результат:



Что-то странное, да? :) На самом деле всё работает верно. Всё дело в том, что пока ни окно нашего приложения, ни параметры 3D-сцены не настроены и поэтому мы смотрим на наши объекты в малюсенькое окошко с непонятной позиции. Если Вы вручную сделаете окно побольше и воспользуетесь скролингом, то увидите нашу сцену примерно такой:



Начало неплохое — в 10 строк кода у нас уже есть приложение, кое-как отображающее группу 3D-объектов (а ну-ка, сколько строк будет в нём, если написать его на С++\Java\.NET\Ваш_язык?).

Идём дальше


Итак, объекты наши, конечно, отображаются, но как-то не так, не там и пока не двигаются. Будем это дело понемногу улучшать. Во-первых, добавим в код корневой элемент Rectangle (для этого нам придётся импортировать модуль QtQuick 1.0), который позволит нам задать размер окошка:
import QtQuick 1.0
import Qt3D 1.0

Rectangle {
    color: "black"
    width: 400
    height: 600

    Viewport {
        anchors.fill: parent

        Mesh { id: refrigirator; source: "refr.3ds" }
        Mesh { id: ufo; source: "ufo.3ds" }
        Mesh { id: bottom_door; source: "door.3ds" }

        Item3D { mesh: refrigirator }
        Item3D { mesh: ufo;}
        Item3D { mesh: bottom_door; }
    }
}

Для Rectangle мы задали начальные размеры и цвет, а для Viewport — сказали, что он должен быть растянут на весь размер родителя. Результат:



Окошко верного размера, но смотрим мы на объект всё еще откуда-то снизу. Поправим положение камеры. Для этого задейстуем свойство camera у нашего Viewport:
camera: Camera {
    id: viewCamera
    eye: Qt.vector3d(15,10,40)
    center: Qt.vector3d(-2,10,0)
}

Полный код на данном этапе

Свойство eye определяет, где находится камера, а center — точку, на которую она смотрит. Текущий результат:



А ведь уже неплохо! Если бы мы хотели просто посмотреть на 3D объект в Qt Quick3D — можно было бы на этом и закончить. Но нет! Наша цель — движение.

Обработчик клика


Итак, у нас есть отображающаяся модель холодильника, которая на самом деле 3 модели в одной сцене. Постараемся сделать так, чтобы по клику дверца открывалась и оттуда вылетало НЛО. Прежде всего — обработчик клика. Это просто, добавляем к Viewport элемент MouseArea, растянутый на весь размер родителя. По клику пропишем пока выход (просто чтобы проверить, что работает):
MouseArea {
	anchors.fill: parent
	onClicked: {
		Qt.quit();
	}
}

Полный код на данном этапе

Запускаем, кликаем — программа завершается. Работает.

Трансформации и анимации


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

1. Создать элемент Rotation3D, описывающий, как именно (вокруг каких осей) будет вращаться некий объект.
Rotation3D {
    id: doorOpen
    angle: 0
    axis: Qt.vector3d(0, 1, 0)
    origin: Qt.vector3d(-3, 0,  0)
}

2. Создать элемент SequentialAnimation, суть которого будет в двух вызовах Rotation3D (один-для открытия дверки, второй — для закрытия) с разными направлениями поворота.
SequentialAnimation { id: doorOpenAndClose;
    NumberAnimation { target: doorOpen; property: "angle"; from: 0; to : -80.0; duration: 800;  easing.type: Easing.OutBounce}
    NumberAnimation { target: doorOpen; property: "angle"; from: -80; to : 0.0; duration: 1200; easing.type: Easing.OutCubic}
            }

3. Привязать элемент Rotation3D к Item3D, соответствующему нашей дверке.
 Item3D { mesh: bottom_door; transform: [doorOpen] }

4. Вызвать из обработчика клика промежуточную функцию, которая запустит анимацию.
MouseArea {
    anchors.fill: parent
    onClicked: {
        fullScene.openDoor();
    }
}
		...
Item3D {
	id: fullScene

	function openDoor()	{
		doorOpenAndClose.loops = 1;
		doorOpenAndClose.start();
	}
	...
}

Полный код на данном этапе

Запускаем, кликаем. Бинго!



Дверца открывается и показывает внутри наше НЛО (пока вполне себе мирно висящее). Задача анимации вылета НЛО один-в-один аналогична открытию дверцы. Поэтому без лишних комментариев финальный код:

import QtQuick 1.0
import Qt3D 1.0

Rectangle {
    color: "black"
    width: 400
    height: 600

    Viewport {
        anchors.fill: parent
        MouseArea {
            anchors.fill: parent
            onClicked: {
                fullScene.openDoor();
            }
        }

        camera: Camera {
            id: viewCamera
            eye: Qt.vector3d(15,10,40)
            center: Qt.vector3d(-2,10,0)
        }

        Item3D {
            id: fullScene
            function openDoor(){
                doorOpenAndClose.loops = 1;
                doorOpenAndClose.start();

                ufoFlyOutAndTeleportBack.loops = 1;
                ufoFlyOutAndTeleportBack.start();
            }

            Mesh { id: refrigirator; source: "refr.3ds" }
            Mesh { id: ufo; source: "ufo.3ds" }
            Mesh { id: bottom_door; source: "door.3ds" }

            Item3D { mesh: refrigirator }
            Item3D { mesh: ufo; transform: [ufoFlyOut]}
            Item3D { mesh: bottom_door;  transform: [doorOpen] }

            // ------------------ Transform + Animations ------------------
            Rotation3D {
                id: doorOpen
                angle: 0
                axis: Qt.vector3d(0, 1, 0)
                origin: Qt.vector3d(-3, 0,  0)
            }

            Rotation3D {
                id: ufoFlyOut
                angle: 0
                axis: Qt.vector3d(0, 3, -1)
                origin: Qt.vector3d(10, 0,  0)
            }

            SequentialAnimation { id: doorOpenAndClose;
                NumberAnimation { target: doorOpen; property: "angle"; from: 0; to : -80.0; duration: 800; easing.type: Easing.OutBounce}
                NumberAnimation { target: doorOpen; property: "angle"; from: -80; to : 0.0; duration: 1200; easing.type: Easing.OutCubic}
            }

            SequentialAnimation { id: ufoFlyOutAndTeleportBack;
                NumberAnimation { target: ufoFlyOut; property: "angle"; from: 0; to : 100.0; duration: 1700; easing.type: Easing.OutCurve}
                NumberAnimation { target: ufoFlyOut; property: "angle"; from: 100; to : 0.0; duration: 0; easing.type: Easing.OutCubic}
            }
        }
    }
}

и еще раз результат:


Все исходники проекта можно взять тут.

Полезные материалы по теме

  1. Представление Qt Quick3D и ссылки на загрузку под разные платформы
  2. Клёвый туториал по созданию модели машинки
  3. Официальная документация по Qt Quick3D
Tags:
Hubs:
Total votes 75: ↑66 and ↓9 +57
Views 29K
Comments Comments 58