概述
SceneKit是用來(lái)構(gòu)建3D場(chǎng)景的框架,且可以與Core Animation和SpriteKit無(wú)縫交互盼理。在SceneKit中可以直接引入COLLADA行業(yè)標(biāo)準(zhǔn)文件制作好的3D模型或場(chǎng)景犁苏。
與SpriteKit一樣纹蝴,SceneKit通過(guò)場(chǎng)景(SCNScene)來(lái)顯示物體华蜒,場(chǎng)景包涵在SCNView官册。場(chǎng)景內(nèi)同樣是以節(jié)點(diǎn)的結(jié)構(gòu)來(lái)呈現(xiàn)物體宾茂。場(chǎng)景里可以包含這些類型的項(xiàng)目:
- 幾何體 代碼建立的3D對(duì)象或從文件加載的3D模型瓷马,在它們上可以附加不同的材料來(lái)達(dá)成控制顏色,紋理跨晴,反光等效果
- 照相機(jī) 這是你觀察這個(gè)場(chǎng)景的入口欧聘,可以設(shè)置位置和視角
- 光源 有各種光源后面會(huì)提到
- 物理實(shí)體 受物理特效控制的各種有實(shí)體的物體,也有許多種
使用SceneKit的方法不止有在SCNView內(nèi)部來(lái)使用端盆。還可以在Core Animation層的層次結(jié)構(gòu)中使用SCNLayer怀骤。使用SCNRender來(lái)渲染你自己的OpenGL渲染器。
SceneKit可以同時(shí)在IOS和OS X下工作
添加場(chǎng)景
首先要做的當(dāng)然是添加場(chǎng)景
//獲取SCNView
let scnView = self.view as! SCNView
//我自己的場(chǎng)景
let myScenes = myScene()
//將SCNView的場(chǎng)景設(shè)置為我的場(chǎng)景
scnView.scene = myScenes
添加照相機(jī)
照相機(jī)是你觀察你構(gòu)建的3D場(chǎng)景的眼睛
//創(chuàng)建視角焕妙,可以說(shuō)是整個(gè)場(chǎng)景的入口蒋伦,沒(méi)有視角沒(méi)法觀察一個(gè)3D場(chǎng)景
let myCamera = SCNCamera()
myCamera.xFov = 45
myCamera.yFov = 45
let myCameraNode = SCNNode()
myCameraNode.camera = myCamera
myCameraNode.position = SCNVector3(0, 0, 20)
//向場(chǎng)景添加節(jié)點(diǎn)與SpriteKit有些不一樣
myScenes.rootNode.addChildNode(myCameraNode)
添加3D對(duì)象
//創(chuàng)建膠囊
let capsule = SCNCapsule(capRadius: 2.5, height: 10)
//SCNCapsule是SCNGeomery的一個(gè)子類,通過(guò)這個(gè)類可以創(chuàng)建更多的形狀
let capsuleNode = SCNNode(geometry: capsule)
capsuleNode.position = SCNVector3(-15, -2.8, 0)//節(jié)點(diǎn)的默認(rèn)位置是0焚鹊,0痕届,0
capsuleNode.name = "myCapsule"
myScenes.rootNode.addChildNode(capsuleNode)
添加光源
光源有許多種:
- 環(huán)境光源,它在整個(gè)場(chǎng)景內(nèi)投射均勻光
- 泛光源,這是用的最多的研叫,就是點(diǎn)光源锤窑,向各個(gè)方向投射光
- 平行光源,在單個(gè)方向投射光
- 聚光源嚷炉,在給定方向從單個(gè)位置投射光
//添加環(huán)境光源
let ambientLight = SCNLight()
//光源的類型渊啰,這里是環(huán)境光源
ambientLight.type = SCNLightTypeAmbient
ambientLight.color = UIColor(white: 0.25, alpha: 1.0)
let myAmbientLightNode = SCNNode()
myAmbientLightNode.light = ambientLight
myScenes.rootNode.addChildNode(myAmbientLightNode)
//添加泛光源
let omniLight = SCNLight()
omniLight.type = SCNLightTypeOmni
omniLight.color = UIColor(red: 0, green: 0, blue: 1, alpha: 1)
let omniLightNode = SCNNode()
omniLightNode.light = omniLight
omniLightNode.position = SCNVector3(-10, 8, 5)
myScenes.rootNode.addChildNode(omniLightNode)
添加動(dòng)畫
//向膠囊節(jié)點(diǎn)添加動(dòng)畫
let moveUpDownAnimation = CABasicAnimation(keyPath: "position")//這里的這個(gè)keyPath很重要,不是隨便寫一個(gè)就好的
moveUpDownAnimation.byValue = NSValue(SCNVector3: SCNVector3(30, 0, 0))//移動(dòng)的坐標(biāo)
moveUpDownAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)//移動(dòng)速度的曲線
moveUpDownAnimation.autoreverses = true//是否自動(dòng)返回
moveUpDownAnimation.repeatCount = Float.infinity//重復(fù)次數(shù)
moveUpDownAnimation.duration = 10.0//持續(xù)時(shí)間
capsuleNode.addAnimation(moveUpDownAnimation, forKey: "updown")//這里的這個(gè)keyPath貌似隨便寫就可以
//膠囊節(jié)點(diǎn)的子節(jié)點(diǎn)申屹,一個(gè)文本
let text = SCNText(string: "BaLaLaLa", extrusionDepth: 1)
text.font = UIFont.systemFontOfSize(2)
let textNode = SCNNode(geometry: text)
textNode.position = SCNVector3(-5, 6, 0)
capsuleNode.addChildNode(textNode)
//在一個(gè)節(jié)點(diǎn)上添加多個(gè)動(dòng)畫結(jié)果會(huì)復(fù)合虽抄,即便是繼承自父節(jié)點(diǎn)的動(dòng)畫也同樣一起復(fù)合
let rotate = CABasicAnimation(keyPath: "eulerAngles")
rotate.byValue = NSValue(SCNVector3: SCNVector3(Float(0), Float(M_PI * 2), Float(0)))
rotate.repeatCount = Float.infinity
rotate.duration = 5.0
textNode.addAnimation(rotate, forKey: "rotation")
使用材料
材料可以用來(lái)改變物體表面的樣子,材料使用SCNMaterial類來(lái)表示独柑,材料對(duì)象擁有許多許多屬性迈窟,比如:
- diffuse(材料的基本顏色,紋理等)
- specular(材料的亮度以及該如何反射光)
- emissive(材料發(fā)光時(shí)的樣子)
- normal(又稱為法向忌栅,設(shè)置材料表面更多的細(xì)節(jié))
每個(gè)屬性都擁有contents屬性车酣,給這個(gè)屬性設(shè)置不同的內(nèi)容來(lái)設(shè)置這些屬性的樣子:
- 顏色
- 圖像(NSImage等)
- SpriteKit場(chǎng)景
- SpriteKit紋理
//使用材料
let redMetallicMateril = SCNMaterial()
//contents設(shè)置為顏色
redMetallicMateril.diffuse.contents = UIColor.blueColor()
redMetallicMateril.specular.contents = UIColor.whiteColor()
redMetallicMateril.shininess = 1.0
//一個(gè)物體的材料是不唯一的,故傳進(jìn)去一個(gè)數(shù)組
capsule.materials = [redMetallicMateril]
let noiseTexture = SKTexture(noiseWithSmoothness: 0.25, size: CGSize(width: 512, height: 512), grayscale: true)
let noiseMaterial = SCNMaterial()
//contents設(shè)置為SpriteKit紋理
noiseMaterial.diffuse.contents = noiseTexture
text.materials = [noiseMaterial]
//法線貼圖
let noiseNormalMapTexture = noiseTexture.textureByGeneratingNormalMapWithSmoothness(1, contrast: 1.0)
redMetallicMateril.normal.contents = noiseNormalMapTexture
命中檢測(cè)
這個(gè)說(shuō)白了就是監(jiān)測(cè)你點(diǎn)擊了哪個(gè)物體索绪,首先當(dāng)然要添加手勢(shì)湖员。
//命中檢測(cè)
let tapRecognizer = UITapGestureRecognizer(target: self, action: "tapped:")
let longTapRecoginizer = UILongPressGestureRecognizer(target: self, action: "longTapped:")
scnView.addGestureRecognizer(tapRecognizer)
scnView.addGestureRecognizer(longTapRecoginizer)
scnView.userInteractionEnabled = true
接下來(lái)添加手勢(shì)對(duì)應(yīng)的執(zhí)行函數(shù)
func tapped(tapRecognize: UIGestureRecognizer){
if tapRecognize.state == UIGestureRecognizerState.Ended {
let scnView = self.view as! SCNView
//檢測(cè)點(diǎn)擊到哪個(gè),返回被點(diǎn)到的物體瑞驱,這里返回的數(shù)組里包含了你點(diǎn)擊的點(diǎn)順著屏幕法線穿過(guò)的所有的物體
let hits = scnView.hitTest(tapRecognize.locationInView(tapRecognize.view), options: nil) as [SCNHitTestResult]
for hit in hits {
if let theMaterial = hit.node.geometry?.materials[0] {
let hightLightAnimation = CABasicAnimation(keyPath: "contents")
hightLightAnimation.fromValue = UIColor.blackColor()
hightLightAnimation.toValue = UIColor.yellowColor()
hightLightAnimation.autoreverses = true
hightLightAnimation.repeatCount = 2
hightLightAnimation.duration = 1
theMaterial.emission.addAnimation(hightLightAnimation, forKey: "heightLight")
}
}
}
}
為節(jié)點(diǎn)添加約束
//添加指向膠囊節(jié)點(diǎn)這個(gè)約束
let lookAtConstraint = SCNLookAtConstraint(target: capsuleNode)
//使其只圍繞一個(gè)軸轉(zhuǎn)動(dòng)
lookAtConstraint.gimbalLockEnabled = true
pointerNode.constraints = [lookAtConstraint]
從COLLADA文件中加載文件
//加載一個(gè)已經(jīng)建好的3D模型或場(chǎng)景娘摔,會(huì)是一個(gè)COLLADA文件,后綴名為.dae
let critterURL = NSBundle.mainBundle().URLForResource("Critter", withExtension: "dae")
let critterData = SCNSceneSource(URL: critterURL!, options: nil)
let critterNode = critterData?.entryWithIdentifier("Critter", withClass: SCNNode.self)
if (critterNode != nil) {
critterNode!.position = SCNVector3(0, 0, -10)
critterNode?.name = "Critter"
myScenes.rootNode.addChildNode(critterNode!)
}
添加物理仿真
物理仿真和SpriteKit中的很像唤反,物理實(shí)體的類別有一些不同:
- 靜態(tài)實(shí)體凳寺,從不移動(dòng),不受重力影響彤侍,但會(huì)碰撞
- 運(yùn)動(dòng)學(xué)實(shí)體不受物理力的作用肠缨,但是它是有實(shí)體的,在動(dòng)畫中碰到其他物體會(huì)將它們推開盏阶。
- 動(dòng)態(tài)實(shí)體受物理力及碰撞影響
神奇的是SceneKit為我們提供了地板類晒奕,它的幾何形狀就是一個(gè)地板
//像節(jié)點(diǎn)添加物理特性
var critterPhysicsShape: SCNPhysicsShape?
if let geometry = critterNode?.geometry {
critterPhysicsShape = SCNPhysicsShape(geometry: geometry, options: nil)
}
let critterPhysicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Dynamic, shape: critterPhysicsShape)
critterPhysicsBody.mass = 1
critterNode?.physicsBody = critterPhysicsBody
//添加一個(gè)地板
let floor = SCNFloor()
let floorNode = SCNNode(geometry: floor)
floorNode.position = SCNVector3(0, -10, 0)
myScenes.rootNode.addChildNode(floorNode)
let floorPhysicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Static, shape: SCNPhysicsShape(geometry: floor, options: nil))
floorNode.physicsBody = floorPhysicsBody