ARKit系列文章目录node
译者注:本文是Raywenderlich上《ARKit by Tutorials》免费章节的翻译,是原书第9章.原书7~9章完成了一个时空门app.
官网原文地址www.raywenderlich.com/195419/buil…ios
本文是咱们书籍ARKit by Tutorials中的第9章,“材质和光照”.这本书向你展现了如何用苹果的加强现实框架ARKit,来构建五个沉浸式的,好看的AR应用.开始吧!swift
在本app三部教程的前两部,你已经学会了如何用SceneKit向场景中添加3D物体.如今是时候将这些知识用起来,构建整个时空门了.在本教程中,你将会学到:app
点击这里下载本文资料,而后打开starter文件夹中的starter项目.在你开始前,你须要知道一点关于SceneKit的知识.框架
正如前一章你看到的那样,SceneKit可能用来添加虚拟物体到你的视图中.SceneKit内容视图包含了一个树状层级结构的节点,也就是scene graph(场景图).场景拥有一个root node(根节点),它定义了场景中的世界坐标空间,其它节点则构成了这个世界中的可见内容.你在屏幕上渲染出的每个node或3D物体都是一个SCNNode类型的对象.一个SCNNode对象定义了自身坐标系相对于父节点的变换(位置,朝向和缩放).它自己并无任何可见内容.ide
场景中的rootNode对象,定义了SceneKit渲染出的世界坐标系.你添加到根节点上的每个子节点都会建立一个本身的坐标系统,一样这个坐标系也会被本身的子节点继承.函数
SceneKit使用了一个右手系的坐标系统(默认),;视图的朝向是z轴的负方向,以下所示. post
SCNNode对象的朝向,也就是pitch(俯仰), yaw(偏航), roll(滚转)角度是由eulerAngles属性定义的.它一样也是一个SCNVector3结构体表示的,每一个份量是一个弧度制表示的角度.ui
SCNNode对象自身是不包含任何可见内容的.你须要将2D和3D物体添加到场景上时,只要将SCNGeometry对象添加到节点上就能够了.几何体中拥有SCNmaterial对象,能够决定它的外观.spa
一个SCNMaterial拥有若干个可见属性.每个可见属性都是一个SCNMaterialProperty类型的实例对象,它提供了一个实体颜色,纹理或其余2D内容.其中的不少可见属性用来完成基础着色,基于物理着色和特殊效果,可让材质看起来更真实.
SceneKit asset catalog是专门设计出来,帮助你无需代码就能管理项目中的素材的.在你的starter项目中,打开Assets.scnassets文件夹.会看到已经有一些图片,用来表示天花板,地板和墙壁中的各类不一样可见属性.
让咱们进入建立时空门地板的环节.打开SCNNodeHelpers.swift,并在文件顶部import SceneKit语句下方添加下列代码.
/ 1
let SURFACE_LENGTH: CGFloat = 3.0
let SURFACE_HEIGHT: CGFloat = 0.2
let SURFACE_WIDTH: CGFloat = 3.0
// 2
let SCALEX: Float = 2.0
let SCALEY: Float = 2.0
// 3
let WALL_WIDTH:CGFloat = 0.2
let WALL_HEIGHT:CGFloat = 3.0
let WALL_LENGTH:CGFloat = 3.0
复制代码
代码含义:
接着,给SCNNodeHelpers添加下面的方法:
func repeatTextures(geometry: SCNGeometry, scaleX: Float, scaleY: Float) {
// 1
geometry.firstMaterial?.diffuse.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.selfIllumination.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.normal.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.specular.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.emission.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.roughness.wrapS = SCNWrapMode.repeat
// 2
geometry.firstMaterial?.diffuse.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.selfIllumination.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.normal.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.specular.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.emission.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.roughness.wrapT = SCNWrapMode.repeat
// 3
geometry.firstMaterial?.diffuse.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.selfIllumination.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.normal.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.specular.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.emission.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.roughness.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
}
复制代码
这里定义一个方法,来使纹理沿X和Y方向重复.
代码解释;
你只让用户进入时空门时,地板和天花板节点显示;其余状况下,隐藏起来.要实现这个效果,在SCNNodeHelpers中添加下列代码:
func makeOuterSurfaceNode(width: CGFloat, height: CGFloat, length: CGFloat) -> SCNNode {
// 1
let outerSurface = SCNBox(width: width,
height: height,
length: length,
chamferRadius: 0)
// 2
outerSurface.firstMaterial?.diffuse.contents = UIColor.white
outerSurface.firstMaterial?.transparency = 0.000001
// 3
let outerSurfaceNode = SCNNode(geometry: outerSurface)
outerSurfaceNode.renderingOrder = 10
return outerSurfaceNode
}
复制代码
代码解释:
如今向SCNNodeHelpers中添加下列代码来建立时空门的地板:
func makeFloorNode() -> SCNNode {
// 1
let outerFloorNode = makeOuterSurfaceNode(
width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH)
// 2
outerFloorNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
-SURFACE_HEIGHT, 0)
let floorNode = SCNNode()
floorNode.addChildNode(outerFloorNode)
// 3
let innerFloor = SCNBox(width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH,
chamferRadius: 0)
// 4
innerFloor.firstMaterial?.lightingModel = .physicallyBased
innerFloor.firstMaterial?.diffuse.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Diffuse.png")
innerFloor.firstMaterial?.normal.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Normal.png")
innerFloor.firstMaterial?.roughness.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Roughness.png")
innerFloor.firstMaterial?.specular.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Specular.png")
innerFloor.firstMaterial?.selfIllumination.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Gloss.png")
// 5
repeatTextures(geometry: innerFloor,
scaleX: SCALEX, scaleY: SCALEY)
// 6
let innerFloorNode = SCNNode(geometry: innerFloor)
innerFloorNode.renderingOrder = 100
// 7
innerFloorNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
0, 0)
floorNode.addChildNode(innerFloorNode)
return floorNode
}
复制代码
代码解释:
打开PortalViewController.swift并添加下列常量:
let POSITION_Y: CGFloat = -WALL_HEIGHT*0.5
let POSITION_Z: CGFloat = -SURFACE_LENGTH*0.5
复制代码
这些常量表明节点在Y和Z方向上的位置偏移.
经过替换makePortal() 来将地板节点添加到时空门中.
func makePortal() -> SCNNode {
// 1
let portal = SCNNode()
// 2
let floorNode = makeFloorNode()
floorNode.position = SCNVector3(0, POSITION_Y, POSITION_Z)
// 3
portal.addChildNode(floorNode)
return portal
}
复制代码
代码很简单:
运行app.你会看到地板节点有些黑暗,那是由于你尚未添加光源而已!
如今添加天花板节点.打开SCNNodeHelpers.swift并添加下列方法:
func makeCeilingNode() -> SCNNode {
// 1
let outerCeilingNode = makeOuterSurfaceNode(
width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH)
// 2
outerCeilingNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
SURFACE_HEIGHT, 0)
let ceilingNode = SCNNode()
ceilingNode.addChildNode(outerCeilingNode)
// 3
let innerCeiling = SCNBox(width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH,
chamferRadius: 0)
// 4
innerCeiling.firstMaterial?.lightingModel = .physicallyBased
innerCeiling.firstMaterial?.diffuse.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Diffuse.png")
innerCeiling.firstMaterial?.emission.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Emis.png")
innerCeiling.firstMaterial?.normal.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Normal.png")
innerCeiling.firstMaterial?.specular.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Specular.png")
innerCeiling.firstMaterial?.selfIllumination.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Gloss.png")
// 5
repeatTextures(geometry: innerCeiling, scaleX:
SCALEX, scaleY: SCALEY)
// 6
let innerCeilingNode = SCNNode(geometry: innerCeiling)
innerCeilingNode.renderingOrder = 100
// 7
innerCeilingNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
0, 0)
ceilingNode.addChildNode(innerCeilingNode)
return ceilingNode
}
复制代码
代码解释:
如今须要在其余地方调用这个方法了.打开PortalViewController.swift添加下面的代码到makePortal() 中,放在return语句前面.
/ 1
let ceilingNode = makeCeilingNode()
ceilingNode.position = SCNVector3(0,
POSITION_Y+WALL_HEIGHT,
POSITION_Z)
// 2
portal.addChildNode(ceilingNode)
复制代码
运行一下app,你会看到:
是时候添加墙壁了!
打开SCNNodeHelpers.swift并添加下列方法.
func makeWallNode(length: CGFloat = WALL_LENGTH, height: CGFloat = WALL_HEIGHT, maskLowerSide:Bool = false) -> SCNNode {
// 1
let outerWall = SCNBox(width: WALL_WIDTH,
height: height,
length: length,
chamferRadius: 0)
// 2
outerWall.firstMaterial?.diffuse.contents = UIColor.white
outerWall.firstMaterial?.transparency = 0.000001
// 3
let outerWallNode = SCNNode(geometry: outerWall)
let multiplier: CGFloat = maskLowerSide ? -1 : 1
outerWallNode.position = SCNVector3(WALL_WIDTH*multiplier,0,0)
outerWallNode.renderingOrder = 10
// 4
let wallNode = SCNNode()
wallNode.addChildNode(outerWallNode)
// 5
let innerWall = SCNBox(width: WALL_WIDTH,
height: height,
length: length,
chamferRadius: 0)
// 6
innerWall.firstMaterial?.lightingModel = .physicallyBased
innerWall.firstMaterial?.diffuse.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Diffuse.png")
innerWall.firstMaterial?.metalness.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Metalness.png")
innerWall.firstMaterial?.roughness.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Roughness.png")
innerWall.firstMaterial?.normal.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Normal.png")
innerWall.firstMaterial?.specular.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Spec.png")
innerWall.firstMaterial?.selfIllumination.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Gloss.png")
// 7
let innerWallNode = SCNNode(geometry: innerWall)
wallNode.addChildNode(innerWallNode)
return wallNode
}
复制代码
代码解释:
如今添加时空门远处的墙壁.打开PortalViewController.swift并添加下列方法,在makePortal() 的末尾return语句前:
// 1
let farWallNode = makeWallNode()
// 2
farWallNode.eulerAngles = SCNVector3(0,
90.0.degreesToRadians, 0)
// 3
farWallNode.position = SCNVector3(0,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z-SURFACE_LENGTH*0.5)
portal.addChildNode(farWallNode)
复制代码
代码很直白:
建立并运行app,你会看到远处的墙壁上方与天花板相接,下方与地板相接.
接下来你将添加左边和右边的墙壁.在makePortal() 中,在return语句前添加下列代码:
// 1
let rightSideWallNode = makeWallNode(maskLowerSide: true)
// 2
rightSideWallNode.eulerAngles = SCNVector3(0, 180.0.degreesToRadians, 0)
// 3
rightSideWallNode.position = SCNVector3(WALL_LENGTH*0.5,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z)
portal.addChildNode(rightSideWallNode)
// 4
let leftSideWallNode = makeWallNode(maskLowerSide: true)
// 5
leftSideWallNode.position = SCNVector3(-WALL_LENGTH*0.5,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z)
portal.addChildNode(leftSideWallNode)
复制代码
代码解释:
编译运行app,你的时空门如今有了三面墙壁了.若是你走出时空门,全部的墙壁都是不可见的.
还有一件事须要完成:一个入口!目前,时空门尚未第四面墙.其实咱们须要的不是第四面墙,仍是须要一个能进入和离开的门框通道.
打开PortalViewController.swift并添加下列常量:
let DOOR_WIDTH:CGFloat = 1.0
let DOOR_HEIGHT:CGFloat = 2.4
复制代码
正如它们的名字含义,它们定义了门框的宽和高.
在PortalViewController中添加下列代码:
func addDoorway(node: SCNNode) {
// 1
let halfWallLength: CGFloat = WALL_LENGTH * 0.5
let frontHalfWallLength: CGFloat =
(WALL_LENGTH - DOOR_WIDTH) * 0.5
// 2
let rightDoorSideNode = makeWallNode(length: frontHalfWallLength)
rightDoorSideNode.eulerAngles = SCNVector3(0,270.0.degreesToRadians, 0)
rightDoorSideNode.position = SCNVector3(halfWallLength - 0.5 * DOOR_WIDTH,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z+SURFACE_LENGTH*0.5)
node.addChildNode(rightDoorSideNode)
// 3
let leftDoorSideNode = makeWallNode(length: frontHalfWallLength)
leftDoorSideNode.eulerAngles = SCNVector3(0, 270.0.degreesToRadians, 0)
leftDoorSideNode.position = SCNVector3(-halfWallLength + 0.5 * frontHalfWallLength,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z+SURFACE_LENGTH*0.5)
node.addChildNode(leftDoorSideNode)
}
复制代码
addDoorway(node:) 这个方法向指定的node添加一个带有入口的墙壁.
代码解释:
在makePortalNode() 方法中,return portal前添加下面语句:
addDoorway(node: portal)
复制代码
这里添加门框通道到时空门节点上.
运行app.你将看到时空门上的门框,可是目前门的上方直通到天花板.咱们须要再加一块墙壁来让门框高度达到预告定义的DOOR_HEIGHT.
// 1
let aboveDoorNode = makeWallNode(length: DOOR_WIDTH,
height: WALL_HEIGHT - DOOR_HEIGHT)
// 2
aboveDoorNode.eulerAngles = SCNVector3(0, 270.0.degreesToRadians, 0)
// 3
aboveDoorNode.position =
SCNVector3(0,
POSITION_Y+(WALL_HEIGHT-DOOR_HEIGHT)*0.5+DOOR_HEIGHT,
POSITION_Z+SURFACE_LENGTH*0.5)
node.addChildNode(aboveDoorNode)
复制代码
运行app.此次你会看到门框通道如今有了合适的墙壁了.
这个时空门看起来并非太诱人.事实上,它至关暗淡和阴郁.你能够添加一个光源来照亮它们! 添加下列方法到PortalViewController中:
func placeLightSource(rootNode: SCNNode) {
// 1
let light = SCNLight()
light.intensity = 10
// 2
light.type = .omni
// 3
let lightNode = SCNNode()
lightNode.light = light
// 4
lightNode.position = SCNVector3(0,
POSITION_Y+WALL_HEIGHT,
POSITION_Z)
rootNode.addChildNode(lightNode)
}
复制代码
代码解释:
placeLightSource(rootNode: portal)
复制代码
这样就在时空门里面放置了一个光源. 运行一下app,你将会看到一个更明亮,更吸引人的通道,通往你的虚拟世界!
到这里咱们的时空门app就完成了!你已经经过创做这个科幻时空门学到了不少.让咱们回顾一下这个app涉及到的内容.
若是想更进一步,你还能够作不少:
若是你喜欢本系列教程,请购买本书的完整版,ARKit by Tutorials, available on our online store.
本章资料下载