博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[ARKit]5-加载自定义几何体
阅读量:6645 次
发布时间:2019-06-25

本文共 5456 字,大约阅读时间需要 18 分钟。

说明

学习ARKit前,需要先学习SceneKit,参考

更多iOS相关知识查看github上

通过顶点,法线,纹理坐标数组加载

1. 创建Source

默认有三种快速创建的方法,可以分别创建vertices,normals和textureCoordinates(即UVs):

let vertexSource = SCNGeometrySource(vertices:cubeVertices())let normalSource = SCNGeometrySource(normals:cubeNormals())let uvSource = SCNGeometrySource(textureCoordinates:cubeUVs())复制代码

如果想要创建更多类型,或者数据混在同一个数组中,需要用另一个方法:

SCNGeometrySource(data: Data, semantic: SCNGeometrySource.Semantic, vectorCount: Int, usesFloatComponents floatComponents: Bool, componentsPerVector: Int, bytesPerComponent: Int, dataOffset offset: Int, dataStride stride: Int)// 如/// 创建顶点坐标        let vertex:[Float] = [-1,1,-5,                              1,1,-5,                              1,-1,-5,                              -1,-1,-5]                                        /// 创建接受顶点的对象        let vertexSource = SCNGeometrySource(data: getData(array: vertex), semantic: SCNGeometrySource.Semantic.vertex, vectorCount: 4, usesFloatComponents: true, componentsPerVector: 3, bytesPerComponent: MemoryLayout
.size, dataOffset: 0, dataStride: MemoryLayout
.size*3) print(vertexSource.debugDescription)/// 获取数据 func getData
(array:[T])->Data{ let data:UnsafeMutableRawPointer = malloc(MemoryLayout
.size*array.count) data.initializeMemory(as: T.self, from: array) return NSData(bytesNoCopy: data, length: MemoryLayout
.size*array.count, freeWhenDone: true) as Data }复制代码

2. 创建Element

仿照以前OC的写法为:

//`[UInt8]`时let solidIndexData = Data(bytes: cubeSolidIndices())let lineElement2 = SCNGeometryElement(data: solidIndexData, primitiveType: .triangles, primitiveCount: 12, bytesPerIndex: MemoryLayout
.size)//`[UInt32]`时(即GLuint),或参照第1步中`func getData
(array:[T])->Data`方法let ptr = UnsafeBufferPointer(start: solidIndices, count: solidIndices.count)let solidIndexData = Data(buffer: ptr) let solidElement = SCNGeometryElement(data: solidIndexData, primitiveType: .triangles, primitiveCount: 12, bytesPerIndex: MemoryLayout
.size)复制代码

需要注意的是cubeSolidIndices()返回类型为[UInt8](UInt32/GLuint也可以,但不能用Int),相应的,bytesPerIndex应为MemoryLayout<UInt8>.size.

但swift中其实有更简单写法:

let solidElement = SCNGeometryElement(indices: cubeSolidIndices(), primitiveType: .triangles)复制代码

3. 创建Geometry

将顶点,法线,uv数据传入,以及实体element:

let geometry = SCNGeometry(sources: [vertexSource, normalSource, uvSource], elements: [solidElement,lineElement])复制代码

4. 设置Material

//设置材质,面/线let solidMaterial = SCNMaterial()solidMaterial.diffuse.contents = UIColor(red: 4/255.0, green: 120.0/255.0, blue: 255.0/255.0, alpha: 1.0)solidMaterial.locksAmbientWithDiffuse = truelet lineMaterial = SCNMaterial()lineMaterial.diffuse.contents = UIColor.whitelineMaterial.lightingModel = .constant//设置到几何体上geometry.materials = [solidMaterial,lineMaterial]复制代码

5. 创建Node

根据geometry创建Node,并添加到根节点上:

let cubeNode = SCNNode(geometry: geometry)scene.rootNode.addChildNode(cubeNode)复制代码

最终结果

顶点顺序与对应关系

cubeVertices重复三次

正方体中明明只有8个顶点,为什么cubeVertices()中要重复三次?

这是因为,一个顶点被三个面共用,而每个面的法线和UV贴图不同,直接共用顶点的话,法线和贴图会出错.

cubeVertices中顶点顺序

cubeVertices()中顶点的顺序如下图:

而法线cubeNormals()和贴图cubeUVs()中也是按相同顺序排列的.

  • 同一个面的法线方向一致,都是朝外的(从中心指向四周);
  • UV也对应同一个面的四个点,刚好能铺满整个UV空间(0~1);

三角形正反面

默认情况下只能看到顶点组成的三角形的正面,即:面对镜头,顶点逆时针为正;背对镜头,顶点排列顺时针为正;

可以通过SCNMaterial中的cullModeisDoubleSided来控制显示.

因此,顶点索引cubeSolidIndices()中以底面为例:

// bottom0, 2, 1,1, 2, 3,复制代码

这个面是背对镜头的,所以组成的两个三角形均是顺时针的,即正面是朝下,就是说可以从下面看到:

其余面同理.

加载外部工具创建的几何形状

在SceneKit和ARKit中,如果将外部3D模型拖拽到Xcode中再通过代码加载,是可行的;但是如果模型的格式Xcode不支持或者需要app在发布后再联网下载,那么用代码直接打开就不可行.

因为3D拖拽到Xcode中时苹果做了一些格式处理才能在Xcode中用代码加载,瑞联网下载的没有处理过,不可行.

网上也有一些方法通过调用Xcode的copySceneKitAssetsscntool来手动处理再下发的,但这其实属于非正规方法.正确的方法是使用ModelIO框架,它支持的格式有:

支持导入格式:

  • Alembic .abc
  • Polygon .ply
  • Triangles .stl
  • Wavefront .obj

输出格式:

  • Triangles .stl
  • Wavefront .obj

ModelIO的功能也非常强大,模型导入/导出,烘焙灯光,处理纹理,改变形状等等:

此处我们只介绍模型加载功能,需要先导入头文件:

import ModelIOimport SceneKit.ModelIO复制代码
// 加载 .OBJ文件guard let url = NSBundle.mainBundle().URLForResource("Fighter", withExtension: "obj") else {    fatalError("Failed to find model file.")} let asset = MDLAsset(URL:url)guard let object = asset.objectAtIndex(0) as? MDLMesh else {    fatalError("Failed to get mesh from asset.")}// 将ModelIO对象包装成SceneKit对象,调整大小和位置let node = SCNNode(mdlObject: object) //需要导入头文件`import SceneKit.ModelIO`node.scale = SCNVector3Make(0.05, 0.05, 0.05)node.position = SCNVector3Make(0, -20, 0)        cubeView.scene?.rootNode.addChildNode(node)复制代码

效果如图:

还需要加载图片纹理:

extension MDLMaterial {    func setTextureProperties(textures: [MDLMaterialSemantic:String]) -> Void {        for (key,value) in textures {            guard let url = NSBundle.mainBundle().URLForResource(value, withExtension: "") else {                fatalError("Failed to find URL for resource \(value).")            }            let property = MDLMaterialProperty(name:value, semantic: key, URL: url)            self.setProperty(property)        }    }}// 创建各种纹理的材质let scatteringFunction = MDLScatteringFunction()let material = MDLMaterial(name: "baseMaterial", scatteringFunction: scatteringFunction)        material.setTextureProperties([      .baseColor:"Fighter_Diffuse_25.jpg",      .specular:"Fighter_Specular_25.jpg",      .emission:"Fighter_Illumination_25.jpg"])        // 将纹理应用到每个网格上for  submesh in object.submeshes!  {      if let submesh = submesh as? MDLSubmesh {            submesh.material = material      }}复制代码

最终结果:

转载地址:http://mvrvo.baihongyu.com/

你可能感兴趣的文章
STL 容器和迭代器连载1_简介
查看>>
git 忽略已经跟踪的文件
查看>>
Servlet JSP
查看>>
如何验证代理服务器
查看>>
深入理解JVM读书笔记--内存管理
查看>>
<转>KMP字符串模式匹配详解
查看>>
d语言之inout关键字
查看>>
android 获得格式化时间字符串
查看>>
用InstallAnywhere工具打包J2EE程序为.exe文件
查看>>
js中apply方法的使用
查看>>
HTML.Title闪烁显示
查看>>
EMS6.0配置存储到数据库过程中遇到的问题
查看>>
ca域名庆祝25岁生日 域名注册量超200万
查看>>
使用 acl_cpp 的 HttpServlet 类及服务器框架编写WEB服务器程序
查看>>
使用 Elastic Stack 来监控和调优 Golang 应用程序
查看>>
Spring MVC 上传文件(图片)
查看>>
为chrome设置独立的代理
查看>>
android不同屏幕分辨率的适配
查看>>
eclipse中Alt+/失效的解决方案
查看>>
如何有效的背单词
查看>>