在本教程中,您将学习如何在SceneKit中创建基本的3D场景,而无需复杂的OpenGL。 这包括基本几何图形,相机,灯光,材质和阴影。
介绍
SceneKit框架最初由Apple与OS X 10.8 Mountain Lion一起启动,后来随着iOS 8的发布而在iOS上可用。此框架的目的是使开发人员能够轻松地将3D图形集成到游戏和应用程序中,而无需复杂性图形API,例如OpenGL和Metal。
SceneKit允许您简单地提供场景中所需资产的描述,而框架本身可以为您处理所有OpenGL渲染代码。 在第一个教程中,我将教您一些使用3D资源的基础知识和SceneKit框架的基础知识。
本教程要求您以Xcode 6或更高版本运行。 虽然不是必需的,但我建议使用运行iOS 8的物理设备来测试SceneKit代码。 您可以使用iOS模拟器,但是如果您的场景变得更加复杂,则性能将不佳。 请注意,在物理iOS设备上进行测试要求您具有注册的iOS开发人员帐户。
1.基本原理
关于SceneKit,您需要了解的第一件事是,由节点表示的资产被安排在称为场景图的层次树中。 如果您熟悉iOS开发,则此树的工作原理类似于常规视图层次结构 在UIKit中。 您创建的每个场景都具有一个根节点,您可以在该根节点上添加后续节点,并为该场景的3D坐标系提供基础。
将节点添加到场景时,其位置由一组三个数字指定,这三个分量由代码中的SCNVector3
结构表示。 这三个组件中的每一个都定义了节点在x,y和z轴上的位置,如下图所示。
场景的根节点位置定义为(0,0,0) 。 在上图中,这是三个轴相交的位置。 图像中包含的相机代表将相机添加到场景时相机指向的默认方向。
既然您已经了解了SceneKit如何表示对象的一些基础知识,那么您就可以开始编写一些代码了。
2.项目设置
打开Xcode并基于Single View Application模板创建一个新的iOS应用 程序 。 尽管您可以使用SceneKit从Game模板轻松创建应用程序,但在本教程中,我将向您展示如何从头开始使用SceneKit。
输入产品名称 ,将Language设置为Swift ,将Devices设置为Universal 。 单击下一步继续。
创建项目后,导航至ViewController.swift并在顶部添加以下import语句以导入SceneKit框架:
import SceneKit
接下来,在ViewController
类中添加以下viewDidLoad
方法的实现:
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
方法:
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
方法:
- 首先通过调用
init
方法为您的视图创建场景。 除非要从外部文件加载准备好的场景,否则这将是您将始终使用的初始化程序。 - 接下来,为摄像机创建一个
SCNCamera
对象和一个SCNNode
实例。 然后,指定SCNCamera
对象到camera
的性能cameraNode
和沿Z轴去看了一下以后你会创建多维数据集移动这个节点。 - 在接下来的步骤中,您创建一个
SCNLight
对象和SCNNode
命名lightNode
。 将SCNLight
实例分配给光源节点的light
属性。SCNLight
的type
属性设置为SCNLightTypeOmni
。 此光源类型从3D空间中的一个点向各个方向均匀地分配光线。 您可以将此灯类型视为普通灯泡。 - 最后,使用
SCNBox
类创建一个多维数据集,使宽度,高度和长度都相同。 该SCNBox
类的子类SCNGeometry
,是你可以创建原始形状之一。 其他形状包括球体,金字塔和圆环。 您还可以创建一个传入geometry
参数的多维数据集的节点。 - 要设置场景,请将三个节点(相机,光源和立方体)添加到场景的场景图中。 不需要额外的设置,因为
SCNScene
对象会自动检测节点何时包含相机或灯光对象,从而相应地渲染场景。
生成并运行您的应用程序,您将看到从右上角的灯光现在可以看到一个黑色的立方体。
不幸的是,该多维数据集目前看起来不是三维的。 这是因为相机直接位于其前面。 现在要做的就是更改摄像机的位置,以便更好地查看立方体。
但是,要使相机直接指向立方体,您还需要向相机添加SCNLookAtConstraint
。 首先,如下所示更新相机的位置。
cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)
接下来,在实例化多维数据集的节点之后,将以下代码片段添加到viewDidLoad方法:
let constraint = SCNLookAtConstraint(target: cubeNode)
constraint.gimbalLockEnabled = true
cameraNode.constraints = [constraint]
位置更改会将摄像机左右移动。 通过添加约束,将多维数据集作为目标,并将gimbalLockEnabled
设置为true
,可以确保相机与水平和视口(在这种情况下为屏幕)保持平行。 这是通过禁用沿滚动轴(从摄像机指向约束目标的轴)的旋转来完成的。
再次构建并运行您的应用程序,您将在其所有3D荣耀中看到您的多维数据集。
4.材质和阴影
现在是时候使用材质和阴影为场景添加更多真实感了。 首先,您需要另一个对象才能在其上投射阴影。 使用以下代码段创建一个平面,一个扁平矩形并将其放置在多维数据集下方。 不要忘记将新节点作为子节点添加到场景的根节点。
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
属性,可以将平面沿x轴向后旋转90度。 我们需要这样做,因为默认情况下会垂直创建平面。 在SceneKit中,旋转角度以弧度而不是度来计算,但是可以使用GLKMathDegreesToRadians(_:)
和GLKMathsRadiansToDegrees(_:)
函数轻松地转换这些值。 GLK代表Apple的OpenGL框架GLKit。
接下来,向多维数据集和平面添加材料。 在此示例中,您将为立方体和平面分别赋予纯色,分别为红色和绿色。 viewDidLoad
添加到viewDidLoad
方法以创建这些材料。
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
对象。 还有许多其他可接受的对象类型可以分配给此属性,例如UIImage
, CALayer
甚至SpriteKit纹理( SKTexture
)。
再次构建并运行您的应用程序,不仅可以第一次看到飞机,还可以看到您创建的材料。
现在是时候为场景添加一些阴影了。 SceneKit中可用的四种光源类型中,只有聚光灯可以创建阴影。 在此示例中,您将把现有的全向光变成针对立方体的聚光灯。 将以下代码添加到viewDidLoad
方法:
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
。 然后,您可以指定聚光灯的内角和外角(以度为单位)。 默认值分别为0和45 。 内角确定光线在直射光中覆盖的面积,而外角确定部分照明的面积。 一旦看到结果场景,这些角度之间的差异将变得清晰。 然后,您可以明确地告诉灯光投射阴影,并添加与SCNLookAtConstraint
为相机创建的相同的SCNLookAtConstraint
。
生成并运行您的应用程序以查看生成的场景。 将显示您在代码中指定的内角,其中平面为纯绿色,正好位于立方体下方。 外角由光的梯度表示,当光从光目标移开时,它逐渐变为黑色。
您会看到您现在已使多维数据集正确投射阴影。 但是,聚光灯仅照亮飞机的一部分。 这是因为场景中没有环境光。
环境光是一种以均匀的光分布照亮一切的光源。 因为环境光照亮了整个场景,所以它的位置无关紧要,您可以将其添加到所需的任何节点,甚至是与相机相同的节点。 使用以下代码片段为场景创建环境光。
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
,该代码段创建了一个SCNLight
。 主要区别在于光源的type
属性,该属性设置为SCNLightTypeAmbient
。 您还可以将其颜色设置为深灰色,以免影响场景。 光源的默认颜色是纯白色(RGB值为1、1、1),并且在环境光下使用该颜色会使整个场景完全照亮,如下面的屏幕快照所示。
最后一次构建并运行您的应用程序以查看最终结果。
结论
如果您已完成本教程的结尾,则现在应该熟悉以下主题:
在本系列的下一个教程中,您将学习SceneKit框架的一些更高级的概念,包括动画,用户交互,粒子系统和模拟物理。
翻译自: https://code.tutsplus.com/tutorials/an-introduction-to-scenekit-fundamentals--cms-23847