增强现实中形状外观修改与交互实现
1. 修改形状外观
1.1 基本方法概述
修改形状外观有多种方式,包括改变光照、透明度和纹理。光照会根据光源类型和位置使形状呈现不同效果;透明度决定形状是实心还是透明;纹理则是在形状表面应用图形图像,例如让形状看起来像由砖块或沙子构成。通过这些修改,能让形状更具视觉吸引力。
1.2 应用纹理
1.2.1 创建项目
创建一个新的增强现实应用项目,命名为 ARAppearance,并确保内容技术使用 SceneKit。
1.2.2 准备纹理图像
在互联网上搜索“公共领域纹理图像”,下载以 .png 或 .jpg 格式存储的纹理图像,然后将其拖放到导航窗格中。
1.2.3 修改代码
修改 ViewController.swift 文件,代码如下:
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin,
ARSCNDebugOptions.showFeaturePoints]
let box = SCNBox(width: 0.1, height: 0.1, length: 0.2,
chamferRadius: 0.01)
let node = SCNNode()
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "stone.jpg")
box.materials = [material]
node.geometry = box
node.position = SCNVector3(0, 0, -0.3)
sceneView.scene.rootNode.addChildNode(node)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
// MARK: - ARSCNViewDelegate
/*
// Override to create and configure nodes for anchors added to the
view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor)
-> SCNNode? {
let node = SCNNode()
return node
}
*/
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for
example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent
tracking is required
}
}
这段代码定义了一个 SCNBox 几何形状和一个 SCNMaterial 数组,将“stone.jpg”图形文件作为其第一个材质,使形状周围呈现石头图像。
1.3 改变透明度
在 viewDidLoad 函数中添加一行代码来定义透明度值,使整个 viewDidLoad 函数如下:
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin,
ARSCNDebugOptions.showFeaturePoints]
let box = SCNBox(width: 0.1, height: 0.1, length: 0.2,
chamferRadius: 0.01)
let node = SCNNode()
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "stone.jpg")
material.transparency = 0.7
box.materials = [material]
node.geometry = box
node.position = SCNVector3(0, 0, -0.3)
sceneView.scene.rootNode.addChildNode(node)
}
上述代码在材质数组中定义了两个特性:一是在盒子周围显示“stone.jpg”图像,二是将透明度定义为 0.7,使盒子呈现半透明效果。
1.4 改变光照
1.4.1 创建光源步骤
创建光源需要以下步骤:
1. 定义一个 SCNLight 对象。
2. 定义 SCNLight 类型。
3. 将 SCNLight 对象分配给一个 SCNNode。
4. 定义 SCNNode 的位置。
5. 将 SCNNode 添加到场景中。
1.4.2 代码示例
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin,
ARSCNDebugOptions.showFeaturePoints]
let box = SCNBox(width: 0.1, height: 0.1, length: 0.1,
chamferRadius: 0.01)
let node = SCNNode()
let material = SCNMaterial()
// material.diffuse.contents = UIImage(named: "stone.jpg")
// material.transparency = 0.7
let spotLight = SCNLight()
spotLight.type = .directional
let spotNode = SCNNode()
spotNode.light = spotLight
spotNode.position = SCNVector3(0, 0.2, 0)
material.diffuse.contents = UIColor.orange
box.materials = [material]
node.geometry = box
node.position = SCNVector3(0, 0, -0.3)
sceneView.scene.rootNode.addChildNode(node)
sceneView.scene.rootNode.addChildNode(spotNode)
}
运行此项目,会创建一个橙色盒子,光源位于原点上方 0.2 米处,由于光照类型为定向光,它只会照亮盒子的正面。
1.4.3 不同光照类型效果
若将光照类型从定向光改为泛光(omni),代码如下:
//spotLight.type = .directional // illuminates only the front of the box
spotLight.type = .omni // illuminates the front and top of the box
运行项目后,泛光会照亮盒子的正面和顶部。不同光照类型和光源位置会产生不同的视觉效果,可以通过实验来探索。
1.5 光照类型总结
| 光照类型 | 效果 |
|---|---|
| ambient | 环境光,均匀照亮场景 |
| directional | 定向光,只照亮特定方向 |
| IES | 基于 IES 文件的光照 |
| probe | 光照探针,用于反射和光照估计 |
| spot | 聚光灯,照亮特定区域 |
1.6 光照创建流程
graph LR
A[定义 SCNLight 对象] --> B[定义 SCNLight 类型]
B --> C[分配 SCNLight 对象到 SCNNode]
C --> D[定义 SCNNode 位置]
D --> E[添加 SCNNode 到场景]
2. 与增强现实对象交互
2.1 交互概述
在现实场景中显示虚拟对象很有趣,但我们通常希望做更多事情,比如让虚拟对象在屏幕上移动,并让用户通过触摸手势(如点击或滑动)与它们进行交互。这样可以使增强现实应用更具视觉吸引力和响应性。
2.2 创建项目
创建一个新的增强现实应用项目,命名为 ARGesture。该项目包含 AppDelegate.swift 文件、ViewController.swift 文件、Main.storyboard 文件,以及一个 art.scnassets 文件夹,其中包含 ship.scn 对象和 texture.png 文件。
2.3 准备工作
2.3.1 导入框架
在 ViewController.swift 文件顶部添加以下行以导入 GLKit 框架:
import GLKit
此时,ViewController.swift 文件应导入 GLKit、UIKit、SceneKit 和 ARKit 框架,共四条导入语句。
2.3.2 创建节点
创建一个 SCNNode 对象作为属性,以便在多个函数中访问,ViewController.swift 文件顶部应如下所示:
import UIKit
import SceneKit
import ARKit
import GLKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
let node = SCNNode()
2.4 创建几何形状并应用纹理
2.4.1 存储图形图像
为避免因文件名拼写错误或文件移动导致 Xcode 找不到文件,可将图形图像存储在 Assets.xcassets 文件夹中,并赋予描述性名称。具体操作步骤如下:
1. 点击导航窗格中的 Assets.xcassets 文件夹打开面板。
2. 点击面板左下角的 + 图标,选择“New Image Set”。
3. 点击 AppIcon 集下的 Image 并按 Return 键,输入描述性名称“Texture”并按 Return 键。
4. 将 texture.png 从导航窗格拖放到 1x 虚线框中。
2.4.2 定义几何形状和应用纹理
在 viewDidLoad 函数中添加以下代码:
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin,
ARSCNDebugOptions.showFeaturePoints]
node.geometry = SCNPyramid(width: 0.15, height: 0.2, length: 0.1)
node.geometry?.firstMaterial?.diffuse.contents = imageLiteral(resourceName: "Texture")
node.position = SCNVector3(0, -0.2, 0)
sceneView.scene.rootNode.addChildNode(node)
}
上述代码定义了一个金字塔形状,并将 texture.png 图形图像应用到其表面。
2.5 处理触摸手势
2.5.1 添加手势识别器
在 viewDidLoad 函数中添加以下代码以检测右滑动手势:
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
sceneView.addGestureRecognizer(swipeGesture)
完整的 viewDidLoad 函数如下:
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin,
ARSCNDebugOptions.showFeaturePoints]
node.geometry = SCNPyramid(width: 0.15, height: 0.2, length: 0.1)
node.geometry?.firstMaterial?.diffuse.contents = imageLiteral(resourceName: "Texture")
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
sceneView.addGestureRecognizer(swipeGesture)
node.position = SCNVector3(0, -0.2, 0)
sceneView.scene.rootNode.addChildNode(node)
}
2.5.2 实现手势处理函数
创建 handleSwipe 函数来处理右滑动手势:
@objc func handleSwipe(sender: UISwipeGestureRecognizer) {
let swipeArea = sender.view as! SCNView
let touchCoordinates = sender.location(in: swipeArea)
let touchedShape = swipeArea.hitTest(touchCoordinates, options: nil)
if (sender.direction == .right) && (touchedShape.isEmpty != true) {
print ("Right swipe")
let degrees: Float = 45
let radians = GLKMathDegreesToRadians(degrees)
let action = SCNAction.rotateBy(x: 0, y: CGFloat(radians), z: 0, duration: 5)
node.runAction(action)
}
}
该函数获取用户滑动的坐标,检查滑动手势是否为向右且在金字塔边界内。如果是,则将金字塔旋转 45 度,旋转持续 5 秒。
2.6 让对象持续旋转
如果希望金字塔持续旋转,可将 viewDidLoad 函数中的最后两行代码替换为:
let forever = SCNAction.repeatForever(action)
node.runAction(forever)
完整的 ViewController.swift 文件如下:
import UIKit
import SceneKit
import ARKit
import GLKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
let node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin,
ARSCNDebugOptions.showFeaturePoints]
node.geometry = SCNPyramid(width: 0.15, height: 0.2, length: 0.1)
node.geometry?.firstMaterial?.diffuse.contents = imageLiteral(resourceName: "Texture")
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe))
sceneView.addGestureRecognizer(swipeGesture)
node.position = SCNVector3(0, -0.2, 0)
sceneView.scene.rootNode.addChildNode(node)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
@objc func handleSwipe(sender: UISwipeGestureRecognizer) {
let swipeArea = sender.view as! SCNView
let touchCoordinates = sender.location(in: swipeArea)
let touchedShape = swipeArea.hitTest(touchCoordinates, options: nil)
if (sender.direction == .right) && (touchedShape.isEmpty != true) {
print ("Right swipe")
let degrees: Float = 45
let radians = GLKMathDegreesToRadians(degrees)
let action = SCNAction.rotateBy(x: 0, y: CGFloat(radians), z: 0, duration: 5)
//let forever = SCNAction.repeatForever(action)
node.runAction(action) //node.runAction(forever)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
2.7 交互操作流程
graph LR
A[创建项目] --> B[导入 GLKit 框架]
B --> C[创建 SCNNode 对象]
C --> D[存储图形图像]
D --> E[定义几何形状并应用纹理]
E --> F[添加手势识别器]
F --> G[实现手势处理函数]
G --> H[可选:让对象持续旋转]
2.8 交互功能总结
| 功能 | 实现步骤 |
|---|---|
| 创建项目 | 创建 ARGesture 项目,包含相关文件和资源 |
| 准备工作 | 导入 GLKit 框架,创建 SCNNode 对象 |
| 应用纹理 | 存储图形图像,定义几何形状并应用纹理 |
| 处理手势 | 添加手势识别器,实现手势处理函数 |
| 持续旋转 | 可选,使用 SCNAction.repeatForever 让对象持续旋转 |
超级会员免费看
9

被折叠的 条评论
为什么被折叠?



