透明SCNFloor()上的SceneKit阴影

时间:2018-10-04 20:24:10

标签: swift scenekit augmented-reality arkit shadow

我有一个floor node,我需要从directional light投射阴影。该节点需要透明(在AR环境中使用)。 当我使用ARKit时,这很好用,但是使用SceneKit的相同设置没有阴影或反射。我该如何在SceneKit中投下阴影? SceneKit的问题是由我设置sceneView.backgroundColor = .clear引起的,但是我在此应用中需要此行为。可以避免这种情况吗?

示例代码,演示了此问题(仅在设备上起作用,在模拟器中不起作用):

@IBOutlet weak var sceneView: SCNView! {
    didSet {

        sceneView.scene = SCNScene()

        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        sceneView.pointOfView = cameraNode

        let testNode = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
        testNode.position = SCNVector3(x: 0, y: 0, z: -5)
        sceneView.scene!.rootNode.addChildNode(testNode)

        let animation = SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 3.0)
        testNode.runAction(SCNAction.repeatForever(animation), completionHandler: nil)

        let floor = SCNFloor()
        floor.firstMaterial!.colorBufferWriteMask = []
        floor.firstMaterial!.readsFromDepthBuffer = true
        floor.firstMaterial!.writesToDepthBuffer = true
        floor.firstMaterial!.lightingModel = .constant
        let floorNode = SCNNode(geometry: floor)
        floorNode.position = SCNVector3(x: 0, y: -2, z: 0)
        sceneView.scene!.rootNode.addChildNode(floorNode)

        let light = SCNLight()
        light.type = .directional
        light.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
        light.color = UIColor.white
        light.castsShadow = true
        light.automaticallyAdjustsShadowProjection = true
        light.shadowMode = .deferred
        let sunLightNode = SCNNode()
        sunLightNode.position = SCNVector3(x: 1_000, y: 1_000, z: 0)
        sunLightNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: .pi * 1.5)
        sunLightNode.light = light
        sceneView.scene!.rootNode.addChildNode(sunLightNode)

        let omniLightNode: SCNNode = {
            let omniLightNode = SCNNode()
            let light: SCNLight = {
                let light = SCNLight()
                light.type = .omni
                return light
            }()
            omniLightNode.light = light
            return omniLightNode
        }()
        sceneView.scene!.rootNode.addChildNode(omniLightNode)
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    let tapGR = UITapGestureRecognizer(target: self, action: #selector(toggleTransparent))
    view.addGestureRecognizer(tapGR)
}

@objc func toggleTransparent() {
    transparent = !transparent
}

var transparent = false {
    didSet {
        sceneView.backgroundColor = transparent ? .clear : .white
    }
}

以下是与macOS相同的示例,建立在SceneKit游戏项目之上:

import SceneKit
import QuartzCore

class GameViewController: NSViewController {

    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)

        let testNode = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))
        testNode.position = SCNVector3(x: 0, y: 0, z: -5)
        scene.rootNode.addChildNode(testNode)

        let animation = SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 3.0)
        testNode.runAction(SCNAction.repeatForever(animation), completionHandler: nil)

        let floor = SCNFloor()
        floor.firstMaterial!.colorBufferWriteMask = []
        floor.firstMaterial!.readsFromDepthBuffer = true
        floor.firstMaterial!.writesToDepthBuffer = true
        floor.firstMaterial!.lightingModel = .constant
        let floorNode = SCNNode(geometry: floor)
        floorNode.position = SCNVector3(x: 0, y: -2, z: 0)
        scene.rootNode.addChildNode(floorNode)

        let light = SCNLight()
        light.type = .directional
        light.shadowColor = NSColor(red: 0, green: 0, blue: 0, alpha: 0.5)
        light.color = NSColor.white
        light.castsShadow = true
        light.automaticallyAdjustsShadowProjection = true
        light.shadowMode = .deferred
        let sunLightNode = SCNNode()
        sunLightNode.position = SCNVector3(x: 1_000, y: 1_000, z: 0)
        sunLightNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: .pi * 1.5)
        sunLightNode.light = light
        scene.rootNode.addChildNode(sunLightNode)

        let omniLightNode: SCNNode = {
            let omniLightNode = SCNNode()
            let light: SCNLight = {
                let light = SCNLight()
                light.type = .omni
                return light
            }()
            omniLightNode.light = light
            return omniLightNode
        }()
        scene.rootNode.addChildNode(omniLightNode)

        // 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

        // configure the view
        scnView.backgroundColor = .clear
//        scnView.backgroundColor = .white // shadow works in this mode, but I need it to be clear
    }
}

示例项目:

MacOS:https://www.dropbox.com/s/1o50mbgzg4gc0fg/Test_macOS.zip?dl=1

iOS:https://www.dropbox.com/s/fk71oay1sopc1vp/Test.zip?dl=1

在macOS中,您可以在ViewController的最后一行中更改backgroundColor-我需要将其清除,以便在其下显示摄像机预览。

在下面的图片中,您可以看到sceneView.backgroundColor为白色时的外观,而在下面-清晰。在透明版本上没有阴影。

Here you can see this effect with white background color of sceneView - shadow is visible

And this if version with sceneView.backgroundColor == .clear. There is UIImageView under this view. I need to use this version, but there is no shadow visible

2 个答案:

答案 0 :(得分:1)

  

要获得透明阴影,有两个步骤

第一:您需要将其作为node连接到scene,而不是geometry type

let floor = SCNNode()
floor.geometry = SCNFloor()
floor.geometry?.firstMaterial!.colorBufferWriteMask = []
floor.geometry?.firstMaterial!.readsFromDepthBuffer = true
floor.geometry?.firstMaterial!.writesToDepthBuffer = true
floor.geometry?.firstMaterial!.lightingModel = .constant
scene.rootNode.addChildNode(floor)

不可见的SCNFloor()上的阴影: enter image description here

可见SCNPlane()上的阴影,我们的相机位于SCNFloor()下: enter image description here

  

要获取transparent shadow,您需要设置shadow color,而不是object's transparency itself

第二:必须为macOS这样设置shadow color

lightNode.light!.shadowColor = NSColor(calibratedRed: 0,
                                               green: 0, 
                                                blue: 0, 
                                               alpha: 0.5)

...对于iOS,它看起来像这样:

lightNode.light!.shadowColor = UIColor(white: 0, alpha: 0.5)

此处的Alpha分量(alpha: 0.5)是阴影的opacity,而RGB分量(white: 0)是阴影的黑色。

enter image description here

enter image description here

PS

  

sceneView.backgroundColor.clear颜色和.white颜色之间切换

在这种特殊情况下,sceneView.backgroundColor = .clear时我无法捕捉到强烈的阴影,因为您需要在RGBA=1,1,1,1之间进行切换(白色模式:白色,alpha = 1 )和RGBA=0,0,0,0清晰模式:黑色,alpha = 0)。

为了在背景上看到半透明阴影,组件应为RGB=1,1,1A=0.5,但是由于SceneKit的内部合成机制,这些值会使图像变白。但是当我设置RGB=1,1,1A=0.02时,阴影非常微弱。

这是目前可以容忍的解决方法(在“解决方案”部分的下方查找解决方案):

@objc func toggleTransparent() {
    transparent = !transparent
}  
var transparent = false {
    didSet {
        // this shadow is very FEEBLE and it's whitening BG image a little bit
        sceneView.backgroundColor = transparent ? 
                                    UIColor(white: 1, alpha: 0.02) : 
                                    .white
    }
}

let light = SCNLight()
light.type = .directional

if transparent == false {
    light.shadowColor = UIColor(white: 0, alpha: 0.9)
}

如果我设置light.shadowColor = UIColor(white: 0, alpha: 1),则BG图像会得到令人满意的阴影,而白色会得到黑色阴影

enter image description here

  

解决方案

     

您应该获取3D对象的渲染,以将RGBA图像与其有用的Alpha通道预乘。之后,您可以在另一个视图中使用经典的rgba image of cube and its shadow合成操作在image of nature上合成OVER

这是OVER操作的公式:

  

((RGB1 * A1)+(RGB2 *(1 – A1))

enter image description here

答案 1 :(得分:0)

自从发布以来已经有一段时间了,但是也许有人会觉得这种替代解决方案很有用。我遇到了类似的情况,最终要做的是通过SCNTechnique使用多次传递进行渲染。首先,我用白色diffuse渲染了一个地板,然后在没有该地板的情况下渲染了场景的其余部分。为此,我将categoryBitMask的{​​{1}}设置为3,而其他设置为默认值1。

接下来,我用这个定义创建了SCNFloor,它将地板和场景的其余部分渲染到单独的缓冲区中,然后将它们组合在一起成为最终场景:

SCNTechnique

接下来,Metal共享代码将这两个缓冲区合并在一起,其中alpha值的范围从白色的0到黑色的1。

self.sceneView.technique = SCNTechnique(dictionary: [
  "passes" : [
    "store-floor": [
      "draw" : "DRAW_NODE",
      "node" : "floor-node",
      "inputs" : [],
      "outputs" : [ "color" : "color_floor" ]
    ],
    "store-scene": [
      "draw" : "DRAW_SCENE",
      "excludeCategoryMask" : 2,
      "inputs" : [],
      "outputs" : [ "color" : "color_scene" ]
    ],
    "recall-scene": [
      "draw" : "DRAW_QUAD",
      "metalVertexShader" : "vertex_tecnique_basic",
      "metalFragmentShader" : "fragment_tecnique_merge",
      "inputs" : [ "alphaTex" : "color_floor", "sceneTex" : "color_scene" ],
      "outputs" : [ "color" : "COLOR" ]
    ]
  ],
  "symbols" : [
    "vertexSymbol" : [ "semantic" : "vertex" ]
  ],
  "targets" : [
    "color_floor" : [ "type" : "color" ],
    "color_scene" : [ "type" : "color" ],
  ],
  "sequence" : [
    "store-floor",
    "store-scene",
    "recall-scene"
  ]
])

最后,下面是一个示例,说明将所有部分放在一起的样子。enter image description here