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

說明

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可能用來添加虛擬物體到你的視圖中.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
複製代碼

代碼含義:

  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.你會看到地板節點有些黑暗,那是由於你尚未添加光源而已!

如今添加天花板節點.打開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,你會看到:

是時候添加牆壁了!

打開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,你會看到遠處的牆壁上方與天花板相接,下方與地板相接.

接下來你將添加左邊和右邊的牆壁.在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,你的時空門如今有了三面牆壁了.若是你走出時空門,全部的牆壁都是不可見的.

添加門框通道

還有一件事須要完成:一個入口!目前,時空門尚未第四面牆.其實咱們須要的不是第四面牆,仍是須要一個能進入和離開的門框通道.

打開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.

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.此次你會看到門框通道如今有了合適的牆壁了.

放置燈光

這個時空門看起來並非太誘人.事實上,它至關暗淡和陰鬱.你能夠添加一個光源來照亮它們! 添加下列方法到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,你將會看到一個更明亮,更吸引人的通道,通往你的虛擬世界!

下一步作什麼?

到這裏咱們的時空門app就完成了!你已經經過創做這個科幻時空門學到了不少.讓咱們回顧一下這個app涉及到的內容.

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

若是想更進一步,你還能夠作不少:

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

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

本章資料下載

相關文章
相關標籤/搜索