Scenekit Shadow on a Transparent Scnfloor()

SceneKit shadow on a transparent SCNFloor()


There are two steps to get a transparent shadow :

First : You need to connect it as a node to the scene, not as a 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)

Shadow on invisible SCNFloor():
Sample Image

Shadow on visible SCNPlane() and our camera is under SCNFloor():
Sample Image

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

Second : A shadow color must be set like this for macOS:

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

...and for iOS it looks like this:

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

Alpha component here (alpha: 0.5) is an opacity of the shadow and RGB components (white: 0) is black color of the shadow.

Sample Image

Sample Image

P.S.

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

In this particular case I can't catch a robust shadow when sceneView.backgroundColor = .clear, because you need to switch between RGBA=1,1,1,1 (white mode: white colour, alpha=1) and RGBA=0,0,0,0 (clear mode: black colour, alpha=0).

In order to see semi-transparent shadow on a background the components should be RGB=1,1,1 and A=0.5, but these values are whitening the image due to internal compositing mechanism of SceneKit. But when I set RGB=1,1,1 and A=0.02 the shadow is very feeble.

Here's a tolerable workaround for now (look for solution below in SOLUTION section):

@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)
}

If I set light.shadowColor = UIColor(white: 0, alpha: 1) I'll get satisfactory shadow on BG image but solid black shadow on white.

Sample Image

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.

Here's a formula for OVER operation :

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

Sample Image

Is it possible to project light on a transparent SCNFloor in ARKit?

Yes, you can do that by casting a white (or bleeded color) shadow instead of a black one. For that you have to use deferred shadows type that are rendered in a post-processing pass.

Consider, you must use two separate lights in your scene – first one for white shadows casting, and a second one for lighting objects that cast these white shadows. You're able to include and exclude objects from lighting scheme implementing categoryBitMask property.

// SETTING A SHADOW CATCHER
let plane = SCNPlane(width: 10, height: 10)
plane.firstMaterial?.isDoubleSided = true
plane.firstMaterial?.lightingModel = .shadowOnly
let planeNode = SCNNode(geometry: plane)
planeNode.eulerAngles.x = -.pi / 2
scene.rootNode.addChildNode(planeNode)

// SETTING A LIGHT
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .directional
lightNode.light?.intensity = 50
lightNode.light?.color = UIColor.black
lightNode.eulerAngles.x = -.pi / 2
scene.rootNode.addChildNode(lightNode)

// DEFERRED SHADOWS
lightNode.light?.castsShadow = true
lightNode.light?.shadowMode = .deferred // important
lightNode.light?.forcesBackFaceCasters = true // important
lightNode.light?.shadowRadius = 10
lightNode.light?.shadowColor = UIColor.white

Can I make shadow that can look through transparent object with scenekit and arkit?

A known issue with deferred shading is that it doesn’t work with transparency so you may have to remove that line and use the default forward shading again. That said, the “simple property” you are looking for is the .renderingOrder property on the SCNNode. Set it to 99 for example. Normally the rendering order doesn’t matter because the z buffer is used to determine what pixel is in front of others. For the shadow to show up through the transparant part of the object you need to make sure the object is rendered last.

Sample Image

On a different note, assuming you used some of the material settings I posted on your other question, try setting the shininess value to something like 0.4.

Note that this will still create a shadow as if the object was not transparent at all, so it won’t create a darker shadow for the label and cap. For additional realism you could opt to fake the shadow entirely, as in using a texture for the shadow and drop that on a plane which you rotate and skew as needed. For even more realism, you could fake the caustics that way too.

You may also want to add a reflection map to the reflective property of the material. Almost the same as texture map but in gray scale, where the label and cap are dark gray (not very reflective) and a lighter gray for the glass portion (else it will look like the label is on the inside of the glass). Last tip: use a Shell modifier (that’s what it’s called in 3Ds max anyway) to give the glass model some thickness.

Hide SCNFloor but show shadow with SceneKit (swift)

In your screenshots I don't see any shadow. I only see the reflection. For shadows you need either a directional or spot light. For the reflections over your map did you try to the the map texture to your SCNFloor? Another option is to use a SCNFloor with a material transparency of 0 but that will have a cost due to the overdraw.

Issue with invisible Shadow Plane in SceneKit / ARKit

There are 3 more instance properties to take into consideration:

var shadowRadius: CGFloat { get set }
var shadowCascadeCount: Int { get set }
var shadowCascadeSplittingFactor: CGFloat { get set }

If you don't setup these ones they definitely cause rendering artifacts.

let lightNode = SCNNode()
lightNode.light = SCNLight()
// POSITION OF DIRECTIONAL LIGHT ISN'T IMPORTANT.
// ONLY DIRECTION IS CRUCIAL FOR DIRECTIONAL LIGHTS.
lightNode.rotation = SCNVector4(x: 0, y: 0, z: 0, w: 1)
lightNode.light!.type = .directional
lightNode.light!.castsShadow = true
lightNode.light?.shadowMode = .deferred

/* THREE INSTANCE PROPERTIES TO SETUP */
lightNode.light?.shadowRadius = 3.25
lightNode.light?.shadowCascadeCount = 3
lightNode.light?.shadowCascadeSplittingFactor = 0.09

lightNode.light?.shadowColor = UIColor(white: 0, alpha: 0.75)
scene.rootNode.addChildNode(lightNode)

And one more thing – when Auto Adjust is off:

Light, like a camera, has near and far clipping planes for setup.

lightNode.light?.zNear = 0
lightNode.light?.zFar = 1000000 // Far Clipping Plane is important

Sample Image

Hope this helps.

Showing a shadow on a transparent floor/plane in scenekit

Ok, so check out this example project on Github:

https://github.com/carolight/Metal-Shadow-Map/blob/master/Shadows/Shader.metal#L67

That link is to the shader that performs the shadow map testing. Basically, it normalizes the Z position of the fragment being rendered and compares it to the Z-Buffer of the shadow map. If it is less than the shadow-Z, the pixel is fully lit (line 67) else it is tinted slightly (line 69).

What you want to do instead is write (0,0,0, shadow_opacity) for line 69 and (0,0,0,0) for line 67. This should emit transparent pixels. Set shadow_opacity as a uniform from [0.0..1.0].

The rest of the setup is shown in the example. Tilt the plane/camera to the desired setting, skip drawing the cube/vase/whatever in the main pass if you don't really want it in the scene (line 279). (However, note the shadow pass and keep it there).



Related Topics



Leave a reply



Submit