Тень SceneKit на прозрачном SCNFloor () - PullRequest
0 голосов
/ 04 октября 2018

У меня есть 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 вы можете изменить backgroundColorв последней строке ViewController - мне нужно, чтобы он был четким, чтобы я мог показывать предварительный просмотр камеры под ним.

На рисунках ниже вы можете видеть, как это выглядит, когда 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 голосов
/ 19 августа 2019

Прошло некоторое время с тех пор, как это было опубликовано, но, возможно, кто-то найдет это альтернативное решение полезным.Я столкнулся с подобной ситуацией, и в итоге я сделал рендеринг с использованием нескольких проходов через SCNTechnique.Сначала я рендерил пол сплошным белым diffuse, а затем я рендерил остальную часть сцены без пола.Для этого я установил categoryBitMask моего SCNFloor на 3 и оставил другим значение по умолчанию 1.

Затем я создал SCNTechnique с этим определением, которое отображает слово и остальныхсцены в отдельные буферы, а затем объединяет их вместе в финальную сцену:

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"
  ]
])

Затем код акции Metal, который берет эти два буфера и объединяет их вместе, где значение альфа варьируется от 0 для белого до 1для черных.

using namespace metal;
#include <SceneKit/scn_metal>

struct TechniqueVertexIn
{
    float4 position [[attribute(SCNVertexSemanticPosition)]];
};

struct TechniqueVertexOut
{
  float4 framePos [[position]];
  float2 centeredLoc;
};

constexpr sampler s = sampler(coord::normalized, address::repeat, filter::linear);

vertex TechniqueVertexOut vertex_tecnique_basic(
  TechniqueVertexIn     in [[stage_in]],
  constant SCNSceneBuffer&  scnFrame [[buffer(0)]])
{
  TechniqueVertexOut vert;

  vert.framePos = float4(in.position.x, in.position.y, 0.0, 1.0);
  vert.centeredLoc = float2((in.position.x + 1.0) * 0.5 , (in.position.y + 1.0) * -0.5);

  return vert;
}

fragment half4 fragment_tecnique_merge(
  TechniqueVertexOut  vert [[stage_in]],
  texture2d<float>  alphaTex [[texture(0)]],
  texture2d<float>  sceneTex [[texture(1)]])
{
    float4 alphaColor = alphaTex.sample(s, vert.centeredLoc);
    float4 sceneColor = sceneTex.sample(s, vert.centeredLoc);
  float alpha     = 1.0 - max(max(alphaColor.r, alphaColor.g), alphaColor.b); // since floor should be white, could just pick a chan

  alpha *= alphaColor.a;
  alpha = max(sceneColor.a, alpha);

  return half4(half3(sceneColor.rgb * alpha), alpha);
}

Наконец, вот пример того, как это выглядит в итоге, когда все кусочки сложены вместе. enter image description here

0 голосов
/ 04 октября 2018

Существует два шага для получения прозрачной тени :

Первый : его необходимо подключить как 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.

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

enter image description here

enter image description here

PS

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

В этом конкретном случае я не могу поймать устойчивую тень при 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, тень очень слаба.

Вот допустимый обходной путь ( ищите решение ниже в разделе РЕШЕНИЕ ):

@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), я получу удовлетворительная тень на фоновом изображении, но сплошная черная тень на белом.

enter image description here

РЕШЕНИЕ :

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

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

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

enter image description here

...