Scene Kit是一個Cocoa式的3D渲染框架,在蘋果推出OS X Mountain Lion的時候嶄露頭角(WWDC 2012)。作為一款普通的3D渲染引擎,SceneKit的最初版本實在驚艷。而在一年之後,蘋果發布OS X Mavericks 時為Scene Kit新增了著色修改器、幾何約束以及骨骼動畫的功能。等到了2014年,這一框架變得更為強大,還支持了粒子系統、物理仿真、腳本事件和多通道渲染。此外還有一個更新(我想對於大多數人來說也極為重要的一個),就是支持了iOS系統,而現在蘋果亦稱其"為休閒游戲而備"。
從早我就發現了Scene Kit最大的優勢和特征就是其整合了像Core Image和Core Animation之類的圖形框架,而現在還整合進了Sprite Kit。當然,通常來說隨便找一款游戲引擎來進行以上這些開發都不是事兒,不過如果你只是Cocoa(或者Cocoa Touch)開發的業余選手,Scene Kit對你來說會倍兒有親切感。
Scene Kit基礎
Scene Kit是基於OpenGL構建的,是一款高階開發框架。你可以在Swift和Objective-C下直接使用燈光、幾何體、材質和相機這樣的對象。如果你使用的OpenGL仍是在有著色器之前的早期版本,前面所提的這些東西都會給你帶來痛苦的回憶,畢竟版本太早很多東西的配置都是有限制的。不過幸運的是,對於大多數情況來說,高階開發框架已經配置完好且足夠頂用——甚至是動態陰影和景深效果這樣的高級渲染也不在話下。
而對於那些沒法實現的,Scene Kit還允許開發者使用自己編碼的著色器(GLSL)對渲染進行底層的配置。
節點
除卻燈光、幾何體、材質和相機之外,Scene Kit對節點采用了分層結構【注:像這樣的節點層次結構在3D圖形學通常意義上叫作場景圖層(scene graph),這也是該框架取名為Scene Kit的一種解釋】來組織所有內容。每一個節點有自己相對於父節點的位置、角度以及縮放比例,反過來將該節點作為父節點也是一樣,一直追溯到根節點為止。當在3D世界中某個位置添加一個對象,它則會歸屬於其中的某個節點,這些結點的層次結構通過以下方法來管理:
addChildNode(_:) insertChildNode(_: atIndex:) removeFromParentNode()
以上這些示例方法用於管理iOS和OS X上的視圖結構和分層結構。
幾何對象
Scene Kit中有一些內建的幾何圖形,比如簡單的盒形、球形、飛機、還有錐體。不過對於游戲開發而言,你需要從文件中加載更多的3D模型。你可以通過引用文件名來導入(或導出)這些模型:
let chessPieces = SCNScene(named: "chess pieces") // SCNScene?
如果文件中包含有場景,那麼該場景會作為一個整體來顯示,之後可以當做場景視圖的場景來使用。假如場景中包含有大量對象,而只應該顯示部分在屏幕上的話,可以通過引用其名字獲取它們,並添加到場景視圖所渲染的場景中。
if let knight = chessPieces.rootNode.childNodeWithName("Knight", recursively: true) { sceneView.scene?.rootNode.addChildNode(knight) }
這是一個導入文件中節點的示例,其中包含了一些子節點,而這些節點上都附有幾何形狀(以及材質)、燈光和相機參數。重復使用相同的名字來獲取子節點會得到同一個對象的引用。
如果要在一個場景中進行大量拷貝,比如當你需要在棋盤上顯示兩個“馬”的時候,你就先要拷貝(copy)該節點或者是克隆(clone)一份(即遞歸拷貝)。這樣會為該節點復制一個具有相同幾何對象和相同材質的拷貝。不過所有的拷貝都指向同一個幾何對象,因此要改變當中某個幾何對象的材質時,該幾何對象也需要有一個拷貝,然後在為其附上新的材質。復制幾何對象也很方便,所要涉及到的頂點數據都是相同的。
那些導入進來用於完成骨骼動畫的節點除了擁有幾何對象之外還有一個skinner對象,用於接入骨架節點層和管理骨骼與幾何對象之間的關系。個別的骨骼是可以移動或者旋轉的,不過對於一些更為復雜的動畫來說需要進行大量的骨骼數據修改——比如人物在繞圈行走的動畫——看上去會更像是從文件導入然後加載到對象上的。
光照
Scene Kit中的光線完全是動態的,這也使得它們會相當容易理解同時也便於使用。當然,易用也便意味著相比那些發展完善的游戲引擎來說會有所欠缺。這裡提供的光線有四種類型:環境光、平行光、全向光(點光源)和聚光燈。
在多數情況下,給光源指定一個旋轉軸和角度來照射對象並非最為直觀的方法。對於這種情況,節點告知光源它的位置和方向的時候是要考慮另一個節點作為約束的,添加約束也就是說光線會保持指向這一節點,就算節點移動也一樣:
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]
動畫
幾乎Scene Kit中所有的對象的屬性都是可用於動畫的,和Cocoa(或是Cocoa Touch)一樣,你可以使用keyPath(甚至是形如"position:x"這樣的Path)來創建CCAnimation並將其添加到對象。同樣的,你還可以將SCNTransaction的值設置為"begin"或者"commit"。這兩種方法幾乎一學就會,不過對於游戲中的動畫來說不會營造出什麼特別的效果。
let move = CABasicAnimation(keyPath: "position.x") move.byValue = 10 move.duration = 1.0 knight.addAnimation(move, forKey: "slide right")
Scene Kit還支持Sprite Kit中行為式(action-style)動畫的API,你可以運行代碼以創建動畫序列然後與其他動畫一起定制行為(action)。與Core Animation不同,這些行為會在整個游戲的循環中運行,並更新每一幀中模型的值,而非只限於展現出來的節點。
事實上,如果你之前使用過Sprite Kit,那麼Scene Kit應該會覺得很熟悉才對。對於3D圖形,iOS 8(第一個支持Scene Kit的iOS版本)和OS X 10.10上可以同時使用兩種框架(Scene Kit和Sprite Kit)。對於Sprite Kit來說,3D模型可以混合2D的精靈(sprite),而對於Scene Kit來說,Sprite Kit的場景和紋理同樣可以作為Scene Kit中的紋理來用,而且Sprite Kit中的場景還可作為Scene Kit場景上面的2D覆蓋層【注:是的,Scene Kit和Sprite Kit的API有太多相似概念(場景、節點、約束等兩者共有的概念)以至於非常容易混淆】。
使用Scene Kit寫游戲
場景行為和材質紋理並非Scene Kit和Sprite Kit的唯一相同之處。當使用Scene Kit開發游戲的時候,就會發現其與2D圖形開發的時候有很大的相似度。這兩種情形下的游戲主循環都會按照相同的步驟進行,以回調函數為代表:
1.更新場景
2.應用動畫/行為
3.物理仿真
4.應用約束
5.渲染
每一幀的渲染之後都會又一次回調函數用於執行游戲相關的邏輯,比如輸入處理、AI(人工智能)以及游戲腳本。
輸入處理
Scene Kit和Cocoa以及Cocoa Touch使用了相同的機制來處理鍵盤、鼠標、觸摸和手勢等輸入,而其稍有區別的地方就是Scene Kit中只有一個視圖,即場景視圖(scene)。鍵盤的輸入以及擠壓、滑動、旋轉此類手勢僅需知道其發生了就行,而點擊、觸碰、拖動這些操作則需要了解其具體信息了。
scene view可以用-hitTest(_: options:)方法來進行點擊的測試。通常的視圖只返回被點擊的子視圖或子層,而Scene Kit則返回一個數組,其中存有每個相交的模型對象,以及從攝像機投向這個測試點的射線。每次點擊測試的結果包含被擊中模型的節點對象,還包含了交點的詳細信息(交點坐標、交點表面法線,交點的紋理坐標)。大多數時候只要知道擊中的第一個節點是哪個就夠了:
if let firstHit = sceneView.hitTest(tapLocation, options: nil)?.first as? SCNHitTestResult { let hitNode = firstHit.node // do something with the node that was hit... }
擴展默認渲染流程
光照和材質的配置比較無腦,因而只能做到這麼多。假如之前有用OpenGL寫好的的著色器(Shader),可以拿來定制材質渲染;如果你只想修改下默認的渲染配置,Scene Kit外露了4個入口用於插入著色器的代碼(GLSL)來修改。Scene Kit在不同入口上提供了對旋轉矩陣、模型數據、樣本貼圖及渲染輸出色值的訪問。
比如,下面的GLSL代碼被用在模型數據的入口點中,可以將模型對象上所有點沿x軸扭曲。通過定義一個函數來創建一個旋轉變換,並將其應用在模型的位置和法線上。另外還自定義了一個統一(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)既可以綁定在模型對象上,也可以綁定在它的材質對象上。這兩個類都完全支持Key-value Coding(KVC),你可以指定任意Key進行賦值。在著色器中聲明名為"twistFactor"的統一變量使得Scene Kit在這個值改變時自動重新綁定uniform,也可以用KVC來實現:
torus.setValue(5.0, forKey: "twistFactor")
使用Keypath的CAAnimation也可以:
let twist = CABasicAnimation(keyPath: "twistFactor") twist.fromValue = 5 twist.toValue = 0 twist.duration = 2.0 torus.addAnimation(twist, forKey: "Twist the torus")
延期著色
哪怕是在OpenGL開發中,一些圖像效果也無法通過一次渲染完成,我們將不同著色器按序列操作,以完成持續處理的目的,稱為延期著色。Scene Kit使用SCNTechnique類來完實現延時著色。它使用字典來創建,其中定義了繪圖步驟、輸入輸出、shader文件以及符號等等。
第一個渲染傳值是Scene Kit的默認渲染,它可以計算出出場景的顏色和景深。如果不想立即計算色值,可以將材質設置成"恆定"的光照模型,或者將場景裡所有光照都設置成環境光。
比如,從Scene Kit渲染流程的第一個渲染傳值以獲取景深,第二個獲取法線,第三個對其執行邊界檢測,還可以沿輪廓也可以沿邊緣描線:
延伸閱讀
想了解更多,可參考2014年WWDC中"使用Scene Kit開發游戲"的視頻,並看看示例代碼。
有關Scene Kit基礎知識,可以參看2013年和2014年"What'New in SceneKit"的視頻。
(本文由CocoaChina翻譯自objc.io,轉載請注明出處)