В этом уроке вы узнаете, как создать базовую 3D-сцену в SceneKit без сложностей OpenGL. Это включает в себя базовую геометрию, камеры, источники света, материалы и тени.
Вступление
Инфраструктура SceneKit была впервые запущена Apple вместе с OS X 10.8 Mountain Lion и позже стала доступна для iOS с выпуском iOS 8. Цель этой платформы — позволить разработчикам легко интегрировать 3D-графику в игры и приложения без сложностей графические API, такие как OpenGL и Metal.
SceneKit позволяет вам просто предоставить описание ресурсов, которые вы хотите видеть в вашей сцене, а сама платформа обрабатывает весь код рендеринга OpenGL для вас. В этом первом уроке я познакомлю вас с некоторыми основами работы с трехмерными активами и основами инфраструктуры SceneKit.
Этот учебник требует, чтобы вы работали на Xcode 6 или выше . Хотя в этом нет необходимости, я рекомендую использовать физическое устройство под управлением iOS 8 для тестирования кода SceneKit. Вы можете использовать iOS Simulator, но производительность не будет хорошей, если ваша сцена станет более сложной. Обратите внимание, что для тестирования на физическом устройстве iOS требуется наличие зарегистрированной учетной записи разработчика iOS.
1. Основы
Первое, что вам нужно знать о SceneKit, — это то, что активы, представленные узлами, расположены в иерархическом дереве, называемом графом сцены . Если вы знакомы с разработкой для iOS, это дерево работает во многом как обычная иерархия представлений. в UIKit. Каждая сцена, которую вы создаете, имеет единственный корневой узел, к которому вы добавляете последующие узлы, и который также обеспечивает основу для трехмерной системы координат этой сцены.
Когда вы добавляете узел к сцене, его положение задается набором из трех чисел, трехкомпонентного вектора, представленного структурой SCNVector3
в вашем коде. Каждый из этих трех компонентов определяет положение узла по осям x, y и z, как показано на рисунке ниже.
Положение корневого узла вашей сцены определяется как (0, 0, 0) . На изображении выше это точка пересечения трех осей. Включенная камера на изображении представляет направление по умолчанию, на которое указывает камера, когда оно добавляется к вашей сцене.
Теперь, когда вы знаете некоторые основы того, как объекты представлены в SceneKit, вы готовы начать писать некоторый код.
2. Настройка проекта
Откройте Xcode и создайте новое приложение iOS на основе шаблона приложения Single View . Хотя вы можете легко создать приложение из шаблона Game, используя SceneKit, для этого урока я покажу вам, как начать работу с SceneKit с нуля.
Введите название продукта , установите для языка значение Swift , а для устройства — Universal . Нажмите Далее, чтобы продолжить.
После создания проекта перейдите к ViewController.swift и добавьте следующий оператор импорта вверху, чтобы импортировать инфраструктуру SceneKit:
1
|
import SceneKit
|
Затем добавьте следующую реализацию метода ViewController
классе ViewController
:
1
2
3
4
5
6
|
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = SCNView(frame: self.view.frame)
self.view.addSubview(sceneView)
}
|
В методе viewDidLoad
мы создаем объект SCNView
, передавая в кадре представление контроллера представления. Мы присваиваем экземпляр SCNView
константе sceneView
и добавляем его как подпредставление представления контроллера представления.
Класс SCNView
является подклассом UIView
и предоставляет выход для вашего содержимого SceneKit. Помимо функциональности обычного представления, SCNView
также имеет несколько свойств и методов, относящихся к содержимому SceneKit.
Чтобы убедиться, что все работает правильно, соберите и запустите ваше приложение. Вы увидите, что у вас просто пустой белый вид.
3. Настройка сцены
Чтобы отобразить содержимое в SCNView
, сначала необходимо создать SCNScene
и назначить его представлению. В этой сцене вам нужно добавить камера и хотя бы один свет. В этом примере вы также собираетесь добавить куб для рендеринга SceneKit. Добавьте следующий код в метод viewDidLoad
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = SCNView(frame: self.view.frame)
self.view.addSubview(sceneView)
let scene = SCNScene()
sceneView.scene = scene
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 3.0)
let light = SCNLight()
light.type = SCNLightTypeOmni
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let cubeNode = SCNNode(geometry: cubeGeometry)
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(cubeNode)
}
|
Давайте viewDidLoad
метод viewDidLoad
шаг за шагом:
- Сначала вы создаете сцену для своего представления, вызывая метод
init
. Если вы не загружаете подготовленную сцену из внешнего файла, этот инициализатор вы всегда будете использовать. - Затем вы создаете объект
SCNNode
экземплярSCNNode
для камеры. Затем вы назначаете объектSCNCamera
свойствуcamera
cameraNode
и перемещаете этот узел вдоль оси z, чтобы увидеть куб, который вы создадите чуть позже. - На следующем шаге вы создадите объект
SCNLight
иSCNNode
именемlightNode
. ЭкземплярSCNLight
назначается свойству light узла light. Свойствоtype
дляSCNLight
установлено вSCNLightTypeOmni
. Этот тип света распределяет свет равномерно во всех направлениях от точки в трехмерном пространстве. Вы можете думать об этом типе света как обычная лампочка. - Наконец, вы создаете куб, используя класс
SCNBox
, делая ширину, высоту и длину одинаковыми по размеру. КлассSCNBox
является подклассомSCNGeometry
и является одной из примитивных фигур, которые вы можете создать. Другие формы включают сферы, пирамиды и торы. Вы также создаете узел, передающий в куб для параметраgeometry
. - Чтобы настроить сцену, вы добавляете три узла (камера, источник света и куб) к графу сцены сцены. Дополнительная настройка не требуется, поскольку объект
SCNScene
автоматически обнаруживает, когда узел содержит камеру или объект освещения, соответственно визуализируя сцену.
Создайте и запустите ваше приложение, и вы увидите, что теперь у вас есть черный куб, освещаемый вашим светом из правого верхнего угла.
К сожалению, на данный момент куб не выглядит трехмерным. Это связано с тем, что камера расположена прямо перед ней. То, что вы собираетесь сделать сейчас, это изменить положение камеры, чтобы она лучше видела куб.
Однако, чтобы камера была направлена прямо на куб, вы также добавите к камере SCNLookAtConstraint
. Начните с обновления положения камеры, как показано ниже.
1
|
cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)
|
Затем добавьте следующий фрагмент кода в метод viewDidLoad после создания экземпляра узла для куба:
1
2
3
|
let constraint = SCNLookAtConstraint(target: cubeNode)
constraint.gimbalLockEnabled = true
cameraNode.constraints = [constraint]
|
Изменение положения перемещает камеру влево и вверх. Добавляя ограничение, в котором куб является его целью, а gimbalLockEnabled
значение true
, вы гарантируете, что камера будет оставаться параллельной горизонту и окну просмотра, в данном случае экрану вашего устройства. Это делается путем отключения вращения вдоль оси крена, ось которого направлена от камеры к цели ограничения.
Снова постройте и запустите приложение, и вы увидите свой куб во всей его трехмерной красе.
4. Материалы и тени
Пришло время добавить больше реализма к сцене с материалами и тенями. Сначала вам понадобится другой объект, чтобы отбросить тень. Используйте следующий фрагмент кода, чтобы создать плоскость, плоский прямоугольник и расположите его под кубом. Не забудьте добавить новый узел в качестве дочернего узла в корневой узел сцены.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
override func viewDidLoad() {
…
let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let cubeNode = SCNNode(geometry: cubeGeometry)
let planeGeometry = SCNPlane(width: 50.0, height: 50.0)
let planeNode = SCNNode(geometry: planeGeometry)
planeNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
planeNode.position = SCNVector3(x: 0, y: -0.5, z: 0)
…
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(cubeNode)
scene.rootNode.addChildNode(planeNode)
}
|
Изменяя свойство eulerAngles
узла плоскости, вы поворачиваете плоскость назад на 90 градусов вдоль оси x. Мы должны сделать это, потому что самолеты созданы вертикально по умолчанию. В SceneKit углы поворота рассчитываются в радианах, а не в градусах, но эти значения можно легко преобразовать с помощью GLKMathDegreesToRadians(_:)
и GLKMathsRadiansToDegrees(_:)
. GLK означает GLKit, каркас Apple OpenGL.
Затем добавьте материал к кубу и плоскости. В этом примере вы дадите кубу и плоскости сплошной цвет, соответственно красный и зеленый. Добавьте следующие строки в метод viewDidLoad
для создания этих материалов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
override func viewDidLoad() {
…
planeNode.position = SCNVector3(x: 0, y: -0.5, z: 0)
let redMaterial = SCNMaterial()
redMaterial.diffuse.contents = UIColor.redColor()
cubeGeometry.materials = [redMaterial]
let greenMaterial = SCNMaterial()
greenMaterial.diffuse.contents = UIColor.greenColor()
planeGeometry.materials = [greenMaterial]
let constraint = SCNLookAtConstraint(target: cubeNode)
…
}
|
Для каждого объекта SCNMaterial
его диффузное содержимое UIColor
значение UIColor
. Диффузное свойство материала определяет его внешний вид при прямом освещении. Обратите внимание, что присвоенное значение не обязательно должно быть объектом UIColor
. Есть много других приемлемых типов объектов для назначения этому свойству, таких как UIImage
, CALayer
и даже текстура SKTexture
( SKTexture
).
Снова создайте и запустите приложение, чтобы не только впервые увидеть самолет, но и материалы, которые вы создали.
Теперь пришло время добавить тени на вашу сцену. Из четырех типов освещения, доступных в SceneKit, только точечные источники света могут создавать тени. В этом примере вы собираетесь превратить ваш существующий омни-свет в точечный источник света, нацеленный на куб. Добавьте следующий код в метод viewDidLoad
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
override func viewDidLoad() {
…
let light = SCNLight()
light.type = SCNLightTypeSpot
light.spotInnerAngle = 30.0
light.spotOuterAngle = 80.0
light.castsShadow = true
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
…
let constraint = SCNLookAtConstraint(target: cubeNode)
constraint.gimbalLockEnabled = true
cameraNode.constraints = [constraint]
lightNode.constraints = [constraint]
…
}
|
Чтобы создать точечный источник света, сначала установите тип SCNLightTypeSpot
света SCNLightTypeSpot
. Затем вы указываете внутренний и внешний углы прожектора в градусах. Значения по умолчанию — 0 и 45 соответственно. Внутренний угол определяет, какую площадь свет освещает при прямом освещении, в то время как внешний угол определяет, какая часть освещена частично. Разница между этими углами станет ясна, как только вы увидите получившуюся сцену. Затем вы явно указываете свету отбрасывать тени, а также добавляете тот же SCNLookAtConstraint
который вы создали для вашей камеры ранее.
Создайте и запустите ваше приложение, чтобы увидеть получившуюся сцену. Внутренний угол, который вы указали в своем коде, отображается там, где плоскость представляет собой сплошной зеленый цвет непосредственно под кубом. Внешний угол показан градиентом света, который исчезает до черного при удалении от цели света.
Вы увидите, что теперь ваш куб правильно отбрасывает тень. Точечный свет, однако, освещает только часть самолета. Это потому, что в вашей сцене нет рассеянного света.
Окружающий свет — это источник света, который освещает все с равным распределением света. Поскольку окружающий свет освещает всю сцену, его положение не имеет значения, и вы можете добавить его к любому нужному узлу, даже к тому же узлу, что и ваша камера. Используйте следующий фрагмент кода, чтобы создать рассеянный свет для вашей сцены.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
override func viewDidLoad() {
…
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)
let ambientLight = SCNLight()
ambientLight.type = SCNLightTypeAmbient
ambientLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
cameraNode.light = ambientLight
…
}
|
Фрагмент кода создает SCNLight
, как вы делали раньше. Основным отличием является свойство type
света, для которого установлено значение SCNLightTypeAmbient
. Вы также устанавливаете его цвет на темно-серый, чтобы он не подавлял вашу сцену. Цвет по умолчанию для источника света — чистый белый (значение RGB 1, 1, 1), а наличие этого цвета на окружающем источнике света приводит к тому, что вся сцена полностью освещается, как показано на снимке экрана ниже.
Создайте и запустите приложение в последний раз, чтобы увидеть конечный результат.
Вывод
Если вы дошли до конца этого урока, вы должны освоить следующие темы:
- 3D-система координат и граф сцены, используемые SceneKit
- настройка
SCNView
с помощьюSCNScene
- добавление камер, источников света и узлов к сцене
- назначение материалов геометрии
- работа со светом для освещения сцены и отбрасывания теней
В следующем уроке этой серии вы узнаете о некоторых более продвинутых концепциях инфраструктуры SceneKit, включая анимацию, взаимодействие с пользователем, системы частиц и симуляцию физики.