獲取示例代碼
前言
上一篇文章中,介紹了系統提供的幾種幾何體,本文將介紹如何自定義幾何體。3D幾何體和2D圖形類似,需要提供組成幾何體的點坐標,不同的是,還要提供繪制的基本單元。在繪制2D圖形的時候,我們將點按照順序連接起來,再按照一定的規則填充顏色即可,但是在繪制3D幾何體的時候,我們得告訴系統如何使用提供的頂點。一般來說繪制3D幾何體都是以三角形為基本單位,比如繪制一個四邊形,就需要繪制2個三角形。下面是四邊形繪制的效果圖。
三角形列表繪制方式
下面我將為大家介紹3D繪制中的一種基本方式,繪制三角形列表。顧名思義,我們提供一個三角形列表給系統,系統將這些三角形繪制出來。
我們看上面這個四邊形,它由兩個三角形組成,ABC和ACD。它們就組成了三角形列表。用代碼表示就是。
let vertices: [SCNVector3] = [
// 第一個三角形
SCNVector3(-0.5 * size.x, 0.5 * size.y, 0),
SCNVector3(-0.5 * size.x, -0.5 * size.y, 0),
SCNVector3(0.5 * size.x, -0.5 * size.y, 0),
// 第二個三角形
SCNVector3(-0.5 * size.x, 0.5 * size.y, 0),
SCNVector3(0.5 * size.x, -0.5 * size.y, 0),
SCNVector3(0.5 * size.x, 0.5 * size.y, 0)
]
SCNVector3
是SceneKit中用來表示3個Float的數據結構。其中size
是四邊形的尺寸,當size為1x1時,剛好是上圖的四邊形。這里有一點要注意,三角形的頂點要按照逆時針的順序排列,因為默認情況下,頂點排序為逆時針的面為正面,在默認情況下,只有正面會被渲染,這樣可以減少性能消耗。也就是說如果你提供的頂點順序為順時針,你將看不到圖形。
SceneKit自定義幾何體實現要點
想要實現自定義幾何體,主要使用public convenience init(sources: [SCNGeometrySource], elements: [SCNGeometryElement]?)
方法,例子中我創建了Plane類繼承SCNGeometry。下面是實現自定義繪制四邊形的幾乎全部代碼。
convenience init(size: CGPoint) {
let vertices: [SCNVector3] = [
// 第一個三角形
SCNVector3(-0.5 * size.x, 0.5 * size.y, 0),
SCNVector3(-0.5 * size.x, -0.5 * size.y, 0),
SCNVector3(0.5 * size.x, -0.5 * size.y, 0),
// 第二個三角形
SCNVector3(-0.5 * size.x, 0.5 * size.y, 0),
SCNVector3(0.5 * size.x, -0.5 * size.y, 0),
SCNVector3(0.5 * size.x, 0.5 * size.y, 0)
]
let vertexSource = SCNGeometrySource.init(vertices: vertices)
let uvs: [CGPoint] = [
CGPoint(x: 0, y: 1),
CGPoint(x: 0, y: 0),
CGPoint(x: 1, y: 0),
CGPoint(x: 0, y: 1),
CGPoint(x: 1, y: 0),
CGPoint(x: 1, y: 1),
]
let uvSource = SCNGeometrySource.init(textureCoordinates: uvs)
let normals: [SCNVector3] = [
// 第一個三角形
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
// 第二個三角形
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
]
let normalSource = SCNGeometrySource.init(normals: normals)
let indices: [UInt8] = [0, 1, 2, 3, 4, 5]
let element = SCNGeometryElement.init(indices: indices, primitiveType: .triangles)
self.init(sources: [vertexSource, uvSource, normalSource], elements: [element])
}
創建一個自定義幾何體主要需要[SCNGeometrySource]
和[SCNGeometryElement]
。SCNGeometrySource
就是頂點的數據,你可能已經注意到不僅僅有位置數據,還有uv和normal數據,這兩個數據對于貼圖和光照模型計算有很大的作用,我會在后面的文章中進行介紹,本文我們主要關注位置數據vertices
。SCNGeometryElement
就是頂點的組織方式,let element = SCNGeometryElement.init(indices: indices, primitiveType: .triangles)
的意思就是我們從頂點里面取第0到5個組成三角形列表,讓系統渲染。primitiveType: .triangles
代表的就是三角形列表繪制方式。最后要注意的是同一種類型的SCNGeometrySource
系統只會使用第一個,比如你用SCNGeometrySource.init(vertices: vertices)
初始化了2個頂點數據并且傳遞給系統,系統只會使用第一個,如果你想顯示多個頂點數據,需要創建多個Geometry。
三角帶繪制方式
primitiveType: .triangles
除了使用triangles
外還可以使用triangleStrip
。我在PlaneUseIndice
中使用了triangleStrip
并且使用indice優化了頂點數據源。
convenience init(size: CGPoint) {
let vertices: [SCNVector3] = [
SCNVector3(-0.5 * size.x, 0.5 * size.y, 0),
SCNVector3(-0.5 * size.x, -0.5 * size.y, 0),
SCNVector3(0.5 * size.x, -0.5 * size.y, 0),
SCNVector3(0.5 * size.x, 0.5 * size.y, 0)
]
let vertexSource = SCNGeometrySource.init(vertices: vertices)
let uvs: [CGPoint] = [
CGPoint(x: 0, y: 1),
CGPoint(x: 0, y: 0),
CGPoint(x: 1, y: 0),
CGPoint(x: 1, y: 1),
]
let uvSource = SCNGeometrySource.init(textureCoordinates:
uvs)
let normals: [SCNVector3] = [
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
]
let normalSource = SCNGeometrySource.init(normals: normals)
let indices: [UInt8] = [1, 2, 0, 3]
let element = SCNGeometryElement.init(indices: indices,
primitiveType: .triangleStrip)
self.init(sources: [vertexSource, uvSource, normalSource],
elements: [element])
}
在三角帶模式下,第n(n > 1且從1開始)個三角形將復用前一個三角形的最后兩個點,三角形的頂點順序則是逆時針,順時針,逆時針,順時針交替。例子中我使用的是BCAD的頂點順序,在三角帶模式下,解析出來就是BCA和CAD兩個三角形。BCA是逆時針,CAD是順時針。
其他模式
除了三角形列表和三角帶,還有point和line兩種模式,point在每個頂點的位置上繪制點,不過我嘗試下來,SCNGeometryElement
的pointSize屬性設置無效,所以繪制出來的點很小,基本無法使用。line模式會將頂點2個2個取出繪制線段,比如[A,B,C,D]繪制出來的就是AB和CD兩條線。不幸的是線的粗細無法直接控制,也是比較雞肋。SceneKit底層應該用的OpenGL或者說至少要兼容OpenGL,所以有這些限制也是可以理解的,不過pointSize倒是可以在GLSL中方便的修改,這些會在后面的文章中詳細介紹。
最后
例子中我還寫了一個正方體的例子,有興趣的可以看下,在Cube類中。繪制正方體其實就是繪制6個不同軸上的四邊形。讀者可以自己參照例子理解其中的規律。