yoppa.org


Blog

Swift + Playgraoundメモ 3 – SceneKitで3Dライブコーディング!

注意: Swiftのソースは全てXcode 6 Beta 6で確認しています。それ以前のバージョンでの動作は確認していません。また今後のXcodeのバージョンで動く保証もありませんので、ご了承ください。

Generative 3D with Swift Playground from Atsushi Tadokoro on Vimeo.

Swift + Playgraoundメモ 1 – SpriteKitでアニメーション」、「Swift + Playgraoundメモ 2 – SpriteKitで物理シミュレーション」に続く、Swift + Playgroundシリーズ第3弾。今回は、3DCGに挑戦してみた。

2Dのグラフィクスの表示やアニメーションには、SprikeKitをつかったのだけれど、3Dではその代わりにSceneKitというフレームワークを使うらしい。説明を読むと、OpenGLなどと比べると、より上位のレベルで、シーンの中の物体とその動きを記述できるとのこと。早速、Swift+Playgroundの環境で使ってみたい。

まずは、SpriteKitのときと同様に、Viewを定義して、そこにSceneに追加する。3DのSceneには、SCNSceneを使用する。

import SceneKit
import XCPlayground

let width:CGFloat = 400
let height:CGFloat = 400

// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene

ここにまずは背景色と照明(ライティング)の設定をしたい。こんな感じでコードを追加。Playgroundのタイムラインでの表示のためのコードも追加している。

import SceneKit
import XCPlayground

let width:CGFloat = 400
let height:CGFloat = 400

// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene

// 背景の設定
view.backgroundColor = NSColor.blackColor()
// ライティング
view.autoenablesDefaultLighting = true

// タイムラインに表示
XCPShowView("View", view)

次にカメラを配置する。3DCGは、三次元の情報を二次元平面に投影して、あたかも奥行があるように見せているものなので、どこから物体を見るのかという「視点」の設定が必須となる。

SceneKitでは、まずカメラを生成したあと、それをシーンのノードとして登録して追加するという手順を踏むようだ。こんな感じ。

import SceneKit
import XCPlayground

let width:CGFloat = 400
let height:CGFloat = 400

// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))

// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene

// 背景の設定
view.backgroundColor = NSColor.blackColor()

// ライティング
view.autoenablesDefaultLighting = true

// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)

これで、環境の準備ができた。さっそく3Dの物体を表示してみたい。まずは、シンプルに立方体を置いてみる。立体感がわかるよう配置したあと、ちょっと回転させて斜めから見るようにしている。

import SceneKit
import XCPlayground

let width:CGFloat = 400
let height:CGFloat = 400

// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))

// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene

// 背景の設定
view.backgroundColor = NSColor.blackColor()

// ライティング
view.autoenablesDefaultLighting = true

// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)

// 立方体のジオメトリを生成
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.1)

// ノードに追加
var boxNode = SCNNode(geometry: box)

// ちょっと回転
boxNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: CGFloat(M_PI * 0.25));

// ノードをシーンに追加
scene.rootNode.addChildNode(boxNode)

// タイムラインに表示
XCPShowView("View", view)

これで、既にリアルな物体として表示される。マテリアル(質感)はデフォルトでも既に設定されているようだ。

screenshot_241

次にこのデフォルトのマテリアルから独自なものに変更してみる。

import SceneKit
import XCPlayground

let width:CGFloat = 400
let height:CGFloat = 400

// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))

// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene

// 背景の設定
view.backgroundColor = NSColor.blackColor()

// ライティング
view.autoenablesDefaultLighting = true

// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)

// 立方体のジオメトリを生成
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.1)

// ノードに追加
var boxNode = SCNNode(geometry: box)

// ちょっと回転
boxNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: CGFloat(M_PI * 0.25));

// ノードをシーンに追加
scene.rootNode.addChildNode(boxNode)

// マテリアルを変更
box.firstMaterial.diffuse.contents = NSColor.blueColor();
box.firstMaterial.specular.contents = NSColor.whiteColor();

// タイムラインに表示
XCPShowView("View", view)

最後にこの立方体にアニメーションを適用してみる。アニメーションにはCABasicAnimationクラスを使用する。いろいろな動きの定義が可能なのだけれど、ここでは、クルクルと回転させる動きにしてみることに。

import SceneKit
import XCPlayground

let width:CGFloat = 400
let height:CGFloat = 400

// Viewを定義
var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))

// Sceneを生成して、Viewに追加
var scene = SCNScene()
view.scene = scene

// 背景の設定
view.backgroundColor = NSColor.blackColor()

// ライティング
view.autoenablesDefaultLighting = true

// カメラ
var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)

// 立方体のジオメトリを生成
var box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.1)

// ノードに追加
var boxNode = SCNNode(geometry: box)

// ちょっと回転
boxNode.rotation = SCNVector4(x: 1.0, y: 1.0, z: 0.0, w: CGFloat(M_PI * 0.25));

// ノードをシーンに追加
scene.rootNode.addChildNode(boxNode)

// マテリアルを変更
box.firstMaterial.diffuse.contents = NSColor.blueColor();
box.firstMaterial.specular.contents = NSColor.whiteColor();

// アニメーション
var spin = CABasicAnimation(keyPath: "rotation")
spin.toValue = NSValue(SCNVector4:SCNVector4(x: 0.5, y: 1, z: 0.1, w: CGFloat(M_PI) * 2.0))
spin.duration = 5
spin.repeatCount = HUGE
boxNode.addAnimation(spin, forKey: "spin")

// タイムラインに表示
XCPShowView("View", view)

これで、アニメーションつきの3Dグラフィクスプログラミングの完成!! 簡単!!

screenshot_243

最後にすこし応用的なサンプル。3Dの図形を立方体からトーラスに変更し、for文でくりかえして大量に描画する。さらに回転するアニメーションの速度を乱数で変化させて、いろいろな速度で回転するようにしてみた。

import SceneKit
import XCPlayground

let width:CGFloat = 600
let height:CGFloat = 600

var view = SCNView(frame: CGRect(x: 0, y: 0, width: width, height: height))
var scene = SCNScene()
view.scene = scene
view.backgroundColor = NSColor.blackColor()
view.autoenablesDefaultLighting = true

var camera = SCNCamera()
var cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
scene.rootNode.addChildNode(cameraNode)

for var i = 0; i < 40; i++ {
  var torus = SCNTorus(
        ringRadius: CGFloat(arc4random_uniform(150)) / 100.0,
        pipeRadius: 0.05)
    var torusNode = SCNNode(geometry: torus)
    scene.rootNode.addChildNode(torusNode)

    torus.firstMaterial.diffuse.contents = NSColor(
        calibratedHue: CGFloat(arc4random_uniform(100)) / 300.0 + 0.3,
        saturation: 0.5,
        brightness: 1.2,
        alpha: 0.9)
    torus.firstMaterial.specular.contents = NSColor.yellowColor()

    var spin = CABasicAnimation(keyPath: "rotation")
    spin.toValue = NSValue(SCNVector4:SCNVector4(
        x: CGFloat(random()),
        y: CGFloat(random()),
        z: CGFloat(random()),
        w: CGFloat(M_PI) * 2.0))
    spin.duration = NSTimeInterval(arc4random_uniform(10) + 10)
    spin.repeatCount = HUGE
    torusNode.addAnimation(spin, forKey: "spin")
}

XCPShowView("View", view)

たったこれだけのコードで複雑な3D図形が生成された。楽しい!!

screenshot_244