本文代碼地址node
ARKit系列文章目錄git
默認有三種快速建立的方法,能夠分別建立vertices,normals和textureCoordinates(即UVs):github
let vertexSource = SCNGeometrySource(vertices:cubeVertices())
let normalSource = SCNGeometrySource(normals:cubeNormals())
let uvSource = SCNGeometrySource(textureCoordinates:cubeUVs())
複製代碼
若是想要建立更多類型,或者數據混在同一個數組中,須要用另外一個方法:swift
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<Float>.size, dataOffset: 0, dataStride: MemoryLayout<Float>.size*3)
print(vertexSource.debugDescription)
/// 獲取數據
func getData<T>(array:[T])->Data{
let data:UnsafeMutableRawPointer = malloc(MemoryLayout<T>.size*array.count)
data.initializeMemory(as: T.self, from: array)
return NSData(bytesNoCopy: data, length: MemoryLayout<T>.size*array.count, freeWhenDone: true) as Data
}
複製代碼
仿照之前OC的寫法爲:數組
//`[UInt8]`時
let solidIndexData = Data(bytes: cubeSolidIndices())
let lineElement2 = SCNGeometryElement(data: solidIndexData, primitiveType: .triangles, primitiveCount: 12, bytesPerIndex: MemoryLayout<UInt8>.size)
//`[UInt32]`時(即GLuint),或參照第1步中`func getData<T>(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<UInt32>.size)
複製代碼
須要注意的是cubeSolidIndices()
返回類型爲[UInt8]
(UInt32/GLuint也能夠,但不能用Int),相應的,bytesPerIndex
應爲MemoryLayout<UInt8>.size
.
但swift中其實有更簡單寫法:bash
let solidElement = SCNGeometryElement(indices: cubeSolidIndices(), primitiveType: .triangles)
複製代碼
將頂點,法線,uv數據傳入,以及實體element:app
let geometry = SCNGeometry(sources: [vertexSource, normalSource, uvSource], elements: [solidElement,lineElement])
複製代碼
//設置材質,面/線
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 = true
let lineMaterial = SCNMaterial()
lineMaterial.diffuse.contents = UIColor.white
lineMaterial.lightingModel = .constant
//設置到幾何體上
geometry.materials = [solidMaterial,lineMaterial]
複製代碼
根據geometry建立Node,並添加到根節點上:框架
let cubeNode = SCNNode(geometry: geometry)
scene.rootNode.addChildNode(cubeNode)
複製代碼
最終結果 ide
正方體中明明只有8個頂點,爲何cubeVertices()
中要重複三次?
這是由於,一個頂點被三個面共用,而每一個面的法線和UV貼圖不一樣,直接共用頂點的話,法線和貼圖會出錯.工具
cubeVertices()
中頂點的順序以下圖:
而法線cubeNormals()
和貼圖cubeUVs()
中也是按相同順序排列的.
默認狀況下只能看到頂點組成的三角形的正面,即:面對鏡頭,頂點逆時針爲正;背對鏡頭,頂點排列順時針爲正;
能夠經過SCNMaterial中的
cullMode
和isDoubleSided
來控制顯示.
所以,頂點索引cubeSolidIndices()
中以底面爲例:
// bottom
0, 2, 1,
1, 2, 3,
複製代碼
這個面是背對鏡頭的,因此組成的兩個三角形均是順時針的,即正面是朝下,就是說能夠從下面看到:
其他面同理.
在SceneKit和ARKit中,若是將外部3D模型拖拽到Xcode中再經過代碼加載,是可行的;可是若是模型的格式Xcode不支持或者須要app在發佈後再聯網下載,那麼用代碼直接打開就不可行.
由於3D拖拽到Xcode中時蘋果作了一些格式處理才能在Xcode中用代碼加載,瑞聯網下載的沒有處理過,不可行.
網上也有一些方法經過調用Xcode的copySceneKitAssets
、scntool
來手動處理再下發的,但這其實屬於非正規方法.正確的方法是使用ModelIO框架,它支持的格式有:
支持導入格式:
輸出格式:
ModelIO的功能也很是強大,模型導入/導出,烘焙燈光,處理紋理,改變形狀等等:
此處咱們只介紹模型加載功能,須要先導入頭文件:
import ModelIO
import 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
}
}
複製代碼
最終結果: