在 WWDC 2012 (那時 OS X 系統(tǒng)還在用喵系命名),Apple 向 OS X 開發(fā)者們介紹了 Scene Kit,這個 Cocoa 下的 3D 渲染框架。在第一版通用 3D 渲染器發(fā)布后,一年內(nèi)又陸續(xù)增加了像 shader (著色器) 修改器、節(jié)點約束、骨骼動畫等幾個強大的特性 (隨 Mavericks 發(fā)布)。今年,Scene Kit 變的更加強大,支持了粒子效果、物理引擎、腳本事件以及多通道分層渲染等多種技術(shù),而且,對于很多人來說更關(guān)鍵的是,它終于可以在 iOS 中使用了。
一上手,我發(fā)現(xiàn) Scene Kit 最強大和脫穎而出的地方,就是可以與 Core Image,Core Animation,Sprite Kit 等已有的圖形框架相互整合及協(xié)作,這在其他游戲引擎中可不常見,但如果你本身就是個 Cocoa 或 Cocoa Touch 框架下的的開發(fā)者的話,就會感到相當親切了。
Scene Kit 建立在 OpenGL 的基礎(chǔ)上,包含了如光照、模型、材質(zhì)、攝像機等高級引擎特性,這些組件都是面向?qū)ο蟮?,你可以用熟悉?Objective-C 或 Swift 語言來編寫代碼。假如你用過 OpenGL 最早的版本,那時還沒有 shader,只能苦逼的使用各種底層受限制的 API 開發(fā)。而 Scene Kit 就好了很多,對于大多數(shù)需求 (甚至像動態(tài)陰影和景深這種高級特性),使用它提供的上層 API 來配置,就已經(jīng)足夠了。
不僅如此,Scene Kit 還允許你直接調(diào)用底層 API,或自己寫 shader 進行手動渲染 (GLSL)。
不僅是光照、模型、材質(zhì)、攝像機這幾個具體的對象,Scene Kit 使用節(jié)點 (在3D圖形學(xué)中,像這樣的樹狀節(jié)點結(jié)構(gòu)一般被稱做 scene graph,這也是 Scene Kit 名稱由來的一種解釋) 以樹狀結(jié)構(gòu)來組織內(nèi)容,每個節(jié)點都存儲了相對其父節(jié)點的位移、旋轉(zhuǎn)角度、縮放等信息,父節(jié)點也是如此,一直向上,直到根節(jié)點。假如要給一個節(jié)點確定一個位置,就必須將它掛載到節(jié)點樹中的某個節(jié)點上,可以使用下面的幾個操作方法:
addChildNode(_:)
insertChildNode(_: atIndex:)
removeFromParentNode()
這些方法與 iOS 和 OS X 中管理 view 和 layer 層級方法如出一轍。
Scene Kit 內(nèi)建了幾種簡單的幾何模型,如盒子、球體、平面、圓錐體等,但對于游戲來說,一般都會從文件中加載3D模型。你可以通過制定文件名來導(dǎo)入 (或?qū)С? COLLADA 格式的模型文件:
let chessPieces = SCNScene(named: "chess pieces") // SCNScene?
如果一個從文件里加載的場景可以全部顯示時,將其設(shè)置成 SCNView 的 scene 就好了;但如果加載的場景文件中包含了多個對象,只有一部分對象要顯示在屏幕上時,就可以通過名字找到這個對象,再手動加載到 view 上:
if let knight = chessPieces.rootNode.childNodeWithName("Knight", recursively: true) {
sceneView.scene?.rootNode.addChildNode(knight)
}
這是一個對導(dǎo)入文件原始節(jié)點的引用,其中包含了任一和每一個子節(jié)點,也包括了模型對象 (包括其材質(zhì)),光照,以及綁定在這些節(jié)點上的攝像機。只要傳入的名字一樣,不論調(diào)用多少次,返回的都是對同一個對象的引用。
http://wiki.jikexueyuan.com/project/objc/images/18-4.png" alt="" />
若需要在場景中擁有一個節(jié)點的多個拷貝,如在一個國際象棋棋盤上顯示兩個馬,你可以對馬這個節(jié)點進行 copy
或 clone
(遞歸的copy)。這將會拷貝一份節(jié)點的引用,但兩份引用所指向的材質(zhì)對象和模型對象仍然是原來那個。所以,想要單獨改變副本材質(zhì)的話,需要再copy一份模型對象,并對這個新的模型對象設(shè)置新材質(zhì)。copy一個模型對象的速度仍然很快,開銷也不高,因為副本引用的頂點數(shù)據(jù)還是同一份。
帶有骨骼動畫的模型對象也會擁有一個皮膚對象,它提供了對骨骼中各個節(jié)點的訪問接口,以及管理骨骼和模型間連接的功能。每個單獨的骨骼都可以被移動和旋轉(zhuǎn),而復(fù)雜的動畫需要同時對多塊骨骼進行操作,如一個角色走路的動畫,很可能就是從文件讀取并加到對象上的 (而不是用代碼一根骨頭一根骨頭的寫)。
Scene Kit 中完全都是動態(tài)光照,使用起來一般會很簡單,但也意味著與完整的游戲引擎相比,光照這塊進步并不明顯。Scene Kit 提供四種類型的光照:環(huán)境光、定向光源、點光源和聚光燈。
通常來說,旋轉(zhuǎn)坐標軸和變換角度并不是設(shè)定光照的最佳方法。下面的例子表示一個光照對象通過一個節(jié)點對象來設(shè)置空間坐標,再通過 "look at" 約束,將光照對象約束到了目標對象上,即使它移動,光照也會一直朝向目標對象。
let spot = SCNLight()
spot.type = SCNLightTypeSpot
spot.castsShadow = true
let spotNode = SCNNode()
spotNode.light = spot
spotNode.position = SCNVector3(x: 4, y: 7, z: 6)
let lookAt = SCNLookAtConstraint(target: knight)
spotNode.constraints = [lookAt]
http://wiki.jikexueyuan.com/project/objc/images/18-5.gif" alt="" />
Scene Kit 的對象中絕大多數(shù)屬性都是可以進行動畫的,就像 Cocoa (或 Cocoa Touch) 框架一樣,你可以創(chuàng)建一個 CAAnimation
對象,并指定一個 key path (甚至可以 "position.x"
) ,然后向一個對象施加這個動畫。同樣的,你可以在 SCNTransaction
的 "begin" 和 "commit" 調(diào)用間去改變值,和剛才的 CAAnimation 非常相似:
let move = CABasicAnimation(keyPath: "position.x")
move.byValue = 10
move.duration = 1.0
knight.addAnimation(move, forKey: "slide right")
Scene Kit 也提供了像 Sprite Kit 那樣的 action 形式的動畫 API,你可以創(chuàng)建串行的動畫組,也支持自定義 action 來協(xié)同使用。與 Core Animation 不同的是,這些 action 作為游戲循環(huán)的一部分執(zhí)行,在每一幀都更新模型對象的值,而不只是更新表現(xiàn)層的節(jié)點。
假如你之前用過 Sprite Kit,會發(fā)現(xiàn) Scene Kit 除了變成了 3D 之外,沒有太多陌生的東西。目前,在 iOS8 (首次支持 Scene Kit) 和 OS X 10.10 下,Scene Kit 和 Sprite Kit 可以協(xié)同工作:對 Sprite Kit 來說,3D 模型可以與 2D 精靈混合使用;對 Scene Kit 來說,Sprite Kit 中的場景和紋理可以作為 Scene Kit 的紋理貼圖,而且 Sprite Kit 的場景可以作為 Scene Kit 場景的蒙層 (如3D游戲中的2D菜單面板,譯者注。兩套非常像的API和概念 (像場景啊,節(jié)點啊,約束啊兩邊都有) 讓人容易混淆。)
不僅是動作和紋理,Scene Kit 和 Sprite Kit 還有很多相同之處。當開始寫游戲的時候,Scene Kit 和它 2D 版本的小伙伴非常相似,它們的游戲循環(huán)步驟完全一致,使用下面幾個代理回調(diào):
http://wiki.jikexueyuan.com/project/objc/images/18-6.png" alt="" />
這些回調(diào)在每幀被調(diào)用,并用來執(zhí)行游戲相關(guān)的邏輯,如用戶輸入,AI (人工智能) 和游戲腳本。
Scene Kit 與普通 Cocoa 或 Cocoa Touch 應(yīng)用使用一樣的機制來處理用戶輸入,如鍵盤事件、鼠標事件、觸摸事件和手勢識別,而主要區(qū)別在于 Scene Kit 中只有一個視圖,場景視圖 (scene view) 。像鍵盤事件或如捏取、滑動、旋轉(zhuǎn)的手勢,只要知道事件的發(fā)生就好了,但像鼠標點擊,或觸碰、拖動手勢等就需要知道具體的事件信息了。
這些情況下,scene view 可以使用 -hitTest(_: options:)
來做點擊測試。與通常的視圖只返回被點擊的子 view 或子 layer 不同,Scene Kit 返回一個數(shù)組,里面存有每個相交的模型對象以及從攝像機投向這個測試點的射線。每個 hit test 的結(jié)果包含被擊中模型的節(jié)點對象,也包含了交點的詳細信息 (交點坐標、交點表面法線,交點的紋理坐標)。多數(shù)情況下,知道第一個被擊中的節(jié)點就足夠了:
if let firstHit = sceneView.hitTest(tapLocation, options: nil)?.first as? SCNHitTestResult {
let hitNode = firstHit.node
// do something with the node that was hit...
}
光照和材質(zhì)的配置方法很易用,但有局限性。假如你有寫好的 OpenGL 著色器 (shader),可以用于完全自定制的進行材質(zhì)渲染;如果你只想修改下默認的渲染,Scene Kit 暴露了 4 個入口用于插入 shader代碼 (GLSL) 來改變默認渲染。Scene Kit 在不同入口點分別提供了對旋轉(zhuǎn)矩陣、模型數(shù)據(jù)、樣本貼圖及渲染后輸出的色值的訪問。
比如,下面的 GLSL 代碼被用在模型數(shù)據(jù)的入口點中,可以將模型對象上所有點沿 x 軸扭曲。這是通過定義一個函數(shù)來創(chuàng)建一個旋轉(zhuǎn)變換,并將其應(yīng)用在模型的位置和法線上。同時,也自定義了一個 "uniform" 變量來決定對象該如何被扭曲。
// a function that creates a rotation transform matrix around X
mat4 rotationAroundX(float angle)
{
return mat4(1.0, 0.0, 0.0, 0.0,
0.0, cos(angle), -sin(angle), 0.0,
0.0, sin(angle), cos(angle), 0.0,
0.0, 0.0, 0.0, 1.0);
}
#pragma body
uniform float twistFactor = 1.0;
float rotationAngle = _geometry.position.x * twistFactor;
mat4 rotationMatrix = rotationAroundX(rotationAngle);
// position is a vec4
_geometry.position *= rotationMatrix;
// normal is a vec3
vec4 twistedNormal = vec4(_geometry.normal, 1.0) * rotationMatrix;
_geometry.normal = twistedNormal.xyz;
著色修改器 (Shader modifier) 既可以綁定在模型對象上,也可以綁定在它的材質(zhì)對象上。這兩個類都完全支持 key-value coding (KVC),你可以指定任意 key 進行賦值。在 shader 中聲明的 "twistFactor" uniform 變量使得 Scene Kit 在這個值改變時自動重新綁定 uniform,這使得你也可以用 KVC 來實現(xiàn):
torus.setValue(5.0, forKey: "twistFactor")
使用這個 key path 的 CAAnimation
也 ok:
let twist = CABasicAnimation(keyPath: "twistFactor")
twist.fromValue = 5
twist.toValue = 0
twist.duration = 2.0
torus.addAnimation(twist, forKey: "Twist the torus")
http://wiki.jikexueyuan.com/project/objc/images/18-7.gif" alt="" />
即使在純 OpenGL 環(huán)境下,有些圖像效果也無法通過一次渲染 pass 完成,我們可以將不同 shader 進行序列操作,以達到后續(xù)處理的目的,稱為延時著色。Scene Kit 使用 SCNTechnique 類來表示這種技術(shù)。它使用字典來創(chuàng)建,字典中定義了繪圖步驟、輸入輸出、shader 文件、符號等等。
第一個渲染 pass 永遠是 Scene Kit 的默認渲染,它輸出場景的顏色和景深。如果你不想這時計算色值,可以將材質(zhì)設(shè)置成"恒定"的光照模型,或者將場景里所有光照都設(shè)置成環(huán)境光。
比如,從 Scene Kit 渲染流程的第一個 pass 獲取景深,第二個獲取法線,第三個對其執(zhí)行邊界檢測,你即可以沿輪廓也可以沿邊緣畫粗線:
http://wiki.jikexueyuan.com/project/objc/images/18-8.png" alt="" />
延伸閱讀
如果你想了解更多使用 Scene Kit 做游戲的知識的話,我推薦今年的 WWDC 中的 "Building a Game with Scene Kit" video,并看看 Bananas sample code.
如果你想學(xué)習(xí) Scene Kit 基礎(chǔ)知識,我推薦看這一年的和去年的 "What's New in Scene Kit" 相關(guān)視頻。如果你還想學(xué)習(xí)更多,可以關(guān)注我即將發(fā)布的這個主題的書。