前言
對ARKit感興趣的同學,可以訂閱ARKit教程專題
源代碼地址在這里
正文
在本章中,你將學習如何導入、轉換、紋理和加載 3D 對象。然后,你將學習如何將這些 3D 對象放入增強空間。你將從 3D 模型材料概述開始,了解如何創建虛擬地球,然后咱們試著構建一個撲克骰子游戲。
添加3D物體
XCode非常強大,真的,因為它支持把一些其他格式的3D模型文件轉換成為XCode支持的格式,并且呈現出來。
導入3D物體
將 COLLADA 文件導入你的項目,就像添加圖片那樣,拖到你的項目中。
從資源文件夾中,將Models/Dice.dae拖放到項目的ARResource.scnassets文件夾中。
這里需要注意的是,這種.dae格式的模型文件XCode是沒有辦法直接使用的,需要轉換成為XCode可以直接使用的格式,轉換步驟如下:
選中你需要轉換的模型文件,然后在上面的控制條上選擇Editor 在彈出來的新菜單中選擇Convert to SceneKit scene ?le format (.scn)。
接下來會有一個彈框,詢問你,是保留兩種格式的文件呢還是取消操作呢,還是直接轉換,新的的格式的文件呢。這個你自己看著辦。我的選擇是Duplicate。
兩個文件都轉換之后如下圖所示:
我們可以把抓換之后的文件改一下名字,如下:
創建一個可以顯示骰子的場景
我們以后盡量把所有的和AR相關的文件都創建在ARResource.scnassets這個文件夾目錄下面。
接下來,我們需要創建一個可以顯示骰子的場景。具體操作步驟如下:
創建成功之后我們可以把文件名改為DiceScene.scn。
復制模型文件
剛才咱們已經轉換了兩個3D模型文件(雖然不是我們自己做的),我們可以試著把Dice.scn添加到DiceScene.scn里面。有兩種方式可以實現:
1: 打開DiceScene.scn,然后直接把Dice.scn拖到DiceScene.scn里面去。
2: 復制粘貼了解一下:打開Dice.scn,之后選中里面的Dice這個節點,然后按Command-C快捷鍵。或者直接用鼠標右鍵選擇復制。之后進入到DiceScene.scn粘貼,OK了。
注意:你可以使用此復制和粘貼技術將模型轉換為本機 SceneKit格式。也就是說,你可以把.dae格式的文件直接拖到DiceScene.scn場景中,或者你可以復制里面的節點到DiceScene.scn場景中。
添加成功之后我們可以復制一下添加進來的骰子模型。分別命名Dice0, Dice1,Dice2 ......看能不能做成如下的效果:
陰影,材質和紋理
那些骰子看起來非常平淡。為了把東西整理得更清晰,你會給他們一些顏色和質地。但首先,是時候看看場景,了解陰影、材料和紋理是如何工作的。
光照模型(陰影)
照明模型本質上是定義不同數學照明模型。這些不同的模型用于計算光線如何在渲染場景中從曲面上反彈的各種技術,最終控制你所看到的結果。
SceneKit 支持幾種不同的照明模型。根據照明模型,它將結合不同紋理、燈光和照明信息的屬性來生成各種類型的材料和效果。
SceneKit 中可用的照明模型掃描器包括:
- Constant: 這是一個平面照明模型,僅包含環境照明。
- Lambert: 此模型包含環境和漫反射照明信息。
- Blinn: 此模型包含環境、漫反射和鏡面照明信息;它使用計算機科學家Jim Blinn對Phong公式的修改。它會使更大,更柔和的鏡面呈現高光效果。
- Phong: 該模型融合了環境、漫反射和鏡面照明信息,使用計算機圖形先驅 Bui Tuong Pong 的公式來計算鏡面高光。
- Physical Based:: 該模型將漫反射與物理燈光和材料的實際抽象結合在一起。它是 SceneKit 最新添加的光照模型,產生的效果更加逼真。
材質
單個材質由前面提到的光照模型之一定義。使用模型表示的材料的確切規格配置照明模型。
這就是所有不同類型的材料,如水、皮革、木材和巖石是如何在虛擬的 3D 世界中構建的。
每種材質都由許多不同的紋理層組成,其中每個層用于生成特定的顏色、紋理或效果。
紋理
紋理是環繞在 3D 幾何體上的平面 2D 圖像,使用存儲在 3D 對象本身中的紋理坐標信息。
紋理應用于不同的圖層中,其中每個圖層用于定義特定屬性,如顏色、鏡面、反射率、光澤度、粗糙度、金屬度甚至透明度。組合后,各種屬性用于定義類似于真實世界的材質和紋理。
例如,以下所有紋理都需要作為各種層來創建逼真的行星地球:
基于物理的渲染 (PBR)
現在,你已經了解了模型中涉及的組件,接下來可以深入了解基于物理的渲染** (PBR)** 照明模型。如果你計劃使 3D 內容看起來盡可能逼真,則 PBR 是你實現此效果的最佳方式。
要了解 PBR 所需的各種紋理層,我們需要遵循一個逼真的行星地球從地面重建,逐層重建!
環境圖
在了解環境地圖之前,首先需要了解多維數據集映射。基本多維數據集由六個邊組成,基本多維數據集映射也是如此,因此它的名稱也是如此。
立方體貼圖通常用于創建反射面和"天空盒"。天空盒 用于在 3D 虛擬環境中創建天空和其他遠地背景。
下面是一個立方體地圖,它用作天空盒,表示陽光明媚的一天草地中間:
SceneKit 支持立方體貼圖的各種模式。最常見的模式稱為水平條形圖案。它由單個1:6 比例圖像組成,其中包含六個大小相等的紋理的集合:
- +X 和 -X: 是立方體貼圖的右面和左面。
- +Y 和 -Y: 是立方體貼圖的頂部和底部。
- +Z 和 -Z: 是立方體地圖的近面和遠面。
環境映射有兩個目的。它類似于反射貼圖,因為它在高反射表面上可見。它還用于為基于 PBR 的對象提供照明信息,為他們提供逼真的照明環境。
下面是一個真實空間環境的示例,其中有星云和塵埃云:
漫反射貼圖
漫反射貼圖為 3D 對象提供其基色。它通常定義對象是什么,而不考慮燈光和任何其他特殊效果。
通過將平面 2D 漫反射紋理環繞在球體周圍,它將球體定義為行星地球。
需要注意的是,漫反射貼圖還可以通過使用 Alpha 通道來指定透明度。
這張漫反射地圖營造出充滿云彩的氛圍。請注意,只有不透明的數據在球體上可見。
Normal map
Normal maps(普通地圖)相當神奇,一開始可能看起來有點混亂。當我們在幾何中使用"法線"一詞時,我們指的是垂直于曲面的矢量。法線貼圖定義自定義每像素法線,其大小由該像素的顏色值決定。此信息描述在照明計算期間,對象表面的每個像素將如何彎曲光線,從而模擬凹凸和凹痕的外觀。
簡單來說,法線貼圖創建曲面凹凸的錯覺,而無需向該曲面添加更多多邊形。
應用法線地圖可將平滑的地球轉變為更逼真的地球,包括山脈、平坦的平原和介于兩者之間的一切。
Height map
高度貼圖不是 PBR 照明模型的一部分。高度貼圖是定義高度信息的黑白圖像,其中白色呈現為對象上的最高點,黑色呈現為地圖上的最低點。
定義高度貼圖后,可以將該高度貼圖轉換為法線貼圖。
有一個好用的免費工具,你可以用于轉換高度地圖到法線地圖:Normal Map Online - 地址http://bit.ly/1ELCePX。
此工具可以從高度貼圖生成法線貼圖。它還可以創建其他貼圖和鏡面貼圖。你甚至不需要高度貼圖 - 此工具也可以將普通圖像轉換為法線!
Occlusion map
遮擋貼圖(更稱為環境遮擋圖)用于禁止環境光到達狹窄的角區域,例如石墻裂縫之間的空間。
Emission map
在沒有光的情況下,如果不是為地球居民,地球就會漆黑。多虧了電力和燈泡的奇跡,人口稠密的地區是可見的,甚至從外太空也可以看到。
Emission map(發射貼圖)覆蓋所有照明和底照信息以創建發光效果。
要看到效果的結果,甚至更多,你必須調暗燈光。這意味著在處理 PBR 時,你必須降低環境圖上的強度。
Self-illumination map
在所有其他效果之后應用自發光貼圖;它可用于使最終結果著色、變亮或變暗。
Displacement map
當法線貼圖使用像素強度在否則平滑的曲面上創建不同高度的錯覺時,置換貼圖使用像素強度來實際更改曲面:
球體不再只是一個球體,因為它現在有一個凹凸不平的幾何體。這些貼圖是灰色縮放的紋理,其中黑色到中灰色顏色將導致幾何體中的縮進,中灰色到白色將凹凸(取代)幾何體向外。
Metalness and roughness maps
PBR 的一個重要特征是能夠可視化微細節,由兩個屬性表示:材料粗糙度和金屬度。
此圖像演示了這些基于物理的屬性在操作中:
Metalness: 從后到前,你將看到金屬屬性如何影響球體的表面。從背面的完全介電和非反射表面開始,物體將過渡到正面的非常金屬和鏡面的表面。
Roughness: 從左到右,你將看到粗糙度屬性如何影響球體的表面。從左側非常粗糙和沉悶的表面開始,表面變為右側光滑拋光的表面。
Metalness map
金屬度近似于低角度的物理表面特性,如折射、反射和Fresnel反射。它產生金屬或電介質(非金屬)外觀。這些貼圖是灰比例紋理。其中黑色表示完全電介質,白色表示完全金屬表面。
Roughness map
粗糙度近似于真實表面的微觀細節。它產生光澤或啞光外觀。這些貼圖是灰度紋理,其中白色表示粗糙曲面,黑色表示平滑曲面。
PBR 地球工程
這邊做好了一個項目感興趣的可以到Github上面下載源代碼查看一下具體實現。
給3D物體添加紋理
我們把注意力轉移到骰子上來,我們可以通過給這些骰子設置一些紋理來使其更加逼真。
我們先把紋理圖片導入到項目中來:
環境紋理的使用
Textures文件夾中的骰子有五種不同的樣式紋理。每種樣式由五個不同的 PBR 紋理組成。你需要將這些紋理應用于每個模具。
進入到DiceScene.scn,選中dice0節點,然后,打開右側的場景檢查器。其中,把Background和Environment全部設置成Environment_CUBE.jpg。
這將禁用默認照明效果。不過暫時不需要擔心。
3D模型紋理的使用
在仍然選擇dice0 節點的情況下,切換到Material inspector,你將向第一個模型添加新材料。為此,應選擇 + 按鈕。然后,選擇New material,然后選擇Done。
這將為當前選定的節點創建一個新材料。將材質重命名為Cracked,并將Lighting model更改為Physical Based。
為不同的對應物選擇相應的Cracked樣式紋理:Diffuse, Metalness, Roughness 和Normal。
由于一遍又一遍地復制同一個模型,因此它們都將共享相同的材料。我們希望每個模型都有自己的材質,因此通過選擇Unshare按鈕來取消共享:
取消共享可確保每個模型都將使用其自己的特殊樣式材質。
你快完了重復與之前相同的步驟,并用以下唯一樣式使每個剩余骰子具有樣式化:
- Cracked
- Ivory
- Metal
- Plate
- Wood
添加3D物體
下面就是把這些骰子添加到場景中。
創建一個空白的場景
打開 ViewController.swift 并在此修改 initScene() 中的以下代碼行:
let scene = SCNScene(named: "ARResource.scnassets/SimpleScene.scn")!
改為:
let scene = SCNScene()
大家應該可以看明白,這個時候scene應該是空白的。
加載環境圖
為AR場景設置環境貼圖。具體實現步驟如下:
scene.lightingEnvironment.contents = "ARResource.scnassets/Textures/Environment_cube.jpg"
scene.lightingEnvironment.intensity = 2
上面代碼的作用是把Environment_cube.jpg這張圖片設置為場景的lightingEnvironment,并且intensity值設置為2:這樣可以使得環境更亮一些。
加載3D物體
現在我們需要把骰子加載到應用中,我們需要先創建一個節點:
var diceNodes: [SCNNode] = []
這個數組可以存放我們之前創建的五個骰子。那么怎么從創阿金的場景中把那五個骰子獲取出來呢?看下面的代碼:
// MARK: - Load Models
func loadModels() {
// 1
let diceScene = SCNScene(
named: "ARResource.scnassets/DiceScene.scn")!
// 2
for count in 0..<5 {
// 3
diceNodes.append(diceScene.rootNode.childNode(
withName: "Dice\(count)",
recursively: false)!)
}
let focusScene = SCNScene(
named: "ARResource.scnassets/Models/FocusScene.scn")!
focusNode = focusScene.rootNode.childNode(
withName: "focus", recursively: false)!
sceneView.scene.rootNode.addChildNode(focusNode)
}
上面的代碼主要做了如下工作:
- 1:把DiceScene.scn加載進來,并且里面的五個骰子存儲到了diceScene中。
- 2: 因為數組李阿敏的篩子數量不止一個,所以用一個循環把里面的所有的??加載到了diceNodes中。
寫好之后,記得在viewDidLoad()中添加loadModels()方法。
放置3D物體
首先添加一些輔助函數和屬性,將骰子節點克隆到AR場景中。
做如下操作,添加三個成員變量:
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)]
上面的代碼做了如下工作:
- diceCount:表示骰子數量。
- diceStyle:索引,用于在各種風格的骰子之間切換。
- diceOffset: 五個骰子相對于原點偏移量的數組。
這里大家需要了解一下SCNVector3這個數據結構:
public struct SCNVector3 {
public var x: Float
public var y: Float
public var z: Float
public init()
public init(x: Float, y: Float, z: Float)
}
也就是說,里面的三個參數分別對應X, Y, Z。
具體的添加骰子的代碼如下:
func throwDiceNode(transform: SCNMatrix4, offset: SCNVector3) {
// 1
let position = SCNVector3(transform.m41 + offset.x,
transform.m42 + offset.y,
transform.m43 + offset.z)
// 2
let diceNode = diceNodes[diceStyle].clone()
diceNode.name = "dice"
diceNode.position = position
//3
sceneView.scene.rootNode.addChildNode(diceNode)
//diceCount -= 1
}
上面的代碼主要做了如下工作:
- 1: 這通過將提供的變換的位置數據與傳遞給函數的向量組合來創建偏移位置。
- 2: 這將創建所選骰子節點的克隆,將其重命名為“骰子”,并設置其位置。
- 3: 最后,將新克隆的骰子節點放入AR場景中,并且遞減diceCount以指示骰子已離開。
現在,注釋掉以下代碼行以享受無窮無盡的骰子:
//diceCount -= 1
添加一個滑動手勢
接下來可以添加一個手勢:
選擇Main.storyboard,然后將Swipe Gesture Recognizer從對象庫拖放到ARSCNView上。
添加如下代碼:
@IBAction func swipeUpGestureHandler(_ sender: Any) {
// 1
guard let frame = self.sceneView.session.currentFrame else { return }
// 2
for count in 0..<diceCount {
throwDiceNode(transform: SCNMatrix4(frame.camera.transform),
offset: diceOffset[count])
}
}
上面的代碼做了如下工作:
- currentFrame是與AR場景關聯的ARFrame對象。它包含最近捕獲的視頻幀圖像(capturedImage),以及捕獲的深度數據,AR相機,當前估計的光,錨點和特征點等其他內容。這行代碼確保有一個有效的框架可用。
- 這部分通過五個骰子進行迭代,每次使用AR相機的變換矩陣將一個骰子投入AR場景,該矩陣包含有關相機位置和旋轉的信息。最終,for循環會將你手中的所有可用骰子扔進AR場景。
改變骰子類型
現在你可以將骰子扔進AR空間,你可以使用其中一個UI按鈕來修改骰子的樣式。
還記得有一個STYLE按鈕不?咱們之前留下了一個點擊事件的方法:
diceStyle = diceStyle >= 4 ? 0 : diceStyle + 1
有五種不同的風格;這循環可用的樣式。
寫完代碼之后,運行項目,向上滑動屏幕,然后把手機向后面移動一點距離,你就可以看到有骰子出現了。點擊STYLE按鈕,還可以切換顯示出來的骰子的樣式。
在下一章中,您將需要一個自定義焦點節點,該節點從名為FocusScene.scn的場景加載。換句話說,在擲骰子時會使用的一種目標。
在場景的根部,創建一個名為focus的節點。這是將在下一章中使用的節點。我們將找到方尖碑和紋理文件夾下的光標所需的所有紋理。請記住將對象添加為焦點節點的子對象。
var focusNode: SCNNode!
然后,將以下內容添加到loadModels()方法中,以確保加載新的焦點場景和節點:
let focusScene = SCNScene(
named: "ARResource.scnassets/Models/FocusScene.scn")!
focusNode = focusScene.rootNode.childNode(
withName: "focus", recursively: false)!
sceneView.scene.rootNode.addChildNode(focusNode)
這會加載場景并將焦點節點存儲在focusNode中。后面的內容我們會在下一章做詳細的介紹。
上一章 | 目錄 | 下一章 |
---|