使用SceneKit进行swift数学可移动网格

时间:2015-06-26 21:24:02

标签: swift animation scenekit metal

我是一名想要编写几何游戏的数学家。

我有几个网格的精确坐标和数学公式,我需要显示它们的单位法线。

每个网格我只需要一个纹理(彩色反光金属)。

我需要让用户移动棋子,即通过简单的数学公式改变网格的坐标。

所以我不需要导入3D文件,而是我可以计算所有内容。

想象一种魔方。计算立方体坐标,并由用户旋转立方体。我的程序在Mathematica中运行。

我现在非常困难,现在因为不眠之日,试图找到如何在SceneKit中显示计算网格 - 每个顶点和法线分别进行动画处理。

任何一个工作示例,例如,一个带有计算坐标的三角形(而不是股票提供的形状),由SceneKit以可动画的坐标显示,将非常感激。

我看起来更多,似乎网格中的各个点可能无法在SceneKit中移动。我喜欢SceneKit(与OpenGL不同)的功能,即可以在用户的​​手指下获取对象。可以在项目中将OpenGL和SceneKit混合在一起吗?

我可以从那里接管......

1 个答案:

答案 0 :(得分:6)

通常,单独设置顶点位置是一个棘手的问题。但是有很好的方法可以在SceneKit中实现它。

GPU真的希望在开始渲染帧之前将顶点数据全部上传到一个块中。这意味着如果你不断在CPU上计算新的顶点位置/法线/等,你就会遇到将每次所有数据传输到GPU的问题,即使只有部分数据发生了变化。

因为您已经在数学上描述了表面,所以您可以在GPU本身上完成这项工作。如果每个顶点位置是某个变量的函数,则可以在着色器中编写该函数,并找到一种方法来传递每个顶点的输入变量。

您可以考虑以下几个选项:

  1. 着色器修改器。从具有所需拓扑的虚拟几何体开始(顶点的数量以及它们如何作为多边形连接)。将输入变量作为额外纹理传递,并在着色器修改器代码中(对于几何入口点),查找纹理,执行功能,并使用结果设置顶点位置。

  2. 金属计算着色器。创建几何源backed by a Metal buffer,然后在渲染时,将根据您的函数将顶点数据写入该缓冲区的计算着色器排入队列。 (链接上有部分内容的骨架代码。)

  3. 更新:从你的评论中听起来你可能处于更容易的位置。

    如果您所拥有的几何图形由相对于自身静态并相互移动的碎片组成 - 就像魔方的立方体一样 - 在渲染时计算顶点是过度的。相反,您可以将几何体的静态部分上传到GPU一次,并使用变换将它们相对于彼此定位。

    在SceneKit中执行此操作的方法是创建单独的节点,每个节点都有自己的(静态)几何体,然后设置节点变换(或位置/方向/比例)以相对于彼此移动节点。要一次移动多个节点,请使用节点层次结构 - 将其中几个节点作为另一个节点的子节点。如果某些人需要一起移动,而另一个子集需要稍后移动,则可以更改层次结构。

    这是魔方概念的具体例子。首先,创建一些cubelets:

    // convenience for creating solid color materials
    func materialWithColor(color: NSColor) -> SCNMaterial {
        let mat = SCNMaterial()
        mat.diffuse.contents = color
        mat.specular.contents = NSColor.whiteColor()
        return mat
    }
    
    // create and arrange a 3x3x3 array of cubelets
    var cubelets: [SCNNode] = []
    for x in -1...1 {
        for y in -1...1 {
            for z in -1...1 {
                let box = SCNBox()
                box.chamferRadius = 0.1
                box.materials = [
                    materialWithColor(NSColor.greenColor()),
                    materialWithColor(NSColor.redColor()),
                    materialWithColor(NSColor.blueColor()),
                    materialWithColor(NSColor.orangeColor()),
                    materialWithColor(NSColor.whiteColor()),
                    materialWithColor(NSColor.yellowColor()),
                ]
                let node = SCNNode(geometry: box)
                node.position = SCNVector3(x: CGFloat(x), y: CGFloat(y), z: CGFloat(z))
                scene.rootNode.addChildNode(node)
                cubelets += [node]
            }
        }
    }
    

    接下来,做一个轮换的过程。这是一个特定的旋转,但您可以将其概括为一个函数,该函数对任何子集的子集进行任何转换:

    // create a temporary node for the rotation
    let rotateNode = SCNNode()
    scene.rootNode.addChildNode(rotateNode)
    
    // grab the set of cubelets whose position is along the right face of the puzzle,
    // and add them to the rotation node
    let rightCubelets = cubelets.filter { node in 
        return abs(node.position.x - 1) < 0.001 
    }
    rightCubelets.map { rotateNode.addChildNode($0) }
    
    // animate a rotation
    SCNTransaction.begin()
    SCNTransaction.setAnimationDuration(2)
    rotateNode.eulerAngles.x += CGFloat(M_PI_2)
    SCNTransaction.setCompletionBlock {
        // after animating, remove the cubelets from the rotation node,
        // and re-add them to the parent node with their transforms altered
        rotateNode.enumerateChildNodesUsingBlock { cubelet, _ in
            cubelet.transform = cubelet.worldTransform
            cubelet.removeFromParentNode()
            scene.rootNode.addChildNode(cubelet)
        }
        rotateNode.removeFromParentNode()
    }
    SCNTransaction.commit()
    

    魔术部分在动画后的清理中。 cubelets以场景的根节点的子节点开始,我们暂时将它们重新父节点到另一个节点,以便我们可以将它们一起转换。在将它们再次作为根节点的子节点返回后,我们将每个节点的transform设置为worldTransform,以便保持临时节点变换的效果。

    然后,您可以重复此过程以捕获(新)一组世界空间位置中的任何节点集,并使用另一个临时节点来转换这些节点。

    我不太清楚Rubik的立方体就像你的问题一样,但听起来你可以概括出类似这样的解决方案。