透明 SCNFloor() 上的 SceneKit 阴影
SceneKit shadow on a transparent SCNFloor()
我有一个 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 的最后一行更改背景颜色 - 我需要它清晰,所以我可以在它下面显示相机预览。
在下面的图片中,您可以看到当 sceneView.backgroundColor 为白色时的样子,而下面的图片 - 清晰。透明版没有阴影。
There are two steps to get a transparent shadow :
首先 :您需要将它作为 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() 下:
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
) 是阴影的黑色。
P.S.
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,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)
,我将在 BG 图像上获得 令人满意的阴影 ,但在白色图像上获得 纯黑色阴影 。
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))
自发布以来已经有一段时间了,但也许有人会发现这个替代解决方案很有用。我遇到了类似的情况,我最终做的是通过 SCNTechnique
使用多个通道进行渲染。首先,我用纯白色 diffuse
渲染了一个地板,然后我渲染了没有地板的场景的其余部分。为此,我将 SCNFloor
的 categoryBitMask
设置为 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 共享代码,其中 alpha 值的范围从 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);
}
最后是一个示例,展示了将所有部分组合在一起后的样子。
我有一个 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 的最后一行更改背景颜色 - 我需要它清晰,所以我可以在它下面显示相机预览。
在下面的图片中,您可以看到当 sceneView.backgroundColor 为白色时的样子,而下面的图片 - 清晰。透明版没有阴影。
There are two steps to get a transparent shadow :
首先 :您需要将它作为 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() 下:
For getting a
transparent shadow
you need to set ashadow color
, not theobject'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
) 是阴影的黑色。
P.S.
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,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)
,我将在 BG 图像上获得 令人满意的阴影 ,但在白色图像上获得 纯黑色阴影 。
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
overimage of nature
using classicalOVER
compositing operation in another View.
这里是 OVER
运算的公式:
(RGB1 * A1) + (RGB2 * (1 – A1))
自发布以来已经有一段时间了,但也许有人会发现这个替代解决方案很有用。我遇到了类似的情况,我最终做的是通过 SCNTechnique
使用多个通道进行渲染。首先,我用纯白色 diffuse
渲染了一个地板,然后我渲染了没有地板的场景的其余部分。为此,我将 SCNFloor
的 categoryBitMask
设置为 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 共享代码,其中 alpha 值的范围从 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);
}
最后是一个示例,展示了将所有部分组合在一起后的样子。