說明
譯者注:本文是Raywenderlich上《ARKit by Tutorials》免費章節的翻譯,是原書第9章.原書7~9章完成了一個時空門app.
官網原文地址www.raywenderlich.com/195419/buil…
本文是我們書籍ARKit by Tutorials中的第9章,“材質和光照”.這本書向你展示瞭如何用蘋果的擴增實境框架ARKit,來構建五個沉浸式的,好看的AR應用.開始吧!
在本app三部教程的前兩部,你已經學會了如何用SceneKit向場景中新增3D物體.現在是時候將這些知識用起來,構建整個時空門了.在本教程中,你將會學到:
- 建立牆壁,天花板和屋頂,並調整他們的位置和朝向.
- 用不同的紋理將時空門內部做的更真實.
- 新增燈光到你的場景.
開始
點選這裡下載本文資料,然後開啟starter資料夾中的starter專案.在你開始前,你需要知道一點關於SceneKit的知識.
SceneKit座標系統
正如前一章你看到的那樣,SceneKit可能用來新增虛擬物體到你的檢視中.SceneKit內容檢視包含了一個樹狀層級結構的節點,也就是scene graph(場景圖).場景擁有一個root node(根節點),它定義了場景中的世界座標空間,其它節點則構成了這個世界中的可見內容.你在螢幕上渲染出的每一個node或3D物體都是一個SCNNode型別的物件.一個SCNNode物件定義了自身座標系相對於父節點的變換(位置,朝向和縮放).它本身並沒有任何可見內容.
場景中的rootNode物件,定義了SceneKit渲染出的世界座標系.你新增到根節點上的每一個子節點都會建立一個自己的座標系統,同樣這個座標系也會被自己的子節點繼承.
SceneKit使用了一個右手系的座標系統(預設),;檢視的朝向是z軸的負方向,如下所示.
SCNNode物件的位置是使用一個SCNVector3來定義的,它代表了自己在父節點座標系中的位置.預設位置是零向量,表示當前節點是位於父節點座標系的原點上.在本例中,SCNVector3是一個三元向量,每個元素代表每個座標軸的Float數值.SCNNode物件的朝向,也就是pitch(俯仰), yaw(偏航), roll(滾轉)角度是由eulerAngles屬性定義的.它同樣也是一個SCNVector3結構體表示的,每個分量是一個弧度製表示的角度.
紋理
SCNNode物件自身是不包含任何可見內容的.你需要將2D和3D物體新增到場景上時,只要將SCNGeometry物件新增到節點上就可以了.幾何體中擁有SCNmaterial物件,可以決定它的外觀.
一個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方向重複.
程式碼解釋;
- 這個方法接收一個SCNGeometry物件和X,Y縮放因子作為輸入.紋理貼圖使用S和T座標系統:S對應X,T對應於Y.這裡你為所有可見屬性都定義了S方向的wrapping mode(包裹模式)為SCNWrapMode.repeat.
- 為所有可見屬性都定義了T方向的wrapping mode(包裹模式)為SCNWrapMode.repeat.在repeat模式下,紋理取樣只使用了紋理座標的一小部分.
- 這裡,每一個可見屬性contentsTransform都設定為用SCNMatrix4結構體表示的縮放變換矩陣.設定X和Y縮放因子為scaleX和scaleY.
你只讓使用者進入時空門時,地板和天花板節點顯示;其他情況下,隱藏起來.要實現這個效果,在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
}
複製程式碼
程式碼解釋:
- 建立一個outerSurface場景立方體幾何體物件,尺寸和地板與天花板相同.
- 新增可見內容到立方體物件的漫反射屬性,使其渲染出來.設定transparency(透明度) 為非常低的數值,這樣這個物體就從檢視中隱藏起來.
- 從outerSurface幾何體建立一個SCNNode物件.設定節點的renderingOrder(渲染順序) 為10.節點的渲染順序值越大就渲染得越晚.為了讓地板和天花板從時空門外面不可見,你將需要使內部的天花板和地板節點的渲染順序遠大於10.
現在向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
}
複製程式碼
程式碼解釋:
- 使用定義好的地板的尺寸建立一個外層的地板節點.
- 放置outerFloorNode,使其位於地板節點的底面下方.將其新增到floorNode上,這個節點將會同時持有地板的內層和外層表面.
- 使用SCNBox物件建立地板幾何體,尺寸使用預先定義的尺寸.
- 地板材質的lightingModel(光照模型) 設定為physicallyBased.這種型別的陰影包含了對現實中燈光和材質物理效果的抽象.材質的可見屬性設定為scnassets素材集中的紋理圖片.
- 材質的紋理沿X和Y方向重複,使用的是先前定義的repeatTextures().
- 給地板建立一個節點,使用innerFloor幾何體物件,並設定渲染順序高於outerFloorNode.這樣確保了當使用者在時空門外面時,地板節點是不可見的.
- 最後,設定innerFloorNode的位置,使其位於outerFloorNode上方,並將其新增到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
}
複製程式碼
程式碼很簡單:
- 建立一個SCNNode物件來持有時空門.
- 用在SCNNodeHelpers中定義的makeFloorNode() 方法來建立地板節點.使用前面的常量偏移值來設定floorNode的位置.SCNGeometry的中心會對準父節點座標系中的座標位置.<比如設定為(1,1,1),則幾何體中心會對準(1,1,1)>
- 新增floorNode到時空門節點並返回時空門節點.注意,時空門節點是在使用者點選檢視時,在被新增到renderer(_ :, didAdd:, for:) 中的錨點位置上的.
執行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
}
複製程式碼
程式碼解釋:
- 類似於地板,建立一個outerCeilingNode.
- 設定外層天花板節點的位置,使其在天花板的上方.建立一個節點來持有內層和外層天花板.將outerCeilingNode新增為ceilingNode的子節點.
- 給innerCeiling建立一個有合適尺寸的SCNBox物件.
- 設定lightModel為physicallyBased,材質的可見屬性設定為scnassets素材集中的紋理圖片.
- 材質的紋理沿X和Y方向重複,使用的是先前定義的repeatTextures().
- 給innerCeilingNode建立一個節點,使用innerCeiling幾何體物件,並設定渲染順序高於outerCeilingNode,這樣它的渲染順序就在外層之後.
- 最後,設定innerCeilingNode的位置,並將其新增到ceilingNode作為子節點.返回ceilingNode給函式呼叫者.
現在需要在其他地方呼叫這個方法了.開啟PortalViewController.swift新增下面的程式碼到makePortal() 中,放在return語句前面.
/ 1
let ceilingNode = makeCeilingNode()
ceilingNode.position = SCNVector3(0,
POSITION_Y+WALL_HEIGHT,
POSITION_Z)
// 2
portal.addChildNode(ceilingNode)
複製程式碼
- 使用剛才定義的makeCeilingNode() 方法建立天花板節點.設定ceilingNode的位置為SCNVector3結構體.中心點的Y座標,偏移了地板厚度加牆壁高度的位置.
你也可以減掉SURFACE_HEIGHT來得到天花板的厚度.類似於地板,Z座標偏移也設定為POSITION_Z.這就是天花板中心點到攝像機在Z軸上的距離. - 新增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
}
複製程式碼
程式碼解釋:
- 建立一個outerWall節點,並放在牆壁的外側,確保它從外面看是透明的.建立一個個SCNBox物件來匹配牆壁的尺寸.
- 設定材質的diffuse內容為純白色並且透明值為一個極低的數值.這幫助我們達到外部透視的效果.
- 給outerWall幾何體建立一個節點.multiplier是根據外層牆壁的哪一面需要被渲染來設定.如果maskLowerSide設定為true,那麼外層牆壁在牆壁節點的座標系統中會被放置在內層牆壁的下面;否則,它就被放置在內層的上面.
設定節點的位置,這樣外層牆壁在X方向上偏移了牆壁的寬度.設定外層牆壁的渲染順序為一個較低的數值,這樣它就會被優先渲染.這樣會使牆壁從外面不可見. - 建立一個節點來持有牆壁,並將outerWallNode新增為其子節點.
- 給innerWall建立一個SCNBox物件,尺寸同牆壁的尺寸.
- 設定lightingModel為physicallyBased.類似於天花板和地板節點,設定可見屬性的內容為各種牆壁紋理圖片.
- 最後,使用innerWall幾何體建立一個innerWallNode物件.新增這個節點到父節點wallNode物件上.預設情況下,innerWallNode會被放置在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)
複製程式碼
程式碼很直白:
- 建立遠處牆壁的節點.farWallNode需要遮蔽低一側.所以使用maskLowerSide的預設值false就可以了.
- 給節點設定eulerAngles.因為牆壁是沿Y軸旋轉的並垂直於攝像機,所以第二個分量旋轉為90度.在X和Z軸方向不旋轉.
- 設定farWallNode的中心位置,使其高度偏移為POSITION_Y.它的深度計算是:天花板中心點的深度加上從天花板中心點到遠端的距離.
建立並執行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)
複製程式碼
程式碼解釋:
- 建立右側牆壁的節點.你想要將外層牆壁在節點中的低一級,所以你設定maskLowerSide為true.
- 設定牆壁沿Y軸旋轉180度.這樣確保了牆壁的內側對著右邊.
- 設定牆壁的位置,使其與遠處牆壁的右側,天花板,還有地板平齊.將rightSideWallNode新增為protal的子節點.
- 類似於右側牆壁節點,建立一個節點代表左側牆壁,並設定maskLowerSide為true.
- 左側牆壁就不需要再旋轉了,但你還是需要調整其位置,讓它和遠處牆壁的左側,天花板,地板接縫對齊.將左側牆壁新增為時空門節點的子節點.
編譯執行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新增一個帶有入口的牆壁.
程式碼解釋:
- 定義常量來儲存牆壁長度的一半,還有門兩側牆壁的長度.
- 用上一步宣告的常量,建立一個節點來代表入門右側的牆壁.你還需要調整一下節點的位置和旋轉,以使它對準到右側牆壁,天花板與地板的接縫處.然後將rightDoorSideNode新增到指定node上,成為其子節點.
- 同第2步,建立門框通道的左側節點,設定leftDoorSideNode位置和旋轉.最後用addChildNode() 將其新增到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)
複製程式碼
- 建立一個牆壁節點,尺寸參照上面的入口.
- 調整aboveDoorNode的旋轉,使它在時空門的前面.掩蔽的面朝外.
- 設定節點的位置,使其正好放置在門框通道的上方.將其新增為node的子節點.
執行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)
}
複製程式碼
程式碼解釋:
- 建立一個SCNLight物件並設定它的intensity(強度).因為我們使用的是physicallyBased(基於物理的) 燈光模型,這個值就是光源的光通量.預設值是1000流明,但你想要一個較低的強度,讓它看起來稍暗些.
- 燈光型別決定了燈光的形狀和方向,同時還有一系列的屬性來修改燈光的行為表現.這裡,你設定燈光型別為omnidirectional(全方向),也就是點光源燈光.一個全方向燈光強度和方向是固定的.燈光相對於場景中其它物體的位置決定了光的方向.
- 建立一個節點來持有燈光,並將light物件附加到節點的light屬性上.
- 用Y和Z偏移值,將燈放在天花板的中央,然後將lightNode新增為rootNode的子節點. 在makePortal() 中,在return portal之前新增下列程式碼.
placeLightSource(rootNode: portal)
複製程式碼
這樣就在時空門裡面放置了一個光源. 執行一下app,你將會看到一個更明亮,更吸引人的通道,通往你的虛擬世界!
下一步做什麼?
到這裡我們的時空門app就完成了!你已經通過創作這個科幻時空門學到了很多.讓我們回顧一下這個app涉及到的內容.
- 你已經對SceneKit的座標系統和材質有了一個基本瞭解.
- 你已經學會如何用不同幾何體來建立SCNNode物件,並給他們附加紋理.
- 你還在場景中放置了光源,讓時空門看起來更真實.
如果想更進一步,你還可以做很多:
- 製作一個門,當使用者點選螢幕時開啟或關閉.
- 探索使用更多不同幾何體來建立一個房間,這樣能無限進入.
- 試著將通道改為不同形狀. 不要止步於此.讓你的科幻想像力充分發揮出來!
如果你喜歡本系列教程,請購買本書的完整版,ARKit by Tutorials, available on our online store.
本章資料下載