Как начать работу с дополненной реальностью в Swift, простой способ

1656579028 kak nachat rabotu s dopolnennoj realnostyu v swift prostoj sposob

автор Ранадгир Дэй

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

Но каждая новая технология имеет достаточно крутое время обучения. Вы просто не можете посмотреть основной доклад или видео на YouTube и начать разрабатывать приложение. Но хорошая новость заключается в том, что благодаря AR у Swift очень легко работать с основными приложениями AR. Apple сделала большую часть тяжелой работы за вас. Следуйте и вы увидите, как легко это может быть.

Давайте копаться…

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

vO7XUgbwu9-jNCpzzBts8M-Q4ObnFsVmSCBB

Начнем с создания a единственный взгляд приложение в Xcode и назовите его Home Decor.

vcKpo7OKMJJrjoVC4NUEaj3CK0ZTDVn1B9kc

Добавление разрешений камеры

Теперь первое, что мы сделаем, это перейти к файлу info.plist и включить использование камеры. Возможность камеры – это первое, что нужно для приложения AR. Найдите клавишу описания использования камеры, как на рисунке ниже, и передайте соответствующее сообщение. Это сообщение появится при первом запуске при запросе разрешения на камеру от пользователя.

nXNhGVVjq4c-CAFVSXuDotT1p4OI44-GWugJ

Добавление возможностей ARKit в программу

Выделите Main.storyboard. Перетащите ARKit SceneKit View на ViewController и прикрепите ARSCNView к краям ViewController.

mw2nLyGFYWFvXMOtDFrZqCHentFfkfQu2LFu

Создайте IBOutlet для класса ViewController и назовите его sceneView. Как только вы это сделаете, появится сообщение об ошибке необъявленный ARSCNView, появится всплывающее окно, поскольку наш контроллер представления не распознает ничего типа ARSCNView. Чтобы решить эту проблему и использовать другие функции ARKit, нам нужно импортировать ARKit в контроллер представления.

vqFrntUcNDJ0ySI-E9hIWjLttHQ3bv24PPRU

Теперь перейдите от раскадровки к view controller.swift файлу. Объявите свойство типа ARWorldTrackingConfiguration перед методом viewDidLoad() и назовите его config. И наш контроллер представление будет выглядеть так (я удалил метод didReceiveMemoryWarning):

import UIKitimport ARKit
class ViewController: UIViewController {
@IBOutlet weak var sceneView: ARSCNView!let config = ARWorldTrackingConfiguration()
override func viewDidLoad() {super.viewDidLoad()}

Разрешить отладку

Эта переменная конфигурации определяет конфигурацию сеанса сцены. Мы увидим его использование позже в разделе. Теперь в метод viewDidLoad после super.viewDidLoad() добавьте следующее:

sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]

Здесь мы включаем параметры настройки для нашего sceneView, который не что иное, как просмотр камеры с возможностями AR Framework. ARSCNDebugOptions.showWorldOrigin отобразит на экране происхождения мира. Это поможет нам найти ориентир для всех остальных позиций. ARSCNDebugOptions.showFeaturePoints отобразит на экране все точки, которые камера AR распознала в окружении.

Теперь, чтобы начать сеанс AR, нам нужно запустить сеанс в нашем sceneView с конфигурациями, указанными в переменном config. Чуть-чуть под строкой sceneView.debugOptions напишите:

sceneView.session.run(config)

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

nfQfMj9tFFxVpQ02jfMQpFttyDtiWLo-HAJV

Если вы здесь, у вас уже запущено приложение AR. Поздравляю!

Как работают AR Axes

Красная полоса или ось X используется для расположения объектов слева или справа от начала мира. Зеленая полоса или ось Y используется для размещения объектов в верхней или нижней части света. Синяя полоса или ось Z используются, чтобы определить, насколько близко или далеко будет расположен объект от источника мира.

Положительное значение X расположит объект справа от начала мира, а отрицательное – слева. Положительное значение для Y разместит его сверху, а отрицательное – внизу источника мира. Положительное значение для Z разместит его поближе, а отрицательное разместит его дальше источника мира.

Добавление виртуального объекта

Давайте добавим несколько виртуальных объектов к сцене. 3D капсула была бы хорошим выбором. Объявите capsuleNode типа SCNNode и дайте ему геометрию капсулы. Дайте ему высоту 0,1 метра, а радиус 0,03 метра.

let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1

Теперь расположите его на 0,1 м слева от начала света, на 0,1 м над начальной точкой мира и на 0,1 м от источника мира:

capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)

Теперь добавьте узел к сцене:

sceneView.scene.rootNode.addChildNode(capsuleNode)

СценеView содержит сцену, которая отвечает за сохранение всех 3D-объектов в формате SCNode, которые будут формировать 3D-сцену. Мы добавляем капсулу к корневому узлу сцены. Позиция корневого узла точно соответствует позиции начала света. Это означает, что его позиция (0,0,0).

Теперь наш метод viewDidLoad выглядит так:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Теперь запустите программу.

oWdSpOkroOQI7EuYJZ8-HAix6Xdg2tim75NN

Круто! Мы только что разместили виртуальный объект в реальном мире. Вы можете играть с разными позициями и разной геометрией, чтобы исследовать больше. Теперь давайте возвратим капсулу на 90 градусов вокруг оси Z, чтобы она лежала на оси X и изменила свой цвет на синий.

Углы Эйлера

Углы Эйлера отвечают за угол отображения SCNode. Мы посмотрим, как использовать его для вращения капсулы.

К каждой SCNGeometry можно добавить материалы, определяющие внешний вид геометрии. Материалы обладают свойством диффузии, которая при закреплении распространяет свое содержимое по всей геометрии.

В viewDidLoad добавьте следующие строки после установки положения капсулы.

capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2

Здесь, в первой строке, мы устанавливаем синий цвет для первого материала узла, который распространится по капсуле и сделает ее синим. В строке 2 мы устанавливаем угол Эйлера Z на 90 градусов радиан. Наконец, наше представление загружается и выглядит так:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1
capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Теперь запустите программу.

KRIWB1PKTsTUtgcALCZrbEIKFxyFAsOOJr3M

Прекрасно! Спящая капсула синего цвета на стене! Вы даже можете добавить текстуры как рассеянное содержимое, чтобы сделать объект более реалистичным. Мы используем это в следующей главе, когда разместим текстуры плитки на полу.

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

ARSCENEView делегаты

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

func renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)

Каждый раз, когда мы перемещаем или наклоняем наше устройство с включенным сеансом AR, ARKit пытается найти различные ARA якоря в окружении. ARAnchor содержит информацию о положениях и ориентациях в реальном мире, которые можно использовать для размещения объекта.

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

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor)

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

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

func renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)

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

Обнаружение самолета

Сейчас наша сцена пытается собрать все встречающиеся привязки, поскольку это поведение по умолчанию. Но поскольку пол представляет собой горизонтальную поверхность, нас интересуют только анкеры, которые находятся на горизонтальных плоскостях. Итак, вернитесь к нашему методу viewDidLoad и напишите следующий код раньше запуск строки сессии (т.е. перед sceneView.session.run(config)).

config.planeDetection = .horizontal

В методе viewDidLoad можно удалить все после sceneView.session.run(config), поскольку это было для размещения капсулы на экране, и нам это больше не нужно. Поскольку мы будем использовать все вышеперечисленные методы делегата, нам нужно сделать наш viewController делегатом sceneView. Перед закрывающей скобкой метода viewDidLoad() добавьте строку ниже.

sceneView.delegate = self

Сейчас вы должны получить ошибку, поскольку наш контроллер просмотра все еще не соответствует делегату sceneView. Чтобы реализовать это, давайте создадим расширение контроллера просмотра в конце файла ViewController.swift.

extension ViewController:ARSCNViewDelegate{}

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

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

Создание напольных узлов AR

Давайте создадим функцию, которая получит ARPlaneAnchor в качестве параметра, создадим узел этажа в позиции привязки и вернем его.

func createFloorNode(anchor:ARPlaneAnchor) ->SCNNode{
let floorNode = SCNNode(geometry: SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))) //1
floorNode.position=SCNVector3(anchor.center.x,0,anchor.center.z)                                               //2
floorNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue                                             //3
floorNode.geometry?.firstMaterial?.isDoubleSided = true                                                        //4
floorNode.eulerAngles = SCNVector3(Double.pi/2,0,0)                                                    //5
return floorNode                                                                                               //6
}

Рассмотрим функцию строчку за строчкой и обсудим ее более подробно. Пожалуйста, следуйте описанию каждой строки, поскольку это самая сложная часть.

1. Мы создаем узел с геометрией SCNPlane, имеющей размер якоря. Экстент ARPlaneAnchor содержит информацию о позиции. Тот факт, что extent.z использовался в качестве высоты, а не extent.y, может быть немного запутанным. Если вы визуализируете, что 3D-куб расположен на полу, и вы хотите сделать его плоским вдоль 2D-поверхности, вы бы изменили y на ноль, и он стал бы плоским. Теперь, чтобы получить длину этой 2D поверхности, вы бы рассмотрели z, не правда ли? Наш пол плоский, поэтому нам нужен плоский узел, а не куб.

2. Задаем положение узла. Поскольку нам не нужен подъем, мы делаем y нулевым.

3. Установите цвет на синий.

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

5. По умолчанию, плоскость будет размещена вертикально. Чтобы сделать его горизонтальным нам нужно вернуть его на 90 градусов.

Реализация методов делегата

Теперь давайте реализуем метод делегата didAdd SCNNode.

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return} //1
let planeNode = createFloorNode(anchor: planeAnchor) //2
node.addChildNode(planeNode) //3
}

В строке 1 мы проверяем, есть ли якорь ARPlaneAnchor, поскольку мы имеем дело только с этим типом привязки.

В строке 2 создается новый узел на основе якоря. В строке 3 он прилагается к узлу.

Теперь в делегате SCNNode didUpdate мы удалим все наши узлы этажа. Мы сделаем это потому, что размеры текущего узла были изменены, а старые узлы этажа не совпадают. Затем мы снова добавим новый узел этажа к этому обновленному узлу.

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
let planeNode = createFloorNode(anchor: planeAnchor)
node.addChildNode(planeNode)
}

В методе делегата didRemove SCNNode мы хотим цивилизованно очистить все наши ненужные узлы.

func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
guard let _ = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
}

Фу! Это! Запустите приложение.

3wVNiLmgzrS3Hjqr7lyE4wQJIssiklvQKZOk

Добавление эффекта плитки

Ждать, что? Синий пол? Нет, мы еще не полностью кончили. Только небольшая смена, и у нас будет потрясающий пол!

Чтобы сменить голубой пол на плитку, нам понадобится текстура. Давайте поищем в Google текстуру напольной плитки. Я искал текстуру деревянного пола и нашел несколько красивых изображений текстуры. Сохраните любой из них на Mac и перетащите его к Assets.xcassets.

k9pct70eqbORC5u5WnCgSPOb5cYSmN9hoKmC

Я назвал это WoodenFloorTile. Вы можете назвать его сколь угодно. Снова вернитесь к файлу ViewController.swift. В функции createFloorNode вместо установки UIColor.blue в качестве рассеянного содержимого сделайте его UIImage с названием, которое вы дали изображению в папке активов.

floorNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "WoodenFloorTile")

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

aO7-9zVFW65MANwJfIgAmwT0OaX6xXIIYEEEx

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

Загрузите полный проект с GitHub здесь.

Теперь, когда у вас хороший пол, вам, пожалуй, не хватает хорошей мебели, чтобы придать вашей комнате отличный вид! Над этим мы поработаем позже.

Добавить комментарий

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