Chapter 4: Adding 3D Objects & Textures

前言

如何從.scene讀取3D物件到真實空間中,就好比如何從storyboard或Xib讀取畫面直接使用。

大綱

Getting started

起手專案裡面多了兩個新區塊,分別是

  • Load Models: 會這個區塊建立一個function將3D物件讀取進來。

  • Helper Function: 就是其他會需要用到的functions。

Adding 3D objects

如何將常見的3D物件的格式- COLLADA(.dae)轉成SceneKit格式- (.scn)

將.dae匯入Xcode的.scnassets中,可以透過Editor ▸ Convert to SceneKit scene file format (.scn)將.dae無痛轉換成.scn。

Creating the dice scene

接下來在.scnassets建立一個DiceScene.scn(會準備在這個scene中放入5顆骰子)。

然後將剛剛從dice.dae轉換出來的dice.scn(只有一顆骰子),重複5次複製貼上到DiceScene.scn中產生5顆骰子。

Shader, meterials and textures

這裡要開始講解一些關於3D物件基本常見名詞

  • Lighting models (shaders)照明模型

    • 理解ambient, diffuse, specular的含義

      • Constant:使用一个扁平的照明模型,在计算渲染像素的颜色时,只包含ambient信息.

      • Lambert:在计算渲染像素的颜色时,只包含ambient信息和diffuse信息.

      • Blinn:在计算渲染像素的颜色时,只包含ambient信息,diffuse信息,specular信息,而且specular高光区是用Blinn-Phong法则计算的.

      • Phong:在计算渲染像素的颜色时,只包含ambient信息,diffuse信息,specular信息,而且specular高光区是用Phong法则计算的.

      • PBR:即Physically Based Rendering基于物理的渲染,包含了真实情况下,物理光源和材质对光的漫反射和吸收.

    • 結論來說,PBR是最真實世界的照亮模型。

Physically based rendering(PBR)

  • Diffuse map(颜色贴图,漫反射贴图):给几何体一个基本的颜色纹理,不考虑灯光和特效

  • Normal map(法线贴图):灯光是使用形状表面的法向量来决定照亮哪个面的.系统自带形状是使用单一的整个面的向量,而法线贴图则以RGB值定义了精确到每个像素的法向量,这样每个像素对灯光的反应都不同,形成表面崎岖不平的灯光效果.

  • Reflective map(反射贴图):以黑白图片精确定义了材质每个像素的反光程度.就是周围环境的光线在物体表面映射出的图像(实际就是天空盒子图像在物体表面的反光).

Texturing the 3D Objects

在瞭解完許多特殊名詞後,我們就要替這5顆骰子,分別貼上不同的Texture。

在DiceScene.scn先選到一顆骰子(child node),然後再Xcode右邊的Material inspector建立新的Material。

在新的material中將shading指定成Physical Based,然後將指定對應的Diffucse, Metalness, Roughness, Normal。

Loading & Placing 3D objects

在之前所有的步驟,都還沒寫到任何一行程式碼,主要的工作就是將desginer提供的3D檔案換成.scn,然後可以讓我們用程式直接讀取這個3D檔案。現在,就開始學著如何讀取剛剛建立的東西。

  • initScene()中建立空白的scene,並讀取environment map。

 func initScene() {
    let scene = SCNScene()
    scene.lightingEnvironment.contents = "PokerDice.scnassets/Textures/Environment_CUBE.jpg"
    scene.lightingEnvironment.intensity = 2
  }
  • 讀取3D物件,在viewDidLoad()呼叫loadModels()

var diceNodes: [SCNNode] = []

func loadModels() {
        let diceScene = SCNScene(named: "PokerDice.scnassets/Models/DiceScene.scn")!
        for count in 0..<5 {
            diceNodes.append(diceScene.rootNode.childNode(withName: "dice\(count)", recursively: false)!)
        }
    let focusScene = SCNScene(named: "PokerDice.scnassets/Models/FocusScene.scn")!
    focusNode = focusScene.rootNode.childNode(withName: "focus", recursively: false)!
    sceneView.scene.rootNode.addChildNode(focusNode)
  }
  • 將3D物件透過swipe手勢來置放骰子到真實事件中。

    • func therowDiceNode: 透過定義好diceOffset將diceNode依序clone到真實世界的場景中。

    • func swipupGestureHandler: 李用Swipe手勢來呼叫therowDiceNode。

  var diceCount: Int = 5
  var diceStyle: Int = 0
  var diceOffset: [SCNVector3] = [SCNVector3(0.0,0.0,0.0),
                                  SCNVector3(-0.05, 0.00, 0.0),
                                  SCNVector3(0.05, 0.00, 0.0),
                                  SCNVector3(-0.05, 0.05, 0.02),
                                  SCNVector3(0.05, 0.05, 0.02)]

func therowDiceNode(transform: SCNMatrix4, offset: SCNVector3) {
        let position = SCNVector3(transform.m41 + offset.x,
                                  transform.m42 + offset.y,
                                  transform.m43 + offset.z)

        let diceNode = diceNodes[diceStyle].clone()
        diceNode.name = "dice"
        diceNode.position = position

        sceneView.scene.rootNode.addChildNode(diceNode)
        diceCount -= 1
    }

 @IBAction func swipupGestureHandler(_ sender: Any) {
    guard let frame = sceneView.session.currentFrame else { return }
    for count in 0..<diceCount {
        therowDiceNode(transform: SCNMatrix4(frame.camera.transform), offset: diceOffset[count])
    }
  • 改變 dice styles

    • 之前我們產生5種不同材質的dice, 我們透過一個button來改變骰子的材質。

  @IBAction func styleButtonPressed(_ sender: Any) {
    diceStyle = diceStyle >= 4 ? 0 : diceStyle + 1
  }

Last updated

Was this helpful?