I am looking to create a light source that will resemble the way the Sun lights up the Earth using SceneKit. This is for an amateur astronomy course project. Since the Sun is larger than the Earth, an omnidirectional SCNLight will not do, since the light from it emanates from a single point. The light emanating from the Sun essentially emanates from a very large sphere, not a single point.
This image was created using an omnidirectional light source, but does not realistically show the Earth's shaded area. Specifically, the north pole is not lit, but it should be (in this case we are in summer.
This second image is more realistic, you can see the north pole is illuminated, as it would indeed be in the summer.
The problem is that to get the second image, I had to position a directional light very tediously to get the image to be correct (which I did manually. For the first image, I simply positioned an omnidirectional light at the same place as the sun sphere). Since the whole thing is intended to be animated, using a directional light and having to reposition it constantly as Earth progresses along its orbit, will require some fairly -for me- complicated math.
So I was thinking to create a SCNLight initialized using a model I/O light object created programmatically. According to Apple's "Documentation", in Swift, an SCNLight can be created with an initializer from a specified model I/O light object, which I understood to allow creation of a "light source [that] illuminates a scene in all directions from an area in the shape of a disc"
The "Documentation" states the following for "creating a light":
init(mdlLight: MDLLight)
it is defined as a convenience initializer:
convenience init(mdlLight: MDLLight)
I was expecting to be able to do the following:
let lightModelObject = MDLLight()
lightModelObject.lightType = .discArea
let discShapedSceneLight = SCNLight(lightModelObject) //cannot convert value ... error.
But the last statement gets me a: "Cannot convert value of type 'MDLLight' to expected argument type 'NSCoder'" error. I have also tried:
let discShapedSceneLight = SCNLight.init(lightModelObject)
but no luck there either.
I am totally stuck! It feels like there is something fundamental that I don't understand about using initializers in Swift.
Any comments would be much appreciated.
EDIT: I also tried the following in objective-C, as per the same documentation:
#import <ModelIO/MDLLight.h>
...
SCNLight *discLight = [SCNLight light];
MDLPhysicallcPlausibleLight *ppl = [MDLPhysicallyPlausibleLight lightWithSCNLight:discLight];
but get this error: "No known class method for selector 'lightWithSCNLight:'"
EDIT: Thanks to @EmilioPelaez for solving this.
I've put the complete code with the desired lighting below, perhaps it will help someone else.
import UIKit
import QuartzCore
import SceneKit
import SceneKit.ModelIO
class GameViewController: UIViewController {
var scnView:SCNView!
var scnScene:SCNScene!
var cameraNode:SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupScene()
setupCamera()
drawSolarSystem()
}
func setupView() {
scnView = self.view as! SCNView
scnView.showsStatistics = true
scnView.allowsCameraControl = true
scnView.autoenablesDefaultLighting = false
}
func setupScene() {
scnScene = SCNScene()
scnView.scene = scnScene
scnScene.background.contents = UIColor.systemBlue
}
func setupCamera() {
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
scnScene.rootNode.addChildNode(cameraNode)
}
func drawSolarSystem() {
var geometryObject:SCNGeometry
//SUN object and light source
let sunX:Float = -3.5 ///position a little off to the left of center
//create the material for the Sun's surface
let sunMaterial = SCNMaterial()
sunMaterial.emission.contents = UIColor.yellow ///the color the material emits.
// sunMaterial.transparency = 0.3
// sunMaterial.diffuse.contents = UIColor.systemYellow ///the color the material reflects when lit.
//create the Sphere and assign it the material
geometryObject = SCNSphere(radius: 1.0)
geometryObject.firstMaterial=sunMaterial
//create the node and assign it the geometry (object) previously created.
let sunNode = SCNNode(geometry: geometryObject)
sunNode.position = SCNVector3(x:sunX, y:0, z:0)
scnScene.rootNode.addChildNode(sunNode)
//create the light source and position it same place as the sun
//create an MDLAreaLight, since the "normal" SCNLight types - such as omni - are not suitable.
//The .omni type emanates from a point, and so doesn't correctly represent the sun lighting the earth
let lightModelObject = MDLAreaLight()
lightModelObject.lightType = .discArea
// lightModelObject.areaRadius = 5.01 ///This doesn't appear to affect the light.
//create the node and assign it the MDLAreaLight
let sunLightNode = SCNNode()
sunLightNode.light = SCNLight(mdlLight:lightModelObject)
sunLightNode.light?.color = UIColor .white
sunLightNode.position = SCNVector3(x:sunX, y:0, z:0)
scnScene.rootNode.addChildNode(sunLightNode)
//EARTH EQUATORIAL PLANE but centered on the Sun
let floorObject = SCNFloor()
floorObject.reflectivity = 0
floorObject.width = 2
floorObject.length = 3
let earthEquatorialPlaneNode = SCNNode(geometry: floorObject)
earthEquatorialPlaneNode.position = SCNVector3(x:sunX, y:0, z:0)
scnScene.rootNode.addChildNode(earthEquatorialPlaneNode)
//EARTH main node - node with 2 subnodes, one sphere and one axis
///a node can only have a single geometry object attached. In order to attach multiple geometries, create a (parent) node without any geometry, and then attach subnodes with one geometry each.
//The parent node
let earthNode = SCNNode()
earthNode.position = SCNVector3(x: 0, y:-1.2, z:0)
scnScene.rootNode.addChildNode(earthNode)
//the child node for the earth axis of rotation object
geometryObject = SCNCylinder(radius: 0.01, height: 1.2)
let earthAxisNode = SCNNode(geometry: geometryObject)
earthNode.addChildNode(earthAxisNode)
//the child node for the earth sphere object
geometryObject = SCNSphere(radius: 0.5)
let earthSphereNode = SCNNode(geometry: geometryObject)
earthNode.addChildNode(earthSphereNode)
//put some meridians and an equator onto the sphere.
let earthSphereMaterial = SCNMaterial()
geometryObject.firstMaterial = earthSphereMaterial
earthSphereMaterial.diffuse.contents = "coordinateGrid.png"
earthSphereMaterial.lightingModel = .lambert
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
}
If you add
import SceneKit.ModelIO
you should be able to use the initializers that are currently not working.