[ARKit]13-[譯]在ARKit中建立一個時空門App:材質和光照

蘋果API搬運工發表於2018-08-31

說明

ARKit系列文章目錄

譯者注:本文是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軸的負方向,如下所示.

[ARKit]13-[譯]在ARKit中建立一個時空門App:材質和光照
SCNNode物件的位置是使用一個SCNVector3來定義的,它代表了自己在父節點座標系中的位置.預設位置是零向量,表示當前節點是位於父節點座標系的原點上.在本例中,SCNVector3是一個三元向量,每個元素代表每個座標軸的Float數值.

SCNNode物件的朝向,也就是pitch(俯仰), yaw(偏航), roll(滾轉)角度是由eulerAngles屬性定義的.它同樣也是一個SCNVector3結構體表示的,每個分量是一個弧度製表示的角度.

紋理

SCNNode物件自身是不包含任何可見內容的.你需要將2D和3D物體新增到場景上時,只要將SCNGeometry物件新增到節點上就可以了.幾何體中擁有SCNmaterial物件,可以決定它的外觀.

一個SCNMaterial擁有若干個可見屬性.每一個可見屬性都是一個SCNMaterialProperty型別的例項物件,它提供了一個實體顏色,紋理或其他2D內容.其中的很多可見屬性用來完成基礎著色,基於物理著色和特殊效果,可以讓材質看起來更真實.

SceneKit asset catalog是專門設計出來,幫助你無需程式碼就能管理專案中的素材的.在你的starter專案中,開啟Assets.scnassets資料夾.會看到已經有一些圖片,用來表示天花板,地板和牆壁中的各種不同可見屬性.

[ARKit]13-[譯]在ARKit中建立一個時空門App:材質和光照
使用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
複製程式碼

程式碼含義:

  1. 定義常量,用來表示時空門中地板和天花板的尺寸.地板和天花板的高度也就是它們的厚度.
  2. 這些常量表示表面紋理的縮放和重複.
  3. 這些定義牆壁節點的寬度,高度和長度.

接著,給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方向重複.

程式碼解釋;

  1. 這個方法接收一個SCNGeometry物件和X,Y縮放因子作為輸入.紋理貼圖使用ST座標系統:S對應X,T對應於Y.這裡你為所有可見屬性都定義了S方向的wrapping mode(包裹模式)為SCNWrapMode.repeat.
  2. 為所有可見屬性都定義了T方向的wrapping mode(包裹模式)為SCNWrapMode.repeat.在repeat模式下,紋理取樣只使用了紋理座標的一小部分.
  3. 這裡,每一個可見屬性contentsTransform都設定為用SCNMatrix4結構體表示的縮放變換矩陣.設定X和Y縮放因子為scaleXscaleY.

你只讓使用者進入時空門時,地板和天花板節點顯示;其他情況下,隱藏起來.要實現這個效果,在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
}
複製程式碼

程式碼解釋:

  1. 建立一個outerSurface場景立方體幾何體物件,尺寸和地板與天花板相同.
  2. 新增可見內容到立方體物件的漫反射屬性,使其渲染出來.設定transparency(透明度) 為非常低的數值,這樣這個物體就從檢視中隱藏起來.
  3. 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
}
複製程式碼

程式碼解釋:

  1. 使用定義好的地板的尺寸建立一個外層的地板節點.
  2. 放置outerFloorNode,使其位於地板節點的底面下方.將其新增到floorNode上,這個節點將會同時持有地板的內層和外層表面.
  3. 使用SCNBox物件建立地板幾何體,尺寸使用預先定義的尺寸.
  4. 地板材質的lightingModel(光照模型) 設定為physicallyBased.這種型別的陰影包含了對現實中燈光和材質物理效果的抽象.材質的可見屬性設定為scnassets素材集中的紋理圖片.
  5. 材質的紋理沿X和Y方向重複,使用的是先前定義的repeatTextures().
  6. 給地板建立一個節點,使用innerFloor幾何體物件,並設定渲染順序高於outerFloorNode.這樣確保了當使用者在時空門外面時,地板節點是不可見的.
  7. 最後,設定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
}
複製程式碼

程式碼很簡單:

  1. 建立一個SCNNode物件來持有時空門.
  2. 用在SCNNodeHelpers中定義的makeFloorNode() 方法來建立地板節點.使用前面的常量偏移值來設定floorNode的位置.SCNGeometry的中心會對準父節點座標系中的座標位置.<比如設定為(1,1,1),則幾何體中心會對準(1,1,1)>
  3. 新增floorNode到時空門節點並返回時空門節點.注意,時空門節點是在使用者點選檢視時,在被新增到renderer(_ :, didAdd:, for:) 中的錨點位置上的.

執行app.你會看到地板節點有些黑暗,那是因為你還沒有新增光源而已!

[ARKit]13-[譯]在ARKit中建立一個時空門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
}
複製程式碼

程式碼解釋:

  1. 類似於地板,建立一個outerCeilingNode.
  2. 設定外層天花板節點的位置,使其在天花板的上方.建立一個節點來持有內層和外層天花板.將outerCeilingNode新增為ceilingNode的子節點.
  3. innerCeiling建立一個有合適尺寸的SCNBox物件.
  4. 設定lightModelphysicallyBased,材質的可見屬性設定為scnassets素材集中的紋理圖片.
  5. 材質的紋理沿X和Y方向重複,使用的是先前定義的repeatTextures().
  6. innerCeilingNode建立一個節點,使用innerCeiling幾何體物件,並設定渲染順序高於outerCeilingNode,這樣它的渲染順序就在外層之後.
  7. 最後,設定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)
複製程式碼
  1. 使用剛才定義的makeCeilingNode() 方法建立天花板節點.設定ceilingNode的位置為SCNVector3結構體.中心點的Y座標,偏移了地板厚度加牆壁高度的位置.
    你也可以減掉SURFACE_HEIGHT來得到天花板的厚度.類似於地板,Z座標偏移也設定為POSITION_Z.這就是天花板中心點到攝像機在Z軸上的距離.
  2. 新增ceilingNode作為時空門的子節點.

執行一下app,你會看到:

[ARKit]13-[譯]在ARKit中建立一個時空門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
}
複製程式碼

程式碼解釋:

  1. 建立一個outerWall節點,並放在牆壁的外側,確保它從外面看是透明的.建立一個個SCNBox物件來匹配牆壁的尺寸.
  2. 設定材質的diffuse內容為純白色並且透明值為一個極低的數值.這幫助我們達到外部透視的效果.
  3. outerWall幾何體建立一個節點.multiplier是根據外層牆壁的哪一面需要被渲染來設定.如果maskLowerSide設定為true,那麼外層牆壁在牆壁節點的座標系統中會被放置在內層牆壁的下面;否則,它就被放置在內層的上面.
    設定節點的位置,這樣外層牆壁在X方向上偏移了牆壁的寬度.設定外層牆壁的渲染順序為一個較低的數值,這樣它就會被優先渲染.這樣會使牆壁從外面不可見.
  4. 建立一個節點來持有牆壁,並將outerWallNode新增為其子節點.
  5. innerWall建立一個SCNBox物件,尺寸同牆壁的尺寸.
  6. 設定lightingModelphysicallyBased.類似於天花板和地板節點,設定可見屬性的內容為各種牆壁紋理圖片.
  7. 最後,使用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)
複製程式碼

程式碼很直白:

  1. 建立遠處牆壁的節點.farWallNode需要遮蔽低一側.所以使用maskLowerSide的預設值false就可以了.
  2. 給節點設定eulerAngles.因為牆壁是沿Y軸旋轉的並垂直於攝像機,所以第二個分量旋轉為90度.在X和Z軸方向不旋轉.
  3. 設定farWallNode的中心位置,使其高度偏移為POSITION_Y.它的深度計算是:天花板中心點的深度加上從天花板中心點到遠端的距離.

建立並執行app,你會看到遠處的牆壁上方與天花板相接,下方與地板相接.

[ARKit]13-[譯]在ARKit中建立一個時空門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)
複製程式碼

程式碼解釋:

  1. 建立右側牆壁的節點.你想要將外層牆壁在節點中的低一級,所以你設定maskLowerSidetrue.
  2. 設定牆壁沿Y軸旋轉180度.這樣確保了牆壁的內側對著右邊.
  3. 設定牆壁的位置,使其與遠處牆壁的右側,天花板,還有地板平齊.將rightSideWallNode新增為protal的子節點.
  4. 類似於右側牆壁節點,建立一個節點代表左側牆壁,並設定maskLowerSidetrue.
  5. 左側牆壁就不需要再旋轉了,但你還是需要調整其位置,讓它和遠處牆壁的左側,天花板,地板接縫對齊.將左側牆壁新增為時空門節點的子節點.

編譯執行app,你的時空門現在有了三面牆壁了.如果你走出時空門,所有的牆壁都是不可見的.

[ARKit]13-[譯]在ARKit中建立一個時空門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新增一個帶有入口的牆壁.
程式碼解釋:

  1. 定義常量來儲存牆壁長度的一半,還有門兩側牆壁的長度.
  2. 用上一步宣告的常量,建立一個節點來代表入門右側的牆壁.你還需要調整一下節點的位置和旋轉,以使它對準到右側牆壁,天花板與地板的接縫處.然後將rightDoorSideNode新增到指定node上,成為其子節點.
  3. 同第2步,建立門框通道的左側節點,設定leftDoorSideNode位置和旋轉.最後用addChildNode() 將其新增到node上作為子節點.

makePortalNode() 方法中,return portal前新增下面語句:

addDoorway(node: portal)
複製程式碼

這裡新增門框通道到時空門節點上.

執行app.你將看到時空門上的門框,但是目前門的上方直通到天花板.我們需要再加一塊牆壁來讓門框高度達到預告定義的DOOR_HEIGHT.

[ARKit]13-[譯]在ARKit中建立一個時空門App:材質和光照
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)
複製程式碼
  1. 建立一個牆壁節點,尺寸參照上面的入口.
  2. 調整aboveDoorNode的旋轉,使它在時空門的前面.掩蔽的面朝外.
  3. 設定節點的位置,使其正好放置在門框通道的上方.將其新增為node的子節點.

執行app.這次你會看到門框通道現在有了合適的牆壁了.

[ARKit]13-[譯]在ARKit中建立一個時空門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)
}
複製程式碼

程式碼解釋:

  1. 建立一個SCNLight物件並設定它的intensity(強度).因為我們使用的是physicallyBased(基於物理的) 燈光模型,這個值就是光源的光通量.預設值是1000流明,但你想要一個較低的強度,讓它看起來稍暗些.
  2. 燈光型別決定了燈光的形狀和方向,同時還有一系列的屬性來修改燈光的行為表現.這裡,你設定燈光型別為omnidirectional(全方向),也就是點光源燈光.一個全方向燈光強度和方向是固定的.燈光相對於場景中其它物體的位置決定了光的方向.
  3. 建立一個節點來持有燈光,並將light物件附加到節點的light屬性上.
  4. 用Y和Z偏移值,將燈放在天花板的中央,然後將lightNode新增為rootNode的子節點. 在makePortal() 中,在return portal之前新增下列程式碼.
placeLightSource(rootNode: portal)
複製程式碼

這樣就在時空門裡面放置了一個光源. 執行一下app,你將會看到一個更明亮,更吸引人的通道,通往你的虛擬世界!

[ARKit]13-[譯]在ARKit中建立一個時空門App:材質和光照

下一步做什麼?

到這裡我們的時空門app就完成了!你已經通過創作這個科幻時空門學到了很多.讓我們回顧一下這個app涉及到的內容.

  • 你已經對SceneKit的座標系統和材質有了一個基本瞭解.
  • 你已經學會如何用不同幾何體來建立SCNNode物件,並給他們附加紋理.
  • 你還在場景中放置了光源,讓時空門看起來更真實.

如果想更進一步,你還可以做很多:

  • 製作一個門,當使用者點選螢幕時開啟或關閉.
  • 探索使用更多不同幾何體來建立一個房間,這樣能無限進入.
  • 試著將通道改為不同形狀. 不要止步於此.讓你的科幻想像力充分發揮出來!

如果你喜歡本系列教程,請購買本書的完整版,ARKit by Tutorials, available on our online store.

本章資料下載

相關文章