objc.io#18#游戏专题之Scene Kit

z_zombie 2014-11-27 11:12:13 5120

86E58PICmCS_1024.jpeg

Scene Kit是一个Cocoa式的3D渲染框架,在苹果推出OS X Mountain Lion的时候崭露头角(WWDC 2012)。作为一款普通的3D渲染引擎,SceneKit的最初版本实在惊艳。而在一年之后,苹果发布OS X Mavericks 时为Scene Kit新增了着色修改器、几何约束以及骨骼动画的功能。等到了2014年,这一框架变得更为强大,还支持了粒子系统、物理仿真、脚本事件和多通道渲染。此外还有一个更新(我想对于大多数人来说也极为重要的一个),就是支持了iOS系统,而现在苹果亦称其"为休闲游戏而备"。

从早我就发现了Scene Kit最大的优势和特征就是其整合了像Core Image和Core Animation之类的图形框架,而现在还整合进了Sprite Kit。当然,通常来说随便找一款游戏引擎来进行以上这些开发都不是事儿,不过如果你只是Cocoa(或者Cocoa Touch)开发的业余选手,Scene Kit对你来说会倍儿有亲切感。

Scene Kit基础

Scene Kit是基于OpenGL构建的,是一款高阶开发框架。你可以在Swift和Objective-C下直接使用灯光、几何体、材质和相机这样的对象。如果你使用的OpenGL仍是在有着色器之前的早期版本,前面所提的这些东西都会给你带来痛苦的回忆,毕竟版本太早很多东西的配置都是有限制的。不过幸运的是,对于大多数情况来说,高阶开发框架已经配置完好且足够顶用——甚至是动态阴影和景深效果这样的高级渲染也不在话下。

而对于那些没法实现的,Scene Kit还允许开发者使用自己编码的着色器(GLSL)对渲染进行底层的配置。

节点

除却灯光、几何体、材质和相机之外,Scene Kit对节点采用了分层结构【注:像这样的节点层次结构在3D图形学通常意义上叫作场景图层(scene graph),这也是该框架取名为Scene Kit的一种解释】来组织所有内容。每一个节点有自己相对于父节点的位置、角度以及缩放比例,反过来将该节点作为父节点也是一样,一直追溯到根节点为止。当在3D世界中某个位置添加一个对象,它则会归属于其中的某个节点,这些结点的层次结构通过以下方法来管理:

addChildNode(_:)
insertChildNode(_: atIndex:)
removeFromParentNode()

以上这些示例方法用于管理iOS和OS X上的视图结构和分层结构。

几何对象

Scene Kit中有一些内建的几何图形,比如简单的盒形、球形、飞机、还有锥体。不过对于游戏开发而言,你需要从文件中加载更多的3D模型。你可以通过引用文件名来导入(或导出)这些模型:

let chessPieces = SCNScene(named: "chess pieces") // SCNScene?

如果文件中包含有场景,那么该场景会作为一个整体来显示,之后可以当做场景视图的场景来使用。假如场景中包含有大量对象,而只应该显示部分在屏幕上的话,可以通过引用其名字获取它们,并添加到场景视图所渲染的场景中。

if let knight = chessPieces.rootNode.childNodeWithName("Knight", recursively: true) { 
     sceneView.scene?.rootNode.addChildNode(knight) 
}

这是一个导入文件中节点的示例,其中包含了一些子节点,而这些节点上都附有几何形状(以及材质)、灯光和相机参数。重复使用相同的名字来获取子节点会得到同一个对象的引用。

687474703a2f2f696d672e6f626a63636e2e696f2f69737375652d31382f74657874757265732e706e67.png

如果要在一个场景中进行大量拷贝,比如当你需要在棋盘上显示两个“马”的时候,你就先要拷贝(copy)该节点或者是克隆(clone)一份(即递归拷贝)。这样会为该节点复制一个具有相同几何对象和相同材质的拷贝。不过所有的拷贝都指向同一个几何对象,因此要改变当中某个几何对象的材质时,该几何对象也需要有一个拷贝,然后在为其附上新的材质。复制几何对象也很方便,所要涉及到的顶点数据都是相同的。

那些导入进来用于完成骨骼动画的节点除了拥有几何对象之外还有一个skinner对象,用于接入骨架节点层和管理骨骼与几何对象之间的关系。个别的骨骼是可以移动或者旋转的,不过对于一些更为复杂的动画来说需要进行大量的骨骼数据修改——比如人物在绕圈行走的动画——看上去会更像是从文件导入然后加载到对象上的。

光照

Scene Kit中的光线完全是动态的,这也使得它们会相当容易理解同时也便于使用。当然,易用也便意味着相比那些发展完善的游戏引擎来说会有所欠缺。这里提供的光线有四种类型:环境光、平行光、全向光(点光源)和聚光灯。

在多数情况下,给光源指定一个旋转轴和角度来照射对象并非最为直观的方法。对于这种情况,节点告知光源它的位置和方向的时候是要考虑另一个节点作为约束的,添加约束也就是说光线会保持指向这一节点,就算节点移动也一样:

let spot = SCNLight()
spot.type = SCNLightTypeSpot
spot.castsShadow = true

let spotNode = SCNNode()
spotNode.light = spot
spotNode.position = SCNVector3(x: 4, y: 7, z: 6)

let lookAt = SCNLookAtConstraint(target: knight)
spotNode.constraints = [lookAt]

687474703a2f2f696d672e6f626a63636e2e696f2f69737375652d31382f7370696e6e696e672e676966.gif

动画

几乎Scene Kit中所有的对象的属性都是可用于动画的,和Cocoa(或是Cocoa Touch)一样,你可以使用keyPath(甚至是形如"position:x"这样的Path)来创建CCAnimation并将其添加到对象。同样的,你还可以将SCNTransaction的值设置为"begin"或者"commit"。这两种方法几乎一学就会,不过对于游戏中的动画来说不会营造出什么特别的效果。

let move = CABasicAnimation(keyPath: "position.x")
move.byValue  = 10
move.duration = 1.0
knight.addAnimation(move, forKey: "slide right")

Scene Kit还支持Sprite Kit中行为式(action-style)动画的API,你可以运行代码以创建动画序列然后与其他动画一起定制行为(action)。与Core Animation不同,这些行为会在整个游戏的循环中运行,并更新每一帧中模型的值,而非只限于展现出来的节点。

事实上,如果你之前使用过Sprite Kit,那么Scene Kit应该会觉得很熟悉才对。对于3D图形,iOS 8(第一个支持Scene Kit的iOS版本)和OS X 10.10上可以同时使用两种框架(Scene Kit和Sprite Kit)。对于Sprite Kit来说,3D模型可以混合2D的精灵(sprite),而对于Scene Kit来说,Sprite Kit的场景和纹理同样可以作为Scene Kit中的纹理来用,而且Sprite Kit中的场景还可作为Scene Kit场景上面的2D覆盖层【注:是的,Scene Kit和Sprite Kit的API有太多相似概念(场景、节点、约束等两者共有的概念)以至于非常容易混淆】。

使用Scene Kit写游戏

场景行为和材质纹理并非Scene Kit和Sprite Kit的唯一相同之处。当使用Scene Kit开发游戏的时候,就会发现其与2D图形开发的时候有很大的相似度。这两种情形下的游戏主循环都会按照相同的步骤进行,以回调函数为代表:

1.更新场景

2.应用动画/行为

3.物理仿真

4.应用约束

5.渲染

687474703a2f2f696d672e6f626a63636e2e696f2f69737375652d31382f67616d656c6f6f702e706e67.png

每一帧的渲染之后都会又一次回调函数用于执行游戏相关的逻辑,比如输入处理、AI(人工智能)以及游戏脚本。

输入处理

Scene Kit和Cocoa以及Cocoa Touch使用了相同的机制来处理键盘、鼠标、触摸和手势等输入,而其稍有区别的地方就是Scene Kit中只有一个视图,即场景视图(scene)。键盘的输入以及挤压、滑动、旋转此类手势仅需知道其发生了就行,而点击、触碰、拖动这些操作则需要了解其具体信息了。

scene view可以用-hitTest(_: options:)方法来进行点击的测试。通常的视图只返回被点击的子视图或子层,而Scene Kit则返回一个数组,其中存有每个相交的模型对象,以及从摄像机投向这个测试点的射线。每次点击测试的结果包含被击中模型的节点对象,还包含了交点的详细信息(交点坐标、交点表面法线,交点的纹理坐标)。大多数时候只要知道击中的第一个节点是哪个就够了:

if let firstHit = sceneView.hitTest(tapLocation, options: nil)?.first as? SCNHitTestResult  {
    let hitNode = firstHit.node
    // do something with the node that was hit...
}

扩展默认渲染流程

光照和材质的配置比较无脑,因而只能做到这么多。假如之前有用OpenGL写好的的着色器(Shader),可以拿来定制材质渲染;如果你只想修改下默认的渲染配置,Scene Kit外露了4个入口用于插入着色器的代码(GLSL)来修改。Scene Kit在不同入口上提供了对旋转矩阵、模型数据、样本贴图及渲染输出色值的访问。

比如,下面的GLSL代码被用在模型数据的入口点中,可以将模型对象上所有点沿x轴扭曲。通过定义一个函数来创建一个旋转变换,并将其应用在模型的位置和法线上。另外还自定义了一个统一(uniform)变量来决定对象该如何扭曲变形。

// a function that creates a rotation transform matrix around X
mat4 rotationAroundX(float angle)
{
    return mat4(1.0,    0.0,         0.0,        0.0,
                0.0,    cos(angle), -sin(angle), 0.0,
                0.0,    sin(angle),  cos(angle), 0.0,
                0.0,    0.0,         0.0,        1.0);
}
#pragma body
uniform float twistFactor = 1.0;
float rotationAngle = _geometry.position.x * twistFactor;
mat4 rotationMatrix = rotationAroundX(rotationAngle);
// position is a vec4
_geometry.position *= rotationMatrix;
// normal is a vec3
vec4 twistedNormal = vec4(_geometry.normal, 1.0) * rotationMatrix;
_geometry.normal   = twistedNormal.xyz;

着色修改器(Shader modifier)既可以绑定在模型对象上,也可以绑定在它的材质对象上。这两个类都完全支持Key-value Coding(KVC),你可以指定任意Key进行赋值。在着色器中声明名为"twistFactor"的统一变量使得Scene Kit在这个值改变时自动重新绑定uniform,也可以用KVC来实现:

torus.setValue(5.0, forKey: "twistFactor")

使用Keypath的CAAnimation也可以:

let twist = CABasicAnimation(keyPath: "twistFactor")
twist.fromValue = 5
twist.toValue   = 0
twist.duration  = 2.0
torus.addAnimation(twist, forKey: "Twist the torus")

687474703a2f2f696d672e6f626a63636e2e696f2f69737375652d31382f74776973742e676966.gif

延期着色

哪怕是在OpenGL开发中,一些图像效果也无法通过一次渲染完成,我们将不同着色器按序列操作,以完成持续处理的目的,称为延期着色。Scene Kit使用SCNTechnique类来完实现延时着色。它使用字典来创建,其中定义了绘图步骤、输入输出、shader文件以及符号等等。

第一个渲染传值是Scene Kit的默认渲染,它可以计算出出场景的颜色和景深。如果不想立即计算色值,可以将材质设置成"恒定"的光照模型,或者将场景里所有光照都设置成环境光。

比如,从Scene Kit渲染流程的第一个渲染传值以获取景深,第二个获取法线,第三个对其执行边界检测,还可以沿轮廓也可以沿边缘描线:

687474703a2f2f696d672e6f626a63636e2e696f2f69737375652d31382f626973686f702e706e67.png

延伸阅读

想了解更多,可参考2014年WWDC中"使用Scene Kit开发游戏"的视频,并看看示例代码。

有关Scene Kit基础知识,可以参看2013年2014年"What'New in SceneKit"的视频。

(本文由CocoaChina翻译自objc.io,转载请注明出处)