如何使用 Swift 在 SceneKit 中创建圆盘形光源
how to create a disc-shaped light source in SceneKit using Swift
我希望使用 SceneKit 创建一个类似于太阳照亮地球的方式的光源。这是一个业余天文学课程项目。由于太阳比地球大,全向 SCNLight 将无法实现,因为它的光从一个点发出。从太阳发出的光本质上是从一个非常大的球体发出的,而不是一个点。
此图像是使用全向光源创建的,但并未真实显示地球的阴影区域。具体来说,北极没有点亮,但它应该是(在这种情况下我们是在夏天。
第二张图更逼真,你可以看到北极被照亮了,就像在夏天一样。
问题是为了获得第二张图片,我不得不非常繁琐地定位定向光以使图像正确(这是我手动完成的。对于第一张图片,我只是将全向光定位在与太阳球体相同的地方)。由于整个事物旨在制作动画,因此使用定向光并且必须随着地球沿着其轨道前进而不断重新定位它,这将需要一些相当 - 对我来说 - 复杂的数学。
所以我想创建一个 SCNLight,它使用以编程方式创建的模型 I/O 灯光对象进行初始化。根据 Apple 的 "Documentation",在 Swift 中,可以使用 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"
创建 SCNLight
"Documentation" 为 "creating a light" 声明如下:
init(mdlLight: MDLLight)
它被定义为便利初始化器:
convenience init(mdlLight: MDLLight)
我希望能够做到以下几点:
let lightModelObject = MDLLight()
lightModelObject.lightType = .discArea
let discShapedSceneLight = SCNLight(lightModelObject) //cannot convert value ... error.
但最后一条语句给我一个:"Cannot convert value of type 'MDLLight' to expected argument type 'NSCoder'" 错误。我也试过:
let discShapedSceneLight = SCNLight.init(lightModelObject)
但也不走运。
我完全卡住了!关于在 Swift.
中使用初始值设定项,感觉有些基本的东西我不明白
如有任何意见,我们将不胜感激。
EDIT:我还在 objective-C 中根据相同的文档尝试了以下操作:
#import <ModelIO/MDLLight.h>
...
SCNLight *discLight = [SCNLight light];
MDLPhysicallcPlausibleLight *ppl = [MDLPhysicallyPlausibleLight lightWithSCNLight:discLight];
但出现此错误:"No known class method for selector 'lightWithSCNLight:'"
编辑:
感谢@EmilioPelaez 解决了这个问题。
我已将包含所需照明的完整代码放在下面,也许它会对其他人有所帮助。
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
}
}
如果您添加 import SceneKit.ModelIO
,您应该能够使用当前无法使用的初始化程序。
请注意,MDLLightTypeDiscArea
目前未桥接到 SceneKit,您将得到的是一个 SCNLightTypeOmni
灯(您可以验证这一点,但要检查初始化程序的结果)。
我希望使用 SceneKit 创建一个类似于太阳照亮地球的方式的光源。这是一个业余天文学课程项目。由于太阳比地球大,全向 SCNLight 将无法实现,因为它的光从一个点发出。从太阳发出的光本质上是从一个非常大的球体发出的,而不是一个点。
此图像是使用全向光源创建的,但并未真实显示地球的阴影区域。具体来说,北极没有点亮,但它应该是(在这种情况下我们是在夏天。
第二张图更逼真,你可以看到北极被照亮了,就像在夏天一样。
问题是为了获得第二张图片,我不得不非常繁琐地定位定向光以使图像正确(这是我手动完成的。对于第一张图片,我只是将全向光定位在与太阳球体相同的地方)。由于整个事物旨在制作动画,因此使用定向光并且必须随着地球沿着其轨道前进而不断重新定位它,这将需要一些相当 - 对我来说 - 复杂的数学。
所以我想创建一个 SCNLight,它使用以编程方式创建的模型 I/O 灯光对象进行初始化。根据 Apple 的 "Documentation",在 Swift 中,可以使用 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"
创建 SCNLight"Documentation" 为 "creating a light" 声明如下:
init(mdlLight: MDLLight)
它被定义为便利初始化器:
convenience init(mdlLight: MDLLight)
我希望能够做到以下几点:
let lightModelObject = MDLLight()
lightModelObject.lightType = .discArea
let discShapedSceneLight = SCNLight(lightModelObject) //cannot convert value ... error.
但最后一条语句给我一个:"Cannot convert value of type 'MDLLight' to expected argument type 'NSCoder'" 错误。我也试过:
let discShapedSceneLight = SCNLight.init(lightModelObject)
但也不走运。
我完全卡住了!关于在 Swift.
中使用初始值设定项,感觉有些基本的东西我不明白如有任何意见,我们将不胜感激。
EDIT:我还在 objective-C 中根据相同的文档尝试了以下操作:
#import <ModelIO/MDLLight.h>
...
SCNLight *discLight = [SCNLight light];
MDLPhysicallcPlausibleLight *ppl = [MDLPhysicallyPlausibleLight lightWithSCNLight:discLight];
但出现此错误:"No known class method for selector 'lightWithSCNLight:'"
编辑: 感谢@EmilioPelaez 解决了这个问题。
我已将包含所需照明的完整代码放在下面,也许它会对其他人有所帮助。
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
}
}
如果您添加 import SceneKit.ModelIO
,您应该能够使用当前无法使用的初始化程序。
请注意,MDLLightTypeDiscArea
目前未桥接到 SceneKit,您将得到的是一个 SCNLightTypeOmni
灯(您可以验证这一点,但要检查初始化程序的结果)。