как создать дискообразный источник света в SceneKit с помощью Swift

Я хочу создать источник света, который будет напоминать то, как Солнце освещает Землю, используя SceneKit. Это для проекта любительского курса астрономии. Поскольку Солнце больше Земли, всенаправленный свет SCNLight не подходит, поскольку свет от него исходит из одной точки. Свет, исходящий от Солнца, по существу исходит из очень большой сферы, а не из одной точки.

Это изображение было создано с использованием всенаправленного источника света, но не реалистично показывает затененную область Земли. Конкретно северный полюс не горит, но должен быть (в данном случае мы летом.

Это второе изображение более реалистично, вы можете видеть, что северный полюс освещен, как это действительно было бы летом.

Проблема в том, что для получения второго изображения мне пришлось очень утомительно расположить направленный свет, чтобы изображение было правильным (что я и сделал вручную. Для первого изображения я просто разместил всенаправленный свет в том же месте, что и солнце. сфера). Поскольку все это предназначено для анимации, использование направленного света и необходимость постоянно менять его положение по мере продвижения Земли по своей орбите потребует некоторой довольно сложной математики.

Итак, я подумал о создании SCNLight, инициализированного с использованием объекта света ввода-вывода модели, созданного программно. Согласно "Документации" Apple, в Swift SCNLight может быть создан с инициализатором из указанного объекта света ввода-вывода модели, который, как я понял, позволяет создать "источник света, [который] освещает сцену во всех направлениях от источника света". область в форме диска "

В "Документации" для "создания света" говорится следующее:

init(mdlLight: MDLLight)

он определяется как инициализатор удобства:

удобство инициализации (mdlLight: MDLLight)

Я ожидал, что смогу сделать следующее:

let lightModelObject = MDLLight()
lightModelObject.lightType = .discArea

let discShapedSceneLight = SCNLight(lightModelObject) //cannot convert value ... error.

Но последнее утверждение вызывает у меня ошибку: "Невозможно преобразовать значение типа 'MDLLight' в ожидаемый тип аргумента 'NSCoder'". Я также пробовал:

let discShapedSceneLight = SCNLight.init(lightModelObject)

но и там не повезло.

Я полностью застрял! Такое ощущение, что есть что-то фундаментальное, чего я не понимаю в использовании инициализаторов в Swift.

Будем очень признательны за любые комментарии.

РЕДАКТИРОВАТЬ: Я также пробовал следующее в objective-C, согласно той же документации:

#import <ModelIO/MDLLight.h>
...
SCNLight *discLight = [SCNLight light];
MDLPhysicallcPlausibleLight *ppl = [MDLPhysicallyPlausibleLight lightWithSCNLight:discLight];

но получаю эту ошибку: "Нет известного метода класса для селектора '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
    }

}

2 ответа

Решение

Если вы добавите import SceneKit.ModelIO у вас должна быть возможность использовать инициализаторы, которые в настоящее время не работают.

Обратите внимание, что MDLLightTypeDiscArea в настоящее время не подключен к SceneKit, вместо этого вы получите SCNLightTypeOmni light (вы можете убедиться в этом, но проверив результат инициализатора).

Другие вопросы по тегам