Тень SceneKit на прозрачном SCNFloor ()

У меня есть floor node , на котором мне нужно отбрасывать тень от directional light . Этот узел должен быть прозрачным (используется в среде AR ). И это отлично работает, когда я использую ARKit , но такая же настройка с использованием SceneKit показывает тени или отражения. Как я могу 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 вы можете изменить backgroundColor в последней строке ViewController - мне нужно, чтобы оно было четким, поэтому я могу показать предварительный просмотр камеры под ним.

На рисунках ниже вы можете видеть, как выглядит, когда sceneView.backgroundColor белый, а ниже - ясный. На четкой версии нет тени.

Здесь вы можете увидеть этот эффект с белым фоном цвета sceneView - тень видна

И это, если версия с sceneView.backgroundColor == .clear. С этой точки зрения существует UIImageView. Мне нужно использовать эту версию, но тени не видно

Всего 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 (): введите описание изображения здесь

Тень на видимом SCNPlane (), и наша камера находится под SCNFloor (): введите описание изображения здесь

Для получения transparent shadow вам нужно установить shadow color , а не object's transparency itself .

Второе : 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: 0.5 ) - opacity тени, а компоненты RGB ( white: 0 ) - черный цвет тени.

введите описание изображения здесь

введите описание изображения здесь

PS

sceneView.backgroundColor переключения между. .clear цветом и .white цветом .

В этом конкретном случае я не могу поймать прочную тень, когда sceneView.backgroundColor = .clear , потому что вам нужно переключиться между RGBA=1,1,1,1 ( белый режим : белый цвет, альфа = 1) и RGBA=0,0,0,0 ( режим очистки : черный цвет, альфа = 0).

Чтобы увидеть полупрозрачную тень на фоне, компоненты должны быть RGB=1,1,1 и A=0.5 , но эти значения отбеливают изображение из-за внутреннего механизма компоновки SceneKit. Но когда я устанавливаю RGB=1,1,1 и A=0.02 тень очень слаба.

Ниже приведено допустимое обходное решение (см. Решение ниже в разделе SOLUTION ):

@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, но сплошная черная тень на белом.

введите описание изображения здесь

РЕШЕНИЕ :

Вы должны захватить рендер 3D-объектов, чтобы иметь предварительно умноженное изображение RGBA с его полезным альфа-каналом. После этого вы можете rgba image of cube and its shadow над image of nature используя классическую операцию компоновки OVER в другом представлении.

Вот формула для операции OVER :

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

введите описание изображения здесь


Есть идеи?

10000