A Simple Introduction Of SceneKit

Setting up for scene kit

open storyboard -> locate the view controller that use to present the 3D content.
select the view go to Identity inspector. change the class to SCNView
go to view controller's source code.
add

import SceneKit

set background. add code in the implementation of viewDidLoad.

let sceneView = self.view as! SCNView
sceneView.backgroundColor = UIColor(white: 0.6, alpha: 1.0)

Creating a Scene Kit scene

creat an SCNScene and tell SCNView to render it.

let scene = SCNScene()
sceneView.scene = scene

In Scene Kit, the 3D content are grouped into scene. Each sene contains a number of nodes, with contain the 3D objects that you want to show.
It\’s great to divide the game into different scene.

Showing a 3D object

Way to render a 3D object
First define a geometry object

let capsule = SCNCapsule(capRadius: 2.5, height: 6)
//it’s a capsule shape. a cylindrical body with both end link to a hemisphere.

then creat a Node with it, set a position. Add it to the scene.

let capsulNode = SCNNode(geometry: capsule)
capsuleNode.position = SCNVector3(x: 0, y: 0, z: 0)
scene.rootNode.addChildNode(capsuleNode)
//add capsuleNode to the sene by calling addChildNode on the scene’s root node

SCNCapsule:

A capsule and its properties
A capsule and its properties

Nodes are invisible that occupy a positon in space. We attach object to the nodes. e.g. geometry(3D Object, camera, light...). Nodes can attach to node, it’s a way to pair objects together. If a node was moved, all the node and object attach to it will move with it.

Working with Scene Kit Cameras

First, create an SCNCamera object

let camera = SCNCamera()
camera.xFov = 45
camera.yFov = 45//xFov, yFov are not available anymore, default value is 60. Indicate the angle of the view in x and y axis

then attach the camera to a node, position it and add to the scene

let cameraNode = SCNNode()
cameraNode.camera = camera//create camera node

cameraNode.position = SCNVector3(x: 0, y: 0, z: 20) //position camera

scene.rootNode.addChildNode(cameraNode)//add to the scene root node

SCNCamera:

Camera coordinate system and projection parameters
Camera coordinate system and projection parameters

Two kinds of camera:

  • perspective: just like normal camera
  • orthographic: object don't get smaller when move away to the object

If you don't need the camera control, change it in the line in setupView(). Otherwise you can use finger to change the viewing angel and zoom-in or out.

scnView.allowsCameraControl = false

Creating lights

First create a light object and attach it to a node.
There’re three kinds of light:

  • Omni light: light from a single point, in all direction
  • Directional lights: light in a single direction, have no positon.(just like sun)
  • Spot light: light from a single potion and direct to a single direction. Angle can be change, so that the light cone will change
  • Ambient light: light that have no position or direction, light from all directions
//adding a ambient light
let ambientLight = SCNLight()
ambientLight.type = SCNLightTypeAmbient
ambientLight.color = UIColor(white: 0.25, alpha: 1.0)

let ambientLightNode = SCNNode()
ambientLightNode.light = ambientLight

scene.rootNode.addChildNode(ambientLightNode)

//adding a point light
let omniLight = SCNLight()
omniLight.type = SCNLightTypeOmni
omniLight.color = UIColor(white: 0.25, alpha: 1.0)

let omniLightNode = SCNNode()
omniLightNode.light = omniLight
omniLightNode.position = SCNVector(x: -5, y: 8, z: 5)

scene.rootNode.addChildNode(omniLightNode)

Animating Objects

To move the object in Scene, we use the animation class from Core Animation.
First define propertie of the animation, then create a animation object.

//indicate the way of the animation: changing the position
let moveUpDownAnimation = CABasicAnimation(keyPath: “position”)

//how it move
moveUpDownAnimation.byValue = 
    NSValue(SCNVector3: SCNVector3(x: 0, y: 3, z: 0))
//NSValue is a container for C or OBJ-C data item.
moveUpDownAnimation.timingFunction = 
    CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
//to add a slow in slow out effect

//animation automatically move back to start point
moveUpDownAnimation.autoReverse = true

//how many times should it repeat
moveUpDownAnimation.repeatCount = Float.infinity

//duration of the animation
moveUpDownAnimation.duration = 2.0

//apply to the node
capsuleNode.addAnimation(moveUpDownAnimation, forKey: “updown”)
//func addAnimation(_ animation: SCNAnimationProtocol, forkey key: String?)
//first parameter is the animation object
//second parameter is a string identifying the animation for later retrieval. can be nil

we can use not only CABasicAnimation but also CAKayframeAnimation to control the animation.
For detail, check the documentation for SCNNode and SCNMaterial, if the property have Animateble then it can be animated.

Working with Text Nodes

To render 3D text on the scene
First create a 3D geometry object, use SCNText class and attach it to a node.

let text = SCNText(string: “Text”, extrusionDepth: 0.2)

//geometry setting
text.font = UIFont.systemFontOfSize(2)
//default: Helvetica 36, font size is Scene Kit units, not screen point
let textNode = SCNNode(geometry: text)
//position relative to capsule
textNode.position = SCNVector3(x: -2, y: 6, z: 0)

//add node to the capsule node(not the scene root node!)
capsuleNode.addChildNode(textNode)//capsuleNode was defined earlier

Customizing Materials

To control the way the object react to light.
First create a material using SCNMaterial class

let greenMaterial = SCNMaterial()
greenMaterial.diffuse.contents = UIColor.greenColor()//diffusion
greenMaterial.specular.contents = UIColor.whiteColor()//reflection
greenMaterial.shininess = 1.0//sharpeness of the hightlight.

//attach to a node
capsule.materials = [greenMaterial]
//capsule is a SCNCapsule object we created ealier

There are lots of element that control the apperence of the surface.
e.g.
diffuse: control the base color of the material
specular: reflection, schiny effect. control the brightness and color of the effect.
emissive: self glowing, shining without light
transparent: area of the transpanrency, amount of transparency
normal: control the "unebenheit"
diffuse and specular can set to be a color or a image, or a CALayer or a file, or a URL, or a Sprite Kit scene or an SKTexture

Textureing Objects

To apply a texture to an object.
First get a texture, e.g. load a file. Then set it to the diffuse component of the material.

//loading a file
let loadedTexture = SKTexture(imageNamed: "Ball")

//set texture
let textureMaterial = SCNMaterial()
textureMaterial.diffuse.contents = loadedTexture

text.materials = [textureMaterial]

//generate noise using Sprite Kit
let noiseTexture = SKTexture(noiseWithSmoothness: 0.25,
    size: CGSize(width: 512, height: 512), grayscale: true)
// noiseWithSmoothness: 0.0 and 1.0. A value of 1.0 generates a smooth surface.
//size: size of the new texture
//grayscale: noise is colorful or gray

Normal Mapping

To make surface looks roughened. It change the way how the light bounce. Makes a simple object that appears to be having more detail. That's to apply a Normal Map to the material.
Source can be a texture, or generated by Sprite Kit.

//first generate texture
let noiseNormalMapTexture = 
    noiseTexture.textureByGeneratingNormalMapWithSmoothness(0.1, contrast: 1.0)
//parameter of textureByGeneratingNormalMapWithSmoothness: smoothness and contrast
//smoothness 0~1: 0 means not smooth at all
//contrast 0~1: 1 means no magnification

//then apply to the normal property
greenMaterial.normal.contents = noiseNormalMapTexture

Constraining Objects

Make the object tied to other objects. So that their movement will be constrained.
First create the object, then create a constrain object and add it to the object.

//create a object
let pointer = SCNPyramid(width: 0.5, height: 0.9, length: 4.0)//geometry
let pointerNode = SCNNode(geometry: pointer)//Node
pointerNode.position = SCNVector3(x: -5, y: 0, z: 0)//set position

scene.rootNode.addChildNode(pointerNode)//add it to scene

// create a constraint object
let lookAtConstraint = SCNLookAtConstraint(target: capsuleNode)// target is always a node
// When enabled, the constraint will try to rotate
// around only a single axis
lookAtConstraint.gimbalLockEnabled = true
pointerNode.constraints = [lookAtConstraint]

SCNPyramid:

A pyramid and its properties
A pyramid and its properties

3 different kinds of constraints:

  • Look At: SCNLookAtConstraint point towards a node
  • Transform: SCNTransformConstraint run a calculation before render the scene. e.g. using a 4x4 transform matrix
  • Inverse kinematics: SCNIKConstraintAn IK constraint moving a chain of nodes toward a target point
    SCNIKConstraint
    SCNIKConstraint

Loading COLLADA file

We can not only create a modell with code, but also by importing a COLLADA file. COLLADA file can be created by other 3D modelling software e.g. Blender.
First load the file, and then reconstruct object from the file.

//loading
let critterDataURL =
    NSBundle.mainBundle().URLForResource("Critter",
    withExtension: "dae")
let critterData = SCNSceneSource(URL: critterDataURL!, options: nil)

// Find the node called 'Critter'; if it exists, add it
let critterNode = 
    critterData?.entryWithIdentifier("Critter",
    withClass: SCNNode.self) as? SCNNode
if critterNode != nil {
    critterNode?.position = SCNVector3(x: 5, y: 0, z: 0)
    scene.rootNode.addChildNode(critterNode!)
}

Using 3D Physics

Adding physical behiavor.
We should provide the shape ( geometry ) with SCNPhysicsShape and the body with SCNPhysicsBody.
Then we add the body to a node.

//create shape, or say a geometry
var critterPhysicsShape: SCNPhysicsShape?
if let geometry = critterNode?.geometry {
    critterPhysicsShape =
        SCNPhysicsShape(geometry: geometry,
            options: nil)
}

//create body, add shape to body
let critterPhysicsBody =
    SCNPhysicsBody(type: SCNPhysicsBodyType.Dynamic,
        shape: critterPhysicsShape)
        
//add body to a node
critterNode?.physicsBody = critterPhysicsBody

Body can be:

  • dynamic: body that can be affected by forces and collisions
  • kinematic: unaffected by forces or collisions but that can cause collisions affecting other bodies
  • ```static` ``: unaffected by forces or collisions and that cannot move

Adding Force and Torque to the object

Add the code after the physics body for geometryNode inside spawnShape

// create random force
let randomX = Float.random(min: -2, max: 2)
let randomY = Float.random(min: 10, max: 18)
let force = SCNVector3(x: randomX, y: randomY , z: 0)

// set the position where where the force apply to 
let position = SCNVector3(x: 0.05, y: 0.05, z: 0.05)

// apply the force to the physics body
geometryNode.physicsBody?.applyForce(force, atPosition: position, impulse: true)

Add the Torque using applyTorque(_: impulse:)

func applyTorque(_ torque: SCNVector4, 
        asImpulse impulse: Bool)

just like applyForce but torque using a SCNVector4, which indicate the rotation axis and the rotation angle, or say, the magnitude of the torque.

Adding a reflective ground

Ground that reflect what's in the scene
We use a SCNFloor object:

let floor = SCNFloor()
let floorNode = SCNNode(geometry: floor)
floorNode.position = SCNVector3(x: 0, y: -5, z: 0)
scene.rootNode.addChildNode(floorNode)

//if you want the ground to be a physical body that object can fall onto
let floorPhysicsBody =
    SCNPhysicsBody(type: SCNPhysicsBodyType.Static,
        shape: SCNPhysicsShape(geometry: floor, options: nil))
floorNode.physicsBody = floorPhysicsBody

Hit-Testing the Scene

Return information of the object being tapped.
Using hitTest function

// Find the object that was tapped
let sceneView = self.view as! SCNView
let hits = sceneView.hitTest(locationToQuery,
    options: nil) as! [SCNHitTestResult]
for hit in hits {
    println("Found a node: \(hit.node)")
}
// locationToQuery is a CGPoint in view-space

This can return lots of nodes. option of hitTestcan be change. For detail check SCNHitTestOption

SceneKit Editor

First new file adding. Right click the folder and click New File.... Template for new file: SceneKit Scne File. Then you are in the SceneKit Editor.
There're 6 area. You will be able to control the node, element, child-parent relationship, properties of the node or object and action. You can see how the scene is like, you can add object from the library. In Node Inspector, you can set your node to suit your need. For different object there's always something different.
In camera inspector, there are some interesting properties to discover:

  • HDR
  • Explosure: control the darkness and brightness of the scene
  • Bloom: control the hazy effect around the bright area
  • Adaption: simulate the effect that human goes from a dark place to a bright place, or goes from a bright place to a dark place.
  • Post processing
    • Vignettin: lightening around the edge of the scene
    • Color Fringe: color mixing
    • Color Grading: overal saturation
  • Motion Blur

Rendering Loop

Frame processing loop
Frame processing loop

For example, you can add spawnShape() function to the renderer sothat every time it update, it will execute spawnShape()

//add a extension to GameViewController
extension GameViewController: SCNSceneRendererDelegate {
  func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
    spawnShape()
  }
}

remember to remove the object as long as it don't need to be appear once again.
add the code to the end of GameViewController class, right below spawnShape()

//set a function that go through all the childNodes to the rootNode, if the potition is out of the screen, then remove it from the parentNode.
func cleanScene() {
  for node in scnScene.rootNode.childNodes {
    if node.presentationNode.position.y < -2 {
      node.removeFromParentNode()
    }
  }
}

then add cleanScene to the renderer(_: updatedAtTime: )
Be aware that if there's nothing on the scree to show, the Scene Kit will enter "paused" state. To prevent this, we have to enable the playing property, by adding code to setupView(), sothat it will be a endless game.

scnView.playing = true

Adding Touch Handling

What we do to a touch event?
1. get touch location
2. convert to view coordinate
3. fire a ray for a hit test(all the things in the normal direction)
here we check if the user touch a node that was labeled "Good"

//this part is in spawnShape()
//label the node according to color
if color == UIColor.blackColor() {
  geometryNode.name = "BAD"
} else {
  geometryNode.name = "GOOD"
}

//this part is following to GameViewController, right below handleTouchFor(_:)
//how the score change according to BAD and GOOD node
func handleTouchFor(node: SCNNode) {
  if node.name == "GOOD" {
    game.score += 1
    node.removeFromParentNode()
  } else if node.name == "BAD" {
    game.lives -= 1
    node.removeFromParentNode()
  }
}

//thispard is following to GameViewController, right below handleTouchFor(_")
//capture the touch and do hit test
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    let location = touch.locationInView(scnView)//catch first touch and transform the location to scnView coordinate
    let hitResults = scnView.hitTest(location, options: nil)
    if hitResults.count > 0 {
        let result = hitResults.first!
        handleTouchFor(result.node)
  }//do hitTest and return the first hit
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耐床,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悼凑,更是在濱河造成了極大的恐慌菠红,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刷后,居然都是意外死亡的畴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門尝胆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丧裁,“玉大人,你說我怎么就攤上這事含衔〖褰浚” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵贪染,是天一觀的道長缓呛。 經(jīng)常有香客問我,道長杭隙,這世上最難降的妖魔是什么哟绊? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮寺渗,結(jié)果婚禮上匿情,老公的妹妹穿的比我還像新娘兰迫。我一直安慰自己信殊,他們只是感情好,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布汁果。 她就那樣靜靜地躺著涡拘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪据德。 梳的紋絲不亂的頭發(fā)上鳄乏,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音棘利,去河邊找鬼橱野。 笑死,一個胖子當著我的面吹牛善玫,可吹牛的內(nèi)容都是我干的水援。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼茅郎,長吁一口氣:“原來是場噩夢啊……” “哼蜗元!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起系冗,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤奕扣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后掌敬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惯豆,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡池磁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了楷兽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片框仔。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拄养,靈堂內(nèi)的尸體忽然破棺而出离斩,到底是詐尸還是另有隱情,我是刑警寧澤瘪匿,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布跛梗,位于F島的核電站,受9級特大地震影響棋弥,放射性物質(zhì)發(fā)生泄漏核偿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一顽染、第九天 我趴在偏房一處隱蔽的房頂上張望漾岳。 院中可真熱鬧,春花似錦粉寞、人聲如沸尼荆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捅儒。三九已至,卻和暖如春振亮,著一層夾襖步出監(jiān)牢的瞬間巧还,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工坊秸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留麸祷,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓褒搔,卻偏偏與公主長得像阶牍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子站超,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容

  • **2014真題Directions:Read the following text. Choose the be...
    又是夜半驚坐起閱讀 9,581評論 0 23
  • 她是我姑姑荸恕,我不知道該怎么形容她,或許是爸爸那一輩都比較胖的緣故死相,她也是一身的贅肉融求。總是雙手插在口袋里算撮,走路一晃一...
    戒子閱讀 506評論 0 0
  • 如果婚姻是一本書生宛,每個人都得學習县昂,就拿我來說吧,我喜歡粘人陷舅,喜歡亂想倒彰,反正女生該有的壞毛病我都有±痴觯可是自從有了婚姻...
    姁姝婷樂哈哈閱讀 152評論 0 0
  • 姓名:劉小瓊 公司:寧波大發(fā)化纖有限公司 期數(shù):第235期六項精進 日精進打卡第187天 [知~學習] 六項精進 ...
    劉小瓊282閱讀 151評論 0 0