獲取示例代碼
前言
上一篇文章中,介紹了系統(tǒng)提供的幾種幾何體,本文將介紹如何自定義幾何體。3D幾何體和2D圖形類似,需要提供組成幾何體的點(diǎn)坐標(biāo),不同的是,還要提供繪制的基本單元。在繪制2D圖形的時(shí)候,我們將點(diǎn)按照順序連接起來,再按照一定的規(guī)則填充顏色即可,但是在繪制3D幾何體的時(shí)候,我們得告訴系統(tǒng)如何使用提供的頂點(diǎn)。一般來說繪制3D幾何體都是以三角形為基本單位,比如繪制一個(gè)四邊形,就需要繪制2個(gè)三角形。下面是四邊形繪制的效果圖。

三角形列表繪制方式
下面我將為大家介紹3D繪制中的一種基本方式,繪制三角形列表。顧名思義,我們提供一個(gè)三角形列表給系統(tǒng),系統(tǒng)將這些三角形繪制出來。

我們看上面這個(gè)四邊形,它由兩個(gè)三角形組成,ABC和ACD。它們就組成了三角形列表。用代碼表示就是。
let vertices: [SCNVector3] = [
// 第一個(gè)三角形
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),
// 第二個(gè)三角形
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個(gè)Float的數(shù)據(jù)結(jié)構(gòu)。其中size是四邊形的尺寸,當(dāng)size為1x1時(shí),剛好是上圖的四邊形。這里有一點(diǎn)要注意,三角形的頂點(diǎn)要按照逆時(shí)針的順序排列,因?yàn)槟J(rèn)情況下,頂點(diǎn)排序?yàn)槟鏁r(shí)針的面為正面,在默認(rèn)情況下,只有正面會(huì)被渲染,這樣可以減少性能消耗。也就是說如果你提供的頂點(diǎn)順序?yàn)轫槙r(shí)針,你將看不到圖形。
SceneKit自定義幾何體實(shí)現(xiàn)要點(diǎn)
想要實(shí)現(xiàn)自定義幾何體,主要使用public convenience init(sources: [SCNGeometrySource], elements: [SCNGeometryElement]?)方法,例子中我創(chuàng)建了Plane類繼承SCNGeometry。下面是實(shí)現(xiàn)自定義繪制四邊形的幾乎全部代碼。
convenience init(size: CGPoint) {
let vertices: [SCNVector3] = [
// 第一個(gè)三角形
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),
// 第二個(gè)三角形
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] = [
// 第一個(gè)三角形
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
SCNVector3(0, 0, 1),
// 第二個(gè)三角形
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])
}
創(chuàng)建一個(gè)自定義幾何體主要需要[SCNGeometrySource]和[SCNGeometryElement]。SCNGeometrySource就是頂點(diǎn)的數(shù)據(jù),你可能已經(jīng)注意到不僅僅有位置數(shù)據(jù),還有uv和normal數(shù)據(jù),這兩個(gè)數(shù)據(jù)對(duì)于貼圖和光照模型計(jì)算有很大的作用,我會(huì)在后面的文章中進(jìn)行介紹,本文我們主要關(guān)注位置數(shù)據(jù)vertices。SCNGeometryElement就是頂點(diǎn)的組織方式,let element = SCNGeometryElement.init(indices: indices, primitiveType: .triangles)的意思就是我們從頂點(diǎn)里面取第0到5個(gè)組成三角形列表,讓系統(tǒng)渲染。primitiveType: .triangles代表的就是三角形列表繪制方式。最后要注意的是同一種類型的SCNGeometrySource系統(tǒng)只會(huì)使用第一個(gè),比如你用SCNGeometrySource.init(vertices: vertices)初始化了2個(gè)頂點(diǎn)數(shù)據(jù)并且傳遞給系統(tǒng),系統(tǒng)只會(huì)使用第一個(gè),如果你想顯示多個(gè)頂點(diǎn)數(shù)據(jù),需要?jiǎng)?chuàng)建多個(gè)Geometry。
三角帶繪制方式
primitiveType: .triangles除了使用triangles外還可以使用triangleStrip。我在PlaneUseIndice中使用了triangleStrip并且使用indice優(yōu)化了頂點(diǎn)數(shù)據(jù)源。
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開始)個(gè)三角形將復(fù)用前一個(gè)三角形的最后兩個(gè)點(diǎn),三角形的頂點(diǎn)順序則是逆時(shí)針,順時(shí)針,逆時(shí)針,順時(shí)針交替。例子中我使用的是BCAD的頂點(diǎn)順序,在三角帶模式下,解析出來就是BCA和CAD兩個(gè)三角形。BCA是逆時(shí)針,CAD是順時(shí)針。

其他模式
除了三角形列表和三角帶,還有point和line兩種模式,point在每個(gè)頂點(diǎn)的位置上繪制點(diǎn),不過我嘗試下來,SCNGeometryElement的pointSize屬性設(shè)置無效,所以繪制出來的點(diǎn)很小,基本無法使用。line模式會(huì)將頂點(diǎn)2個(gè)2個(gè)取出繪制線段,比如[A,B,C,D]繪制出來的就是AB和CD兩條線。不幸的是線的粗細(xì)無法直接控制,也是比較雞肋。SceneKit底層應(yīng)該用的OpenGL或者說至少要兼容OpenGL,所以有這些限制也是可以理解的,不過pointSize倒是可以在GLSL中方便的修改,這些會(huì)在后面的文章中詳細(xì)介紹。
最后
例子中我還寫了一個(gè)正方體的例子,有興趣的可以看下,在Cube類中。繪制正方體其實(shí)就是繪制6個(gè)不同軸上的四邊形。讀者可以自己參照例子理解其中的規(guī)律。