swift - 透明 SCNFloor() 上的 SceneKit 阴影

标签 swift scenekit augmented-reality arkit shadow

我有一个地板节点,我需要在其上转换来自定向光的阴影。这个节点需要是透明的(在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
    }
}

示例项目:

苹果操作系统: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

最佳答案

获得透明阴影有两个步骤:

首先:您需要将其连接为 nodescene ,而不是 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

For getting a transparent shadow you need to set a shadow color, not the object's transparency itself.

第二个:A shadow color对于 macOS 必须这样设置:

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

附言

sceneView.backgroundColor switching between .clear colour and .white colour.

在这种特殊情况下,当 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

SOLUTION:

You should grab a render of 3D objects to have premultiplied RGBA image with its useful Alpha channel. After that, you can composite rgba image of cube and its shadow over image of nature using classical OVER compositing operation in another View.

这是 OVER 的公式操作:

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

enter image description here

关于swift - 透明 SCNFloor() 上的 SceneKit 阴影,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52654624/

相关文章:

ios - ARKit 添加 X 翻转的 2D 视频

rotation - 应用旋转后如何将 SCNNode 沿其指向的方向移动

ios - 在 ARKit session 中左右移动对象

swift - 如何在相机 ARKit 顶部添加半透明背景

swift - 这段 Swift 代码应该会产生内存泄漏,但实际上并没有。有人可以指出为什么吗?

ios - AirPrint:将 pdf 文件直接打印到打印机

swift - Algolia helper searcher方法Filter不过滤字符串,只过滤数字

ios - (ARKit 2 Room Scanner) 如何使用 UIBezierPath 和 featurePoints 通过 ARKit 2 和 ARWorldMap 创建一个具有周围环境几何形状的节点

swift - RealityKit 和 Reality Composer——图像识别

ios - 使用 TouchID 添加仅在应用程序内使用的指纹