SceneKit – 如何使用材质访问节点的变换?

SceneKit – How can I use materials to access node's transform?

我有一个免费的 3d 头骨,就像这张照片和下面的代码:

import UIKit
import SceneKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let scene = SCNScene(named: "skull.scn")!
        
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
        
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        

        let scnView = self.view as! SCNView
        scnView.scene = scene
        scnView.allowsCameraControl = true
        scnView.backgroundColor = UIColor.black
        
        
        let button = UIButton(frame: CGRect(x: 100, y: 600, width: 200, height: 60))
        button.setTitle("Open Jaw", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        
        button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
        
        self.view.addSubview(button)

    }
    
    @objc func buttonAction() {
        print("Button pressed")
        // How can I open jaw here?
    }

}

正如你在代码中看到的,我想通过按钮打开下巴,因为我有所有的材料在文件中,我怎么能用 Dflt2 来做到这一点,我想知道如何访问 Dflt2 以进行旋转,因为打开 Jaw 时动画流畅甚至更改颜色,这可能吗?

Node-centric 行为

与 Autodesk Maya 等专业应用程序不同,通过 material 您可以 select 分配给 material 的几何体,SceneKit 不允许您这样做.然而,SceneKit 和 Maya 一样,具有节点层次场景结构...

SCNView –> SCNScene -> RootNode 
                           | -> Node_01
                           |       | -> Subnode_A -> Geometry -> Materials
                           |       | -> Subnode_B -> Geometry -> Materials
                           |
                           | -> Node_02 -> Geometry -> Materials
                           |
                           | -> Node_03 -> Geometry -> Materials
                           |
                           | -> Node_04
                           |       | -> Subnode_A
                           |                | -> Subnode_B -> Camera
                           |
                           | -> Node_05 -> SpotLight
                           |
                           | -> Node_06 -> ParticleSystem
                           |
                           | -> Node_07 -> AudioPlayer

...您只能通过 select 特定节点内的特定几何形状才能获得所需的 material。这种方法称为 node-centric,默认情况下没有 material-centric 方法。上面的抽象图向我们展示了场景中节点的连接,其中每个节点都有自己的变换参数(平移、旋转和缩放)。

因此,您可以只在检索节点时做您想做的事情:

import SceneKit

class GameViewController: UIViewController {
    
    @IBOutlet var sceneView: SCNView!
    let scene = SCNScene(named: "art.scnassets/Skull.usdz")!
    var lowerJaw: SCNNode? = nil
    var counter: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.scene = self.scene
        sceneView.allowsCameraControl = true
        sceneView.autoenablesDefaultLighting = true
        sceneView.backgroundColor = .black
        
        guard let skullNode = self.scene.rootNode.childNode(
                                  withName: "Geom", recursively: true)
        else { return }
        
        self.lowerJaw = skullNode.childNodes[0].childNode(
                            withName: "lower_part", recursively: true)
        
        lowerJaw?.childNodes[0].geometry?.firstMaterial?.diffuse.contents = 
                                                         UIColor.systemBlue
    }
    
    @IBAction func pressed(_ sender: UIButton) {
        
        self.counter += 1
        
        let opened = SCNAction.rotateTo(x: .pi/20, y: 0, z: 0, duration: 0.3)
        let closed = SCNAction.rotateTo(x: 0, y: 0, z: 0, duration: 0.3)
        
        if (counter % 2 == 0) {
            self.lowerJaw?.runAction(closed)
        } else {
            self.lowerJaw?.runAction(opened)
        }
    }
}

如您所见,在 Xcode Scene graph 中,"Geom" 节点包含头骨的所有部分,以及 "lower_part" 子节点,我们'重新旋转,包含头骨的下颌和下牙。因此,关于 3D 对象最重要的事情 – 您需要在场景中具有单独的 3D 几何部件(节点)以便为它们设置动画(不是 mono-model)"lower_part" 节点围绕其枢轴点旋转。每个 SceneKit 的节点都有自己的个人枢轴。如果您需要将枢轴点移动到另一个位置,请使用 .

在 SceneKit 中,您可以使用 .usdz.scn.dae.obj 场景。 Xcode 的 Scene graph 允许您创建、删除、重新定位、嵌套和重命名节点。然而,构建节点层次结构的最稳健的地方是 3D 创作应用程序(如 Maya 或 Blender)。


Material-centric接近

但是,没有什么能阻止您创建像 [SCNMaterial: SCNNode] 这样的字典集合。

抽象示例(2 个球体,2 materials):

import SceneKit

let sceneView = SCNView(frame: .zero)
sceneView.scene = SCNScene()

let redMaterial = SCNMaterial()
redMaterial.diffuse.contents = UIColor.red

let greenMaterial = SCNMaterial()
redMaterial.diffuse.contents = UIColor.green

let nodeA = SCNNode(geometry: SCNSphere(radius: 0.1))
nodeA.position.z = -2.5
nodeA.geometry?.materials[0] = redMaterial

let nodeB = SCNNode(geometry: SCNSphere(radius: 0.1))
nodeB.position.y = 1.73
nodeB.geometry?.materials[0] = greenMaterial

sceneView.scene?.rootNode.addChildNode(nodeA)
sceneView.scene?.rootNode.addChildNode(nodeB)

// Dictionary
var getNode = [redMaterial: nodeA, greenMaterial: nodeB]

getNode[redMaterial]?.position.z             // -2.5
getNode[greenMaterial]?.position.y           // 1.73