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對象的位置是使用一個 SCNVector3來定義的,它表明了本身在父節點座標系中的位置.默認位置是零向量,表示當前節點是位於父節點座標系的原點上.在本例中, SCNVector3是一個三元向量,每一個元素表明每一個座標軸的 Float數值.SCNNode對象的朝向,也就是pitch(俯仰), yaw(偏航), roll(滾轉)角度是由eulerAngles屬性定義的.它一樣也是一個SCNVector3結構體表示的,每一個份量是一個弧度製表示的角度.ui
SCNNode對象自身是不包含任何可見內容的.你須要將2D和3D物體添加到場景上時,只要將SCNGeometry對象添加到節點上就能夠了.幾何體中擁有SCNmaterial對象,能夠決定它的外觀.spa
一個SCNMaterial擁有若干個可見屬性.每個可見屬性都是一個SCNMaterialProperty類型的實例對象,它提供了一個實體顏色,紋理或其餘2D內容.其中的不少可見屬性用來完成基礎着色,基於物理着色和特殊效果,可讓材質看起來更真實.
SceneKit asset catalog是專門設計出來,幫助你無需代碼就能管理項目中的素材的.在你的starter項目中,打開Assets.scnassets文件夾.會看到已經有一些圖片,用來表示天花板,地板和牆壁中的各類不一樣可見屬性.
使用SceneKit,你還可使用添加了 SCNLight對象的節點,來給場景中的幾何體添加光照和陰影效果.讓咱們進入建立時空門地板的環節.打開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.
在 addDoorway(node:) 方法的末尾添加下面代碼:// 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.
本章資料下載