ARKit教程06_第四章:添加3D物体和纹理
前言
对ARKit感兴趣的同学,可以订阅ARKit教程专题
源代码地址在这里
正文
在本章中,你将学习如何导入、转换、纹理和加载 3D 对象。然后,你将学习如何将这些 3D 对象放入增强空间。你将从 3D 模型材料概述开始,了解如何创建虚拟地球,然后咱们试着构建一个扑克骰子游戏。
添加3D物体
XCode非常强大,真的,因为它支持把一些其他格式的3D模型文件转换成为XCode支持的格式,并且呈现出来。
导入3D物体
将 COLLADA 文件导入你的项目,就像添加图片那样,拖到你的项目中。
从资源文件夹中,将Models/Dice.dae拖放到项目的ARResource.scnassets文件夹中。
这里需要注意的是,这种.dae格式的模型文件XCode是没有办法直接使用的,需要转换成为XCode可以直接使用的格式,转换步骤如下:
选中你需要转换的模型文件,然后在上面的控制条上选择Editor 在弹出来的新菜单中选择Convert to SceneKit scene file 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中。后面的内容我们会在下一章做详细的介绍。
上一章 | 目录 | 下一章 |
---|