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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,427评论 0 23
  • 她是我姑姑,我不知道该怎么形容她,或许是爸爸那一辈都比较胖的缘故,她也是一身的赘肉。总是双手插在口袋里,走路一晃一...
    戒子阅读 498评论 0 0
  • 如果婚姻是一本书,每个人都得学习,就拿我来说吧,我喜欢粘人,喜欢乱想,反正女生该有的坏毛病我都有。可是自从有了婚姻...
    姁姝婷乐哈哈阅读 147评论 0 0
  • 姓名:刘小琼 公司:宁波大发化纤有限公司 期数:第235期六项精进 日精进打卡第187天 [知~学习] 六项精进 ...
    刘小琼282阅读 151评论 0 0