Apple Engine

Apple, iPhone, iOS, その周辺のことについて

iOS で SceneKit を試す(Swift 3) その82 - キューブマップを設定する

立方体6面の内側に指定された画像を内側に貼り付ける360度の背景画像をキューブマップと呼び、
SCNMaterial の親玉である SCNMaterialProperty から使用する。
(内部的には Model I/O の機能だったはず)

SceneKit ではシーンの background や lightingEnvironment、または Blinn や Phong マテリアルの Reflective プロパティ で使用可能。 lightingEnvironment は Physically Based のシェーダーで効果を表す。

 

シーンの background や lightingEnvironment にテクスチャを適応したもの。

f:id:x67x6fx74x6f:20170828192744p:plain

 

キューブマップ使用できる画像配置と画像サイズ

ドキュメントには +X, -X, +Y, -Y, +Z, -Z で配置させると書いているが、ワールド座標軸的には +X, -X, +Y, -Y, -Z, +Z にしないと Z 軸に貼られる画像がおかしくなる。

 

以下4つの画像はどれを適応しても同じにため、状況によって最適なものを選べば良いと思われる。

 

Vertical strip (縦並びの1枚画像)

高さは幅の6倍の画像。
Vertical strip が SceneKit で使用する全てのキューブマップ処理の中で一番パフォーマンスが良いとのこと。

f:id:x67x6fx74x6f:20170828191521p:plain

 

Horizontal strip (横並びの1枚画像)

幅は高さの6倍の画像。

f:id:x67x6fx74x6f:20170828191509p:plain

 

Spherical projection (1枚のパノラマ画像)

幅は高さの2倍の画像。

f:id:x67x6fx74x6f:20170828191959p:plain

 

Array of six images (配列に6枚の画像を入れたもの)

幅と高さが同じの画像。

f:id:x67x6fx74x6f:20170828192023p:plain f:id:x67x6fx74x6f:20170828192020p:plain f:id:x67x6fx74x6f:20170828192031p:plain f:id:x67x6fx74x6f:20170828192026p:plain f:id:x67x6fx74x6f:20170828192038p:plain f:id:x67x6fx74x6f:20170828192034p:plain

 

コードで書く

Array of six images がコードのみでしか実現できないので、 以下のもの

scene.background.contents = [
    UIImage(named:"x_p.png"),
    UIImage(named:"x_m.png"),
    UIImage(named:"y_p.png"),
    UIImage(named:"y_m.png"),
    UIImage(named:"z_m.png"),
    UIImage(named:"z_p.png"),
]

 

参照元

contents - SCNMaterialProperty | Apple Developer Documentation

 

今回はこれまで。

iOS で SceneKit を試す(Swift 3) その81 - シーンに音楽や効果音をつける。

正直なところ、Core Audio など iOS 標準機能が使用できるので、 SceneKit のオーディオ再生機能を使用する必要はないけどご紹介。

SceneKit でのオーディオ再生の特徴としては、VR や HoloLens と同様に 3D 空間の位置に対して音源を再生(ミキシング)させることができる。

 

SceneKit では音を鳴らす方法は2つ。

  • ノードの addAudioPlayer に SCNAudioSource (AVAudioNode) を設定した SCNAudioPlayer で再生する
  • SCNAction の playAudio で SCNAudioSource を指定して再生する

 

使用するクラス

  • SCNAudioSource
  • SCNAudioPlayer

 

SCNAudioSource

プロパティ名 デフォルト値 説明
init?(named: String) なし オーディオファイル名の指定
init?(fileNamed: String) なし main bundle からのオーディオファイル名の指定
init?(url: URL) なし URL からのオーディオファイル名の指定
isPositional true オーディオの音量やリバーブなど距離によって自動的に変化させる。false にすると場所に関係なく一定で再生される
load() なし 事前読み込み用メソッド。shouldStream プロパティの値が true の場合は無効
volume 1.0 音量を設定する
rate 1.0 再生スピード。値を大きくすると再生速度が速くなる
reverbBlend 0.0 リバーブ (お風呂場のような音響効果)を付加する
loops false 繰り返し再生するか否か。デフォルトは false で1回のみの再生
shouldStream false true の場合はソースファイルから直接読み込み、オーディオバッファデータにロードを行わない。

 

SCNAudioPlayer

プロパティ名 説明
init(source: SCNAudioSource) SCNAudioSource を設定し初期化する
init(avAudioNode: AVAudioNode) AVAudioNode を設定し初期化する
audioSource SCNAudioSource を設定する
audioNode AVAudioNode を設定する
willStartPlayback SCNAudioPlayer 再生前に呼ばれる。ループの場合は最初に戻った際毎回呼ばれるので注意
didFinishPlayback 再生が止まった時呼ばれる

 

コードを書いてみる

いつも通り、Xcode の Game テンプレートで SceneKit 選択して、GameViewController.swift を開き、viewDidLoad() の下のところに以下のコードを書く。

あと、適当なオーディオファイルをプロジェクトに追加して、ファイル名を変更すること。

m4a ファイルなどインポート時にターゲットのチェックが外れるものがある。ターゲットが外れていると設定していてもファイルが見つからず音が鳴らないため注意。

let audioNode = SCNNode()
audioNode.name = "audioNode"
audioNode.position = SCNVector3(0,0,0)
scene.rootNode.addChildNode(audioNode)

let audioSouce = SCNAudioSource(named: "audio.wav")
audioSouce?.loops = true

let audioPlayer = SCNAudioPlayer(source: audioSouce!)
audioNode.addAudioPlayer(audioPlayer)

audioPlayer.willStartPlayback = {
    print("willStartPlayback")
}

audioPlayer.didFinishPlayback = {
    print("didFinishPlayback")
}

 

画面タップで音を止める

いつも通り、func handleTap(_ gestureRecognize: UIGestureRecognizer) の中身を変える。

func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    let scnView = self.view as! SCNView
        
    let audio = scnView.scene?.rootNode.childNode(withName: "audioNode", recursively: true)!
    
    audio?.removeAllAudioPlayers()
}

 

シーンから名前をつけた audioNode のノードを探し出し、全ての SCNAudioPlayer を消す。

今回、設定していないが SCNAudioPlayer は複数設定でき、個別で SCNAudioPlayer を消すことができる。

 

SCNAction で設定する

使用するのは playAudio。

waitForCompletion が true の場合は再生持続時間が SCNAction の長さになる。
false の場合は再生後すぐ完了され、他のアクションがあれば再生中でも移行される。

let audioSouce = SCNAudioSource(named: "a.wav")

node.runAction(SCNAction.playAudio(audioSouce!, waitForCompletion: true))

 

Action なので、他の Action との併用、
順番に行う sequence や全て同時に実行する group が使用できる。

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その80 - ジオメトリにフィルター効果(Core Image Filter)をつける

SceneKit ではジオメトリなどノードに Core Image Filter を使用してエフェクトをつけることができ、Metal Shader で行うより簡単にできる。

多分、他のゲームエンジンではこれほど簡単にエフェクトをかけるのは難しいと思われる。

とりあえず、試した感じだと、Metal Shader の方がパフォーマンスが良さそう。

 

コードを書いてみる

いつも通り、Xcode で Game のテンプレートで SceneKit を選択。
ship の下らへんに以下のコードを書いてみる。

let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!

let filter = CIFilter.init(name: "CIPixellate")
ship.filters = [filter!]

 

ビルドすると宇宙船にモザイクがかかる。

f:id:x67x6fx74x6f:20170826003744p:plain

 

コードを見るとわかるように SCNNode のプロパティ filters に CIFilter を入れるだけ。
今回はパラメーターは入れていないが設定可能かつ、複数のフィルターを適応可能。

 

背景にジオメトリを置くと手前だけフィルターがかかっているのがわかる。

f:id:x67x6fx74x6f:20170826003926p:plain

 

CICMYKHalftone を適応するとカラー印刷の網点のような効果が出たりする。

f:id:x67x6fx74x6f:20170826004250p:plain

 

Core Image Filter の一覧。
2つ画像が必要なものやジェネレーターなどはあるが、ある程度のものは使用できる。

developer.apple.com

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その79 - 画面操作などからオブジェクトを探し出すヒットテストについて

今回は、特定のオブジェクトを SCNView から探し出すヒットテストをみていく。

Xcode の Game テンプレートではレンダリングされた画像から調べ、
宇宙船をタップするとマテリアルの色が赤く変わるアニメーションが実装されている。
(ドキュメントではレンダリングされた画像と書いてあるが、カメラからレイを飛ばして調べているのだと思われる)

ヒットテストで検索が終わり、ぶつかった対象が見つかると、ノードの情報などを持った SCNHitTestResult を返す。

 

SceneKit で用意されているヒットテスト

ヒットテストの検索を実行できるメソッドは3つ。 レンダリング画像 (SCNSceneRenderer)、SCNNode、物理シミュレーションを使用した PhysicsWorld 内で調べることができる。

  • hitTest(_:options :)
  • hitTestWithSegment(from:to:options :)
  • rayTestWithSegment(from:to:options :)

 

hitTest

シーンをレンダリングした画像内でタップされたポイントに対応するオブジェクトを検索する。 画面操作からオブジェクトを探す場合など。

 

hitTestWithSegment

指定された2点間の線分を交差するオブジェクトのノードのチルドノード内を検索する。 キャラクタがある線を超えた時など。

 

rayTestWithSegment

PhysicsWorld 内で2点間の線分を交差する PhysicsBody を検索する。 視線のようなものができるため、敵キャラの領域にプレイヤーが侵入した際の処理など。

 

ヒットテストのオプションとは?

オブジェクトを調べる際に、チルドノードを無視するなど、 どのように調べるかその方法を決める。

hitTest、hitTestWithSegment と rayTestWithSegment では設定する値が異なるので注意。

 

hitTest、hitTestWithSegment

rootNode 以外の値は Bool を含む NSNumber。

オプション名 デフォルト値 説明
backFaceCulling true カメラに向いてない面を無視する。ジオメトリの裏面など
boundingBoxOnly false バウンディングボックスのみでオブジェクトを検索。検索範囲が荒くなるがパフォーマンスが向上する
categoryBitMask categoryBitMask に適合するものを検索する。デフォルト値は全て適応する
clipToZRange true カメラの zNear と zFardistances の間のオブジェクトのみを検索
firstFoundOnly false 見つかった最初のオブジェクトのみを返す。一番近くにあるものではないので注意
ignoreChildNodes false チルトノードを無視する
ignoreHiddenNodes true 表示していないノードを無視する。カメラからジオメトリの重なりで見えなくなっているものではなく、ノードの hidden のプロパティを ture しているもの
rootNode ルートノード及び設定したノード 検索開始するルートのノードを決める。渡す値は SCNNode。hitTestWithSegment は設定したノードが検索対象になる
sortResults true ヒットテストの結果をソートする。false の場合は結果を任意の順序で返される

 

SCNPhysicsWorld.TestOption

オプション名 デフォルト値 説明
backfaceCulling false 裏側の衝突対象にするか否か。
collisionBitMask categoriesBitMask に適合するものだけ検索する。デフォルト値は全て適応する
searchMode any どのように検索するかを決める。パラメーターは以下参照

 

SearchMode
  • all - パラメータに一致するすべてを返す
  • any - 位置に関係なく最初のものを返す
  • closest - 最も近いものを返す

 

ヒットテスト後 SCNHitTestResult が返す値

node

検索に適合したジオメトリのノードを返す。

 

geometryIndex

SCNGeometryElement のインデックスを返す。
ノード内のジオメトリ要素が複数のない場合は初めの 0 を返す。

チルドノードではないので注意。

 

faceIndex

ジオメトリの面のインデックスを返す。

 

localCoordinates

検索時にヒットした場所のローカル座標を返す。

 

worldCoordinates

検索時にヒットした場所のワールド座標を返す。

 

localNormal

検索時にヒットした面の法線ベクトルをローカル座標で返す。

 

worldNormal

検索時にヒットした面の法線ベクトルをワールド座標で返す。

 

modelTransform

検索時にヒットしたノードのワールド座標を行列 (SCNMatrix4) で返す。

 

textureCoordinates(withMappingChannel:)

テクスチャマッピングの UV 座標を返す。
UV を使用している チャネル を指定する必要があるので注意。

例えば銃を撃たれた際、表面に銃痕をつけるなどで使用する。

 

Game テンプレートの hitTest はどのように動いているのか

Xcode の SceneKit の Game テンプレートは以下のような流れ。

GameViewController.swift の viewDidLoad() で、UITapGestureRecognizer からタップのジェスチャーを呼び、SCNView がタップされるとアクションから handleTap(_:) が呼ばれる

// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)

 

handleTap では GestureRecognize の location から画面の位置を渡し、SCNView から hitTest の関数を読んでいる。

func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    // retrieve the SCNView
    let scnView = self.view as! SCNView
    
    // check what nodes are tapped
    let p = gestureRecognize.location(in: scnView)
    let hitResults = scnView.hitTest(p, options: [:])
       ・
       ・
       ・
}

 

検索が成功すると hitResults に SCNHitTestResult が配列で渡されるので、最初のものを呼び出して、ノードを取得し、マテリアル情報を取得

       ・
       ・
       ・
    if hitResults.count > 0 {
            // retrieved the first clicked object
            let result: AnyObject = hitResults[0]
            
            // get its material
            let material = result.node!.geometry!.firstMaterial!
            
       ・
       ・
       ・

SCNTransaction でマテリアルの色を変更して、completionBlock で色を戻しているいる。

       ・
       ・
       ・
            // highlight it
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 0.5
            
            // on completion - unhighlight
            SCNTransaction.completionBlock = {
                SCNTransaction.begin()
                SCNTransaction.animationDuration = 0.5
                
                material.emission.contents = UIColor.black
                
                SCNTransaction.commit()
            }
            
            material.emission.contents = UIColor.red
            
            SCNTransaction.commit()
        }
    }

 

SCNHitTestResult の他のパラメータを見てみる

Game テンプレートを使用して見ていく。
宇宙船が回転しているとタップしづらいので GameViewController.swift の 以下の部分(44行目あたり)を消す。

// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!

// animate the 3d object
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

 

func handleTap(_ gestureRecognize: UIGestureRecognizer) { 〜 の let material = result.node!.geometry!.firstMaterial! の下に以下のものを書くと、ビルド後に宇宙船をタップすると、デバッガーコンソール、情報が出力される。

// get its material
let material = result.node!.geometry!.firstMaterial!

print("\n----")
print("name: \(String(describing: result.node!.name))")
print("geometryIndex: \(result.geometryIndex)")
print("faceIndex: \(result.faceIndex)")
print("localCoordinates: \(result.localCoordinates)")
print("worldCoordinates: \(result.worldCoordinates)")
print("localNormal: \(result.localNormal)")
print("worldNormal: \(result.worldNormal)")
print("modelTransform: \(result.modelTransform)")
print("textureCoordinates: \(result.textureCoordinates(withMappingChannel:0))")

 

試しに SCNHitTestResult のパラメーターを使ってみる

先ほどの print 文の下に以下のものを書いて実行すると、宇宙船をタップすると SCNSphere が付着する。
とりあえず、今回はワールド座標に設置していて、球の法線に合わせて向きは変えてない。

 

// 球の半径
let radius:Float = 0.2

// 球のノード作成
let node = SCNNode(geometry: SCNSphere(radius: CGFloat(radius)))

// 球のマテリアルの色を設定
node.geometry?.firstMaterial?.diffuse.contents =
    UIColor(red: 1.0, green: 0.0, blue: 0.501960784313725, alpha: 1.0)

// result.worldCoordinates でジオメトリをタップした場所を取得し SCNMatrix4 へ
let pos = SCNMatrix4MakeTranslation(
    result.worldCoordinates.x,
    result.worldCoordinates.y,
    result.worldCoordinates.z
)

// サーフェイスの法線から球の半径分移動したベクトルを SCNMatrix4 へ
let normal = SCNMatrix4MakeTranslation(
    result.worldNormal.x * radius,
    result.worldNormal.y * radius,
    result.worldNormal.z * radius
)

// 球のピボットが中心にあるため、ジオメトリの場所に半径分移動させている
node.transform = SCNMatrix4Mult(pos, normal)

scnView.scene?.rootNode.addChildNode(node)

f:id:x67x6fx74x6f:20170824192211g:plain

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その78 - パーティクルシステムのパラメーターをみてみる

パーティクルシステムのパラメーターをみてゆく。

ちなみに、画像を設定しないと Scene Editor 上で、何も表示されない場合があるので注意。

 

Scene Editor の Attributes Inspector での表示

f:id:x67x6fx74x6f:20170823201446p:plain

 

Δ=0 となっているものは、コードでは Variation と呼ばれているもの。

ここで設定した値は、時間に応じてその値の幅をプラスマイナスの値でランダムに加算する。

Image Size を 5、Birth rate の Variation 8 にした場合、 Variation が ±4 されるためフレーム毎 1〜9 の値で生成される。

Variation のデフォルト値は 0。

 

Emitter

f:id:x67x6fx74x6f:20170824100235p:plain

 

Birth rate (birthRate / birthRateVariation)

emissionDuration の設定時間中にパーティクルをどのぐらい発生させるかを決める値。
デフォルト値は 100。Variation あり。

birthRateVariation を指定しない場合で、birthRate を 0 にするとパーティクルが放出されなくなるので注意。

 

Warmup duration (warmupDuration)

パーティクルが生成開始される時間。
デフォルト値は 0。

例えば 10 に設定するとパーティクルアニメーション再生 10 後から表示する。 雨とか雪とか降り始めではなく、降っている状態から、表示するなど。

 

Location (birthLocation)

パーティクルを放出する場所の指定する。
ジオメトリの表面か、ジオメトリの体積となる部分か、頂点かを決める。

デフォルト値は surface で表面から放出される。

 

Emission space

Scene Editor のみ。
World space、Local space が設定でき、エミッターの位置をワールドかローカル座標で決める。

デフォルト値は World space。

 

Direction mode (birthDirection)

パーティクルを放出する方向。
コードで設定する場合、emitterShape が設定されていないと動作しないものがある。

設定は一定方向に放出する constant、emitterShape で設定されたジオメトリの表面を使用する surfaceNormal、 ランダム方向から放出する random。

デフォルト値は constant。 Scene Editor では random。

 

Spreading angle (spreadingAngl)

birthDirection を constant か surfaceNormal に設定した場合、個々のパーティクルを放射する角度を設定することがで、 値を設定するとその角度ないでラインダムな方向で放出される。

デフォルト値は 0

random に設定している場合、この設定は無視される。

 

Direction (emittingDirection)

birthDirection を constant に設定した場合は、パーティクルを放射する方向を設定することができる。

デフォルト値は (x:0.0, y:0.0, z:1.0)
Scene Editor では (x:0.0, y:1.0, z:0.0)

 

Initial angle (particleAngle / particleAngleVariation)

放出するパーティクル画像の回転角度。
Variation あり。

デフォルト値は 0 で画像は回転されずに放出される。

 

Shape (emitterShape)

放出するパーティクルのエミッターの形状をジオメトリで指定する。

コードの場合は、前回行ったように SCNGeometry として代入する。

 

Scene Editor での値は以下のもの。

パラメーター名 設定値 説明
Box width, height, lenght SCNBox のジオメトリを使用する
Cylinder radius, height SCNCylinder のジオメトリを使用する
Plane width, height SCNPlane のジオメトリを使用する
Point なし 1つの頂点から放出する。デフォルト値
Sphere radius SCNSphere のジオメトリを使用する
Other 不明 設定不能。やり方があったら教えて欲しい

 

Simulation

f:id:x67x6fx74x6f:20170824100312p:plain

 

Life Span (particleLifeSpan / particleLifeSpanVariation)

各パーティクルが生成され、シーンから削除されるまでの表示時間を設定。
Variation あり。

デフォルト値は 1.0。

 

Linear velocity (particleVelocity / particleVelocityVariation)

各パーティクルの生成時の速度。
Variation あり。

デフォルト値は 0.0。

 

Angular velocity (particleAngularVelocity / particleAngularVelocityVariation)

各パーティクルの回転角度を決める。
Variation あり。

orientationMode で回転方法変更可能。

 

Acceleration (acceleration)

加速度ベクトルを付加し簡易的な物理シミュレーションを施す。
単位は毎秒/秒。

デフォルト値は (x:0.0, y:0.0, z:0.0)

複雑な物理シミュレーションを行いたい場合は Affected by gravity、または Affected by physics fields をオン。
もしくは、コードで isAffectedByGravity、または isAffectedByPhysicsFields、を true にする。

 

Speed factor (speedFactor)

アニメーションスピードを変更する。

デフォルト値は 1。

パラメーターは乗数となっているため 2 にするとアニメーションスピードは倍になる。

 

Stretch factor (stretchFactor)

パーティクルの画像を動きの方向によって引き伸ばす設定。

デフォルト値は 0.0 値。

  

Image

f:id:x67x6fx74x6f:20170824100343p:plain

 

Image (particleImage)

パーティクル画像を設定する。

 

同サイズで左〜右、上〜下に並べた画像をつくりこちらに適応。
Image Sequence で設定をすることで、アニメーションするパーティクルを作成することができる。

f:id:x67x6fx74x6f:20170823201916p:plain

particleImage - SCNParticleSystem | Apple Developer Documentation

 

Color (particleColor)

パーティクル画像に色をつける。

Animate Color

Scene Editor のみの値。

チェックをオンにするとパーティクルが発生して消滅する間の色を設定しそれに沿ってアニメーションさせる。

f:id:x67x6fx74x6f:20170823202815p:plain

 

Color variation (particleColorVariation)

指定した色の間でランダムに変化させる。

SCNVector4 となってをり HSB(HSV) 色設定と アルファとなっている。 よって、4つのパラメーターは色相、彩度、明度、アルファ値となる。

 

Size (particleSize / particleSizeVariation)

パーティクル画像の大きさを変更できる。 Variation あり。

デフォルト値は 1.0。

 

Custom Animation

Scene Editor のみの機能。

歯車のボタンをクリックすることで消滅するまでのアニメーションタイミングを設定できる。

f:id:x67x6fx74x6f:20170823202652p:plain    

Image Sequence

f:id:x67x6fx74x6f:20170824100418p:plain

 

Initial frame (imageSequenceInitialFrame / imageSequenceInitialFrameVariation)

連続した画像のアニメーションをどのフレームから始めるかを決める。
Variation あり。

デフォルト値は 0 で画像左上から表示する。

 

Frame rate (imageSequenceFrameRate / imageSequenceFrameRateVariation)

連続した画像のアニメーションのフレームレートを決める。
Variation あり。

デフォルト値は 0 でアニメーションを行わない。

そのため、このパラメータを設定しないとアニメーションが再生されない。

 

Aniamtion (imageSequenceAnimationMode)

アニメーションがどうのように行われるか設定する。

 

設定値は以下のもの。

パラメーター名 説明
repeat 繰り返し再生する。デフォルト値
clamp 再生後停止
autoReverse 再生・逆再生を繰り返す

 

Dimentions - Row (imageSequenceRowCount)

アニメーション画像の行数。

デフォルト値は 0。

 

Dimentions - Columns (imageSequenceColumnCount)

アニメーション画像の列数。

デフォルト値は 0。

 

Rendering

f:id:x67x6fx74x6f:20170824100445p:plain

 

Blending (blendMode)

パーティクルが重なり合った時、画像のブレンドモードを設定する。

設定値はマテリアルと同じ、additive、subtract、multiply、screen、alpha、replace。

デフォルト値は additive。

 

Enable black pass (isBlackPassEnabled)

パーティクルの裏に黒色を重ねコントラストを高くする。
画面上に白身が多いと ブレンドモードなどによってはパーティクルが見えなくなるため。

デフォルト値は false。

こちらを使用すると、パーティクルのレンダリング処理が 2 回行われるのでパフォーマンスを優先したい場合など注意して使う。

 

Orientation (orientationMode)

パーティクル画像がどうのような向き出表示されるか設定する。

 

設定値は以下のもの。

パラメーター名 説明
billboardScreenAligned 画像が必ずカメラに向く。デフォルト値
billboardViewAligned 画像がカメラに向くが平行な軸で回転する
free 自由に回転する。向きによってはパーティクル画像が見えなくなるので注意
billboardYAligned Y 軸方向のみ、カメラに対して常に固定される

 

Sorting (sortingMode)

blendMode とともに、レンダリング時に重なり合ったパーティクルイメージの見た目を決める。

 

設定値は以下のもの。

パラメーター名 説明
none ソートされず任意で表示される。デフォルト値
projectedDepth 深度情報から近いパーティクルを前にレンダリングする
distance 遠いものが近いものより前にレンダリングされる
oldestFirst 先に放出したパーティクルが最前面でレンダリングされる
youngestFirst 新しいく放出されたパーティクルが最前面でレンダリングされる

 

Enable lighting (isLightingEnabled)

パーティクルにライトを適応するか決める。
スモークなどヴォリュームエフェクトなどで使用する。

ライト、1つにのみ適応されるので、複数ライトがある場合は categoryBitMask で振り分ける。

デフォルト値は false。

 

Physics

f:id:x67x6fx74x6f:20170824100513p:plain

 

Affected by gravity (isAffectedByGravity)

PhysicsWorld などの重力の影響を受けるか否かを設定する。
true にすると質量などの影響で重力の物理シミュレーションが行われる。

デフォルト値は false。

 

Affected by physics fields (isAffectedByPhysicsFields)

PhysicsField の影響を受けるか否かを設定する。
true にすると PhysicsField による質量やコードでしか設定できないが電荷に影響して物理シミュレーションが行われる。

デフォルト値は false。

 

Die on collision (particleDiesOnCollision)

true にすると設定した障害物(コリジョン)にぶつかった場合、パーティクルが消滅する。

デフォルト値は false。

 

Mass (particleMass / particleMassVariation)

物理シミュレーション同様に質量を設定する。 単位は kg で、Variation あり。

デフォルト値は 1。

 

Bounce (particleBounce / particleBounceVariation)

物理シミュレーション同様に衝突で得られたエネルギーの量を決める。
Variation あり。

デフォルト値は 0.7。

 

Friction (particleFriction / particleFrictionVariation)

物理シミュレーション同様に摩擦を決める。

デフォルト値は 1.0。

 

Damping (dampingFactor)

物理シミュレーション同様に流体摩擦や空気抵抗などの効果を付加する。

デフォルト値は 0.0 で、 acceleration の設定、重力や PhysicsField の影響を受けない場合は減速しない。

 

Life cycle

f:id:x67x6fx74x6f:20170824100554p:plain

 

Emission Duration (emissionDuration / emissionDurationVariation)

パーティクルの再生する時間を指定する。単位は秒。
Variation あり。

デフォルト値は 1.0。

 

Idle Duration (idleDuration / idleDurationVariation)

パーティクルの再生するまでの時間を指定する。単位は秒。
Variation あり。

デフォルト値は 0.0。

emissionDuration を 1 にして、idleDuration を 1 にすると再生されなくなるので注意。

 

Loopting (loops)

パーティクルシステムのアニメーションをループさせるか決める。 false (Play Once) に設定すると設定されたもの1回分だけのパーティクルを放出する。

デフォルト値は true。 (Loops continuously)  

 

コードで設定するもの

fresnelExponent

キューブマップのリフレクトマップが使用されている場合、パーティクルにフレネル効果を適応する度合いを決める。

デフォルト値は 1.0。

 

particleCharge / particleChargeVariation

プラス、またはマイナスの値入れることで、
パーティクルに電荷を付加し PhysicsField の影響を受けるようにする。

デフォルト値は 0.0 で効果はない。

 

colliderNodes

障害物となるノードを配列で設定する。

 

その他

パーティクルをさらに細かく制御するものがあるが今回は割愛。

また、iOS 11 では以下のものが増えている。

  • orientationDirection
  • particleIntensity / particleIntensityVariation

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その77 - パーティクルシステムを scn ファイルの Scene Editor で確認してみる

パーティクルシステムを scn ファイルの Scene Editor で確認してみようと思うが、 カメラ、ライト、ジオメトリ、アクションなどと特に変わりはない。

 

scn ファイルを開いた状態で Object Library (Command + Option + Control + 3) を開き、 Particle System をドラッグ&ドロップするだけ。

f:id:x67x6fx74x6f:20170822185913p:plain

 

Scene Graph View にもパーティクルシステムが適応されたノードが配置され、
Attributes Inspector でパーティクル用の画像と各種設定を行って調整することとなる。

コードでも設定可能だが設定値が多いため、scn や scnp ファイルで編集した方が楽だと思われる

 

次回は設定値をみていく。

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その76 - パーティクルの障害物判定と新しいエミッターの派生

SceneKit の パーティクルシステムは他のゲームエンジン同様に、パーティクルの障害物判定と新しいパーティクルのエミッター派生させることができる。

パーティクルの処理自体軽いわけではないので多用は禁物。

 

今回の流れ

  • 雨のパーティクルを落とし床に衝突
  • 衝突の際にパーティクルの削除
  • 削除した場所から新しいパーティクルのエミッター(パーティクルシステム)を派生させる

 

プロジェクトファイルを作る

いつも通り、Xcode の SceneKit の Game テンプレートを作成。

GameViewController.swift を開き、viewDidLoad() を編集する。
今回 ship.scn を使用しないので、19行目を以下に変更。

let scene = SCNScene()

 

ship.scn を使用しなくなり、宇宙船のアニメーションはいらないので、44、47行目を削除

let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

 

準備完了。

 

床のジオメトリを追加する

物理シミュレーションを行うため、PhysicsBody で Static に設定。 viewDidLoad() に以下のコードを書く。

let floor = SCNFloor()
floor.firstMaterial?.diffuse.contents = UIColor.darkGray
floor.reflectivity = 0.0

let floorNode = SCNNode(geometry: floor)
floorNode.physicsBody = SCNPhysicsBody(type: .static, shape: nil)
scene.rootNode.addChildNode(floorNode)

 

SCNP ファイルを作成、編集する

2つの SCNP ファイルを作成する

Command + N から Resource の項目の「SceneKit Particle System File」を選択し「Next」ボタンを押下。
テンプレートの選択が出るので「Rain」を選択し、ここでは Rain.scnp で保存。

さらにサブエミッターとして「Rain」のテンプレートでもうひとつ scnp ファイルを作成。
名前を Splash.scnp で保存。

 

Splash.scnp 編集する

以下に変更すると5つのパーティクルが Z 軸方向に飛ぶようになる。

 

パラメーター名 設定値
Birth rate 5
Direction X:0 Y:0 Z:1
Speading angle 50°
Shape Point
Life span 1
Linner velocity 2
Stretch factor 0.1

 

f:id:x67x6fx74x6f:20170822182744p:plain

 

コードを書いてパーティクルを表示してみる

行うことは簡単で SCNParticleSystem(named:〜 でパーティクルを2つ設定する。
元となるパーティクルシステムをノードに適応。

SCNParticleSystem のプロパティ、colliderNodes で障害物を設定して、 particleDiesOnCollision を true にし設定した障害物に衝突するとパーティクルを消す。

そして、systemSpawnedOnCollision でぶつかった際に、派生させるパーティクルを設定する。

 

以下コード。

let emitter1 = SCNParticleSystem(named: "Rain.scnp", inDirectory: "")
let emitter2 = SCNParticleSystem(named: "Splash.scnp", inDirectory: "")
emitter2?.loops = false

let particleNode = SCNNode()
particleNode.position = SCNVector3(0,30,0)
particleNode.addParticleSystem(emitter1!)

emitter1?.colliderNodes = [floorNode]
emitter1?.particleDiesOnCollision = true

emitter1?.systemSpawnedOnCollision = emitter2

scene.rootNode.addChildNode(particleNode)

emitter2?.loops と emitter1?.particleDiesOnCollision をコードで書いているが、
Rain.scnp や Splash.scnp を Scene Editor で開き Attributes Inspector からでも設定変更が可能。

 

わかりづらいが地面に落ちると新しいパーティクルが作成されている。

f:id:x67x6fx74x6f:20170822183259g:plain

 

GameViewController.swift の全コード

import UIKit
import QuartzCore
import SceneKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let scene = SCNScene()
        
        
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3(x: 0, y: 5, z: 15)
        scene.rootNode.addChildNode(cameraNode)
        
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        
        let scnView = self.view as! SCNView
        scnView.scene = scene
        scnView.allowsCameraControl = true
        scnView.showsStatistics = true
        scnView.backgroundColor = UIColor.black
        
        
        // 床
        let floor = SCNFloor()
        floor.firstMaterial?.diffuse.contents = UIColor.darkGray
        floor.reflectivity = 0.0
        
        let floorNode = SCNNode(geometry: floor)
        floorNode.physicsBody = SCNPhysicsBody(type: .static, shape: nil)
        scene.rootNode.addChildNode(floorNode)
        
        
        // パーティクルの設定
        let emitter1 = SCNParticleSystem(named: "Rain.scnp", inDirectory: "")
        let emitter2 = SCNParticleSystem(named: "Splash.scnp", inDirectory: "")
        emitter2?.loops = false
        
        let particleNode = SCNNode()
        particleNode.position = SCNVector3(0,30,0)
        particleNode.addParticleSystem(emitter1!)
        
        emitter1?.colliderNodes = [floorNode]
        emitter1?.particleDiesOnCollision = true
        
        emitter1?.systemSpawnedOnCollision = emitter2
        scene.rootNode.addChildNode(particleNode)
    }
    
    override var shouldAutorotate: Bool {
        return true
    }
    
    override var prefersStatusBarHidden: Bool {
        return true
    }
    
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .allButUpsideDown
        } else {
            return .all
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

  

今回はここまで。