iOS で SceneKit を試す(Swift 3) その53 - SCNLight の Gobo と Category BitMask
シーン上でキャストシャドウを行うと基本的にはシャドウマップが作成される。
もし、ジオメトリの影を落とす際、細かなフォルムが必要なければ
事前にテクスチャ画像を作成したものを使用し、シャドウマップを生成させないことでライド使用時の負荷を減らすことができる。
Gobo に設定したライトは影を落とすためのもので光沢や色等のライティング効果は出ない。
Gobo って何?
ライブや舞台等で、ライトの前に光の当てたい部分を削ったプレートを置いて照明を照らすことで、
壁面に絵や文字を投影することができ、そのプレートや効果を指すとのこと。
ひとまず書いてみる
Scene Editor では設定できないためコードで書いていく。
いつも通り iOS の Game テンプレートでプロジェクトを作成。
Gobo 用の画像をプロジェクトに追加し、
Gobo 用のライトと落とした影を確認するために SCNFloor を設定する。
影となる画像 gabo1.png

GameViewController.swift の「let scene = SCNScene(named: “art.scnassets/ship.scn”)!」より下に以下のコードを書く。
// --- 追加分 --- let goboNode = SCNNode() let light = SCNLight() light.type = .directional light.gobo?.contents = UIImage(named: "gobo1.png") light.gobo?.intensity = 0.8 light.orthographicScale = 5 light.shadowMode = SCNShadowMode.modulated light.castsShadow = false goboNode.position = SCNVector3(x: 0, y: 5, z: 0) goboNode.eulerAngles = SCNVector3(x: -Float.pi * 0.5, y: 0, z: 0) goboNode.light = light scene.rootNode.addChildNode(goboNode) let floorNode = SCNNode(geometry: SCNFloor()) floorNode.position = SCNVector3(x: 0.0, y: -4.0, z: 0.0) scene.rootNode.addChildNode(floorNode)
内容的には Y の値が 5 で下に向いた普通のライトを設定をして、 gobo の contents で画像設定、intensity で影の透過を決める。
そして、SCNLight の shadowMode を modulated にしている。
影の大きさを変更するため orthographicScale でサイズを変更している。
ちなみにドキュメントではスポットライトで使用できるとは書いてあるが、ディレクショナルライトでも動作する。
ビルドするとこのような感じになる。

ジオメトリにも影が落ちているので落ちないようにしたい
Category BitMask を使って適応範囲を変更する。
以下のように合わせると、下の床のノード部分だけに影を落とすことができる
light.categoryBitMask = 2 floorNode.categoryBitMask = 2

注意点
画像が適応できるものはモノクロが画像。
透過が含まれていると表示されない。
スポットライトと同等であるため、ライトの設置上限がある。
そのため、8個ライトが設定されていて Gobo のライトを追加すると
シーン内のいずれかのライトが消える。
Gobo はライトの向きで投影される画像が固定される。
そのため、方向の決まった影の画像を適応し、ジオメトリが回転すると問題が起きる。
画像

動作

今回修正した GameViewController.swift のコード
hitTest 部分が必要ないので省いている。
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// --- 追加分 ---
let goboNode = SCNNode()
let light = SCNLight()
light.type = .directional
light.gobo?.contents = UIImage(named: "gobo1.png")
light.gobo?.intensity = 0.8
light.orthographicScale = 5
light.shadowMode = .modulated
light.castsShadow = false
light.categoryBitMask = 2
goboNode.position = SCNVector3(x: 0, y: 5, z: 0)
goboNode.eulerAngles = SCNVector3(x: -Float.pi * 0.5, y: 0, z: 0)
goboNode.light = light
scene.rootNode.addChildNode(goboNode)
let floorNode = SCNNode(geometry: SCNFloor())
floorNode.position = SCNVector3(x: 0.0, y: -4.0, z: 0.0)
floorNode.categoryBitMask = 2
scene.rootNode.addChildNode(floorNode)
// 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)))
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
}
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()
// Release any cached data, images, etc that aren't in use.
}
}
今回はここまで