Adding SCNLight inside SK3DNode - swift

I am creating an SK3DNode inside an SKScene:
let ball: SK3DNode = {
let scnScene = SCNScene()
let ballGeometry = SCNSphere(radius: 200)
let ballNode = SCNNode(geometry: ballGeometry)
ballNode.position = SCNVector3(0, 0, 0)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "wall")
ballGeometry.materials = [material]
let light = SCNLight()
light.type = .omni
light.color = UIColor.white
let lightNode = SCNNode()
lightNode.light = light
scnScene.rootNode.addChildNode(ballNode)
scnScene.rootNode.addChildNode(lightNode)
let node = SK3DNode(viewportSize: CGSize(width: 1000, height: 1000))
node.scnScene = scnScene
node.autoenablesDefaultLighting = false
return node
}()
However, the sphere renders black. Tried it with or without the material. Is there something I am missing?

The sphere is manually placed at (0, 0, 0) and so is the light (default value). This means that the light is placed inside the sphere. This means that the surface of the sphere is facing away from the light source and thus isn't lit.

Related

Increase displacement resolution / smooth vertices in SceneKit tessellated mesh

I'm trying to generate a mesh from a depth/displacement map using SceneKit
The source depth map I'm using looks like this:
I then generate a plane with an increased segment count and heavily tessellate it and apply the displacement material. Here's the code I'm using:
import Cocoa
import SceneKit
import PlaygroundSupport
// MARK: - View setup
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.camera?.zFar = 1000
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
let scnView = SCNView(frame: NSRect(x: 0, y: 0, width: 400, height: 400))
scnView.autoenablesDefaultLighting = true
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = NSColor.black
PlaygroundPage.current.liveView = scnView
// MARK: - Plane
let plane = SCNPlane(width: 200, height: 200)
plane.widthSegmentCount = 10
plane.heightSegmentCount = 10
let planeNode = SCNNode(geometry: plane)
planeNode.name = "Plane"
planeNode.geometry?.firstMaterial?.diffuse.contents = NSColor.white
let tessellator = SCNGeometryTessellator()
tessellator.tessellationFactorScale = 25
tessellator.tessellationPartitionMode = .pow2
tessellator.insideTessellationFactor = 4
tessellator.edgeTessellationFactor = 4
tessellator.smoothingMode = .phong
planeNode.geometry?.tessellator = tessellator
planeNode.geometry?.firstMaterial?.displacement.contents = "rabbit"
planeNode.geometry?.firstMaterial?.displacement.textureComponents = .red
planeNode.geometry?.firstMaterial?.displacement.intensity = 200
planeNode.geometry?.firstMaterial?.displacement.maxAnisotropy = 1
planeNode.geometry?.firstMaterial?.displacement.magnificationFilter = .none
planeNode.geometry?.firstMaterial?.lightingModel = .phong
scene.rootNode.addChildNode(planeNode)
Which mostly gives me the 3D mesh I'm aiming for:
But the resulting "blockiness" messes with the lighting. Wireframe view shows it clearer:
Is there any way to "smoothen" the resulting mesh, average vertex positions, or something similar?
I'm very tangentially familiar with 3D and basically unfamiliar with SceneKit and google/docs haven't yielded much.
Subdivision / adaptive subdivision doesn't solve the problem, neither does increasing tessellation detail to the highest count possible. It can make the shadow/highlight patches smaller, but they're still there.
Any help or pointers are much appreciated!
I figured it out!
The ladder effect is an artifact of 8-bit banding in the depth map
Converting the image to 32-bit depth and adding a slight blur pretty much fixed it:

Can't get shadow to appear with SCNLight

I added in some ambient light, directional light, and a floor but still no shadow appears! Any idea why?
// create and add ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.init(hexString: "ececec")
ambientLightNode.light!.intensity = 100
ambientLightNode.light!.shadowColor = UIColor(white: 0, alpha: 0.5)
scene.rootNode.addChildNode(ambientLightNode)
//Main Light
let floor = SCNNode()
floor.geometry = SCNFloor()
floor.geometry?.firstMaterial!.diffuse.contents = UIColor.white
floor.geometry?.firstMaterial!.colorBufferWriteMask = SCNColorMask(rawValue: 0)
floor.castsShadow = false
scene.rootNode.addChildNode(floor)
// Create directional light
let directionalLight = SCNNode()
directionalLight.light = SCNLight()
directionalLight.castsShadow = true
directionalLight.light?.type = .directional
directionalLight.position = SCNVector3(2, 10, 2)
scene.rootNode.addChildNode(directionalLight)
Use a light's castsShadow property:
directionalLight.light!.castsShadow = true
Not a node's castsShadow:
directionalLight.castsShadow = true

How does SCNIKConstraint work?

I have the following code (this can be run by replacing the standard ViewController code in the Game base project for macOS):
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
/* RELEVANT CODE BEGINS */
let boxGeo = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
let boxMaterial = SCNMaterial()
boxMaterial.diffuse.contents = NSColor.gray
boxGeo.firstMaterial = boxMaterial
let boxNode = SCNNode(geometry: boxGeo)
scene.rootNode.addChildNode(boxNode)
boxNode.name = "box0"
let sphereGeo = SCNSphere(radius: 0.5)
let sphereMaterial = SCNMaterial()
sphereMaterial.diffuse.contents = NSColor.green
sphereGeo.firstMaterial = sphereMaterial
let sphereNode = SCNNode(geometry: sphereGeo)
boxNode.addChildNode(sphereNode)
sphereNode.name = "sphere0"
sphereNode.constraints = [SCNConstraint]()
let distance = SCNDistanceConstraint(target: boxNode)
distance.minimumDistance = 2.0
distance.maximumDistance = 5.0
sphereNode.constraints?.append(distance)
let ik = SCNIKConstraint.inverseKinematicsConstraint(chainRootNode: boxNode)
sphereNode.constraints?.append(ik)
let anim = CABasicAnimation(keyPath: "targetPosition.y")
anim.fromValue = -2.0
anim.toValue = 2.0
anim.duration = 1
anim.autoreverses = true
anim.repeatCount = .infinity
ik.addAnimation(anim, forKey: nil)
/* RELEVANT CODE ENDS */
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = NSColor.black
From what I can gather from the documentation, the animation (and yes, the scene kit view animation setting is set to both play and loop in IB) should move the sphere as close as possible to the points 2.0 and -2.0 on the y-axis by rotating the cube. However, the sphere simply stays stationary. I have also tried setting the initial position of the sphere and cube by manipulating their position vectors directly instead of via the distance constraint, but again the animation did nothing.
Additionally, I have attempted to use the distance constraint in combination with the box having a lookAt constraint to make it rotate to constantly look at the sphere - these caused the rendering of the box and sphere to completely freak out.
I feel as though maybe I am missing something in the documentation here, such as another constraint or some kind of transform matrix to setup some kind of initial value. But I have encountered some other issues with constraints, animations and skeletons that is making me begin to believe that there is either a bug or some undocumented aspects of SceneKit.
You have added the sphereNode as child of the boxNode. If you move the boxNode all childs are also moved and the constraint has no effect.

ARKit –Drop a shadow of 3D object on the plane surface

This is the function that I use to display object on the plane surface.
private func loadScene(path: String) -> SCNNode {
let spotLight = SCNLight()
spotLight.type = SCNLight.LightType.probe
spotLight.spotInnerAngle = 30.0
spotLight.spotOuterAngle = 80.0
spotLight.castsShadow = true
let result = SCNNode()
result.light = spotLight
result.position = SCNVector3(-10.0, 20.0, 10.5)
result.addChildNode(result)
let scene = SCNScene(named: path)!
for node in scene.rootNode.childNodes {
result.addChildNode(node)
}
return result
}
I want to display shadow on the plane surface like this image.
When I set spotlight type like below
spotLight.type = SCNLight.LightType.directional
It shows the object itself with light/dark shadow and does not drop the shadow on the surface.
Can someone please guide me how can I achieve the output as shown in the image?
// To Add Shadow on 3D Model Just Copy Paste this code and it will appear a shadow of 3D Model on Ground
let flourPlane = SCNFloor()
let groundPlane = SCNNode()
let groundMaterial = SCNMaterial()
groundMaterial.lightingModel = .constant
groundMaterial.writesToDepthBuffer = true
groundMaterial.colorBufferWriteMask = []
groundMaterial.isDoubleSided = true
flourPlane.materials = [groundMaterial]
groundPlane.geometry = flourPlane
//
mainNode.addChildNode(groundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let myNode = SCNNode()
myNode.light = SCNLight()
myNode.light?.type = SCNLight.LightType.directional
myNode.light?.color = UIColor.white
myNode.light?.castsShadow = true
myNode.light?.automaticallyAdjustsShadowProjection = true
myNode.light?.shadowSampleCount = 64
myNode.light?.shadowRadius = 16
myNode.light?.shadowMode = .deferred
myNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
myNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
myNode.position = SCNVector3(x: 0,y: 5,z: 0)
myNode.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
// Add the lights to the container
mainNode.addChildNode(ambientLight)
mainNode.addChildNode(myNode)
// End
This edited version of the answer worked for me. I used material with shadowOnly model.
let flourPlane = SCNFloor()
let groundPlane = SCNNode()
let groundMaterial = SCNMaterial()
groundMaterial.lightingModel = .shadowOnly
flourPlane.materials = [groundMaterial]
groundPlane.geometry = flourPlane
//
node.addChildNode(groundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
ambientLight.light?.shadowMode = .deferred
ambientLight.light?.color = UIColor.white
ambientLight.light?.type = SCNLight.LightType.ambient
ambientLight.position = SCNVector3(x: 0,y: 5,z: 0)
// Create a directional light node with shadow
let myNode = SCNNode()
myNode.light = SCNLight()
myNode.light?.type = SCNLight.LightType.directional
myNode.light?.color = UIColor.white
myNode.light?.castsShadow = true
myNode.light?.automaticallyAdjustsShadowProjection = true
myNode.light?.shadowSampleCount = 64
myNode.light?.shadowRadius = 16
myNode.light?.shadowMode = .deferred
myNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
myNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
myNode.position = SCNVector3(x: 0,y: 1,z: -3)
myNode.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
// Add the lights to the container
node.addChildNode(ambientLight)
node.addChildNode(myNode)

Simple SceneKit scene shows black screen instead of SCNPlane

I am getting my feet wet with iOS SceneKit, however the following sample code (which executes inside viewDidLoad) is not behaving as expected. I want it to
place a camera at origin with direction of view towards positive z axis
place a red rectangle parallel to xy-plane at z = 100
Why does the rendering not reveal the red rectangle but only a black screen?
let scene = SCNScene()
// prepare camera
let camera = SCNCamera()
camera.zNear = 90
camera.zFar = 110
let cameraNode = SCNNode()
cameraNode.position = SCNVector3Make(0, 0, 0)
cameraNode.rotation = SCNVector4Make(1, 0, 0, Float(M_PI))
cameraNode.camera = camera
scene.rootNode.addChildNode(cameraNode)
// prepare light
let light = SCNLight()
light.type = SCNLightTypeOmni
light.color = SKColor(white: 0.3, alpha: 1.0)
let lightNode = SCNNode()
lightNode.light = light;
scene.rootNode.addChildNode(lightNode)
// prepare plane
let plane = SCNPlane(width: 400, height: 400)
plane.firstMaterial!.doubleSided = true
plane.firstMaterial!.diffuse.contents = UIColor.redColor().CGColor
let planeNode = SCNNode(geometry: plane)
planeNode.position = SCNVector3Make(0, 0, 100)
scene.rootNode.addChildNode(planeNode)
// prepare view as SCNView
let sceneView = view as SCNView
sceneView.backgroundColor = SKColor.blackColor()
sceneView.scene = scene
sceneView.delegate = self
sceneView.jitteringEnabled = true // i.e. improve visual rendering
sceneView.pointOfView = cameraNode
looks like you rotate around the x axis instead of the y axis (so that the camera looks in the desired direction)