Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SceneKitを使ってアプリのクオリティを劇的に上げる / Dramatically imp...

ring
September 19, 2021

SceneKitを使ってアプリのクオリティを劇的に上げる / Dramatically improve the quality of your apps with SceneKit

SceneKitはUIViewやCALayerでは難しい3Dやパーティクルの表現を非常に簡単に使用できるフレームワークです。

「3Dモデルのレンダリングなどで使用するフレームワークだから私には関係ない」
「AR系のアプリ開発に携わっているわけではないので使用する機会はなさそう」

このように思っていませんか?

これは大きな間違いです。

SceneKitはUIKitと非常に親和性が高くUIKitにそのままViewとして統合できます。
そして何より、UIKitだけでは実現が難しいリッチな表現(アニメーション)がとても簡単に実装できるのです!

本トークでは「SceneKit × UIKitの導入事例を軸にアプリのクオリティを上げるtips」をご紹介します。

ring

September 19, 2021
Tweet

More Decks by ring

Other Decks in Programming

Transcript

  1. © ZOZO Technologies, Inc. IUUQT[P[PKQ 3 • ೔ຊ࠷େڃͷϑΝογϣϯ௨ൢαΠτ • 

    Ҏ্ͷγϣοϓɺ Ҏ্ͷϒϥϯυͷऔΓѻ͍ʢͱ΋ʹ೥ ݄຤࣌఺ʣ • ৗ࣌ສ఺Ҏ্ͷ঎඼ΞΠςϜ਺ͱຖ೔ฏۉ ఺Ҏ্ͷ৽ண঎඼Λ ܝࡌ • ίεϝઐ໳Ϟʔϧʮ;0;0$04.&ʯ΍ۺͷઐ໳Ϟʔϧ ʮ;0;04)0&4ʯɺϥάδϡΞϦʔˍσβΠφʔζκʔϯ ʮ;0;07*--"ʯΛల։ • ଈ೔഑ૹαʔϏε • ΪϑτϥοϐϯάαʔϏε • πέ෷͍ͳͲ
  2. © ZOZO Technologies, Inc. 4DFOF,JUDPNCJOFTBIJHIQFSGPSNBODFSFOEFSJOHFOHJOFXJUIBEFTDSJQUJWF"1*GPSJNQPSU  NBOJQVMBUJPO BOESFOEFSJOHPG%BTTFUT6OMJLFMPXFSMFWFM"1*TTVDIBT.FUBMBOE 0QFO(-UIBUSFRVJSFZPVUPJNQMFNFOUJOQSFDJTFEFUBJMUIFSFOEFSJOHBMHPSJUINTUIBU EJTQMBZBTDFOF

    4DFOF,JUSFRVJSFTPOMZEFTDSJQUJPOTPGZPVSTDFOF`TDPOUFOUTBOEUIF BDUJPOTPSBOJNBUJPOTZPVXBOUJUUPQFSGPSN 14 Ҿ༻IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOTDFOFLJU "QQMF%PDVNFOUBUJPO4DFOF,JU0WFSWJFX
  3. © ZOZO Technologies, Inc. 25 4DFOFΛߏ੒͢Δཁૉ 4$/(FPNFUSZ 4$/-JHIU 4$/$BNFSB SCNActionɾSCNTransaction

    ͳͲ ͲΜͳਓɾ෺͕഑ஔ͞Ε͍ͯͨͷ͔ ໌Δ͍ͷ͔ɾ҉͍ͷ͔ ͲΜͳΞϯάϧͰඳࣸ͞Ε͍ͯΔ͔ ͲͷΑ͏มԽɾҠಈ͕͋Δ͔
  4. © ZOZO Technologies, Inc. 4$/7JFX 27 4DFOFΛߏ੒͢Δཁૉ ɾɾɾ 4$/(FPNFUSZ 4$/-JHIU

    4$/$BNFSB SCNActionɾSCNTransaction 4DFOFΛߏ੒͢Δཁૉ͸ 4$/7JFXͱ͍͏6*7JFXͷαϒΫϥε ্ͰऔΓѻ͏  1 0 * / 5
  5. © ZOZO Technologies, Inc. 47 private var scnView: SCNView? private

    var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } }
  6. © ZOZO Technologies, Inc. 48 TDOϑΝΠϧ͔ΒQBSUJDMFTΛಡΈࠐΉ private var scnView: SCNView?

    private var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } }
  7. © ZOZO Technologies, Inc. 49 private var scnView: SCNView? private

    var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } } TDFOFΛੜ੒
  8. © ZOZO Technologies, Inc. 50 private var scnView: SCNView? private

    var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } } 4$/$BNFSBΛ TDFOFͷSPPU/PEFʹ௥Ճ
  9. © ZOZO Technologies, Inc. 51 private var scnView: SCNView? private

    var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } } QBSUJDMFΛ TDFOFͷSPPU/PEFʹ௥Ճ
  10. © ZOZO Technologies, Inc. 52 private var scnView: SCNView? private

    var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } } TDO7JFXΛੜ੒͠ GBW#VUUPOʹJOTFSU͢Δ
  11. © ZOZO Technologies, Inc. 53 private var scnView: SCNView? private

    var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } } TDO7JFXͷTDFOFʹ ্ड़Ͱ࡞੒ͨ͠TDFOFΛ୅ೖ
  12. © ZOZO Technologies, Inc. 54 private var scnView: SCNView? private

    var favParticle: SCNParticleSystem? override func viewDidLoad() { super.viewDidLoad() guard let scene = SCNScene(named: "fav.scn", inDirectory: "./") else { return } let node: SCNNode = scene.rootNode.childNode(withName: "particles", recursively: true) favParticle = node.particleSystems?.first } private func addFavParticle() { let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) scene.rootNode.addParticleSystem(favParticle) scnView = SCNView(frame: view.bounds) view.insertSubview(scnView!, belowSubview: favButton) scnView?.scene = scene } @IBAction private func favButtonTapped() { addFavParticle() UIView.animate(withDuration: 0.6) { [weak self] in self?.scnView!.alpha = 0 } completion: { [weak self] _ in self?.scnView?.removeFromSuperview() self?.scnView = nil } } Ϙλϯλοϓ࣌ʹύʔςΟΫϧΛੜ੒
  13. © ZOZO Technologies, Inc. 61 TIBEFS.PEJGJFST δΦϝτϦͷද໘ΛύϥϝτϦοΫʹมܗ  ෳࡶͳࡐྉಛੑΛ࣋ͭΦϒδΣΫτͷද໘ΛγϛϡϨʔτ 

    ඒ͍͠র໌ޮՌͳͲΛ௥ՃՄೳ  4DFOF,JUͷγΣʔσΟϯά͕׬ྃͨ͠ޙʹϐΫηϧΛ ޙॲཧͯ͠ಛघޮՌΛ࡞੒  Ҿ༻IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOTDFOFLJUTDOTIBEBCMF
  14. © ZOZO Technologies, Inc. 65 public class GlitterView: SCNView {

    let glitter = SCNNode() public override func awakeFromNib() { super.awakeFromNib() let scene = SCNScene() self.scene = scene glitter.geometry = SCNPlane(width: frame.size.width, height: frame.size.height) glitter.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "iosdc") glitter.geometry?.firstMaterial?.shaderModifiers = [ .surface: surfaceShaderModifier ] glitter.geometry?.firstMaterial?.setValue(0.0, forKey: "glitter") scene.rootNode.addChildNode(glitter) } public func playAnimation() { let animation = CABasicAnimation(keyPath: "geometry.firstMaterial.glitter") animation.fromValue = 0.0 animation.toValue = 1.0 animation.duration = 0.8 glitter.addAnimation(animation, forKey: nil) } }
  15. © ZOZO Technologies, Inc. 66 public class GlitterView: SCNView {

    let glitter = SCNNode() public override func awakeFromNib() { super.awakeFromNib() let scene = SCNScene() self.scene = scene glitter.geometry = SCNPlane(width: frame.size.width, height: frame.size.height) glitter.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "iosdc") glitter.geometry?.firstMaterial?.shaderModifiers = [ .surface: surfaceShaderModifier ] glitter.geometry?.firstMaterial?.setValue(0.0, forKey: "glitter") scene.rootNode.addChildNode(glitter) } public func playAnimation() { let animation = CABasicAnimation(keyPath: "geometry.firstMaterial.glitter") animation.fromValue = 0.0 animation.toValue = 1.0 animation.duration = 0.8 glitter.addAnimation(animation, forKey: nil) } } 4$/7JFXΛαϒΫϥεʹ ΋ͭ7JFXΛ࡞੒ 7$ͳͲʹBEE4VC7JFX ͯ͠1MBZ"OJNBUJPOΛݺͿ
  16. © ZOZO Technologies, Inc. 67 public class GlitterView: SCNView {

    let glitter = SCNNode() public override func awakeFromNib() { super.awakeFromNib() let scene = SCNScene() self.scene = scene glitter.geometry = SCNPlane(width: frame.size.width, height: frame.size.height) glitter.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "iosdc") glitter.geometry?.firstMaterial?.shaderModifiers = [ .surface: surfaceShaderModifier ] glitter.geometry?.firstMaterial?.setValue(0.0, forKey: "glitter") scene.rootNode.addChildNode(glitter) } public func playAnimation() { let animation = CABasicAnimation(keyPath: "geometry.firstMaterial.glitter") animation.fromValue = 0.0 animation.toValue = 1.0 animation.duration = 0.8 glitter.addAnimation(animation, forKey: nil) } } ฏ໘ͷδΦϝτϦʹ ը૾Λදࣔ
  17. © ZOZO Technologies, Inc. 68 public class GlitterView: SCNView {

    let glitter = SCNNode() public override func awakeFromNib() { super.awakeFromNib() let scene = SCNScene() self.scene = scene glitter.geometry = SCNPlane(width: frame.size.width, height: frame.size.height) glitter.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "iosdc") glitter.geometry?.firstMaterial?.shaderModifiers = [ .surface: surfaceShaderModifier ] glitter.geometry?.firstMaterial?.setValue(0.0, forKey: "glitter") scene.rootNode.addChildNode(glitter) } public func playAnimation() { let animation = CABasicAnimation(keyPath: "geometry.firstMaterial.glitter") animation.fromValue = 0.0 animation.toValue = 1.0 animation.duration = 0.8 glitter.addAnimation(animation, forKey: nil) } } (-4-ͷεχϖοτΛ 4USJOHͰ౉͢
  18. © ZOZO Technologies, Inc. 69 public class GlitterView: SCNView {

    let glitter = SCNNode() public override func awakeFromNib() { super.awakeFromNib() let scene = SCNScene() self.scene = scene glitter.geometry = SCNPlane(width: frame.size.width, height: frame.size.height) glitter.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "iosdc") glitter.geometry?.firstMaterial?.shaderModifiers = [ .surface: surfaceShaderModifier ] glitter.geometry?.firstMaterial?.setValue(0.0, forKey: "glitter") scene.rootNode.addChildNode(glitter) } public func playAnimation() { let animation = CABasicAnimation(keyPath: "geometry.firstMaterial.glitter") animation.fromValue = 0.0 animation.toValue = 1.0 animation.duration = 0.8 glitter.addAnimation(animation, forKey: nil) } } surfaceShaderModifier
  19. © ZOZO Technologies, Inc. 70 let surfaceShaderModifier = """ #pragma

    arguments float glitter; #pragma transparent #pragma body float t = 2 * clamp(glitter - _surface.diffuseTexcoord.y / 2, 0.0, 0.5); _surface.diffuse.rgb += float3(pow(sin(3.14159 * t), 12) / 6.0); """ (-4-εχϖοτޫͷ൓ࣹ (-4-4BOECPY IUUQTHMTMTBOECPYDPN ͔ΒҠ২ (-4-4BOECPYʹ͸ૉ੖Β͍͠γΣʔμʔͷαϯϓϧ͕ଟ਺
  20. © ZOZO Technologies, Inc. 71 public class GlitterView: SCNView {

    let glitter = SCNNode() public override func awakeFromNib() { super.awakeFromNib() let scene = SCNScene() self.scene = scene glitter.geometry = SCNPlane(width: frame.size.width, height: frame.size.height) glitter.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "iosdc") glitter.geometry?.firstMaterial?.shaderModifiers = [ .surface: surfaceShaderModifier ] glitter.geometry?.firstMaterial?.setValue(0.0, forKey: "glitter") scene.rootNode.addChildNode(glitter) } public func playAnimation() { let animation = CABasicAnimation(keyPath: "geometry.firstMaterial.glitter") animation.fromValue = 0.0 animation.toValue = 1.0 animation.duration = 0.8 glitter.addAnimation(animation, forKey: nil) } } OPEFʹ$""OJNBUJPOΛ Ճ͑ͯ׬੒