I created a floor in a scene and loaded a collada file(.dae) into my scene and tried to create a plane below that model. But I have problems like below.
This problem generates only when custom camera is used. Scene renders properly when system camera is used i.e if no custom camera is added
My code is as below:
import UIKit import SceneKit
class TestVC: UIViewController, SCNSceneRendererDelegate {
var cameraNode: SCNNode!
var modelNode: SCNNode!
var scnView: SCNView!
var camNode: SCNNode!
var cameraPosition: SCNVector3!
override func viewDidLoad() {
super.viewDidLoad()
scnView = SCNView(frame: self.view.frame)
scnView.delegate = self
view.addSubview(scnView)
scnView.delegate = self
let scene = SCNScene()
scnView.scene = scene
//camera
let camera = SCNCamera()
cameraNode = SCNNode()
cameraNode.camera = camera
camera.zFar = 1000
cameraPosition = SCNVector3(0, 100, 150)
cameraNode.position = cameraPosition
scene.rootNode.addChildNode(cameraNode)
//floor
let floor = SCNFloor()
floor.reflectivity = 0
let floorNode = SCNNode(geometry: floor)
let firstmaterial = SCNMaterial()
firstmaterial.diffuse.contents = UIImage(named: "art.scnassets/grass.jpg")
firstmaterial.diffuse.wrapS = SCNWrapMode.Repeat
firstmaterial.diffuse.wrapT = SCNWrapMode.Repeat
floor.materials = [firstmaterial]
scene.rootNode.addChildNode(floorNode)
let light = SCNLight()
let lightNode = SCNNode()
lightNode.light = light
light.type = SCNLightTypeAmbient
light.color = UIColor.darkGrayColor()
scene.rootNode.addChildNode(lightNode)
let light1 = SCNLight()
let light1Node = SCNNode()
light1Node.light = light1
light1Node.position = SCNVector3(0, 200, -100)
light1.type = SCNLightTypeOmni
scene.rootNode.addChildNode(light1Node)
let modelScene = SCNScene(named: "Barcelona Chair.dae")
let solNode = modelScene?.rootNode
solNode?.eulerAngles = SCNVector3(GLKMathDegreesToRadians(-90), 0, 0)
scene.rootNode.addChildNode(solNode!)
scnView.allowsCameraControl = true
let plane = SCNPlane(width: 100, height: 100)
let planeNode = SCNNode(geometry: plane)
planeNode.pivot = SCNMatrix4MakeRotation(Float(M_PI_2), 1, 0, 0)//(CGFloat(M_PI_2), 1, 0, 0)
planeNode.position = SCNVector3(0, 1, 0)
plane.firstMaterial?.diffuse.contents = UIColor.redColor()
scene.rootNode.addChildNode(planeNode)
}
What you're probably seeing is known as "z-fighting". Basically, your floor and your plane are coexisting - they are coplanar - so the rendering of them is ambiguous. Try lifting the plane up a little by setting its position higher than the floor plane, and you should see this problem go away.
Don't know why the above problem exists but I got a different way of doing the thing. You can use SCNShape to draw an overlay over a floor. I drew a rectangular path using UIBezierPath and set the path property of the SCNShape class. :)
Related
I have added a Human 3d Model in Scene Kit. Its background is black how to make it as white as front view? I have used this in swift app, used scene kit and human 3d model, Please check image I have attached..
Back View
3d Model Settings
Code :-
//MARK: - Scene Related Methods
func loadScene() {
self.removeExistingNodes()
loadSceneLayer(fileName: "FinalBaseMesh.obj")
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = false
load3DScene()
//layerSelectionIndex = 0
sceneView.scene = scene
}
func load3DScene() {
sceneView.scene = scene
// Allow user to manipulate camera
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.white
sceneView.cameraControlConfiguration.allowsTranslation = true
sceneView.cameraControlConfiguration.panSensitivity = 0.9
sceneView.delegate = self as SCNSceneRendererDelegate
// sceneView.isPlaying = true
for reco in sceneView.gestureRecognizers! {
if let panReco = reco as? UIPanGestureRecognizer {
panReco.maximumNumberOfTouches = 1
}
}
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action:#selector(handleTap(_:)))
sceneView.addGestureRecognizer(tapGesture)
self.addSavedNode()
}
func loadSceneLayer(fileName: String) {
scene = SCNScene(named: fileName) ?? SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 6.5, z: 20)
scene.rootNode.addChildNode(cameraNode)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(x: 0, y: 6.5, z: 20)
scene.rootNode.addChildNode(lightNode)
// 6: Creating and adding ambien light to scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
}
I forgot to add last method please check loadstonelayer method
Remove all the camera control code
See How to rotate object in a scene with pan gesture - SceneKit for rotating an object
Put in a light node at a negative z facing the object
I'm working on an ARKit project in swift.
In SWIFT I add objects to the scene. What I want to do with those objects is show a shadow beneath them to make them more realistic. I tried some things and those all didn't work. I'll explain that here.
Please understand that I'm still new to swift. I'm creating objects programmatically in SWIFT. I thought I could do it by creating a invisible plane underneath all the objects and place a light above the scene. I learned that I can cast a shadow on an invisible plane in the Scene editor from Xcode by unchecking the "write to color" values red, green, blue and alpha. I placed a spot light above it and it worked. Now I want to do this programatically.
In swift I created the light's and the plane as shown below. I don't use a spot anymore because the scene is so large. That's why I create a really large plane. I added those to light's ambient and directional. Ambient so it doesn't look black on the side and directional so the shadow is shown. This doesn't. The objects look weirdly lit and there are no shadows.
let worldGroundPlaneGeometry = SCNPlane(width: 1000, height: 1000)
worldGroundPlaneGeometry.firstMaterial?.colorBufferWriteMask = SCNColorMask(rawValue: 0)
let worldGroundPlane = SCNNode()
worldGroundPlane.geometry = worldGroundPlaneGeometry
worldGroundPlane.position = worldPosition
worldGroundPlane.castsShadow = true
worldGroundPlane.eulerAngles = SCNVector3(Float.pi / 2, 0, 0)
self.addChildNode(worldGroundPlane)
// Create a ambient light
let ambientLight = SCNNode()
ambientLight.light = SCNLight()
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 directionalNode = SCNNode()
directionalNode.light = SCNLight()
directionalNode.light?.type = SCNLight.LightType.directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.automaticallyAdjustsShadowProjection = true
directionalNode.light?.shadowSampleCount = 64
directionalNode.light?.shadowMode = .deferred
directionalNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
directionalNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
directionalNode.position = SCNVector3(x: 0,y: 5,z: 0)
// Add the lights to the container
self.addChildNode(ambientLight)
self.addChildNode(directionalNode)
It needs to look like this in the whole scene:
P.S. The object and shadow look the same if I render it on my phone inside the app.
I Hope you can help me find a solution to achieve the goal described above.
I hope you can help me! Thanks 😊
EDIT:
The solution provided below is not the solution. Now my scene looks like this with still no shadow:
the code:
let worldGroundPlaneGeometry = SCNPlane(width: 1000, height: 1000)
let worldGroundPlane = SCNNode()
worldGroundPlane.geometry?.firstMaterial?.lightingModel = .constant
worldGroundPlane.geometry?.firstMaterial?.writesToDepthBuffer = true
worldGroundPlane.geometry?.firstMaterial?.colorBufferWriteMask = []
worldGroundPlane.geometry = worldGroundPlaneGeometry
worldGroundPlane.position = worldPosition
worldGroundPlane.castsShadow = true
worldGroundPlane.eulerAngles = SCNVector3(Float.pi / 2, 0, 0)
self.addChildNode(worldGroundPlane)
// 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 directionalNode = SCNNode()
directionalNode.light = SCNLight()
directionalNode.light?.type = SCNLight.LightType.directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.automaticallyAdjustsShadowProjection = true
directionalNode.light?.shadowSampleCount = 64
directionalNode.light?.shadowMode = .deferred
directionalNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
directionalNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
directionalNode.position = SCNVector3(x: 0,y: 5,z: 0)
// Add the lights to the container
self.addChildNode(ambientLight)
self.addChildNode(directionalNode)
I was playing around with your lighting setup and found the ambient light gave it a very washed out look... & the shadow seemed too unnaturally dark.
Anwway I tweaked your light setup configuration to the following. I got rid of the ambient light all together, and I added a constraint on the object node (timber box) is this case. I also controlled the lighting intensity with just one directional light.
let directionalNode = SCNNode()
let constraint = SCNLookAtConstraint(target:node)
directionalNode.light = SCNLight()
directionalNode.light?.type = .directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.intensity = 2000
directionalNode.light?.shadowRadius = 16
directionalNode.light?.shadowMode = .deferred
directionalNode.eulerAngles = SCNVector3(Float.pi/2,0,0)
directionalNode.light?.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
directionalNode.position = SCNVector3((node?.position.x)! + 10,(node?.position.y)! + 30,(node?.position.z)!+30)
directionalNode.constraints = [constraint]
// Add the lights to the container
self.sceneView.scene.rootNode.addChildNode(directionalNode)
The problem was that I wasn't actually using the material. Working Code:
let worldGroundPlaneGeometry = SCNFloor()
let worldGroundPlane = SCNNode()
let worldGroundMaterial = SCNMaterial()
worldGroundMaterial.lightingModel = .constant
worldGroundMaterial.writesToDepthBuffer = true
worldGroundMaterial.colorBufferWriteMask = []
worldGroundMaterial.isDoubleSided = true
worldGroundPlaneGeometry.materials = [worldGroundMaterial]
worldGroundPlane.geometry = worldGroundPlaneGeometry
self.addChildNode(worldGroundPlane)
// 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 directionalNode = SCNNode()
directionalNode.light = SCNLight()
directionalNode.light?.type = SCNLight.LightType.directional
directionalNode.light?.color = UIColor.white
directionalNode.light?.castsShadow = true
directionalNode.light?.automaticallyAdjustsShadowProjection = true
directionalNode.light?.shadowSampleCount = 64
directionalNode.light?.shadowRadius = 16
directionalNode.light?.shadowMode = .deferred
directionalNode.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
directionalNode.light?.shadowColor = UIColor.black.withAlphaComponent(0.75)
directionalNode.position = SCNVector3(x: 0,y: 5,z: 0)
directionalNode.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
// Add the lights to the container
self.addChildNode(ambientLight)
self.addChildNode(directionalNode)
Can anyone please suggest me on how to add a shadow to a 3D object programmatically. Please see the below image. I want the same way to add a shadow under the chair programmatically.
// 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
I need to focus to certain node, for example a pyramid. Then apply distance to the camera, then move the camera based on user click. My approach is like this :
import SceneKit
class GameViewController: UIViewController {
let scene = SCNScene()
override func viewDidLoad() {
super.viewDidLoad()
let camera = SCNCamera()
camera.usesOrthographicProjection = true
camera.orthographicScale = 4
camera.zNear = 1
camera.zFar = 100
let cameraNode = SCNNode()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 6)
cameraNode.camera = camera
let cameraOrbit = SCNNode()
cameraOrbit.name = "orbit"
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraOrbit)
let Py = SCNPyramid(width: 2, height: 3, length: 2)
Py.firstMaterial?.diffuse.contents = UIColor.purple()
let P = SCNNode(geometry: Py)
P.position = SCNVector3(x:0,y:0,z:2) //see the note
scene.rootNode.addChildNode(P)
/* N O T E :
the position of the pyramid must not be changed
as my intention is to rotate the camera
not the pyramid node
I repeat, I don't want to rotate the pyramid
*/
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = false
scnView.backgroundColor = UIColor.black()
// user rotates the camera by tapping
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
//the function which does camera rotation :
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
//I guess the solution is around here
//like modify the cameraOrbit.position ?
//or cameraNode.position ?
//tried both but doesn't work
//or I used them wrong
let cameraOrbit = scene.rootNode.childNode(withName: "orbit", recursively: true)!
SCNTransaction.begin()
SCNTransaction.animationDuration = 2
cameraOrbit.eulerAngles.z += Float(M_PI_2) //see below
SCNTransaction.commit()
/*
I realise that by performing rotation on the camera
(the camera position is unchanged) works for z rotation,
but this is not what I want. What I want is a solution,
which also works for eulerAngles.x and eulerAngles.y.
I used eulerAngles.z as example since it's easier to observe.
I guess the true solution involves moving the camera
with specific trajectory, not only rotation of "anchored" camera.
*/
}
//...
}
The result is :
What I want to achieve is to make the rotation relative to its centre :
My question is, how to adjust the pivot so I can achieve the rotation relative to the pyramid's centre?
Note : I don't want to rotate the pyramid.
I found the solution, but I can't explain why. Please comment if you can explain.
The camera orbit position must be set to match the object's location (in this case, the pyramid). So
cameraOrbit.position = P.position
Then here comes the mystery solution, add cameraOrbit.position.y by half of pi :
cameraOrbit.position.y += Float(M_PI_2)
//therefore we have the final cameraOrbit.position as (0, pi/2, 2)
Tested and works perfectly for all cameraOrbit.eulerAngles.
But I don't have a clue why it works. If pi/2 is coming from some projection thingy, then why it's tucked on y only? I mean, when I do any of the cameraOrbit.eulerAngles, I don't need to assign this pi/2 to either x or z.
Here it is the complete code
import SceneKit
class GameViewController: UIViewController {
let scene = SCNScene()
override func viewDidLoad() {
super.viewDidLoad()
let camera = SCNCamera()
camera.usesOrthographicProjection = true
camera.orthographicScale = 4
camera.zNear = 1
camera.zFar = 100
let cameraNode = SCNNode()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 6)
cameraNode.camera = camera
let cameraOrbit = SCNNode()
cameraOrbit.name = "orbit"
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraOrbit)
let Py = SCNPyramid(width: 2, height: 3, length: 2)
Py.firstMaterial?.diffuse.contents = UIColor.purple()
let P = SCNNode(geometry: Py)
P.position = SCNVector3(x:0,y:0,z:2)
scene.rootNode.addChildNode(P)
// S O L U T I O N :
cameraOrbit.position = P.position
cameraOrbit.position.y += Float(M_PI_2)
//therefore we have the final cameraOrbit.position as (0, pi/2, 2)
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = false
scnView.backgroundColor = UIColor.black()
// user rotates the camera by tapping
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
//the function which does camera rotation :
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
//I was wrong, the solution is not here
let cameraOrbit = scene.rootNode.childNode(withName: "orbit", recursively: true)!
SCNTransaction.begin()
SCNTransaction.animationDuration = 2
cameraOrbit.eulerAngles.z += Float(M_PI_2) //works also for x and y
SCNTransaction.commit()
}
...
}
I need lights to stay "stationary" in my scene. The best lighting method I've found so far is to actually to use scnView.autoenablesDefaultLighting = true however, I can't figure out if there's any way to control some of the attributes. The intensity of the light is a BIT too bright, the location of the light is a BIT different than where I'd like it to be, those kinds of properties.
I've tried using all sorts of other lights, coding them individually BUT since they add to the scene as nodes, the lights (in those cases) themselves will move when I set scnView.allowsCameraControl = true. The default lighting is the only one that will remain "stationary" once the user begins to move the camera around. Can you access/control the properties of the default lighting?
Forget allowsCameraControl and default cameras and lights if you want control of your scene.
let sceneView = SCNView()
let cameraNode = SCNNode() // the camera
var baseNode = SCNNode() // the basic model-root
let keyLight = SCNLight() ; let keyLightNode = SCNNode()
let ambientLight = SCNLight() ; let ambientLightNode = SCNNode()
func sceneSetup() {
let scene = SCNScene()
// add to an SCNView
sceneView.scene = scene
// add the container node containing all model elements
sceneView.scene!.rootNode.addChildNode(baseNode)
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 50)
scene.rootNode.addChildNode(cameraNode)
keyLight.type = SCNLightTypeOmni
keyLightNode.light = keyLight
keyLightNode.position = SCNVector3(x: 10, y: 10, z: 5)
cameraNode.addChildNode(keyLightNode)
ambientLight.type = SCNLightTypeAmbient
let shade: CGFloat = 0.40
ambientLight.color = UIColor(red: shade, green: shade, blue: shade, alpha: 1.0)
ambientLightNode.light = ambientLight
cameraNode.addChildNode(ambientLightNode)
// view the scene through your camera
sceneView.pointOfView = cameraNode
// add gesture recognizers here
}
Move or rotate cameraNode to effect motion in view. Or, move or rotate baseNode. Either way, your light stay fixed relative to the camera.
If you want your lights fixed relative to the model, make them children of baseNode instead of the camera.
If someone who wondering how to setup entire scene with new Scenekit integration with swift ui go though this. This work fine.
struct TestControllerNew: View {
let sceneView = SCNView()
let cameraNode = SCNNode()
var baseNode = SCNNode()
let id = "D69A09F8-EA80-4231-AD35-4A9908B4343C"
var scene = SCNScene()
var body: some View {
SceneView(
scene: sceneSetup(),
pointOfView: cameraNode,
options: [
.autoenablesDefaultLighting
]
)
}
func getTermsOfUseURL() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("\(id).usdz")
}
func sceneSetup() -> SCNScene {
let scene = try! SCNScene(url: getTermsOfUseURL())
// add to an SCNView
sceneView.scene = scene
// add the container node containing all model elements
sceneView.scene!.rootNode.addChildNode(baseNode)
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 1, 10)
scene.rootNode.addChildNode(cameraNode)
// view the scene through your camera
sceneView.pointOfView = cameraNode
// add gesture recognizers here
return scene
}
}
Here the USDZ file get from document directory as a URL, you can use name instead of that.