Индивидуальная анимация положения вершин, как правило, непростая задача. Но есть хорошие способы приблизиться к этому в SceneKit.
Графический процессор действительно хочет, чтобы все данные вершин были загружены в один блок, прежде чем он начнет рендеринг кадра. Это означает, что если вы постоянно вычисляете новые позиции вершин/нормали/и т. д. на ЦП, у вас возникает проблема переноса всех этих данных на ГП каждый раз, когда меняется хотя бы часть из них.
Поскольку вы уже описываете свою поверхность математически, у вас есть все шансы проделать эту работу на самом графическом процессоре. Если каждая позиция вершины является функцией некоторой переменной, вы можете написать эту функцию в шейдере и найти способ передать входную переменную для каждой вершины.
Для этого можно рассмотреть несколько вариантов:
Модификаторы шейдеров. Начните с фиктивной геометрии, имеющей нужную вам топологию (количество вершин и способ их соединения в виде полигонов). Передайте свою входную переменную как дополнительную текстуру и в коде модификатора шейдера (для точки входа геометрии) найдите текстуру, выполните свою функцию и установите положение вершины с результатом.
Металлические вычислительные шейдеры. Создайте источник геометрии с буфером Metal, затем во время рендеринга поставьте в очередь вычислительный шейдер, который записывает данные вершин в этот буфер в соответствии с вашей функцией. (Скелетный код части этого по ссылке.)
Обновление. Судя по вашим комментариям, вам может быть легче.
Если у вас есть геометрия, состоящая из частей, которые являются статичными по отношению друг к другу и перемещаются относительно друг друга — как кубики кубика Рубика — вычисление вершин во время рендеринга является излишним. Вместо этого вы можете один раз загрузить статические части вашей геометрии в GPU и использовать преобразования для их позиционирования относительно друг друга.
Способ сделать это в SceneKit состоит в том, чтобы создать отдельные узлы, каждый со своей собственной (статической) геометрией для каждой части, а затем установить преобразования узлов (или положения/ориентации/масштабы) для перемещения узлов относительно друг друга. Чтобы переместить сразу несколько узлов, используйте иерархию узлов — сделайте несколько из них потомками другого узла. Если некоторые из них должны перемещаться вместе в один момент, а другое подмножество — позже, вы можете изменить иерархию.
Вот конкретный пример идеи кубика Рубика. Сначала создадим кублеты:
// 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()
Волшебная часть находится в очистке после анимации. Кублеты начинаются как дочерние элементы корневого узла сцены, и мы временно переназначаем их родителем другому узлу, чтобы мы могли преобразовать их вместе. После того, как они снова станут дочерними узлами корневого узла, мы устанавливаем каждому локальному transform
его worldTransform
, чтобы он сохранял эффект изменений преобразования временного узла.
Затем вы можете повторить этот процесс, чтобы захватить любой набор узлов, находящихся в (новом) наборе позиций в мировом пространстве, и использовать другой временный узел для их преобразования.
Я не совсем уверен, насколько ваша проблема похожа на кубик Рубика, но похоже, что вы, вероятно, можете обобщить решение из чего-то подобного.
person
rickster
schedule
28.06.2015