ios开发 scenekit导入模型后怎么让模型动起来

ios开发 scenekit导入模型后怎么让模型动起来,第1张

ios8之后苹果推出了一个3D模型渲染框架。SceneKit。但是国内针对这方面的教程并不是很多。前两天搞了一下也是一头雾水,终于把最基础的内容搞明白了之后,写下这篇随笔作为cnblogs的开篇,希望能一直写下去。

SceneKit现在可以支持有限的几种模型,截止到我写这篇文章为止似乎只有.dae和.abc后一种模型我没有使用过。这篇文章只针对.dae模型写。

首先如果是希望加载一个已有的,不需要程序在运行的时候动态添加的dae模型。那么我们可以直接新建一个game类型的工程。在选项中选择SceneKit,在程序中加载自带模型的那句话中将模型名称替换即可。本文主要讲一下如何导出dae模型,并在server端动态下载并显示。

首先我们手中有一个.stl或者其他的模型文件,将模型文件转换成.dae文件我使用Blender。

(1)在Blender中新建场景

(2)在右上侧栏中将自动生成的Cube、Camera等3个物体删掉

(3)导入我们已有的模型文件

(4)调整我们的模型文件的方向、大小

(5)在右上侧栏更改模型文件及子文件的名字为你要导出的dae文件的名字(这一步很重要!)

(6)在左侧栏中Edit Options中点击Smooth

(7)File->export->dae

(8)在接下来的页面中,我们选择导出的位置和文件的名字,并且在左侧选项Texture中选择include material texture(同样重要!)

接下来我们在桌面上新建一个文件夹,暂时起名为model,更改后缀为.scnassets,将我们生成好的模型文件拷贝进去。SceneKit对于动态添加文件夹写了两个脚本。不太清楚作用原理是什么,以后再研究吧。暂时知道怎么用就行。将copySceneKitAssets、scntool文件拷贝到model.scnassets所在的目录下,进入终端并cd到该目录下,运行

1 ./copySceneKitAssets model.scnassets -o model-o.scnassets

如果终端没有报错,并且生成了model-o.scnassets,则代表运行成功。

接下来我们把生成的model-o.scnassets文件打包成zip文件,目的是为了能让iPhone客户端下载的时候文件更小。

打包好了之后上传至服务器即可。

两个可执行文件下载链接 http://download.csdn.net/detail/u013588047/8937773

接下来是重头戏,如何在程序中下载,解压,并显示呢。

下载解压我使用了两个开源框架 AFNetworking 和 SSZipArchive ,朋友们可以自行查阅使用方法。

一步一步来,先是下载,解压

1 - (void)downloadZip {

2

3 NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]

4 AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration]

5 //这里我们用本地链接替代一下,可以使用任意url链接

6 NSURL *URL = [NSURL URLWithString:@"file:///User/name/Desktop/model.scnassets.zip"]

7 NSURLRequest *request = [NSURLRequest requestWithURL:URL]

8

9 NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {

10 NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]

11 return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]]

12 } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {

13 NSLog(@"File downloaded to: %@", filePath)

14

15 //对文件解压

16 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)

17 NSString *documentsDirectory = [paths objectAtIndex:0]

18 NSString *inputPath = [documentsDirectory stringByAppendingPathComponent:@"/product-1-optimized.scnassets.zip"]

19

20 NSError *zipError = nil

21

22 [SSZipArchive unzipFileAtPath:inputPath toDestination:documentsDirectory overwrite:YES password:nil error:&zipError]

23

24 if( zipError ){

25 NSLog(@"[GameVC] Something went wrong while unzipping: %@", zipError.debugDescription)

26 }else {

27 NSLog(@"[GameVC] Archive unzipped successfully")

28 [self startScene]

29 }

30

31 }]

32 [downloadTask resume]

33 }

而对于3d模型场景的创建,我们使用SCNSceneSource,代码如下

1 NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]

2//这里的dae文件名字是我们导出时定义的文件名,下面一段代码中加载的SCNNode是我们之前在面板中改过的模型名

3 documentsDirectoryURL = [documentsDirectoryURL URLByAppendingPathComponent:@"model.scnassets/cube.dae"]

4

5 SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:documentsDirectoryURL options:nil]

然后我们加载.dae文件中的模型,作为一个SCNNode,名字为我们在一开始改过的模型名

1 SCNNode *theCube = [sceneSource entryWithIdentifier:@"Cube" withClass:[SCNNode class]]

最后我们设置一下灯光等效果,其实是新建game文件中设置好了的,我们要做的是将SCNNode *theCube加载到Scene中

// Create a new scene

SCNScene *scene = [SCNScene scene]

// create and add a camera to the scene

SCNNode *cameraNode = [SCNNode node]

cameraNode.camera = [SCNCamera camera]

[scene.rootNode addChildNode:cameraNode]

// place the camera

cameraNode.position = SCNVector3Make(0, 0, 15)

// create and add a light to the scene

SCNNode *lightNode = [SCNNode node]

lightNode.light = [SCNLight light]

lightNode.light.type = SCNLightTypeOmni

lightNode.position = SCNVector3Make(0, 10, 10)

[scene.rootNode addChildNode:lightNode]

// create and add an ambient light to the scene

SCNNode *ambientLightNode = [SCNNode node]

ambientLightNode.light = [SCNLight light]

ambientLightNode.light.type = SCNLightTypeAmbient

ambientLightNode.light.color = [UIColor darkGrayColor]

[scene.rootNode addChildNode:ambientLightNode]

// Add our cube to the scene

[scene.rootNode addChildNode:theCube]

// retrieve the SCNView

SCNView *scnView = (SCNView *)self.view

// set the scene to the view

scnView.scene = scene

// allows the user to manipulate the camera

scnView.allowsCameraControl = YES

// show statistics such as fps and timing information

scnView.showsStatistics = YES

// configure the view

scnView.backgroundColor = [UIColor blackColor]

这样我们就可以动态下载一个dae文件并显示了。

上文说到AR的一些基础内容,这篇我们就来把这个demo走一遍,在做的过程中,补充一些ARKit里常用的方法,这样应该会容易理解一些。

本文demo链接

用ARKit追踪地板的平面。取首次追踪到的平面作为参考平面,此时这个平面与地面大致是重合的(这里读者也可以自己通过一些方法提高精准度,笔者就不再继续深入了)。

取现实中的地面上的点,得到它在我们的平面上的坐标,通过坐标计算距离。

2、追踪平面

在上一篇demo上(没看上一篇的可以不看直接创建一个项目,template选argumented reality app就好了)继续。

我们先在viewDidLoad里设置一下debugOptions,作用是显示点位,方便后面看追踪情况。

self.sceneView.debugOptions = ARSCNDebugOptionShowFeaturePoints//显示追踪到的点位

追踪锚点的代理方法:- (void)renderer:(id)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor,没有设置代理的先设置一下代理

#pragma mark - ARSCNViewDelegate

//添加节点时候调用(当开启平地捕捉模式之后,如果捕捉到平地,ARKit会自动添加一个平地节点)

-(void)renderer:(id)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{

if ([anchor isMemberOfClass:[ARAnchor class]]) {

NSLog(@"平地捕捉")

}

// 添加一个3D平面模型,ARKit只有捕捉能力,锚点只是一个空间位置,要想更加清楚看到这个空间,我们需要给空间添加一个平地的3D模型来渲染他

ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor

//放一个box,长宽为锚点平面的长宽

SCNBox *plane = [SCNBox boxWithWidth:planeAnchor.extent.x height:0.001 length:planeAnchor.extent.z chamferRadius:0]

SCNMaterial *transparentMaterial = [SCNMaterial new]

transparentMaterial = [self materialNamed:@"tron"]

plane.materials = @[transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial, transparentMaterial]

SCNNode *planeNode = [SCNNode nodeWithGeometry:plane]

planeNode.position =SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z)

[node addChildNode:planeNode]

//为了方便看,在锚点处放一个“坐标系”

SCNTube *tubey = [SCNTube tubeWithInnerRadius:0 outerRadius:0.001 height:5]

tubey.firstMaterial.diffuse.contents = [UIColor blackColor]

SCNNode *tubeNodey = [SCNNode nodeWithGeometry:tubey]

tubeNodey.position = SCNVector3Make(0, 2.5, 0)

[node addChildNode:tubeNodey]

SCNTube *tubex = [SCNTube tubeWithInnerRadius:0 outerRadius:0.001 height:5]

tubex.firstMaterial.diffuse.contents = [UIColor whiteColor]

SCNNode *tubeNodex = [SCNNode nodeWithGeometry:tubex]

tubeNodex.position =  SCNVector3Make(0, 0, 0)

tubeNodex.rotation = SCNVector4Make(1, 0, 0, M_PI/2)

[node addChildNode:tubeNodex]

SCNTube *tubez = [SCNTube tubeWithInnerRadius:0 outerRadius:0.001 height:5]

tubez.firstMaterial.diffuse.contents = [UIColor grayColor]

SCNNode *tubeNodez = [SCNNode nodeWithGeometry:tubez]

tubeNodez.position =  SCNVector3Make(0, 0, 0)

tubeNodez.rotation = SCNVector4Make(0, 0, 1, M_PI/2)

[node addChildNode:tubeNodez]

_zeroNode = [[SCNNode alloc]init]

_zeroNode = node

//捕捉到第一个平面之后关闭捕捉

self.arConfiguration.planeDetection = ARPlaneDetectionNone

[self.sceneView.session runWithConfiguration:self.arConfiguration]

//添加第一根随着屏幕移动的杆子

[self handleTap:tapGesture]

// [self insertGeometry]

}

//材质修改,具体可以看这篇http://www.jianshu.com/p/07b96c800a1d

- (SCNMaterial *)materialNamed:(NSString *)name {

NSMutableDictionary *materials = [NSMutableDictionary new]

SCNMaterial *mat = materials[name]

if (mat) {

return mat

}

mat = [SCNMaterial new]

mat.lightingModelName = SCNLightingModelPhysicallyBased

mat.diffuse.contents = [UIImage imageNamed:@"tron-albedo"]

mat.roughness.contents = [UIImage imageNamed:@"tron-albedo"]

mat.metalness.contents = [UIImage imageNamed:@"tron-albedo"]

mat.normal.contents = [UIImage imageNamed:@"tron-albedo"]

mat.diffuse.wrapS = SCNWrapModeRepeat

mat.diffuse.wrapT = SCNWrapModeRepeat

mat.roughness.wrapS = SCNWrapModeRepeat

mat.roughness.wrapT = SCNWrapModeRepeat

mat.metalness.wrapS = SCNWrapModeRepeat

mat.metalness.wrapT = SCNWrapModeRepeat

mat.normal.wrapS = SCNWrapModeRepeat

mat.normal.wrapT = SCNWrapModeRepeat

materials[name] = mat

return mat

}

这里是个坑,一定要选ARHitTestResultTypeExistingPlane这个,并且关掉追踪。

- (NSArray*)hitTest:(CGPoint)point types:(ARHitTestResultType)types此方法会返回空值,返回空值的原因:

ARHitTestResultTypeExistingPlane:手机晃动导致无法确定此平面与重力相垂直

ARHitTestResultTypeExistingPlaneUsingExtent:触碰到点在平面之外,则不返回值。

ARHitTestResultTypeEstimatedHorizontalPlane:正常情况一定会返回值。

#pragma mark - handleTap

- (void) handleTap:(UIGestureRecognizer*)gestureRecognize{CGPoint tapPoint = CGPointMake(redPoint.center.x, redPoint.center.y)NSArray*result = [self.sceneView hitTest:tapPoint types:ARHitTestResultTypeExistingPlane]

if (result.count == 0) {

return

}

// If there are multiple hits, just pick the closest plane

ARHitTestResult * hitResult = [result firstObject]

SCNTube *calculareTube = [SCNTube tubeWithInnerRadius:0 outerRadius:0.01 height:1]

calculareTube.firstMaterial.diffuse.contents = [UIColor colorWithRed:0 green:255 blue:0 alpha:0.6]

_calculateNode = [SCNNode nodeWithGeometry:calculareTube ]

// 设置节点的位置为捕捉到的平地的锚点的中心位置  SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标SCNVector3Make

_calculateNode.position =  SCNVector3Make(

hitResult.worldTransform.columns[3].x,

hitResult.worldTransform.columns[3].y+0.5,

hitResult.worldTransform.columns[3].z

)

[self.sceneView.scene.rootNode addChildNode:_calculateNode]

if (cubeNumber >0) {

SCNNode *previousNode

previousNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-2]

previousNode.geometry.firstMaterial.diffuse.contents = [UIColor greenColor]

if (cubeNumber>1) {

SCNNode *currentNode

currentNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-3]

currentNode.geometry.firstMaterial.diffuse.contents = [UIColor redColor]

if (cubeNumber>2) {

SCNNode *oldNode

oldNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-4]

oldNode.geometry.firstMaterial.diffuse.contents = [UIColor whiteColor]

}

[self calculateDistance]

}

}

cubeNumber += 1

}

//让透明的杆子随着摄像头移动在捕捉到的平面上动起来

#pragma mark - ARSessionDelegate

- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame{

if (cubeNumber == 0)  return

CGPoint tapPoint = CGPointMake(redPoint.center.x, redPoint.center.y)NSArray*result = [self.sceneView hitTest:tapPoint types:ARHitTestResultTypeExistingPlane]

if (result.count == 0) {

return

}

// If there are multiple hits, just pick the closest plane

ARHitTestResult * hitResult = [result firstObject]

SCNNode *currentNode

currentNode = [self.sceneView.scene.rootNode.childNodes lastObject]

// 设置节点的位置为捕捉到的平地的锚点的中心位置  SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标SCNVector3Make

currentNode.position =  SCNVector3Make(

hitResult.worldTransform.columns[3].x,

hitResult.worldTransform.columns[3].y+0.5,

hitResult.worldTransform.columns[3].z

)

}

//计算距离

-(void)calculateDistance{

CGFloat distance

SCNNode *previousNode

previousNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-3]

SCNNode *currentNode

currentNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-2]

distance = sqrt((previousNode.position.x-currentNode.position.x)*(previousNode.position.x-currentNode.position.x)+(previousNode.position.z-currentNode.position.z)*(previousNode.position.z-currentNode.position.z))

distanceLab.text = [NSString stringWithFormat:@"上两点距离:%f 米",distance]

}

//手势添加杆子方法

#pragma mark - handleTap

- (void) handleTap:(UIGestureRecognizer*)gestureRecognize{

CGPoint tapPoint = CGPointMake(redPoint.center.x, redPoint.center.y)NSArray*result = [self.sceneView hitTest:tapPoint types:ARHitTestResultTypeExistingPlane]

if (result.count == 0) {

return

}

// If there are multiple hits, just pick the closest plane

ARHitTestResult * hitResult = [result firstObject]

SCNTube *calculareTube = [SCNTube tubeWithInnerRadius:0 outerRadius:0.01 height:1]

calculareTube.firstMaterial.diffuse.contents = [UIColor colorWithRed:0 green:255 blue:0 alpha:0.6]

_calculateNode = [SCNNode nodeWithGeometry:calculareTube ]

// 设置节点的位置为捕捉到的平地的锚点的中心位置  SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标SCNVector3Make

_calculateNode.position =  SCNVector3Make(

hitResult.worldTransform.columns[3].x,

hitResult.worldTransform.columns[3].y+0.5,

hitResult.worldTransform.columns[3].z

)

[self.sceneView.scene.rootNode addChildNode:_calculateNode]

if (cubeNumber >0) {

SCNNode *previousNode

previousNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-2]

previousNode.geometry.firstMaterial.diffuse.contents = [UIColor greenColor]

if (cubeNumber>1) {

SCNNode *currentNode

currentNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-3]

currentNode.geometry.firstMaterial.diffuse.contents = [UIColor redColor]

if (cubeNumber>2) {

SCNNode *oldNode

oldNode = self.sceneView.scene.rootNode.childNodes[self.sceneView.scene.rootNode.childNodes.count-4]

oldNode.geometry.firstMaterial.diffuse.contents = [UIColor whiteColor]

}

[self calculateDistance]

}

}

cubeNumber += 1

}


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/bake/11799369.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-18
下一篇2023-05-18

发表评论

登录后才能评论

评论列表(0条)

    保存