前言
對ARKit感興趣的同學(xué),可以訂閱ARKit教程專題
源代碼地址在這里
正文
在本章中,你將學(xué)習(xí)如何導(dǎo)入、轉(zhuǎn)換、紋理和加載 3D 對象。然后,你將學(xué)習(xí)如何將這些 3D 對象放入增強(qiáng)空間。你將從 3D 模型材料概述開始,了解如何創(chuàng)建虛擬地球,然后咱們試著構(gòu)建一個撲克骰子游戲。
添加3D物體
XCode非常強(qiáng)大,真的,因?yàn)樗С职岩恍┢渌袷降?strong>3D模型文件轉(zhuǎn)換成為XCode支持的格式,并且呈現(xiàn)出來。
導(dǎo)入3D物體
將 COLLADA 文件導(dǎo)入你的項目,就像添加圖片那樣,拖到你的項目中。
從資源文件夾中,將Models/Dice.dae拖放到項目的ARResource.scnassets文件夾中。

這里需要注意的是,這種.dae格式的模型文件XCode是沒有辦法直接使用的,需要轉(zhuǎn)換成為XCode可以直接使用的格式,轉(zhuǎn)換步驟如下:
選中你需要轉(zhuǎn)換的模型文件,然后在上面的控制條上選擇Editor 在彈出來的新菜單中選擇Convert to SceneKit scene ?le format (.scn)。

接下來會有一個彈框,詢問你,是保留兩種格式的文件呢還是取消操作呢,還是直接轉(zhuǎn)換,新的的格式的文件呢。這個你自己看著辦。我的選擇是Duplicate。

兩個文件都轉(zhuǎn)換之后如下圖所示:

我們可以把抓換之后的文件改一下名字,如下:

創(chuàng)建一個可以顯示骰子的場景
我們以后盡量把所有的和AR相關(guān)的文件都創(chuàng)建在ARResource.scnassets這個文件夾目錄下面。
接下來,我們需要創(chuàng)建一個可以顯示骰子的場景。具體操作步驟如下:

創(chuàng)建成功之后我們可以把文件名改為DiceScene.scn。
復(fù)制模型文件
剛才咱們已經(jīng)轉(zhuǎn)換了兩個3D模型文件(雖然不是我們自己做的),我們可以試著把Dice.scn添加到DiceScene.scn里面。有兩種方式可以實(shí)現(xiàn):
1: 打開DiceScene.scn,然后直接把Dice.scn拖到DiceScene.scn里面去。
2: 復(fù)制粘貼了解一下:打開Dice.scn,之后選中里面的Dice這個節(jié)點(diǎn),然后按Command-C快捷鍵?;蛘咧苯佑檬髽?biāo)右鍵選擇復(fù)制。之后進(jìn)入到DiceScene.scn粘貼,OK了。

注意:你可以使用此復(fù)制和粘貼技術(shù)將模型轉(zhuǎn)換為本機(jī) SceneKit格式。也就是說,你可以把.dae格式的文件直接拖到DiceScene.scn場景中,或者你可以復(fù)制里面的節(jié)點(diǎn)到DiceScene.scn場景中。
添加成功之后我們可以復(fù)制一下添加進(jìn)來的骰子模型。分別命名Dice0, Dice1,Dice2 ......看能不能做成如下的效果:

陰影,材質(zhì)和紋理
那些骰子看起來非常平淡。為了把東西整理得更清晰,你會給他們一些顏色和質(zhì)地。但首先,是時候看看場景,了解陰影、材料和紋理是如何工作的。
光照模型(陰影)
照明模型本質(zhì)上是定義不同數(shù)學(xué)照明模型。這些不同的模型用于計算光線如何在渲染場景中從曲面上反彈的各種技術(shù),最終控制你所看到的結(jié)果。
SceneKit 支持幾種不同的照明模型。根據(jù)照明模型,它將結(jié)合不同紋理、燈光和照明信息的屬性來生成各種類型的材料和效果。
SceneKit 中可用的照明模型掃描器包括:

- Constant: 這是一個平面照明模型,僅包含環(huán)境照明。
- Lambert: 此模型包含環(huán)境和漫反射照明信息。
- Blinn: 此模型包含環(huán)境、漫反射和鏡面照明信息;它使用計算機(jī)科學(xué)家Jim Blinn對Phong公式的修改。它會使更大,更柔和的鏡面呈現(xiàn)高光效果。
- Phong: 該模型融合了環(huán)境、漫反射和鏡面照明信息,使用計算機(jī)圖形先驅(qū) Bui Tuong Pong 的公式來計算鏡面高光。
- Physical Based:: 該模型將漫反射與物理燈光和材料的實(shí)際抽象結(jié)合在一起。它是 SceneKit 最新添加的光照模型,產(chǎn)生的效果更加逼真。
材質(zhì)
單個材質(zhì)由前面提到的光照模型之一定義。使用模型表示的材料的確切規(guī)格配置照明模型。
這就是所有不同類型的材料,如水、皮革、木材和巖石是如何在虛擬的 3D 世界中構(gòu)建的。

每種材質(zhì)都由許多不同的紋理層組成,其中每個層用于生成特定的顏色、紋理或效果。
紋理
紋理是環(huán)繞在 3D 幾何體上的平面 2D 圖像,使用存儲在 3D 對象本身中的紋理坐標(biāo)信息。
紋理應(yīng)用于不同的圖層中,其中每個圖層用于定義特定屬性,如顏色、鏡面、反射率、光澤度、粗糙度、金屬度甚至透明度。組合后,各種屬性用于定義類似于真實(shí)世界的材質(zhì)和紋理。
例如,以下所有紋理都需要作為各種層來創(chuàng)建逼真的行星地球:

基于物理的渲染 (PBR)
現(xiàn)在,你已經(jīng)了解了模型中涉及的組件,接下來可以深入了解基于物理的渲染** (PBR)** 照明模型。如果你計劃使 3D 內(nèi)容看起來盡可能逼真,則 PBR 是你實(shí)現(xiàn)此效果的最佳方式。
要了解 PBR 所需的各種紋理層,我們需要遵循一個逼真的行星地球從地面重建,逐層重建!
環(huán)境圖
在了解環(huán)境地圖之前,首先需要了解多維數(shù)據(jù)集映射?;径嗑S數(shù)據(jù)集由六個邊組成,基本多維數(shù)據(jù)集映射也是如此,因此它的名稱也是如此。
立方體貼圖通常用于創(chuàng)建反射面和"天空盒"。天空盒 用于在 3D 虛擬環(huán)境中創(chuàng)建天空和其他遠(yuǎn)地背景。
下面是一個立方體地圖,它用作天空盒,表示陽光明媚的一天草地中間:

SceneKit 支持立方體貼圖的各種模式。最常見的模式稱為水平條形圖案。它由單個1:6 比例圖像組成,其中包含六個大小相等的紋理的集合:

- +X 和 -X: 是立方體貼圖的右面和左面。
- +Y 和 -Y: 是立方體貼圖的頂部和底部。
- +Z 和 -Z: 是立方體地圖的近面和遠(yuǎn)面。
環(huán)境映射有兩個目的。它類似于反射貼圖,因?yàn)樗诟叻瓷浔砻嫔峡梢姟K€用于為基于 PBR 的對象提供照明信息,為他們提供逼真的照明環(huán)境。
下面是一個真實(shí)空間環(huán)境的示例,其中有星云和塵埃云:

漫反射貼圖
漫反射貼圖為 3D 對象提供其基色。它通常定義對象是什么,而不考慮燈光和任何其他特殊效果。

通過將平面 2D 漫反射紋理環(huán)繞在球體周圍,它將球體定義為行星地球。
需要注意的是,漫反射貼圖還可以通過使用 Alpha 通道來指定透明度。

這張漫反射地圖營造出充滿云彩的氛圍。請注意,只有不透明的數(shù)據(jù)在球體上可見。
Normal map
Normal maps(普通地圖)相當(dāng)神奇,一開始可能看起來有點(diǎn)混亂。當(dāng)我們在幾何中使用"法線"一詞時,我們指的是垂直于曲面的矢量。法線貼圖定義自定義每像素法線,其大小由該像素的顏色值決定。此信息描述在照明計算期間,對象表面的每個像素將如何彎曲光線,從而模擬凹凸和凹痕的外觀。
簡單來說,法線貼圖創(chuàng)建曲面凹凸的錯覺,而無需向該曲面添加更多多邊形。
應(yīng)用法線地圖可將平滑的地球轉(zhuǎn)變?yōu)楦普娴牡厍?包括山脈、平坦的平原和介于兩者之間的一切。
Height map
高度貼圖不是 PBR 照明模型的一部分。高度貼圖是定義高度信息的黑白圖像,其中白色呈現(xiàn)為對象上的最高點(diǎn),黑色呈現(xiàn)為地圖上的最低點(diǎn)。
定義高度貼圖后,可以將該高度貼圖轉(zhuǎn)換為法線貼圖。

有一個好用的免費(fèi)工具,你可以用于轉(zhuǎn)換高度地圖到法線地圖:Normal Map Online - 地址http://bit.ly/1ELCePX。

此工具可以從高度貼圖生成法線貼圖。它還可以創(chuàng)建其他貼圖和鏡面貼圖。你甚至不需要高度貼圖 - 此工具也可以將普通圖像轉(zhuǎn)換為法線!
Occlusion map
遮擋貼圖(更稱為環(huán)境遮擋圖)用于禁止環(huán)境光到達(dá)狹窄的角區(qū)域,例如石墻裂縫之間的空間。

Emission map
在沒有光的情況下,如果不是為地球居民,地球就會漆黑。多虧了電力和燈泡的奇跡,人口稠密的地區(qū)是可見的,甚至從外太空也可以看到。
Emission map(發(fā)射貼圖)覆蓋所有照明和底照信息以創(chuàng)建發(fā)光效果。

要看到效果的結(jié)果,甚至更多,你必須調(diào)暗燈光。這意味著在處理 PBR 時,你必須降低環(huán)境圖上的強(qiáng)度。
Self-illumination map
在所有其他效果之后應(yīng)用自發(fā)光貼圖;它可用于使最終結(jié)果著色、變亮或變暗。

Displacement map
當(dāng)法線貼圖使用像素強(qiáng)度在否則平滑的曲面上創(chuàng)建不同高度的錯覺時,置換貼圖使用像素強(qiáng)度來實(shí)際更改曲面:

球體不再只是一個球體,因?yàn)樗F(xiàn)在有一個凹凸不平的幾何體。這些貼圖是灰色縮放的紋理,其中黑色到中灰色顏色將導(dǎo)致幾何體中的縮進(jìn),中灰色到白色將凹凸(取代)幾何體向外。
Metalness and roughness maps
PBR 的一個重要特征是能夠可視化微細(xì)節(jié),由兩個屬性表示:材料粗糙度和金屬度。

此圖像演示了這些基于物理的屬性在操作中:
Metalness: 從后到前,你將看到金屬屬性如何影響球體的表面。從背面的完全介電和非反射表面開始,物體將過渡到正面的非常金屬和鏡面的表面。
Roughness: 從左到右,你將看到粗糙度屬性如何影響球體的表面。從左側(cè)非常粗糙和沉悶的表面開始,表面變?yōu)橛覀?cè)光滑拋光的表面。
Metalness map
金屬度近似于低角度的物理表面特性,如折射、反射和Fresnel反射。它產(chǎn)生金屬或電介質(zhì)(非金屬)外觀。這些貼圖是灰比例紋理。其中黑色表示完全電介質(zhì),白色表示完全金屬表面。

Roughness map
粗糙度近似于真實(shí)表面的微觀細(xì)節(jié)。它產(chǎn)生光澤或啞光外觀。這些貼圖是灰度紋理,其中白色表示粗糙曲面,黑色表示平滑曲面。
PBR 地球工程
這邊做好了一個項目感興趣的可以到Github上面下載源代碼查看一下具體實(shí)現(xiàn)。

給3D物體添加紋理
我們把注意力轉(zhuǎn)移到骰子上來,我們可以通過給這些骰子設(shè)置一些紋理來使其更加逼真。
我們先把紋理圖片導(dǎo)入到項目中來:

環(huán)境紋理的使用
Textures文件夾中的骰子有五種不同的樣式紋理。每種樣式由五個不同的 PBR 紋理組成。你需要將這些紋理應(yīng)用于每個模具。
進(jìn)入到DiceScene.scn,選中dice0節(jié)點(diǎn),然后,打開右側(cè)的場景檢查器。其中,把Background和Environment全部設(shè)置成Environment_CUBE.jpg。

這將禁用默認(rèn)照明效果。不過暫時不需要擔(dān)心。
3D模型紋理的使用
在仍然選擇dice0 節(jié)點(diǎn)的情況下,切換到Material inspector,你將向第一個模型添加新材料。為此,應(yīng)選擇 + 按鈕。然后,選擇New material,然后選擇Done。

這將為當(dāng)前選定的節(jié)點(diǎn)創(chuàng)建一個新材料。將材質(zhì)重命名為Cracked,并將Lighting model更改為Physical Based。
為不同的對應(yīng)物選擇相應(yīng)的Cracked樣式紋理:Diffuse, Metalness, Roughness 和Normal。

由于一遍又一遍地復(fù)制同一個模型,因此它們都將共享相同的材料。我們希望每個模型都有自己的材質(zhì),因此通過選擇Unshare按鈕來取消共享:

取消共享可確保每個模型都將使用其自己的特殊樣式材質(zhì)。
你快完了重復(fù)與之前相同的步驟,并用以下唯一樣式使每個剩余骰子具有樣式化:
- Cracked
- Ivory
- Metal
- Plate
- Wood

添加3D物體
下面就是把這些骰子添加到場景中。
創(chuàng)建一個空白的場景
打開 ViewController.swift 并在此修改 initScene() 中的以下代碼行:
let scene = SCNScene(named: "ARResource.scnassets/SimpleScene.scn")!
改為:
let scene = SCNScene()
大家應(yīng)該可以看明白,這個時候scene應(yīng)該是空白的。
加載環(huán)境圖
為AR場景設(shè)置環(huán)境貼圖。具體實(shí)現(xiàn)步驟如下:
scene.lightingEnvironment.contents = "ARResource.scnassets/Textures/Environment_cube.jpg"
scene.lightingEnvironment.intensity = 2
上面代碼的作用是把Environment_cube.jpg這張圖片設(shè)置為場景的lightingEnvironment,并且intensity值設(shè)置為2:這樣可以使得環(huán)境更亮一些。
加載3D物體
現(xiàn)在我們需要把骰子加載到應(yīng)用中,我們需要先創(chuàng)建一個節(jié)點(diǎn):
var diceNodes: [SCNNode] = []
這個數(shù)組可以存放我們之前創(chuàng)建的五個骰子。那么怎么從創(chuàng)阿金的場景中把那五個骰子獲取出來呢?看下面的代碼:
// 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加載進(jìn)來,并且里面的五個骰子存儲到了diceScene中。
- 2: 因?yàn)閿?shù)組李阿敏的篩子數(shù)量不止一個,所以用一個循環(huán)把里面的所有的??加載到了diceNodes中。
寫好之后,記得在viewDidLoad()中添加loadModels()方法。
放置3D物體
首先添加一些輔助函數(shù)和屬性,將骰子節(jié)點(diǎn)克隆到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:表示骰子數(shù)量。
- diceStyle:索引,用于在各種風(fēng)格的骰子之間切換。
- diceOffset: 五個骰子相對于原點(diǎn)偏移量的數(shù)組。
這里大家需要了解一下SCNVector3這個數(shù)據(jù)結(jié)構(gòu):
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)
}
也就是說,里面的三個參數(shù)分別對應(yīng)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: 這通過將提供的變換的位置數(shù)據(jù)與傳遞給函數(shù)的向量組合來創(chuàng)建偏移位置。
- 2: 這將創(chuàng)建所選骰子節(jié)點(diǎn)的克隆,將其重命名為“骰子”,并設(shè)置其位置。
- 3: 最后,將新克隆的骰子節(jié)點(diǎn)放入AR場景中,并且遞減diceCount以指示骰子已離開。
現(xiàn)在,注釋掉以下代碼行以享受無窮無盡的骰子:
//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場景關(guān)聯(lián)的ARFrame對象。它包含最近捕獲的視頻幀圖像(capturedImage),以及捕獲的深度數(shù)據(jù),AR相機(jī),當(dāng)前估計的光,錨點(diǎn)和特征點(diǎn)等其他內(nèi)容。這行代碼確保有一個有效的框架可用。
- 這部分通過五個骰子進(jìn)行迭代,每次使用AR相機(jī)的變換矩陣將一個骰子投入AR場景,該矩陣包含有關(guān)相機(jī)位置和旋轉(zhuǎn)的信息。最終,for循環(huán)會將你手中的所有可用骰子扔進(jìn)AR場景。
改變骰子類型
現(xiàn)在你可以將骰子扔進(jìn)AR空間,你可以使用其中一個UI按鈕來修改骰子的樣式。
還記得有一個STYLE按鈕不?咱們之前留下了一個點(diǎn)擊事件的方法:
diceStyle = diceStyle >= 4 ? 0 : diceStyle + 1
有五種不同的風(fēng)格;這循環(huán)可用的樣式。
寫完代碼之后,運(yùn)行項目,向上滑動屏幕,然后把手機(jī)向后面移動一點(diǎn)距離,你就可以看到有骰子出現(xiàn)了。點(diǎn)擊STYLE按鈕,還可以切換顯示出來的骰子的樣式。

在下一章中,您將需要一個自定義焦點(diǎn)節(jié)點(diǎn),該節(jié)點(diǎn)從名為FocusScene.scn的場景加載。換句話說,在擲骰子時會使用的一種目標(biāo)。
在場景的根部,創(chuàng)建一個名為focus的節(jié)點(diǎn)。這是將在下一章中使用的節(jié)點(diǎn)。我們將找到方尖碑和紋理文件夾下的光標(biāo)所需的所有紋理。請記住將對象添加為焦點(diǎn)節(jié)點(diǎn)的子對象。
var focusNode: SCNNode!
然后,將以下內(nèi)容添加到loadModels()方法中,以確保加載新的焦點(diǎn)場景和節(jié)點(diǎn):
let focusScene = SCNScene(
named: "ARResource.scnassets/Models/FocusScene.scn")!
focusNode = focusScene.rootNode.childNode(
withName: "focus", recursively: false)!
sceneView.scene.rootNode.addChildNode(focusNode)

這會加載場景并將焦點(diǎn)節(jié)點(diǎn)存儲在focusNode中。后面的內(nèi)容我們會在下一章做詳細(xì)的介紹。
| 上一章 | 目錄 | 下一章 |
|---|