Apple Engine

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

iOS で SceneKit を試す(Swift 3) その53 - SCNLight の Gobo と Category BitMask

シーン上でキャストシャドウを行うと基本的にはシャドウマップが作成される。

もし、ジオメトリの影を落とす際、細かなフォルムが必要なければ
事前にテクスチャ画像を作成したものを使用し、シャドウマップを生成させないことでライド使用時の負荷を減らすことができる。

Gobo に設定したライトは影を落とすためのもので光沢や色等のライティング効果は出ない。

 

Gobo って何?

ライブや舞台等で、ライトの前に光の当てたい部分を削ったプレートを置いて照明を照らすことで、
壁面に絵や文字を投影することができ、そのプレートや効果を指すとのこと。

 

ひとまず書いてみる

Scene Editor では設定できないためコードで書いていく。

いつも通り iOS の Game テンプレートでプロジェクトを作成。

Gobo 用の画像をプロジェクトに追加し、
Gobo 用のライトと落とした影を確認するために SCNFloor を設定する。

 

影となる画像 gabo1.png

f:id:x67x6fx74x6f:20170807180245p:plain

 

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 でサイズを変更している。

ちなみにドキュメントではスポットライトで使用できるとは書いてあるが、ディレクショナルライトでも動作する。

 

ビルドするとこのような感じになる。

f:id:x67x6fx74x6f:20170807180321p:plain

 

ジオメトリにも影が落ちているので落ちないようにしたい

Category BitMask を使って適応範囲を変更する。
以下のように合わせると、下の床のノード部分だけに影を落とすことができる

light.categoryBitMask = 2
floorNode.categoryBitMask = 2

f:id:x67x6fx74x6f:20170807180347p:plain

 

注意点

画像が適応できるものはモノクロが画像。
透過が含まれていると表示されない。

 

スポットライトと同等であるため、ライトの設置上限がある。
そのため、8個ライトが設定されていて Gobo のライトを追加すると
シーン内のいずれかのライトが消える。

 

Gobo はライトの向きで投影される画像が固定される。
そのため、方向の決まった影の画像を適応し、ジオメトリが回転すると問題が起きる。

画像

f:id:x67x6fx74x6f:20170807180416p:plain

 

動作

f:id:x67x6fx74x6f:20170807180524g:plain

 

今回修正した 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.
    }

}

 

今回はここまで