I tried to turn on and off default lighting in SCNView via .autoenablesDefaultLighting instance property but in doesn't work (Neither in UI nor programmatically).
I need all objects to be black when there's no light.
How to turn default lighting off?
Here's a code:
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scnView = SCNView(frame: NSRect(x: 0,
y: 0,
width: 450,
height: 300))
view.addSubview(scnView)
scnView.autoenablesDefaultLighting = false // DOESN'T WORK
scnView.allowsCameraControl = true
scnView.backgroundColor = NSColor.blue
let scene = SCNScene()
scnView.scene = scene
let sphereGeo = SCNSphere(radius: 2)
sphereGeo.segmentCount = 4
sphereGeo.materials.first?.diffuse.contents = NSColor.lightGray
let sphereNode = SCNNode(geometry: sphereGeo)
sphereNode.name = "Sphere Node"
scene.rootNode.addChildNode(sphereNode)
}
}
It seems it's working only when I'm using Physically Based Rendering shading model.
let material = SCNMaterial()
material.lightingModel = SCNMaterial.LightingModel.physicallyBased
sceneView.autoenablesDefaultLighting = false
If I use .physicallyBased type property for shading my models the lighting works as supposed.
Related
Here is a simple SceneKit project with two boxes. I’ve added a CIBloom filter to one of them. When I rotate the scene, the ”glow” effect is rendered behind the other box?
I saw someone else had this issue and solved it by setting writesToDepthBuffer to false but in my case it’s important to keep all 3D data (I want to rotate around the scene).
I’ve provided a couple of images and the code.
I would really appreciate any help!
Here is the code I used:
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene()
// 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 box = SCNNode(geometry: SCNBox(width: 8.0, height: 8.0, length: 2.0, chamferRadius: 0.0))
box.geometry?.firstMaterial?.diffuse.contents = UIColor.green
box.position.z = -4
scene.rootNode.addChildNode(box)
let box2 = SCNNode(geometry: SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0))
box2.geometry?.firstMaterial?.diffuse.contents = UIColor.red
scene.rootNode.addChildNode(box2)
let bloomFilter = CIFilter(name:"CIBloom")!
bloomFilter.setValue(10.0, forKey: "inputIntensity")
bloomFilter.setValue(100.0, forKey: "inputRadius")
box2.filters = [bloomFilter]
// 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
scnView.autoenablesDefaultLighting = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}
For performance reasons I have to switch from SceneView to SpriteView in my macOS project (showing more than 63 scenes did not work with SceneView, but it does with SpriteView).
But now im facing an issue that SpriteView is rendering colors differently than SceneView. Below is a simple reproduction of the issue I am facing.
I have tried a multitude of material and lighting options, but I seem to miss something more fundamental. Help is very much appreciated.
var body: some View {
HStack {
// SpriteView
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.backgroundColor = .white
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SK3DNode()
node.scnScene = self.sphereScene
scene.addChild(node)
return scene
}())
// SceneView
SceneView(scene: sphereScene,
options: [.autoenablesDefaultLighting])
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
let ballGeometry = SCNSphere(radius: 5)
let ballNode = SCNNode(geometry: ballGeometry)
let material = SCNMaterial()
material.diffuse.contents = NSColor.purple
material.lightingModel = .physicallyBased
ballGeometry.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
You're absolutely right: SpriteKit processes SceneKit's scenes differently than SceneKit. It's visually noticeable that the lighting intensity, blurring of highlights and decolorization of edges with 90 degree reflection are different. The main tool that can be advised in this case is the use of Ambient Light to additionally illuminate the SpriteKit scene based on the SceneKit content. You should turn a default lighting off (in order to get rid of colorization artifacts) and use regular lights. Here I used directional light.
SpriteView:
import SwiftUI
import SceneKit
import SpriteKit
struct SpriteView: NSViewRepresentable {
var scene = SKScene()
func makeNSView(context: Context) -> SKView {
let skView = SKView(frame: .zero)
skView.presentScene(scene)
scene.backgroundColor = .black
return skView
}
func updateNSView(_ uiView: SKView, context: Context) { }
}
ContentView:
struct ContentView: View {
var body: some View {
ZStack {
HStack {
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let ambient = SCNNode()
ambient.light = SCNLight()
ambient.light?.type = .ambient
ambient.light?.intensity = 1200
let node = SK3DNode()
node.autoenablesDefaultLighting = false
node.scnScene = self.sphereScene
node.scnScene?.rootNode.addChildNode(ambient)
scene.addChild(node)
return scene
}() )
SceneView(scene: sphereScene, options: [])
}
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
scnScene.background.contents = NSColor.black
let ballNode = SCNNode(geometry: SCNSphere(radius: 5.0))
let directional = SCNNode()
directional.light = SCNLight()
directional.light?.type = .directional
directional.light?.intensity = 500
scnScene.rootNode.addChildNode(directional)
let material = SCNMaterial()
material.lightingModel = .physicallyBased
material.diffuse.contents = NSColor.purple
ballNode.geometry?.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
}
The following worked for me, correcting saturation and brightness brought me near to the SceneKit defaultLighting appearance:
// get object and manipulate
let object = scene.rootNode.childNode(withName: "object", recursively: false)
let color = NSColor(named: "\(colorNr)")?
.usingColorSpace(.displayP3) // specify color space, important!
object?.geometry?.firstMaterial?.lightingModel = .physicallyBased
// correct color for SpriteView
let color2 = NSColor(hue: color?.hueComponent ?? 0,
saturation: (color?.saturationComponent ?? 0) * 0.55,
brightness: (color?.brightnessComponent ?? 0) * 0.55 + 0.45,
alpha: 1.0)
object?.geometry?.firstMaterial?.diffuse.contents = color2
object?.geometry?.firstMaterial?.diffuse.intensity = 0.9
object?.geometry?.firstMaterial?.roughness.contents = 0.9
I'm investigating iOS and trying to understand is it possible to change the texture of the model using any other picture?
Let's say I have model.obj with related texture green.png which is applied to this model so the appearance of the product is green. Is it possible to choose any other image, for example, blue.png, and apply it programmatically in runtime to 3d model and make the appearance of the product blue?
I have one working example
override func viewDidLoad() {
super.viewDidLoad()
let node = SCNNode()
let geometry = SCNSphere(radius: 0.2)
node.position = SCNVector3(0, 0, -1)
sceneView.backgroundColor = .black
sceneView.scene = SCNScene()
node.geometry = self.geometry
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
But when I try apply image to uploaded 3d model it's appearance doesn't change, here is a code.
override func viewDidLoad() {
super.viewDidLoad()
let tempScene = SCNScene(named: "art.scnassets/California_chair_1.obj")!
let node = tempScene.rootNode
node.position = SCNVector3(0, 0, -1)
sceneView.scene = SCNScene()
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
/**/
let material = SCNMaterial()
material.isDoubleSided = false
material.diffuse.contents = image
node.geometry?.materials = [material]
/**/
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
How to apply image to any 3d model created by designer?
Many thanks for any help!
For retrieving a model from SCNScene, you may use subscript .childNodes[0] several times to get to geometry and its corresponding materials in hierarchy.
import ARKit
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView!
let node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene(named: "art.scnassets/California_chair.scn")!
sceneView.autoenablesDefaultLighting = true
node = sceneView.scene.rootNode.childNode(withName: "firstChair",
recursively: true)
let green = UIColor.green
node?.childNodes[0].geometry?.firstMaterial?.diffuse.contents = green
}
#IBAction func changeTexture(_ sender: UIButton) {
let blue = UIImage(named: "art.scnassets/blueTexture.png")
node.geometry?.firstMaterial?.diffuse.contents = blue
}
}
Consider that SCNGeometry may be nested inside deep hierarchy:
node?.childNodes[0].childNodes[0].childNodes[0].geometry.firstMaterial?.diffuse
In Xcode's Scene graph such a nested hierarchy looks like this:
Also always check your node's size (scale), to find out if your camera is inside 3D model or not.
P.S.
In case you use obj models – use their corresponding mtl textures:
sceneView.scene = SCNScene(named: "art.scnassets/file.obj")!
let obj = sceneView.scene.rootNode.childNode(withName: "default",
recursively: true)
obj?.geometry?.firstMaterial?.diffuse.contents = UIImage(named:
"art.scnassets/file.mtl")
im trying to show a earth model on screen, loaded model from here (tried and other same models), but id didn't show on screen
[let scene = SCNScene(named: "tierra-1.obj")
if scene == nil {
print("nuuuul")
return
}
// 2: Add camera node
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
// 3: Place camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
// 4: Set camera on scene
scene?.rootNode.addChildNode(cameraNode)
// 5: Adding light to scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(x: 0, y: 0, z: 0)
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)
// If you don't want to fix manually the lights
sceneView.autoenablesDefaultLighting = true
// Allow user to manipulate camera
sceneView.allowsCameraControl = true
// Show FPS logs and timming
sceneView.showsStatistics = true
// Set background color
sceneView.backgroundColor = UIColor.white
// Allow user translate image
sceneView.cameraControlConfiguration.allowsTranslation = false
// Set scene settings
sceneView.scene = scene][2]
but after loading I have empty screen (example), how to fix that?
I copied and ran your code using a custom obj, I got it loaded and in the viewport.
I loaded in an obj, using a node, and not trying to load the obj as the scene itself.
I noticed that your camera is at world space 0,0,0. that would most likely be inside the mesh, which would appear see through as materials are defaulted with backface culling on.
I assign the material to be blue, just to make it easier to spot incase the asset is very small. I would suggest selecting the asset in XCode, and you can see the bounding box size. This will help get a better idea for how far away you need to move the camera to get it to see the mesh.
import ModelIO
import SceneKit
import SceneKit.ModelIO
import PlaygroundSupport
let scene = SCNScene()
let sceneView = SCNView(frame: CGRect(x:0, y:0, width:800, height:800))
// Set scene settings
sceneView.scene = scene
// 2: Add camera node
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
// 3: Place camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
// 4: Set camera on scene
scene.rootNode.addChildNode(cameraNode)
// Material
let blueMaterial = SCNMaterial()
blueMaterial.diffuse.contents = UIColor.blue
// Accessing the Playground resource folder
let path = Bundle.main.path(forResource:"randomAsset", ofType: "obj")
let asset = MDLAsset(url: URL(string: path!)!)
let object = asset.object(at: 0)
let objNode = SCNNode(mdlObject: object)
objNode.geometry?.materials = [blueMaterial]
scene.rootNode.addChildNode(objNode)
// Allow user to manipulate camera
sceneView.allowsCameraControl = true
// Show FPS logs and timming
//sceneView.showsStatistics = true
// Set background color
sceneView.backgroundColor = UIColor.gray
// Allow user translate image
sceneView.cameraControlConfiguration.allowsTranslation = true
PlaygroundPage.current.setLiveView(sceneView)
I dont think SceneKit supporst loading an Obj as a scene. They currently only allow types that have the ability to export scene data eg.camera,lights. https://developer.apple.com/documentation/scenekit/scnscenesource
If you want to load the obj file you will have to import it into your scene.
This post should help https://forums.developer.apple.com/thread/3979
I'm having a difficult time trying to figure out how to get my SceneKit camera to orbit around a specific node in my game.
If I have a single node (a ship) and a camera in my scene everything works fine. If I add an additional node (a planet) the cameras pivot point appears to change from my ship to a space between my ship and planet.
Things I've tried:
Setting a lookat constraint on my camera (set to the ship)
Settingcamera position to my ship (it will move but the pivot point
still seems to be between the two objects)
Changing the cameras pivot point
example:
class TestSceneViewController: UIViewController, SCNSceneRendererDelegate {
var scnView: SCNView = SCNView()
var scnScene: SCNScene!
var cameraNode: SCNNode!
var ship: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupScene()
setupCamera()
...
func setupView() {
// scnView = self.view as! SCNView
// retrieve the SCNView
scnView = SCNView(frame: view.frame)
scnView.showsStatistics = true
view.addSubview(scnView)
scnView.allowsCameraControl = true
scnView.defaultCameraController.interactionMode = .orbitTurntable
scnView.defaultCameraController.inertiaEnabled = true
scnView.delegate = self
scnView.isPlaying = true
scnView.loops = true
}
func setupScene () {
scnScene = SCNScene()
scnView.scene = scnScene
let ships = SCNScene(named: "art.scnassets/simpleshuttle3.scn")
ship = ships!.rootNode.childNode(withName: "ship", recursively: true)
ship?.position = SCNVector3(x: 0, y: 0, z: 0)
scnScene.rootNode.addChildNode(ship!)
let planets = SCNScene(named: "art.scnassets/sphere.scn")!
if let planet = planets.rootNode.childNode(withName: "Ball", recursively: true){
planet.position = SCNVector3(x: 0, y: 0, z: 40)
scnScene.rootNode.addChildNode(planet)
}
}
func setupCamera() {
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: ship.position.x, y: ship.position.y, z: 80)
cameraNode.camera?.motionBlurIntensity = 1.0
cameraNode.camera?.automaticallyAdjustsZRange = true
scnScene.rootNode.addChildNode(cameraNode)
}
You are enabling the manual camera control.
scnView.allowsCameraControl = true
If you want to use e.g. SCNLookAtConstraint you have to disable that. Otherwise you have a conflict. The camera is not supposed to point at a certain position while simultaneously being rotated by the user.
If you want to stay with the default camera controller you can create an additional SCNNode as the parent for your planet and the ships. This parent node can now be moved so that the pivot point is at your desired position.