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